1 /* $Cambridge: exim/src/src/exim_lock.c,v 1.3 2005/06/27 14:29:43 ph10 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, *command;
193 for (i = 1; i < argc; i++)
196 if (*arg != '-') break;
197 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
198 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
199 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
200 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
201 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
202 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
203 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
206 int value = atoi(argv[i]);
207 if (strcmp(arg, "-retries") == 0) lock_retries = value;
208 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
209 else if (strcmp(arg, "-timeout") == 0)
210 lock_fcntl_timeout = lock_flock_timeout = value;
216 if (quiet) verbose = 0;
218 /* Can't use flock() if the OS doesn't provide it */
223 printf("exim_lock: can't use flock() because it was not available in the\n"
224 " operating system when exim_lock was compiled\n");
229 /* Default is to use lockfiles and fcntl(). */
231 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
232 use_lockfile = use_fcntl = TRUE;
234 /* Default fcntl() for use with mbx */
236 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
238 /* Unset unused timeouts */
240 if (!use_fcntl) lock_fcntl_timeout = 0;
241 if (!use_flock) lock_flock_timeout = 0;
243 /* A file name is required */
245 if (i >= argc) usage();
247 filename = argv[i++];
249 /* Expand file names starting with ~ */
251 if (*filename == '~')
255 if (*(++filename) == '/')
256 pw = getpwuid(getuid());
260 while (*filename != 0 && *filename != '/')
263 pw = getpwnam(buffer);
268 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
272 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
274 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
279 strcpy(buffer, pw->pw_dir);
280 strcat(buffer, filename);
284 /* If using a lock file, prepare by creating the lock file name and
285 the hitching post name. */
291 printf("exim_lock: failed to find host name using uname()\n");
294 primary_hostname = s.nodename;
296 len = (int)strlen(filename);
297 lockname = malloc(len + 8);
298 sprintf(lockname, "%s.lock", filename);
299 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
300 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
304 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
308 /* Locking retry loop */
310 for (j = 0; j < lock_retries; j++)
312 int sleep_before_retry = TRUE;
313 struct stat statbuf, ostatbuf;
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 md = open(tempname, O_RDWR | O_CREAT, 0600);
437 printf("exim_lock: failed to create mbx lock file %s: %s\n",
438 tempname, strerror(errno));
442 (void)chmod(tempname, 0600);
444 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
445 lock_flock_timeout) >= 0)
450 printf("exim_lock: fcntl() lock successfully applied to mbx "
451 "lock file %s\n", tempname);
453 printf("exim_lock: flock() lock successfully applied to mbx "
454 "lock file %s\n", tempname);
457 /* This test checks for a race condition */
459 if (lstat(tempname, &statbuf) != 0 ||
460 fstat(md, &ostatbuf) != 0 ||
461 statbuf.st_dev != ostatbuf.st_dev ||
462 statbuf.st_ino != ostatbuf.st_ino)
464 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
465 "creation and locking\n", tempname);
470 else goto RETRY; /* Message already output */
473 /* Clean up before retrying */
480 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
482 if (!quiet) printf("exim_lock: %s closed\n", tempname);
489 printf("exim_lock: close failed: %s\n", strerror(errno));
491 if (!quiet) printf("exim_lock: file closed\n");
497 if (unlink(lockname) < 0)
498 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
500 if (!quiet) printf("exim_lock: lock file removed\n");
504 /* If a blocking call timed out, break the retry loop if the total time
505 so far is not less than than retries * interval. */
508 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
509 lock_fcntl_timeout : lock_flock_timeout) >=
510 lock_retries * lock_interval)
513 /* Wait a bit before retrying, except when it was a blocked fcntl() that
514 caused the problem. */
516 if (j < lock_retries && sleep_before_retry)
518 printf(" ... waiting\n");
519 sleep(lock_interval);
523 if (j >= lock_retries)
525 printf("exim_lock: locking failed too many times\n");
530 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
532 /* If there are no further arguments, run the user's shell; otherwise
533 the next argument is a command to run. */
537 command = getenv("SHELL");
538 if (command == NULL || *command == 0) command = "/bin/sh";
539 if (!quiet) printf("running %s ...\n", command);
544 if (!quiet) printf("running the command ...\n");
547 /* Run the command, saving and restoring the times if required. */
551 struct stat strestore;
553 stat(filename, &strestore);
554 (void)system(command);
555 ut.actime = strestore.st_atime;
556 ut.modtime = strestore.st_mtime;
557 utime(filename, &ut);
559 else (void)system(command);
561 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
562 lock on the mailbox. This should be a non-blocking lock call, as there is no
569 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
571 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
575 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
578 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
580 if (!quiet) printf("exim_lock: %s closed\n", tempname);
586 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
588 if (!quiet) printf("exim_lock: %s closed\n", filename);
593 if (unlink(lockname) < 0)
594 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
596 if (!quiet) printf("exim_lock: lock file removed\n");