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
13 Copyright (c) The Exim Maintainers 2016 - 2021
28 #include <sys/utsname.h>
33 /* Not all systems have flock() available. Those that do must define LOCK_SH
41 typedef unsigned BOOL;
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 #ifndef OS_GET_DNS_RESOLVER_RES
76 #define OS_GET_DNS_RESOLVER_RES
79 #include "../src/os.c"
83 /*************************************************
85 *************************************************/
88 sigalrm_handler(int sig)
95 /*************************************************
96 * Give usage and die *
97 *************************************************/
102 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
103 " [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
104 " <file name> [command]\n");
110 /*************************************************
111 * Apply a lock to a file descriptor *
112 *************************************************/
115 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
120 struct flock lock_data;
121 lock_data.l_type = fcntltype;
122 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
124 sigalrm_seen = FALSE;
130 os_non_restarting_signal(SIGALRM, sigalrm_handler);
132 yield = fcntl(fd, F_SETLKW, &lock_data);
137 else yield = fcntl(fd, F_SETLK, &lock_data);
138 if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
142 if (doflock && (yield >= 0))
144 int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
147 os_non_restarting_signal(SIGALRM, sigalrm_handler);
149 yield = flock(fd, flocktype);
154 else yield = flock(fd, flocktype | LOCK_NB);
155 if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
164 /*************************************************
165 * The exim_lock program *
166 *************************************************/
168 int main(int argc, char **argv)
170 int lock_retries = 10;
171 int lock_interval = 3;
172 int lock_fcntl_timeout = 0;
173 int lock_flock_timeout = 0;
179 time_t now = time(NULL);
180 BOOL use_lockfile = FALSE;
181 BOOL use_fcntl = FALSE;
182 BOOL use_flock = FALSE;
183 BOOL use_mbx = FALSE;
184 BOOL verbose = FALSE;
186 BOOL restore_times = FALSE;
188 char *lockname = NULL, *hitchname = NULL;
189 char *primary_hostname;
197 for (i = 1; i < argc; i++)
200 if (*arg != '-') break;
201 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
202 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
203 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
204 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
205 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
206 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
207 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
210 int value = atoi(argv[i]);
211 if (strcmp(arg, "-retries") == 0) lock_retries = value;
212 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
213 else if (strcmp(arg, "-timeout") == 0)
214 lock_fcntl_timeout = lock_flock_timeout = value;
220 if (quiet) verbose = FALSE;
222 /* Can't use flock() if the OS doesn't provide it */
227 printf("exim_lock: can't use flock() because it was not available in the\n"
228 " operating system when exim_lock was compiled\n");
233 /* Default is to use lockfiles and fcntl(). */
235 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
236 use_lockfile = use_fcntl = TRUE;
238 /* Default fcntl() for use with mbx */
240 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
242 /* Unset unused timeouts */
244 if (!use_fcntl) lock_fcntl_timeout = 0;
245 if (!use_flock) lock_flock_timeout = 0;
247 /* A file name is required */
249 if (i >= argc) usage();
251 filename = argv[i++];
253 /* Expand file names starting with ~ */
255 if (*filename == '~')
259 if (*(++filename) == '/')
260 pw = getpwuid(getuid());
264 while (*filename != 0 && *filename != '/')
267 pw = getpwnam(buffer);
272 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
276 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
278 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
283 strcpy(buffer, pw->pw_dir);
284 strcat(buffer, filename);
288 /* If using a lock file, prepare by creating the lock file name and
289 the hitching post name. */
295 printf("exim_lock: failed to find host name using uname()\n");
298 primary_hostname = s.nodename;
300 len = (int)strlen(filename);
301 lockname = malloc(len + 8);
302 sprintf(lockname, "%s.lock", filename);
303 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
305 /* Presumably, this must match appendfile.c */
306 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
307 (unsigned int)now, (unsigned int)getpid());
310 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
314 /* Locking retry loop */
316 for (j = 0; j < lock_retries; j++)
318 int sleep_before_retry = TRUE;
319 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
322 /* Try to build a lock file if so configured */
327 if (verbose) printf("exim_lock: creating lock file\n");
328 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
331 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
336 /* Apply hitching post algorithm. */
338 if ((rc = link(hitchname, lockname)) != 0)
339 rc2 = fstat(hd, &statbuf);
343 if (rc != 0 && (rc2 != 0 || statbuf.st_nlink != 2))
345 printf("exim_lock: failed to link hitching post to lock file\n");
350 if (!quiet) printf("exim_lock: lock file successfully created\n");
353 /* We are done if no other locking required. */
355 if (!use_fcntl && !use_flock && !use_mbx) break;
357 /* Open the file for writing. */
359 if ((fd = open(filename, O_RDWR + O_APPEND)) < 0)
361 printf("exim_lock: failed to open %s for writing: %s\n", filename,
367 /* If there is a timeout, implying blocked locking, we don't want to
368 sleep before any retries after this. */
370 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
371 sleep_before_retry = FALSE;
373 /* Lock using fcntl. There are pros and cons to using a blocking call vs
374 a non-blocking call and retries. Exim is non-blocking by default, but setting
375 a timeout changes it to blocking. */
377 if (!use_mbx && (use_fcntl || use_flock))
378 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
379 lock_flock_timeout) >= 0)
383 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
384 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
389 goto RETRY; /* Message already output */
391 /* Lock using MBX rules. This is complicated and is documented with the
392 source of the c-client library that goes with Pine and IMAP. What has to
393 be done to interwork correctly is to take out a shared lock on the mailbox,
394 and an exclusive lock on a /tmp file. */
398 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
399 lock_flock_timeout) >= 0)
404 printf("exim_lock: fcntl() read lock successfully applied\n");
406 printf("exim_lock: fcntl() read lock successfully applied\n");
409 else goto RETRY; /* Message already output */
411 if (fstat(fd, &statbuf) < 0)
413 printf("exim_lock: fstat() of %s failed: %s\n", filename,
419 /* Set up file in /tmp and check its state if already existing. */
421 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
422 (long)statbuf.st_ino);
424 if (lstat(tempname, &statbuf) >= 0)
426 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
428 printf("exim_lock: symbolic link on lock name %s\n", tempname);
432 if (statbuf.st_nlink > 1)
434 printf("exim_lock: hard link to lock name %s\n", tempname);
440 mbx_tmp_oflags = O_RDWR | O_CREAT;
442 mbx_tmp_oflags |= O_NOFOLLOW;
444 md = open(tempname, mbx_tmp_oflags, 0600);
447 printf("exim_lock: failed to create mbx lock file %s: %s\n",
448 tempname, strerror(errno));
452 /* security fixes from 2010-05 */
453 if (lstat(tempname, &lstatbuf) < 0)
455 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
456 tempname, strerror(errno));
459 if (fstat(md, &statbuf2) < 0)
461 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
462 tempname, strerror(errno));
465 if ((statbuf2.st_nlink > 1) ||
466 (lstatbuf.st_nlink > 1) ||
467 (!S_ISREG(lstatbuf.st_mode)) ||
468 (lstatbuf.st_dev != statbuf2.st_dev) ||
469 (lstatbuf.st_ino != statbuf2.st_ino))
471 printf("exim_lock: race condition exploited against us when "
472 "locking \"%s\"\n", tempname);
476 (void)chmod(tempname, 0600);
478 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
479 lock_flock_timeout) >= 0)
484 printf("exim_lock: fcntl() lock successfully applied to mbx "
485 "lock file %s\n", tempname);
487 printf("exim_lock: flock() lock successfully applied to mbx "
488 "lock file %s\n", tempname);
491 /* This test checks for a race condition */
493 if (lstat(tempname, &statbuf) != 0 ||
494 fstat(md, &ostatbuf) != 0 ||
495 statbuf.st_dev != ostatbuf.st_dev ||
496 statbuf.st_ino != ostatbuf.st_ino)
498 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
499 "creation and locking\n", tempname);
504 else goto RETRY; /* Message already output */
507 /* Clean up before retrying */
514 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
516 if (!quiet) printf("exim_lock: %s closed\n", tempname);
523 printf("exim_lock: close failed: %s\n", strerror(errno));
525 if (!quiet) printf("exim_lock: file closed\n");
531 if (unlink(lockname) < 0)
532 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
534 if (!quiet) printf("exim_lock: lock file removed\n");
538 /* If a blocking call timed out, break the retry loop if the total time
539 so far is not less than than retries * interval. */
542 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
543 lock_fcntl_timeout : lock_flock_timeout) >=
544 lock_retries * lock_interval)
547 /* Wait a bit before retrying, except when it was a blocked fcntl() that
548 caused the problem. */
550 if (j < lock_retries && sleep_before_retry)
552 printf(" ... waiting\n");
553 sleep(lock_interval);
557 if (j >= lock_retries)
559 printf("exim_lock: locking failed too many times\n");
564 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
566 /* If there are no further arguments, run the user's shell; otherwise
567 the next argument is a command to run. */
571 command = getenv("SHELL");
572 if (command == NULL || *command == 0) command = "/bin/sh";
573 if (!quiet) printf("running %s ...\n", command);
578 if (!quiet) printf("running the command ...\n");
581 /* Run the command, saving and restoring the times if required. */
585 struct stat strestore;
586 #ifdef EXIM_HAVE_FUTIMENS
587 int fd = open(filename, O_RDWR); /* use fd for both get & restore */
588 struct timespec tt[2];
592 printf("open '%s': %s\n", filename, strerror(errno));
596 if (fstat(fd, &strestore) != 0)
598 printf("fstat '%s': %s\n", filename, strerror(errno));
604 tt[0] = strestore.st_atim;
605 tt[1] = strestore.st_mtim;
606 (void) futimens(fd, tt);
611 stat(filename, &strestore);
613 ut.actime = strestore.st_atime;
614 ut.modtime = strestore.st_mtime;
615 utime(filename, &ut);
618 else i = system(command);
620 if(i && !quiet) printf("warning: nonzero status %d\n", i);
622 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
623 lock on the mailbox. This should be a non-blocking lock call, as there is no
630 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
632 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
636 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
639 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
641 if (!quiet) printf("exim_lock: %s closed\n", tempname);
647 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
649 if (!quiet) printf("exim_lock: %s closed\n", filename);
654 if (unlink(lockname) < 0)
655 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
657 if (!quiet) printf("exim_lock: lock file removed\n");