|
|
1.1 ! root 1: /* lock.c ! 2: Lock and unlock a file name. ! 3: ! 4: Copyright (C) 1991, 1992 Ian Lance Taylor ! 5: ! 6: This file is part of the Taylor UUCP package. ! 7: ! 8: This program is free software; you can redistribute it and/or ! 9: modify it under the terms of the GNU General Public License as ! 10: published by the Free Software Foundation; either version 2 of the ! 11: License, or (at your option) any later version. ! 12: ! 13: This program is distributed in the hope that it will be useful, but ! 14: WITHOUT ANY WARRANTY; without even the implied warranty of ! 15: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ! 16: General Public License for more details. ! 17: ! 18: You should have received a copy of the GNU General Public License ! 19: along with this program; if not, write to the Free Software ! 20: Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ! 21: ! 22: The author of the program may be contacted at [email protected] or ! 23: c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. ! 24: */ ! 25: ! 26: #include "uucp.h" ! 27: ! 28: #if USE_RCS_ID ! 29: const char lock_rcsid[] = "$Id: lock.c,v 1.1 93/07/30 08:02:37 bin Exp Locker: bin $"; ! 30: #endif ! 31: ! 32: #include "uudefs.h" ! 33: #include "sysdep.h" ! 34: #include "system.h" ! 35: ! 36: #include <errno.h> ! 37: ! 38: #if HAVE_FCNTL_H ! 39: #include <fcntl.h> ! 40: #else ! 41: #if HAVE_SYS_FILE_H ! 42: #include <sys/file.h> ! 43: #endif ! 44: #endif ! 45: ! 46: #ifndef O_RDONLY ! 47: #define O_RDONLY 0 ! 48: #define O_WRONLY 1 ! 49: #define O_RDWR 2 ! 50: #endif ! 51: ! 52: #ifndef O_NOCTTY ! 53: #define O_NOCTTY 0 ! 54: #endif ! 55: ! 56: #ifndef SEEK_SET ! 57: #define SEEK_SET 0 ! 58: #endif ! 59: ! 60: /* Lock something. If the fspooldir argument is TRUE, the argument is ! 61: a file name relative to the spool directory; otherwise the argument ! 62: is a simple file name which should be created in the system lock ! 63: directory (under HDB this is /etc/locks). */ ! 64: ! 65: boolean ! 66: fsdo_lock (zlock, fspooldir, pferr) ! 67: const char *zlock; ! 68: boolean fspooldir; ! 69: boolean *pferr; ! 70: { ! 71: char *zfree; ! 72: const char *zpath, *zslash; ! 73: size_t cslash; ! 74: pid_t ime; ! 75: char *ztempfile; ! 76: char abtempfile[sizeof "TMP1234567890"]; ! 77: int o; ! 78: #if HAVE_V2_LOCKFILES ! 79: int i; ! 80: #else ! 81: char ab[12]; ! 82: #endif ! 83: int cwrote; ! 84: const char *zerr; ! 85: boolean fret; ! 86: ! 87: if (pferr != NULL) ! 88: *pferr = TRUE; ! 89: ! 90: if (fspooldir) ! 91: { ! 92: zfree = NULL; ! 93: zpath = zlock; ! 94: } ! 95: else ! 96: { ! 97: zfree = zsysdep_in_dir (zSlockdir, zlock); ! 98: zpath = zfree; ! 99: } ! 100: ! 101: ime = getpid (); ! 102: ! 103: /* We do the actual lock by creating a file and then linking it to ! 104: the final file name we want. This avoids race conditions due to ! 105: one process checking the file before we have finished writing it, ! 106: and also works even if we are somehow running as root. ! 107: ! 108: First, create the file in the right directory (we must create the ! 109: file in the same directory since otherwise we might attempt a ! 110: cross-device link). */ ! 111: zslash = strrchr (zpath, '/'); ! 112: if (zslash == NULL) ! 113: cslash = 0; ! 114: else ! 115: cslash = zslash - zpath + 1; ! 116: ! 117: sprintf (abtempfile, "TMP%010lx", (unsigned long) ime); ! 118: ztempfile = zbufalc (cslash + sizeof abtempfile); ! 119: memcpy (ztempfile, zpath, cslash); ! 120: memcpy (ztempfile + cslash, abtempfile, sizeof abtempfile); ! 121: ! 122: o = creat (ztempfile, IPUBLIC_FILE_MODE); ! 123: if (o < 0) ! 124: { ! 125: if (errno == ENOENT) ! 126: { ! 127: if (! fsysdep_make_dirs (ztempfile, FALSE)) ! 128: { ! 129: ubuffree (zfree); ! 130: ubuffree (ztempfile); ! 131: return FALSE; ! 132: } ! 133: o = creat (ztempfile, IPUBLIC_FILE_MODE); ! 134: } ! 135: if (o < 0) ! 136: { ! 137: ulog (LOG_ERROR, "creat (%s): %s", ztempfile, strerror (errno)); ! 138: ubuffree (zfree); ! 139: ubuffree (ztempfile); ! 140: return FALSE; ! 141: } ! 142: } ! 143: ! 144: #if HAVE_V2_LOCKFILES ! 145: i = ime; ! 146: cwrote = write (o, &i, sizeof i); ! 147: #else ! 148: sprintf (ab, "%10d\n", (int) ime); ! 149: cwrote = write (o, ab, strlen (ab)); ! 150: #endif ! 151: ! 152: zerr = NULL; ! 153: if (cwrote < 0) ! 154: zerr = "write"; ! 155: if (close (o) < 0) ! 156: zerr = "close"; ! 157: if (zerr != NULL) ! 158: { ! 159: ulog (LOG_ERROR, "%s (%s): %s", zerr, ztempfile, strerror (errno)); ! 160: (void) remove (ztempfile); ! 161: ubuffree (zfree); ! 162: ubuffree (ztempfile); ! 163: return FALSE; ! 164: } ! 165: ! 166: /* Now try to link the file we just created to the lock file that we ! 167: want. If it fails, try reading the existing file to make sure ! 168: the process that created it still exists. We do this in a loop ! 169: to make it easy to retry if the old locking process no longer ! 170: exists. */ ! 171: fret = TRUE; ! 172: if (pferr != NULL) ! 173: *pferr = FALSE; ! 174: o = -1; ! 175: zerr = NULL; ! 176: ! 177: while (link (ztempfile, zpath) != 0) ! 178: { ! 179: int cgot; ! 180: int ipid; ! 181: boolean freadonly; ! 182: ! 183: fret = FALSE; ! 184: ! 185: if (errno != EEXIST) ! 186: { ! 187: ulog (LOG_ERROR, "link (%s, %s): %s", ztempfile, zpath, ! 188: strerror (errno)); ! 189: if (pferr != NULL) ! 190: *pferr = TRUE; ! 191: break; ! 192: } ! 193: ! 194: freadonly = FALSE; ! 195: o = open ((char *) zpath, O_RDWR | O_NOCTTY, 0); ! 196: if (o < 0) ! 197: { ! 198: if (errno == EACCES) ! 199: { ! 200: freadonly = TRUE; ! 201: o = open ((char *) zpath, O_RDONLY, 0); ! 202: } ! 203: if (o < 0) ! 204: { ! 205: if (errno == ENOENT) ! 206: { ! 207: /* The file was presumably removed between the link ! 208: and the open. Try the link again. */ ! 209: fret = TRUE; ! 210: continue; ! 211: } ! 212: zerr = "open"; ! 213: break; ! 214: } ! 215: } ! 216: ! 217: /* The race starts here. See below for a discussion. */ ! 218: ! 219: #if HAVE_V2_LOCKFILES ! 220: cgot = read (o, &i, sizeof i); ! 221: #else ! 222: cgot = read (o, ab, sizeof ab - 1); ! 223: #endif ! 224: ! 225: if (cgot < 0) ! 226: { ! 227: zerr = "read"; ! 228: break; ! 229: } ! 230: ! 231: #if HAVE_V2_LOCKFILES ! 232: ipid = i; ! 233: #else ! 234: ab[cgot] = '\0'; ! 235: ipid = strtol (ab, (char **) NULL, 10); ! 236: #endif ! 237: ! 238: /* On NFS, the link might have actually succeeded even though we ! 239: got a failure return. This can happen if the original ! 240: acknowledgement was lost or delayed and the operation was ! 241: retried. In this case the pid will be our own. This ! 242: introduces a rather improbable race condition: if a stale ! 243: lock was left with our process ID in it, and another process ! 244: just did the kill, below, but has not yet changed the lock ! 245: file to hold its own process ID, we could start up and make ! 246: it all the way to here and think we have the lock. I'm not ! 247: going to worry about this possibility. */ ! 248: if (ipid == ime) ! 249: { ! 250: fret = TRUE; ! 251: break; ! 252: } ! 253: ! 254: /* If the process still exists, we will get EPERM rather than ! 255: ESRCH. We then return FALSE to indicate that we cannot make ! 256: the lock. */ ! 257: if (kill (ipid, 0) == 0 || errno == EPERM) ! 258: break; ! 259: ! 260: ulog (LOG_ERROR, "Found stale lock %s held by process %d", ! 261: zpath, ipid); ! 262: ! 263: /* This is a stale lock, created by a process that no longer ! 264: exists. ! 265: ! 266: Now we could remove the file (and, if the file mode disallows ! 267: writing, that's what we have to do), but we try to avoid ! 268: doing so since it causes a race condition. If we remove the ! 269: file, and are interrupted any time after we do the read until ! 270: we do the remove, another process could get in, open the ! 271: file, find that it was a stale lock, remove the file and ! 272: create a new one. When we regained control we would remove ! 273: the file the other process just created. ! 274: ! 275: These files are being generated partially for the benefit of ! 276: cu, and it would be nice to avoid the race however cu avoids ! 277: it, so that the programs remain compatible. Unfortunately, ! 278: nobody seems to know how cu avoids the race, or even if it ! 279: tries to avoid it at all. ! 280: ! 281: There are a few ways to avoid the race. We could use kernel ! 282: locking primitives, but they may not be available. We could ! 283: link to a special file name, but if that file were left lying ! 284: around then no stale lock could ever be broken (Henry Spencer ! 285: would think this was a good thing). ! 286: ! 287: Instead I've implemented the following procedure: seek to the ! 288: start of the file, write our pid into it, sleep for five ! 289: seconds, and then make sure our pid is still there. Anybody ! 290: who checks the file while we're asleep will find our pid ! 291: there and fail the lock. The only race will come from ! 292: another process which has done the read by the time we do our ! 293: write. That process will then have five seconds to do its ! 294: own write. When we wake up, we'll notice that our pid is no ! 295: longer in the file, and retry the lock from the beginning. ! 296: ! 297: This relies on the atomicity of write(2). If it possible for ! 298: the writes of two processes to be interleaved, the two ! 299: processes could livelock. POSIX unfortunately leaves this ! 300: case explicitly undefined; however, given that the write is ! 301: of less than a disk block, it's difficult to imagine an ! 302: interleave occurring. ! 303: ! 304: Note that this is still a race. If it takes the second ! 305: process more than five seconds to do the kill, the lseek, and ! 306: the write, both processes will think they have the lock. ! 307: Perhaps the length of time to sleep should be configurable. ! 308: Even better, perhaps I should add a configuration option to ! 309: use a permanent lock file, which eliminates any race and ! 310: forces the installer to be aware of the existence of the ! 311: permanent lock file. ! 312: ! 313: We stat the file after the sleep, to make sure some other ! 314: program hasn't deleted it for us. */ ! 315: if (freadonly) ! 316: { ! 317: (void) close (o); ! 318: o = -1; ! 319: (void) remove (zpath); ! 320: continue; ! 321: } ! 322: ! 323: if (lseek (o, (off_t) 0, SEEK_SET) != 0) ! 324: { ! 325: zerr = "lseek"; ! 326: break; ! 327: } ! 328: ! 329: #if HAVE_V2_LOCKFILES ! 330: i = ime; ! 331: cwrote = write (o, &i, sizeof i); ! 332: #else ! 333: sprintf (ab, "%10d\n", (int) ime); ! 334: cwrote = write (o, ab, strlen (ab)); ! 335: #endif ! 336: ! 337: if (cwrote < 0) ! 338: { ! 339: zerr = "write"; ! 340: break; ! 341: } ! 342: ! 343: (void) sleep (5); ! 344: ! 345: if (lseek (o, (off_t) 0, SEEK_SET) != 0) ! 346: { ! 347: zerr = "lseek"; ! 348: break; ! 349: } ! 350: ! 351: #if HAVE_V2_LOCKFILES ! 352: cgot = read (o, &i, sizeof i); ! 353: #else ! 354: cgot = read (o, ab, sizeof ab - 1); ! 355: #endif ! 356: ! 357: if (cgot < 0) ! 358: { ! 359: zerr = "read"; ! 360: break; ! 361: } ! 362: ! 363: #if HAVE_V2_LOCKFILES ! 364: ipid = i; ! 365: #else ! 366: ab[cgot] = '\0'; ! 367: ipid = strtol (ab, (char **) NULL, 10); ! 368: #endif ! 369: ! 370: if (ipid == ime) ! 371: { ! 372: struct stat sfile, sdescriptor; ! 373: ! 374: /* It looks like we have the lock. Do the final stat ! 375: check. */ ! 376: if (stat ((char *) zpath, &sfile) < 0) ! 377: { ! 378: if (errno != ENOENT) ! 379: { ! 380: zerr = "stat"; ! 381: break; ! 382: } ! 383: /* Loop around and try again. */ ! 384: } ! 385: else ! 386: { ! 387: if (fstat (o, &sdescriptor) < 0) ! 388: { ! 389: zerr = "fstat"; ! 390: break; ! 391: } ! 392: ! 393: if (sfile.st_ino == sdescriptor.st_ino ! 394: && sfile.st_dev == sdescriptor.st_dev) ! 395: { ! 396: /* Close the file before assuming we've succeeded to ! 397: pick up any trailing errors. */ ! 398: if (close (o) < 0) ! 399: { ! 400: zerr = "close"; ! 401: break; ! 402: } ! 403: ! 404: o = -1; ! 405: ! 406: /* We have the lock. */ ! 407: fret = TRUE; ! 408: break; ! 409: } ! 410: } ! 411: } ! 412: ! 413: /* Loop around and try the lock again. We keep doing this until ! 414: the lock file holds a pid that exists. */ ! 415: (void) close (o); ! 416: o = -1; ! 417: fret = TRUE; ! 418: } ! 419: ! 420: if (zerr != NULL) ! 421: { ! 422: ulog (LOG_ERROR, "%s (%s): %s", zerr, zpath, strerror (errno)); ! 423: if (pferr != NULL) ! 424: *pferr = TRUE; ! 425: } ! 426: ! 427: if (o >= 0) ! 428: (void) close (o); ! 429: ! 430: ubuffree (zfree); ! 431: ! 432: /* It would be nice if we could leave the temporary file around for ! 433: future calls, but considering that we create lock files in ! 434: various different directories it's probably more trouble than ! 435: it's worth. */ ! 436: if (remove (ztempfile) != 0) ! 437: ulog (LOG_ERROR, "remove (%s): %s", ztempfile, strerror (errno)); ! 438: ! 439: ubuffree (ztempfile); ! 440: ! 441: return fret; ! 442: } ! 443: ! 444: /* Unlock something. The fspooldir argument is as in fsdo_lock. */ ! 445: ! 446: boolean ! 447: fsdo_unlock (zlock, fspooldir) ! 448: const char *zlock; ! 449: boolean fspooldir; ! 450: { ! 451: char *zfree; ! 452: const char *zpath; ! 453: ! 454: if (fspooldir) ! 455: { ! 456: zfree = NULL; ! 457: zpath = zlock; ! 458: } ! 459: else ! 460: { ! 461: zfree = zsysdep_in_dir (zSlockdir, zlock); ! 462: zpath = zfree; ! 463: } ! 464: ! 465: if (remove (zpath) == 0 ! 466: || errno == ENOENT) ! 467: { ! 468: ubuffree (zfree); ! 469: return TRUE; ! 470: } ! 471: else ! 472: { ! 473: ulog (LOG_ERROR, "remove (%s): %s", zpath, strerror (errno)); ! 474: ubuffree (zfree); ! 475: return FALSE; ! 476: } ! 477: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.