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
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)
90 sig = sig; /* Keep picky compilers happy */
96 /*************************************************
97 * Give usage and die *
98 *************************************************/
103 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
104 " [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
105 " <file name> [command]\n");
111 /*************************************************
112 * Apply a lock to a file descriptor *
113 *************************************************/
116 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
121 struct flock lock_data;
122 lock_data.l_type = fcntltype;
123 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
125 sigalrm_seen = FALSE;
131 os_non_restarting_signal(SIGALRM, sigalrm_handler);
133 yield = fcntl(fd, F_SETLKW, &lock_data);
138 else yield = fcntl(fd, F_SETLK, &lock_data);
139 if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
143 if (doflock && (yield >= 0))
145 int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
148 os_non_restarting_signal(SIGALRM, sigalrm_handler);
150 yield = flock(fd, flocktype);
155 else yield = flock(fd, flocktype | LOCK_NB);
156 if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
165 /*************************************************
166 * The exim_lock program *
167 *************************************************/
169 int main(int argc, char **argv)
171 int lock_retries = 10;
172 int lock_interval = 3;
173 int lock_fcntl_timeout = 0;
174 int lock_flock_timeout = 0;
180 time_t now = time(NULL);
181 BOOL use_lockfile = FALSE;
182 BOOL use_fcntl = FALSE;
183 BOOL use_flock = FALSE;
184 BOOL use_mbx = FALSE;
185 BOOL verbose = FALSE;
187 BOOL restore_times = FALSE;
189 char *lockname = NULL, *hitchname = NULL;
190 char *primary_hostname;
198 for (i = 1; i < argc; i++)
201 if (*arg != '-') break;
202 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
203 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
204 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
205 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
206 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
207 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
208 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
211 int value = atoi(argv[i]);
212 if (strcmp(arg, "-retries") == 0) lock_retries = value;
213 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
214 else if (strcmp(arg, "-timeout") == 0)
215 lock_fcntl_timeout = lock_flock_timeout = value;
221 if (quiet) verbose = FALSE;
223 /* Can't use flock() if the OS doesn't provide it */
228 printf("exim_lock: can't use flock() because it was not available in the\n"
229 " operating system when exim_lock was compiled\n");
234 /* Default is to use lockfiles and fcntl(). */
236 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
237 use_lockfile = use_fcntl = TRUE;
239 /* Default fcntl() for use with mbx */
241 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
243 /* Unset unused timeouts */
245 if (!use_fcntl) lock_fcntl_timeout = 0;
246 if (!use_flock) lock_flock_timeout = 0;
248 /* A file name is required */
250 if (i >= argc) usage();
252 filename = argv[i++];
254 /* Expand file names starting with ~ */
256 if (*filename == '~')
260 if (*(++filename) == '/')
261 pw = getpwuid(getuid());
265 while (*filename != 0 && *filename != '/')
268 pw = getpwnam(buffer);
273 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
277 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
279 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
284 strcpy(buffer, pw->pw_dir);
285 strcat(buffer, filename);
289 /* If using a lock file, prepare by creating the lock file name and
290 the hitching post name. */
296 printf("exim_lock: failed to find host name using uname()\n");
299 primary_hostname = s.nodename;
301 len = (int)strlen(filename);
302 lockname = malloc(len + 8);
303 sprintf(lockname, "%s.lock", filename);
304 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
306 /* Presumably, this must match appendfile.c */
307 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
308 (unsigned int)now, (unsigned int)getpid());
311 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
315 /* Locking retry loop */
317 for (j = 0; j < lock_retries; j++)
319 int sleep_before_retry = TRUE;
320 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
323 /* Try to build a lock file if so configured */
328 if (verbose) printf("exim_lock: creating lock file\n");
329 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
332 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
337 /* Apply hitching post algorithm. */
339 if ((rc = link(hitchname, lockname)) != 0)
340 rc2 = fstat(hd, &statbuf);
344 if (rc != 0 && (rc2 != 0 || statbuf.st_nlink != 2))
346 printf("exim_lock: failed to link hitching post to lock file\n");
351 if (!quiet) printf("exim_lock: lock file successfully created\n");
354 /* We are done if no other locking required. */
356 if (!use_fcntl && !use_flock && !use_mbx) break;
358 /* Open the file for writing. */
360 fd = open(filename, O_RDWR + O_APPEND);
363 printf("exim_lock: failed to open %s for writing: %s\n", filename,
369 /* If there is a timeout, implying blocked locking, we don't want to
370 sleep before any retries after this. */
372 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
373 sleep_before_retry = FALSE;
375 /* Lock using fcntl. There are pros and cons to using a blocking call vs
376 a non-blocking call and retries. Exim is non-blocking by default, but setting
377 a timeout changes it to blocking. */
379 if (!use_mbx && (use_fcntl || use_flock))
381 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
382 lock_flock_timeout) >= 0)
386 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
387 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
391 else goto RETRY; /* Message already output */
394 /* Lock using MBX rules. This is complicated and is documented with the
395 source of the c-client library that goes with Pine and IMAP. What has to
396 be done to interwork correctly is to take out a shared lock on the mailbox,
397 and an exclusive lock on a /tmp file. */
401 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
402 lock_flock_timeout) >= 0)
407 printf("exim_lock: fcntl() read lock successfully applied\n");
409 printf("exim_lock: fcntl() read lock successfully applied\n");
412 else goto RETRY; /* Message already output */
414 if (fstat(fd, &statbuf) < 0)
416 printf("exim_lock: fstat() of %s failed: %s\n", filename,
422 /* Set up file in /tmp and check its state if already existing. */
424 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
425 (long)statbuf.st_ino);
427 if (lstat(tempname, &statbuf) >= 0)
429 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
431 printf("exim_lock: symbolic link on lock name %s\n", tempname);
435 if (statbuf.st_nlink > 1)
437 printf("exim_lock: hard link to lock name %s\n", tempname);
443 mbx_tmp_oflags = O_RDWR | O_CREAT;
445 mbx_tmp_oflags |= O_NOFOLLOW;
447 md = open(tempname, mbx_tmp_oflags, 0600);
450 printf("exim_lock: failed to create mbx lock file %s: %s\n",
451 tempname, strerror(errno));
455 /* security fixes from 2010-05 */
456 if (lstat(tempname, &lstatbuf) < 0)
458 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
459 tempname, strerror(errno));
462 if (fstat(md, &statbuf2) < 0)
464 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
465 tempname, strerror(errno));
468 if ((statbuf2.st_nlink > 1) ||
469 (lstatbuf.st_nlink > 1) ||
470 (!S_ISREG(lstatbuf.st_mode)) ||
471 (lstatbuf.st_dev != statbuf2.st_dev) ||
472 (lstatbuf.st_ino != statbuf2.st_ino))
474 printf("exim_lock: race condition exploited against us when "
475 "locking \"%s\"\n", tempname);
479 (void)chmod(tempname, 0600);
481 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
482 lock_flock_timeout) >= 0)
487 printf("exim_lock: fcntl() lock successfully applied to mbx "
488 "lock file %s\n", tempname);
490 printf("exim_lock: flock() lock successfully applied to mbx "
491 "lock file %s\n", tempname);
494 /* This test checks for a race condition */
496 if (lstat(tempname, &statbuf) != 0 ||
497 fstat(md, &ostatbuf) != 0 ||
498 statbuf.st_dev != ostatbuf.st_dev ||
499 statbuf.st_ino != ostatbuf.st_ino)
501 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
502 "creation and locking\n", tempname);
507 else goto RETRY; /* Message already output */
510 /* Clean up before retrying */
517 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
519 if (!quiet) printf("exim_lock: %s closed\n", tempname);
526 printf("exim_lock: close failed: %s\n", strerror(errno));
528 if (!quiet) printf("exim_lock: file closed\n");
534 if (unlink(lockname) < 0)
535 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
537 if (!quiet) printf("exim_lock: lock file removed\n");
541 /* If a blocking call timed out, break the retry loop if the total time
542 so far is not less than than retries * interval. */
545 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
546 lock_fcntl_timeout : lock_flock_timeout) >=
547 lock_retries * lock_interval)
550 /* Wait a bit before retrying, except when it was a blocked fcntl() that
551 caused the problem. */
553 if (j < lock_retries && sleep_before_retry)
555 printf(" ... waiting\n");
556 sleep(lock_interval);
560 if (j >= lock_retries)
562 printf("exim_lock: locking failed too many times\n");
567 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
569 /* If there are no further arguments, run the user's shell; otherwise
570 the next argument is a command to run. */
574 command = getenv("SHELL");
575 if (command == NULL || *command == 0) command = "/bin/sh";
576 if (!quiet) printf("running %s ...\n", command);
581 if (!quiet) printf("running the command ...\n");
584 /* Run the command, saving and restoring the times if required. */
588 struct stat strestore;
590 stat(filename, &strestore);
592 ut.actime = strestore.st_atime;
593 ut.modtime = strestore.st_mtime;
594 utime(filename, &ut);
596 else i = system(command);
598 if(i && !quiet) printf("warning: nonzero status %d\n", i);
600 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
601 lock on the mailbox. This should be a non-blocking lock call, as there is no
608 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
610 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
614 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
617 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
619 if (!quiet) printf("exim_lock: %s closed\n", tempname);
625 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
627 if (!quiet) printf("exim_lock: %s closed\n", filename);
632 if (unlink(lockname) < 0)
633 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
635 if (!quiet) printf("exim_lock: lock file removed\n");