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 #ifndef OS_GET_DNS_RESOLVER_RES
74 #define OS_GET_DNS_RESOLVER_RES
77 #include "../src/os.c"
81 /*************************************************
83 *************************************************/
86 sigalrm_handler(int sig)
88 sig = sig; /* Keep picky compilers happy */
94 /*************************************************
95 * Give usage and die *
96 *************************************************/
101 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
102 " [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
103 " <file name> [command]\n");
109 /*************************************************
110 * Apply a lock to a file descriptor *
111 *************************************************/
114 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
119 struct flock lock_data;
120 lock_data.l_type = fcntltype;
121 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
123 sigalrm_seen = FALSE;
129 os_non_restarting_signal(SIGALRM, sigalrm_handler);
131 yield = fcntl(fd, F_SETLKW, &lock_data);
136 else yield = fcntl(fd, F_SETLK, &lock_data);
137 if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
141 if (doflock && (yield >= 0))
143 int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
146 os_non_restarting_signal(SIGALRM, sigalrm_handler);
148 yield = flock(fd, flocktype);
153 else yield = flock(fd, flocktype | LOCK_NB);
154 if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
163 /*************************************************
164 * The exim_lock program *
165 *************************************************/
167 int main(int argc, char **argv)
169 int lock_retries = 10;
170 int lock_interval = 3;
171 int lock_fcntl_timeout = 0;
172 int lock_flock_timeout = 0;
178 int now = time(NULL);
179 BOOL use_lockfile = FALSE;
180 BOOL use_fcntl = FALSE;
181 BOOL use_flock = FALSE;
182 BOOL use_mbx = FALSE;
183 BOOL verbose = FALSE;
185 BOOL restore_times = FALSE;
187 char *lockname = NULL, *hitchname = NULL;
188 char *primary_hostname;
196 for (i = 1; i < argc; i++)
199 if (*arg != '-') break;
200 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
201 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
202 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
203 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
204 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
205 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
206 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
209 int value = atoi(argv[i]);
210 if (strcmp(arg, "-retries") == 0) lock_retries = value;
211 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
212 else if (strcmp(arg, "-timeout") == 0)
213 lock_fcntl_timeout = lock_flock_timeout = value;
219 if (quiet) verbose = 0;
221 /* Can't use flock() if the OS doesn't provide it */
226 printf("exim_lock: can't use flock() because it was not available in the\n"
227 " operating system when exim_lock was compiled\n");
232 /* Default is to use lockfiles and fcntl(). */
234 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
235 use_lockfile = use_fcntl = TRUE;
237 /* Default fcntl() for use with mbx */
239 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
241 /* Unset unused timeouts */
243 if (!use_fcntl) lock_fcntl_timeout = 0;
244 if (!use_flock) lock_flock_timeout = 0;
246 /* A file name is required */
248 if (i >= argc) usage();
250 filename = argv[i++];
252 /* Expand file names starting with ~ */
254 if (*filename == '~')
258 if (*(++filename) == '/')
259 pw = getpwuid(getuid());
263 while (*filename != 0 && *filename != '/')
266 pw = getpwnam(buffer);
271 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
275 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
277 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
282 strcpy(buffer, pw->pw_dir);
283 strcat(buffer, filename);
287 /* If using a lock file, prepare by creating the lock file name and
288 the hitching post name. */
294 printf("exim_lock: failed to find host name using uname()\n");
297 primary_hostname = s.nodename;
299 len = (int)strlen(filename);
300 lockname = malloc(len + 8);
301 sprintf(lockname, "%s.lock", filename);
302 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
303 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
307 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
311 /* Locking retry loop */
313 for (j = 0; j < lock_retries; j++)
315 int sleep_before_retry = TRUE;
316 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
319 /* Try to build a lock file if so configured */
324 if (verbose) printf("exim_lock: creating lock file\n");
325 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
328 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
333 /* Apply hitching post algorithm. */
335 if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
339 if (rc != 0 && statbuf.st_nlink != 2)
341 printf("exim_lock: failed to link hitching post to lock file\n");
346 if (!quiet) printf("exim_lock: lock file successfully created\n");
349 /* We are done if no other locking required. */
351 if (!use_fcntl && !use_flock && !use_mbx) break;
353 /* Open the file for writing. */
355 fd = open(filename, O_RDWR + O_APPEND);
358 printf("exim_lock: failed to open %s for writing: %s\n", filename,
364 /* If there is a timeout, implying blocked locking, we don't want to
365 sleep before any retries after this. */
367 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
368 sleep_before_retry = FALSE;
370 /* Lock using fcntl. There are pros and cons to using a blocking call vs
371 a non-blocking call and retries. Exim is non-blocking by default, but setting
372 a timeout changes it to blocking. */
374 if (!use_mbx && (use_fcntl || use_flock))
376 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
377 lock_flock_timeout) >= 0)
381 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
382 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
386 else goto RETRY; /* Message already output */
389 /* Lock using MBX rules. This is complicated and is documented with the
390 source of the c-client library that goes with Pine and IMAP. What has to
391 be done to interwork correctly is to take out a shared lock on the mailbox,
392 and an exclusive lock on a /tmp file. */
396 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
397 lock_flock_timeout) >= 0)
402 printf("exim_lock: fcntl() read lock successfully applied\n");
404 printf("exim_lock: fcntl() read lock successfully applied\n");
407 else goto RETRY; /* Message already output */
409 if (fstat(fd, &statbuf) < 0)
411 printf("exim_lock: fstat() of %s failed: %s\n", filename,
417 /* Set up file in /tmp and check its state if already existing. */
419 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
420 (long)statbuf.st_ino);
422 if (lstat(tempname, &statbuf) >= 0)
424 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
426 printf("exim_lock: symbolic link on lock name %s\n", tempname);
430 if (statbuf.st_nlink > 1)
432 printf("exim_lock: hard link to lock name %s\n", tempname);
438 mbx_tmp_oflags = O_RDWR | O_CREAT;
440 mbx_tmp_oflags |= O_NOFOLLOW;
442 md = open(tempname, mbx_tmp_oflags, 0600);
445 printf("exim_lock: failed to create mbx lock file %s: %s\n",
446 tempname, strerror(errno));
450 /* security fixes from 2010-05 */
451 if (lstat(tempname, &lstatbuf) < 0)
453 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
454 tempname, strerror(errno));
457 if (fstat(md, &statbuf2) < 0)
459 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
460 tempname, strerror(errno));
463 if ((statbuf2.st_nlink > 1) ||
464 (lstatbuf.st_nlink > 1) ||
465 (!S_ISREG(lstatbuf.st_mode)) ||
466 (lstatbuf.st_dev != statbuf2.st_dev) ||
467 (lstatbuf.st_ino != statbuf2.st_ino))
469 printf("exim_lock: race condition exploited against us when "
470 "locking \"%s\"\n", tempname);
474 (void)chmod(tempname, 0600);
476 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
477 lock_flock_timeout) >= 0)
482 printf("exim_lock: fcntl() lock successfully applied to mbx "
483 "lock file %s\n", tempname);
485 printf("exim_lock: flock() lock successfully applied to mbx "
486 "lock file %s\n", tempname);
489 /* This test checks for a race condition */
491 if (lstat(tempname, &statbuf) != 0 ||
492 fstat(md, &ostatbuf) != 0 ||
493 statbuf.st_dev != ostatbuf.st_dev ||
494 statbuf.st_ino != ostatbuf.st_ino)
496 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
497 "creation and locking\n", tempname);
502 else goto RETRY; /* Message already output */
505 /* Clean up before retrying */
512 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
514 if (!quiet) printf("exim_lock: %s closed\n", tempname);
521 printf("exim_lock: close failed: %s\n", strerror(errno));
523 if (!quiet) printf("exim_lock: file closed\n");
529 if (unlink(lockname) < 0)
530 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
532 if (!quiet) printf("exim_lock: lock file removed\n");
536 /* If a blocking call timed out, break the retry loop if the total time
537 so far is not less than than retries * interval. */
540 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
541 lock_fcntl_timeout : lock_flock_timeout) >=
542 lock_retries * lock_interval)
545 /* Wait a bit before retrying, except when it was a blocked fcntl() that
546 caused the problem. */
548 if (j < lock_retries && sleep_before_retry)
550 printf(" ... waiting\n");
551 sleep(lock_interval);
555 if (j >= lock_retries)
557 printf("exim_lock: locking failed too many times\n");
562 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
564 /* If there are no further arguments, run the user's shell; otherwise
565 the next argument is a command to run. */
569 command = getenv("SHELL");
570 if (command == NULL || *command == 0) command = "/bin/sh";
571 if (!quiet) printf("running %s ...\n", command);
576 if (!quiet) printf("running the command ...\n");
579 /* Run the command, saving and restoring the times if required. */
583 struct stat strestore;
585 stat(filename, &strestore);
586 (void)system(command);
587 ut.actime = strestore.st_atime;
588 ut.modtime = strestore.st_mtime;
589 utime(filename, &ut);
591 else (void)system(command);
593 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
594 lock on the mailbox. This should be a non-blocking lock call, as there is no
601 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
603 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
607 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
610 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
612 if (!quiet) printf("exim_lock: %s closed\n", tempname);
618 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
620 if (!quiet) printf("exim_lock: %s closed\n", filename);
625 if (unlink(lockname) < 0)
626 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
628 if (!quiet) printf("exim_lock: lock file removed\n");