Start
[exim.git] / src / src / exim_lock.c
1 /* $Cambridge: exim/src/src/exim_lock.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
2
3 /* A program to lock a file exactly as Exim would, for investigation of
4 interlocking problems.
5
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()
10
11 Default is -fcntl -lockfile.
12
13 Argument: the name of the lock file
14 */
15
16 #include "os.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <signal.h>
22 #include <errno.h>
23 #include <time.h>
24 #include <netdb.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <utime.h>
28 #include <sys/utsname.h>
29 #include <sys/stat.h>
30 #include <sys/file.h>
31 #include <pwd.h>
32
33 /* Not all systems have flock() available. Those that do must define LOCK_SH
34 in sys/file.h. */
35
36 #ifndef LOCK_SH
37 #define NO_FLOCK
38 #endif
39
40
41 typedef int BOOL;
42 #define FALSE 0
43 #define TRUE  1
44
45
46 /* Flag for timeout signal handler */
47
48 static int sigalrm_seen = FALSE;
49
50
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. */
54
55 #ifndef OS_RESTARTING_SIGNAL
56   #define OS_RESTARTING_SIGNAL
57 #endif
58
59 #ifndef OS_STRSIGNAL
60   #define OS_STRSIGNAL
61 #endif
62
63 #ifndef OS_STREXIT
64   #define OS_STREXIT
65 #endif
66
67 #ifndef OS_LOAD_AVERAGE
68   #define OS_LOAD_AVERAGE
69 #endif
70
71 #ifndef FIND_RUNNING_INTERFACES
72   #define FIND_RUNNING_INTERFACES
73 #endif
74
75 #include "../src/os.c"
76
77
78
79 /*************************************************
80 *             Timeout handler                    *
81 *************************************************/
82
83 static void
84 sigalrm_handler(int sig)
85 {
86 sig = sig;      /* Keep picky compilers happy */
87 sigalrm_seen = TRUE;
88 }
89
90
91
92 /*************************************************
93 *           Give usage and die                   *
94 *************************************************/
95
96 static void
97 usage(void)
98 {
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");
102 exit(1);
103 }
104
105
106
107 /*************************************************
108 *         Apply a lock to a file descriptor      *
109 *************************************************/
110
111 static int
112 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
113     int flocktime)
114 {
115 int yield = 0;
116 int save_errno;
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;
120
121 sigalrm_seen = FALSE;
122
123 if (dofcntl)
124   {
125   if (fcntltime > 0)
126     {
127     os_non_restarting_signal(SIGALRM, sigalrm_handler);
128     alarm(fcntltime);
129     yield = fcntl(fd, F_SETLKW, &lock_data);
130     save_errno = errno;
131     alarm(0);
132     errno = save_errno;
133     }
134   else yield = fcntl(fd, F_SETLK, &lock_data);
135   if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
136   }
137
138 #ifndef NO_FLOCK
139 if (doflock && (yield >= 0))
140   {
141   int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
142   if (flocktime > 0)
143     {
144     os_non_restarting_signal(SIGALRM, sigalrm_handler);
145     alarm(flocktime);
146     yield = flock(fd, flocktype);
147     save_errno = errno;
148     alarm(0);
149     errno = save_errno;
150     }
151   else yield = flock(fd, flocktype | LOCK_NB);
152   if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
153   }
154 #endif
155
156 return yield;
157 }
158
159
160
161 /*************************************************
162 *           The exim_lock program                *
163 *************************************************/
164
165 int main(int argc, char **argv)
166 {
167 int  lock_retries = 10;
168 int  lock_interval = 3;
169 int  lock_fcntl_timeout = 0;
170 int  lock_flock_timeout = 0;
171 int  i, j, len;
172 int  fd = -1;
173 int  hd = -1;
174 int  md = -1;
175 int  yield = 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;
182 BOOL quiet = FALSE;
183 BOOL restore_times = FALSE;
184 char *filename;
185 char *lockname = NULL, *hitchname = NULL;
186 char *primary_hostname, *command;
187 struct utsname s;
188 char buffer[256];
189 char tempname[256];
190
191 /* Decode options */
192
193 for (i = 1; i < argc; i++)
194   {
195   char *arg = argv[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;
204   else if (++i < argc)
205     {
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;
211     else usage();
212     }
213   else usage();
214   }
215
216 if (quiet) verbose = 0;
217
218 /* Can't use flock() if the OS doesn't provide it */
219
220 #ifdef NO_FLOCK
221 if (use_flock)
222   {
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");
225   exit(1);
226   }
227 #endif
228
229 /* Default is to use lockfiles and fcntl(). */
230
231 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
232   use_lockfile = use_fcntl = TRUE;
233
234 /* Default fcntl() for use with mbx */
235
236 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
237
238 /* Unset unused timeouts */
239
240 if (!use_fcntl) lock_fcntl_timeout = 0;
241 if (!use_flock) lock_flock_timeout = 0;
242
243 /* A file name is required */
244
245 if (i >= argc) usage();
246
247 filename = argv[i++];
248
249 /* Expand file names starting with ~ */
250
251 if (*filename == '~')
252   {
253   struct passwd *pw;
254
255   if (*(++filename) == '/')
256     pw = getpwuid(getuid());
257   else
258     {
259     char *s = buffer;
260     while (*filename != 0 && *filename != '/')
261       *s++ = *filename++;
262     *s = 0;
263     pw = getpwnam(buffer);
264     }
265
266   if (pw == NULL)
267     {
268     printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
269     exit(1);
270     }
271
272   if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
273     {
274     printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
275       filename);
276     exit(1);
277     }
278
279   strcpy(buffer, pw->pw_dir);
280   strcat(buffer, filename);
281   filename = buffer;
282   }
283
284 /* If using a lock file, prepare by creating the lock file name and
285 the hitching post name. */
286
287 if (use_lockfile)
288   {
289   if (uname(&s) < 0)
290     {
291     printf("exim_lock: failed to find host name using uname()\n");
292     exit(1);
293     }
294   primary_hostname = s.nodename;
295
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,
301     now, (int)getpid());
302
303   if (verbose)
304     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
305       hitchname);
306   }
307
308 /* Locking retry loop */
309
310 for (j = 0; j < lock_retries; j++)
311   {
312   int sleep_before_retry = TRUE;
313   struct stat statbuf, ostatbuf;
314
315   /* Try to build a lock file if so configured */
316
317   if (use_lockfile)
318     {
319     int rc;
320     if (verbose) printf("exim_lock: creating lock file\n");
321     hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
322     if (hd < 0)
323       {
324       printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
325         strerror(errno));
326       exit(1);
327       }
328
329     /* Apply hitching post algorithm. */
330
331     if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
332     close(hd);
333     unlink(hitchname);
334
335     if (rc != 0 && statbuf.st_nlink != 2)
336       {
337       printf("exim_lock: failed to link hitching post to lock file\n");
338       hd = -1;
339       goto RETRY;
340       }
341
342     if (!quiet) printf("exim_lock: lock file successfully created\n");
343     }
344
345   /* We are done if no other locking required. */
346
347   if (!use_fcntl && !use_flock && !use_mbx) break;
348
349   /* Open the file for writing. */
350
351   fd = open(filename, O_RDWR + O_APPEND);
352   if (fd < 0)
353     {
354     printf("exim_lock: failed to open %s for writing: %s\n", filename,
355       strerror(errno));
356     yield = 1;
357     goto CLEAN_UP;
358     }
359
360   /* If there is a timeout, implying blocked locking, we don't want to
361   sleep before any retries after this. */
362
363   if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
364     sleep_before_retry = FALSE;
365
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. */
369
370   if (!use_mbx && (use_fcntl || use_flock))
371     {
372     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
373         lock_flock_timeout) >= 0)
374       {
375       if (!quiet)
376         {
377         if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
378         if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
379         }
380       break;
381       }
382     else goto RETRY;   /* Message already output */
383     }
384
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. */
389
390   else
391     {
392     if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
393         lock_flock_timeout) >= 0)
394       {
395       if (!quiet)
396         {
397         if (use_fcntl)
398           printf("exim_lock: fcntl() read lock successfully applied\n");
399         if (use_flock)
400           printf("exim_lock: fcntl() read lock successfully applied\n");
401         }
402       }
403     else goto RETRY;   /* Message already output */
404
405     if (fstat(fd, &statbuf) < 0)
406       {
407       printf("exim_lock: fstat() of %s failed: %s\n", filename,
408         strerror(errno));
409       yield = 1;
410       goto CLEAN_UP;
411       }
412
413     /* Set up file in /tmp and check its state if already existing. */
414
415     sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
416       (long)statbuf.st_ino);
417
418     if (lstat(tempname, &statbuf) >= 0)
419       {
420       if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
421         {
422         printf("exim_lock: symbolic link on lock name %s\n", tempname);
423         yield = 1;
424         goto CLEAN_UP;
425         }
426       if (statbuf.st_nlink > 1)
427         {
428         printf("exim_lock: hard link to lock name %s\n", tempname);
429         yield = 1;
430         goto CLEAN_UP;
431         }
432       }
433
434     md = open(tempname, O_RDWR | O_CREAT, 0600);
435     if (md < 0)
436       {
437       printf("exim_lock: failed to create mbx lock file %s: %s\n",
438         tempname, strerror(errno));
439       goto CLEAN_UP;
440       }
441
442     chmod (tempname, 0600);
443
444     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
445         lock_flock_timeout) >= 0)
446       {
447       if (!quiet)
448         {
449         if (use_fcntl)
450           printf("exim_lock: fcntl() lock successfully applied to mbx "
451             "lock file %s\n", tempname);
452         if (use_flock)
453           printf("exim_lock: flock() lock successfully applied to mbx "
454             "lock file %s\n", tempname);
455         }
456
457       /* This test checks for a race condition */
458
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)
463        {
464        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
465            "creation and locking\n", tempname);
466        goto RETRY;
467        }
468       else break;
469       }
470     else goto RETRY;   /* Message already output */
471     }
472
473   /* Clean up before retrying */
474
475   RETRY:
476
477   if (md >= 0)
478     {
479     if (close(md) < 0)
480       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
481     else
482       if (!quiet) printf("exim_lock: %s closed\n", tempname);
483     md = -1;
484     }
485
486   if (fd >= 0)
487     {
488     if (close(fd) < 0)
489       printf("exim_lock: close failed: %s\n", strerror(errno));
490     else
491       if (!quiet) printf("exim_lock: file closed\n");
492     fd = -1;
493     }
494
495   if (hd >= 0)
496     {
497     if (unlink(lockname) < 0)
498       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
499     else
500       if (!quiet) printf("exim_lock: lock file removed\n");
501     hd = -1;
502     }
503
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. */
506
507   if (sigalrm_seen &&
508       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
509         lock_fcntl_timeout : lock_flock_timeout) >=
510           lock_retries * lock_interval)
511     j = lock_retries;
512
513   /* Wait a bit before retrying, except when it was a blocked fcntl() that
514   caused the problem. */
515
516   if (j < lock_retries && sleep_before_retry)
517     {
518     printf(" ... waiting\n");
519     sleep(lock_interval);
520     }
521   }
522
523 if (j >= lock_retries)
524   {
525   printf("exim_lock: locking failed too many times\n");
526   yield = 1;
527   goto CLEAN_UP;
528   }
529
530 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
531
532 /* If there are no further arguments, run the user's shell; otherwise
533 the next argument is a command to run. */
534
535 if (i >= argc)
536   {
537   command = getenv("SHELL");
538   if (command == NULL || *command == 0) command = "/bin/sh";
539   if (!quiet) printf("running %s ...\n", command);
540   }
541 else
542   {
543   command = argv[i];
544   if (!quiet) printf("running the command ...\n");
545   }
546
547 /* Run the command, saving and restoring the times if required. */
548
549 if (restore_times)
550   {
551   struct stat strestore;
552   struct utimbuf ut;
553   stat(filename, &strestore);
554   system(command);
555   ut.actime = strestore.st_atime;
556   ut.modtime = strestore.st_mtime;
557   utime(filename, &ut);
558   }
559 else system(command);
560
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
563 point in waiting. */
564
565 CLEAN_UP:
566
567 if (md >= 0)
568   {
569   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
570     {
571     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
572     unlink(tempname);
573     }
574   else if (!quiet)
575     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
576       tempname);
577   if (close(md) < 0)
578     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
579   else
580     if (!quiet) printf("exim_lock: %s closed\n", tempname);
581   }
582
583 if (fd >= 0)
584   {
585   if (close(fd) < 0)
586     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
587   else
588     if (!quiet) printf("exim_lock: %s closed\n", filename);
589   }
590
591 if (hd >= 0)
592   {
593   if (unlink(lockname) < 0)
594     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
595   else
596     if (!quiet) printf("exim_lock: lock file removed\n");
597   }
598
599 return yield;
600 }
601
602 /* End */