1 /* $Cambridge: exim/src/src/exim_lock.c,v 1.4 2010/05/29 12:11:48 pdp Exp $ */
3 /* A program to lock a file exactly as Exim would, for investigation of
6 Options: -fcntl use fcntl() lock
7 -flock use flock() lock
8 -lockfile use lock file
9 -mbx use mbx locking rules, with either fcntl() or flock()
11 Default is -fcntl -lockfile.
13 Argument: the name of the lock file
28 #include <sys/utsname.h>
33 /* Not all systems have flock() available. Those that do must define LOCK_SH
46 /* Flag for timeout signal handler */
48 static int sigalrm_seen = FALSE;
51 /* We need to pull in strerror() and os_non_restarting_signal() from the
52 os.c source, if they are required for this OS. However, we don't need any of
53 the other stuff in os.c, so force the other macros to omit it. */
55 #ifndef OS_RESTARTING_SIGNAL
56 #define OS_RESTARTING_SIGNAL
67 #ifndef OS_LOAD_AVERAGE
68 #define OS_LOAD_AVERAGE
71 #ifndef FIND_RUNNING_INTERFACES
72 #define FIND_RUNNING_INTERFACES
75 #include "../src/os.c"
79 /*************************************************
81 *************************************************/
84 sigalrm_handler(int sig)
86 sig = sig; /* Keep picky compilers happy */
92 /*************************************************
93 * Give usage and die *
94 *************************************************/
99 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
100 " [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
101 " <file name> [command]\n");
107 /*************************************************
108 * Apply a lock to a file descriptor *
109 *************************************************/
112 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
117 struct flock lock_data;
118 lock_data.l_type = fcntltype;
119 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
121 sigalrm_seen = FALSE;
127 os_non_restarting_signal(SIGALRM, sigalrm_handler);
129 yield = fcntl(fd, F_SETLKW, &lock_data);
134 else yield = fcntl(fd, F_SETLK, &lock_data);
135 if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
139 if (doflock && (yield >= 0))
141 int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
144 os_non_restarting_signal(SIGALRM, sigalrm_handler);
146 yield = flock(fd, flocktype);
151 else yield = flock(fd, flocktype | LOCK_NB);
152 if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
161 /*************************************************
162 * The exim_lock program *
163 *************************************************/
165 int main(int argc, char **argv)
167 int lock_retries = 10;
168 int lock_interval = 3;
169 int lock_fcntl_timeout = 0;
170 int lock_flock_timeout = 0;
176 int now = time(NULL);
177 BOOL use_lockfile = FALSE;
178 BOOL use_fcntl = FALSE;
179 BOOL use_flock = FALSE;
180 BOOL use_mbx = FALSE;
181 BOOL verbose = FALSE;
183 BOOL restore_times = FALSE;
185 char *lockname = NULL, *hitchname = NULL;
186 char *primary_hostname;
194 for (i = 1; i < argc; i++)
197 if (*arg != '-') break;
198 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
199 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
200 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
201 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
202 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
203 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
204 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
207 int value = atoi(argv[i]);
208 if (strcmp(arg, "-retries") == 0) lock_retries = value;
209 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
210 else if (strcmp(arg, "-timeout") == 0)
211 lock_fcntl_timeout = lock_flock_timeout = value;
217 if (quiet) verbose = 0;
219 /* Can't use flock() if the OS doesn't provide it */
224 printf("exim_lock: can't use flock() because it was not available in the\n"
225 " operating system when exim_lock was compiled\n");
230 /* Default is to use lockfiles and fcntl(). */
232 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
233 use_lockfile = use_fcntl = TRUE;
235 /* Default fcntl() for use with mbx */
237 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
239 /* Unset unused timeouts */
241 if (!use_fcntl) lock_fcntl_timeout = 0;
242 if (!use_flock) lock_flock_timeout = 0;
244 /* A file name is required */
246 if (i >= argc) usage();
248 filename = argv[i++];
250 /* Expand file names starting with ~ */
252 if (*filename == '~')
256 if (*(++filename) == '/')
257 pw = getpwuid(getuid());
261 while (*filename != 0 && *filename != '/')
264 pw = getpwnam(buffer);
269 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
273 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
275 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
280 strcpy(buffer, pw->pw_dir);
281 strcat(buffer, filename);
285 /* If using a lock file, prepare by creating the lock file name and
286 the hitching post name. */
292 printf("exim_lock: failed to find host name using uname()\n");
295 primary_hostname = s.nodename;
297 len = (int)strlen(filename);
298 lockname = malloc(len + 8);
299 sprintf(lockname, "%s.lock", filename);
300 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
301 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
305 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
309 /* Locking retry loop */
311 for (j = 0; j < lock_retries; j++)
313 int sleep_before_retry = TRUE;
314 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
317 /* Try to build a lock file if so configured */
322 if (verbose) printf("exim_lock: creating lock file\n");
323 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
326 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
331 /* Apply hitching post algorithm. */
333 if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
337 if (rc != 0 && statbuf.st_nlink != 2)
339 printf("exim_lock: failed to link hitching post to lock file\n");
344 if (!quiet) printf("exim_lock: lock file successfully created\n");
347 /* We are done if no other locking required. */
349 if (!use_fcntl && !use_flock && !use_mbx) break;
351 /* Open the file for writing. */
353 fd = open(filename, O_RDWR + O_APPEND);
356 printf("exim_lock: failed to open %s for writing: %s\n", filename,
362 /* If there is a timeout, implying blocked locking, we don't want to
363 sleep before any retries after this. */
365 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
366 sleep_before_retry = FALSE;
368 /* Lock using fcntl. There are pros and cons to using a blocking call vs
369 a non-blocking call and retries. Exim is non-blocking by default, but setting
370 a timeout changes it to blocking. */
372 if (!use_mbx && (use_fcntl || use_flock))
374 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
375 lock_flock_timeout) >= 0)
379 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
380 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
384 else goto RETRY; /* Message already output */
387 /* Lock using MBX rules. This is complicated and is documented with the
388 source of the c-client library that goes with Pine and IMAP. What has to
389 be done to interwork correctly is to take out a shared lock on the mailbox,
390 and an exclusive lock on a /tmp file. */
394 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
395 lock_flock_timeout) >= 0)
400 printf("exim_lock: fcntl() read lock successfully applied\n");
402 printf("exim_lock: fcntl() read lock successfully applied\n");
405 else goto RETRY; /* Message already output */
407 if (fstat(fd, &statbuf) < 0)
409 printf("exim_lock: fstat() of %s failed: %s\n", filename,
415 /* Set up file in /tmp and check its state if already existing. */
417 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
418 (long)statbuf.st_ino);
420 if (lstat(tempname, &statbuf) >= 0)
422 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
424 printf("exim_lock: symbolic link on lock name %s\n", tempname);
428 if (statbuf.st_nlink > 1)
430 printf("exim_lock: hard link to lock name %s\n", tempname);
436 mbx_tmp_oflags = O_RDWR | O_CREAT;
438 mbx_tmp_oflags |= O_NOFOLLOW;
440 md = open(tempname, mbx_tmp_oflags, 0600);
443 printf("exim_lock: failed to create mbx lock file %s: %s\n",
444 tempname, strerror(errno));
448 /* security fixes from 2010-05 */
449 if (lstat(tempname, &lstatbuf) < 0)
451 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
452 tempname, strerror(errno));
455 if (fstat(md, &statbuf2) < 0)
457 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
458 tempname, strerror(errno));
461 if ((statbuf2.st_nlink > 1) ||
462 (lstatbuf.st_nlink > 1) ||
463 (!S_ISREG(lstatbuf.st_mode)) ||
464 (lstatbuf.st_dev != statbuf2.st_dev) ||
465 (lstatbuf.st_ino != statbuf2.st_ino))
467 printf("exim_lock: race condition exploited against us when "
468 "locking \"%s\"\n", tempname);
472 (void)chmod(tempname, 0600);
474 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
475 lock_flock_timeout) >= 0)
480 printf("exim_lock: fcntl() lock successfully applied to mbx "
481 "lock file %s\n", tempname);
483 printf("exim_lock: flock() lock successfully applied to mbx "
484 "lock file %s\n", tempname);
487 /* This test checks for a race condition */
489 if (lstat(tempname, &statbuf) != 0 ||
490 fstat(md, &ostatbuf) != 0 ||
491 statbuf.st_dev != ostatbuf.st_dev ||
492 statbuf.st_ino != ostatbuf.st_ino)
494 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
495 "creation and locking\n", tempname);
500 else goto RETRY; /* Message already output */
503 /* Clean up before retrying */
510 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
512 if (!quiet) printf("exim_lock: %s closed\n", tempname);
519 printf("exim_lock: close failed: %s\n", strerror(errno));
521 if (!quiet) printf("exim_lock: file closed\n");
527 if (unlink(lockname) < 0)
528 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
530 if (!quiet) printf("exim_lock: lock file removed\n");
534 /* If a blocking call timed out, break the retry loop if the total time
535 so far is not less than than retries * interval. */
538 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
539 lock_fcntl_timeout : lock_flock_timeout) >=
540 lock_retries * lock_interval)
543 /* Wait a bit before retrying, except when it was a blocked fcntl() that
544 caused the problem. */
546 if (j < lock_retries && sleep_before_retry)
548 printf(" ... waiting\n");
549 sleep(lock_interval);
553 if (j >= lock_retries)
555 printf("exim_lock: locking failed too many times\n");
560 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
562 /* If there are no further arguments, run the user's shell; otherwise
563 the next argument is a command to run. */
567 command = getenv("SHELL");
568 if (command == NULL || *command == 0) command = "/bin/sh";
569 if (!quiet) printf("running %s ...\n", command);
574 if (!quiet) printf("running the command ...\n");
577 /* Run the command, saving and restoring the times if required. */
581 struct stat strestore;
583 stat(filename, &strestore);
584 (void)system(command);
585 ut.actime = strestore.st_atime;
586 ut.modtime = strestore.st_mtime;
587 utime(filename, &ut);
589 else (void)system(command);
591 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
592 lock on the mailbox. This should be a non-blocking lock call, as there is no
599 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
601 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
605 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
608 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
610 if (!quiet) printf("exim_lock: %s closed\n", tempname);
616 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
618 if (!quiet) printf("exim_lock: %s closed\n", filename);
623 if (unlink(lockname) < 0)
624 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
626 if (!quiet) printf("exim_lock: lock file removed\n");