1 /* A program to lock a file exactly as Exim would, for investigation of
4 Options: -fcntl use fcntl() lock
5 -flock use flock() lock
6 -lockfile use lock file
7 -mbx use mbx locking rules, with either fcntl() or flock()
9 Default is -fcntl -lockfile.
11 Argument: the name of the lock file
26 #include <sys/utsname.h>
31 /* Not all systems have flock() available. Those that do must define LOCK_SH
44 /* Flag for timeout signal handler */
46 static int sigalrm_seen = FALSE;
49 /* We need to pull in strerror() and os_non_restarting_signal() from the
50 os.c source, if they are required for this OS. However, we don't need any of
51 the other stuff in os.c, so force the other macros to omit it. */
53 #ifndef OS_RESTARTING_SIGNAL
54 #define OS_RESTARTING_SIGNAL
65 #ifndef OS_LOAD_AVERAGE
66 #define OS_LOAD_AVERAGE
69 #ifndef FIND_RUNNING_INTERFACES
70 #define FIND_RUNNING_INTERFACES
73 #include "../src/os.c"
77 /*************************************************
79 *************************************************/
82 sigalrm_handler(int sig)
84 sig = sig; /* Keep picky compilers happy */
90 /*************************************************
91 * Give usage and die *
92 *************************************************/
97 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
98 " [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
99 " <file name> [command]\n");
105 /*************************************************
106 * Apply a lock to a file descriptor *
107 *************************************************/
110 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
115 struct flock lock_data;
116 lock_data.l_type = fcntltype;
117 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
119 sigalrm_seen = FALSE;
125 os_non_restarting_signal(SIGALRM, sigalrm_handler);
127 yield = fcntl(fd, F_SETLKW, &lock_data);
132 else yield = fcntl(fd, F_SETLK, &lock_data);
133 if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
137 if (doflock && (yield >= 0))
139 int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
142 os_non_restarting_signal(SIGALRM, sigalrm_handler);
144 yield = flock(fd, flocktype);
149 else yield = flock(fd, flocktype | LOCK_NB);
150 if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
159 /*************************************************
160 * The exim_lock program *
161 *************************************************/
163 int main(int argc, char **argv)
165 int lock_retries = 10;
166 int lock_interval = 3;
167 int lock_fcntl_timeout = 0;
168 int lock_flock_timeout = 0;
174 int now = time(NULL);
175 BOOL use_lockfile = FALSE;
176 BOOL use_fcntl = FALSE;
177 BOOL use_flock = FALSE;
178 BOOL use_mbx = FALSE;
179 BOOL verbose = FALSE;
181 BOOL restore_times = FALSE;
183 char *lockname = NULL, *hitchname = NULL;
184 char *primary_hostname;
192 for (i = 1; i < argc; i++)
195 if (*arg != '-') break;
196 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
197 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
198 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
199 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
200 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
201 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
202 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
205 int value = atoi(argv[i]);
206 if (strcmp(arg, "-retries") == 0) lock_retries = value;
207 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
208 else if (strcmp(arg, "-timeout") == 0)
209 lock_fcntl_timeout = lock_flock_timeout = value;
215 if (quiet) verbose = 0;
217 /* Can't use flock() if the OS doesn't provide it */
222 printf("exim_lock: can't use flock() because it was not available in the\n"
223 " operating system when exim_lock was compiled\n");
228 /* Default is to use lockfiles and fcntl(). */
230 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
231 use_lockfile = use_fcntl = TRUE;
233 /* Default fcntl() for use with mbx */
235 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
237 /* Unset unused timeouts */
239 if (!use_fcntl) lock_fcntl_timeout = 0;
240 if (!use_flock) lock_flock_timeout = 0;
242 /* A file name is required */
244 if (i >= argc) usage();
246 filename = argv[i++];
248 /* Expand file names starting with ~ */
250 if (*filename == '~')
254 if (*(++filename) == '/')
255 pw = getpwuid(getuid());
259 while (*filename != 0 && *filename != '/')
262 pw = getpwnam(buffer);
267 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
271 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
273 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
278 strcpy(buffer, pw->pw_dir);
279 strcat(buffer, filename);
283 /* If using a lock file, prepare by creating the lock file name and
284 the hitching post name. */
290 printf("exim_lock: failed to find host name using uname()\n");
293 primary_hostname = s.nodename;
295 len = (int)strlen(filename);
296 lockname = malloc(len + 8);
297 sprintf(lockname, "%s.lock", filename);
298 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
299 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
303 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
307 /* Locking retry loop */
309 for (j = 0; j < lock_retries; j++)
311 int sleep_before_retry = TRUE;
312 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
315 /* Try to build a lock file if so configured */
320 if (verbose) printf("exim_lock: creating lock file\n");
321 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
324 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
329 /* Apply hitching post algorithm. */
331 if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
335 if (rc != 0 && statbuf.st_nlink != 2)
337 printf("exim_lock: failed to link hitching post to lock file\n");
342 if (!quiet) printf("exim_lock: lock file successfully created\n");
345 /* We are done if no other locking required. */
347 if (!use_fcntl && !use_flock && !use_mbx) break;
349 /* Open the file for writing. */
351 fd = open(filename, O_RDWR + O_APPEND);
354 printf("exim_lock: failed to open %s for writing: %s\n", filename,
360 /* If there is a timeout, implying blocked locking, we don't want to
361 sleep before any retries after this. */
363 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
364 sleep_before_retry = FALSE;
366 /* Lock using fcntl. There are pros and cons to using a blocking call vs
367 a non-blocking call and retries. Exim is non-blocking by default, but setting
368 a timeout changes it to blocking. */
370 if (!use_mbx && (use_fcntl || use_flock))
372 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
373 lock_flock_timeout) >= 0)
377 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
378 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
382 else goto RETRY; /* Message already output */
385 /* Lock using MBX rules. This is complicated and is documented with the
386 source of the c-client library that goes with Pine and IMAP. What has to
387 be done to interwork correctly is to take out a shared lock on the mailbox,
388 and an exclusive lock on a /tmp file. */
392 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
393 lock_flock_timeout) >= 0)
398 printf("exim_lock: fcntl() read lock successfully applied\n");
400 printf("exim_lock: fcntl() read lock successfully applied\n");
403 else goto RETRY; /* Message already output */
405 if (fstat(fd, &statbuf) < 0)
407 printf("exim_lock: fstat() of %s failed: %s\n", filename,
413 /* Set up file in /tmp and check its state if already existing. */
415 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
416 (long)statbuf.st_ino);
418 if (lstat(tempname, &statbuf) >= 0)
420 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
422 printf("exim_lock: symbolic link on lock name %s\n", tempname);
426 if (statbuf.st_nlink > 1)
428 printf("exim_lock: hard link to lock name %s\n", tempname);
434 mbx_tmp_oflags = O_RDWR | O_CREAT;
436 mbx_tmp_oflags |= O_NOFOLLOW;
438 md = open(tempname, mbx_tmp_oflags, 0600);
441 printf("exim_lock: failed to create mbx lock file %s: %s\n",
442 tempname, strerror(errno));
446 /* security fixes from 2010-05 */
447 if (lstat(tempname, &lstatbuf) < 0)
449 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
450 tempname, strerror(errno));
453 if (fstat(md, &statbuf2) < 0)
455 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
456 tempname, strerror(errno));
459 if ((statbuf2.st_nlink > 1) ||
460 (lstatbuf.st_nlink > 1) ||
461 (!S_ISREG(lstatbuf.st_mode)) ||
462 (lstatbuf.st_dev != statbuf2.st_dev) ||
463 (lstatbuf.st_ino != statbuf2.st_ino))
465 printf("exim_lock: race condition exploited against us when "
466 "locking \"%s\"\n", tempname);
470 (void)chmod(tempname, 0600);
472 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
473 lock_flock_timeout) >= 0)
478 printf("exim_lock: fcntl() lock successfully applied to mbx "
479 "lock file %s\n", tempname);
481 printf("exim_lock: flock() lock successfully applied to mbx "
482 "lock file %s\n", tempname);
485 /* This test checks for a race condition */
487 if (lstat(tempname, &statbuf) != 0 ||
488 fstat(md, &ostatbuf) != 0 ||
489 statbuf.st_dev != ostatbuf.st_dev ||
490 statbuf.st_ino != ostatbuf.st_ino)
492 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
493 "creation and locking\n", tempname);
498 else goto RETRY; /* Message already output */
501 /* Clean up before retrying */
508 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
510 if (!quiet) printf("exim_lock: %s closed\n", tempname);
517 printf("exim_lock: close failed: %s\n", strerror(errno));
519 if (!quiet) printf("exim_lock: file closed\n");
525 if (unlink(lockname) < 0)
526 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
528 if (!quiet) printf("exim_lock: lock file removed\n");
532 /* If a blocking call timed out, break the retry loop if the total time
533 so far is not less than than retries * interval. */
536 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
537 lock_fcntl_timeout : lock_flock_timeout) >=
538 lock_retries * lock_interval)
541 /* Wait a bit before retrying, except when it was a blocked fcntl() that
542 caused the problem. */
544 if (j < lock_retries && sleep_before_retry)
546 printf(" ... waiting\n");
547 sleep(lock_interval);
551 if (j >= lock_retries)
553 printf("exim_lock: locking failed too many times\n");
558 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
560 /* If there are no further arguments, run the user's shell; otherwise
561 the next argument is a command to run. */
565 command = getenv("SHELL");
566 if (command == NULL || *command == 0) command = "/bin/sh";
567 if (!quiet) printf("running %s ...\n", command);
572 if (!quiet) printf("running the command ...\n");
575 /* Run the command, saving and restoring the times if required. */
579 struct stat strestore;
581 stat(filename, &strestore);
582 (void)system(command);
583 ut.actime = strestore.st_atime;
584 ut.modtime = strestore.st_mtime;
585 utime(filename, &ut);
587 else (void)system(command);
589 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
590 lock on the mailbox. This should be a non-blocking lock call, as there is no
597 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
599 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
603 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
606 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
608 if (!quiet) printf("exim_lock: %s closed\n", tempname);
614 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
616 if (!quiet) printf("exim_lock: %s closed\n", filename);
621 if (unlink(lockname) < 0)
622 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
624 if (!quiet) printf("exim_lock: lock file removed\n");