Compiler masochism compliance.
[exim.git] / src / src / exim_lock.c
1 /* $Cambridge: exim/src/src/exim_lock.c,v 1.4 2010/05/29 12:11:48 pdp 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;
187 const char *command;
188 struct utsname s;
189 char buffer[256];
190 char tempname[256];
191
192 /* Decode options */
193
194 for (i = 1; i < argc; i++)
195   {
196   char *arg = argv[i];
197   if (*arg != '-') break;
198   if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
199   else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
200   else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
201   else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
202   else if (strcmp(arg, "-v") == 0) verbose = TRUE;
203   else if (strcmp(arg, "-q") == 0) quiet = TRUE;
204   else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
205   else if (++i < argc)
206     {
207     int value = atoi(argv[i]);
208     if (strcmp(arg, "-retries") == 0) lock_retries = value;
209     else if (strcmp(arg, "-interval") == 0) lock_interval = value;
210     else if (strcmp(arg, "-timeout") == 0)
211       lock_fcntl_timeout = lock_flock_timeout = value;
212     else usage();
213     }
214   else usage();
215   }
216
217 if (quiet) verbose = 0;
218
219 /* Can't use flock() if the OS doesn't provide it */
220
221 #ifdef NO_FLOCK
222 if (use_flock)
223   {
224   printf("exim_lock: can't use flock() because it was not available in the\n"
225          "           operating system when exim_lock was compiled\n");
226   exit(1);
227   }
228 #endif
229
230 /* Default is to use lockfiles and fcntl(). */
231
232 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
233   use_lockfile = use_fcntl = TRUE;
234
235 /* Default fcntl() for use with mbx */
236
237 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
238
239 /* Unset unused timeouts */
240
241 if (!use_fcntl) lock_fcntl_timeout = 0;
242 if (!use_flock) lock_flock_timeout = 0;
243
244 /* A file name is required */
245
246 if (i >= argc) usage();
247
248 filename = argv[i++];
249
250 /* Expand file names starting with ~ */
251
252 if (*filename == '~')
253   {
254   struct passwd *pw;
255
256   if (*(++filename) == '/')
257     pw = getpwuid(getuid());
258   else
259     {
260     char *s = buffer;
261     while (*filename != 0 && *filename != '/')
262       *s++ = *filename++;
263     *s = 0;
264     pw = getpwnam(buffer);
265     }
266
267   if (pw == NULL)
268     {
269     printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
270     exit(1);
271     }
272
273   if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
274     {
275     printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
276       filename);
277     exit(1);
278     }
279
280   strcpy(buffer, pw->pw_dir);
281   strcat(buffer, filename);
282   filename = buffer;
283   }
284
285 /* If using a lock file, prepare by creating the lock file name and
286 the hitching post name. */
287
288 if (use_lockfile)
289   {
290   if (uname(&s) < 0)
291     {
292     printf("exim_lock: failed to find host name using uname()\n");
293     exit(1);
294     }
295   primary_hostname = s.nodename;
296
297   len = (int)strlen(filename);
298   lockname = malloc(len + 8);
299   sprintf(lockname, "%s.lock", filename);
300   hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
301   sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
302     now, (int)getpid());
303
304   if (verbose)
305     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
306       hitchname);
307   }
308
309 /* Locking retry loop */
310
311 for (j = 0; j < lock_retries; j++)
312   {
313   int sleep_before_retry = TRUE;
314   struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
315   int mbx_tmp_oflags;
316
317   /* Try to build a lock file if so configured */
318
319   if (use_lockfile)
320     {
321     int rc;
322     if (verbose) printf("exim_lock: creating lock file\n");
323     hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
324     if (hd < 0)
325       {
326       printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
327         strerror(errno));
328       exit(1);
329       }
330
331     /* Apply hitching post algorithm. */
332
333     if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
334     (void)close(hd);
335     unlink(hitchname);
336
337     if (rc != 0 && statbuf.st_nlink != 2)
338       {
339       printf("exim_lock: failed to link hitching post to lock file\n");
340       hd = -1;
341       goto RETRY;
342       }
343
344     if (!quiet) printf("exim_lock: lock file successfully created\n");
345     }
346
347   /* We are done if no other locking required. */
348
349   if (!use_fcntl && !use_flock && !use_mbx) break;
350
351   /* Open the file for writing. */
352
353   fd = open(filename, O_RDWR + O_APPEND);
354   if (fd < 0)
355     {
356     printf("exim_lock: failed to open %s for writing: %s\n", filename,
357       strerror(errno));
358     yield = 1;
359     goto CLEAN_UP;
360     }
361
362   /* If there is a timeout, implying blocked locking, we don't want to
363   sleep before any retries after this. */
364
365   if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
366     sleep_before_retry = FALSE;
367
368   /* Lock using fcntl. There are pros and cons to using a blocking call vs
369   a non-blocking call and retries. Exim is non-blocking by default, but setting
370   a timeout changes it to blocking. */
371
372   if (!use_mbx && (use_fcntl || use_flock))
373     {
374     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
375         lock_flock_timeout) >= 0)
376       {
377       if (!quiet)
378         {
379         if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
380         if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
381         }
382       break;
383       }
384     else goto RETRY;   /* Message already output */
385     }
386
387   /* Lock using MBX rules. This is complicated and is documented with the
388   source of the c-client library that goes with Pine and IMAP. What has to
389   be done to interwork correctly is to take out a shared lock on the mailbox,
390   and an exclusive lock on a /tmp file. */
391
392   else
393     {
394     if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
395         lock_flock_timeout) >= 0)
396       {
397       if (!quiet)
398         {
399         if (use_fcntl)
400           printf("exim_lock: fcntl() read lock successfully applied\n");
401         if (use_flock)
402           printf("exim_lock: fcntl() read lock successfully applied\n");
403         }
404       }
405     else goto RETRY;   /* Message already output */
406
407     if (fstat(fd, &statbuf) < 0)
408       {
409       printf("exim_lock: fstat() of %s failed: %s\n", filename,
410         strerror(errno));
411       yield = 1;
412       goto CLEAN_UP;
413       }
414
415     /* Set up file in /tmp and check its state if already existing. */
416
417     sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
418       (long)statbuf.st_ino);
419
420     if (lstat(tempname, &statbuf) >= 0)
421       {
422       if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
423         {
424         printf("exim_lock: symbolic link on lock name %s\n", tempname);
425         yield = 1;
426         goto CLEAN_UP;
427         }
428       if (statbuf.st_nlink > 1)
429         {
430         printf("exim_lock: hard link to lock name %s\n", tempname);
431         yield = 1;
432         goto CLEAN_UP;
433         }
434       }
435
436     mbx_tmp_oflags = O_RDWR | O_CREAT;
437 #ifdef O_NOFOLLOW
438     mbx_tmp_oflags |= O_NOFOLLOW;
439 #endif
440     md = open(tempname, mbx_tmp_oflags, 0600);
441     if (md < 0)
442       {
443       printf("exim_lock: failed to create mbx lock file %s: %s\n",
444         tempname, strerror(errno));
445       goto CLEAN_UP;
446       }
447
448     /* security fixes from 2010-05 */
449     if (lstat(tempname, &lstatbuf) < 0)
450       {
451       printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
452           tempname, strerror(errno));
453       goto CLEAN_UP;
454       }
455     if (fstat(md, &statbuf2) < 0)
456       {
457       printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
458           tempname, strerror(errno));
459       goto CLEAN_UP;
460       }
461     if ((statbuf2.st_nlink > 1) ||
462         (lstatbuf.st_nlink > 1) ||
463         (!S_ISREG(lstatbuf.st_mode)) ||
464         (lstatbuf.st_dev != statbuf2.st_dev) ||
465         (lstatbuf.st_ino != statbuf2.st_ino))
466       {
467       printf("exim_lock: race condition exploited against us when "
468           "locking \"%s\"\n", tempname);
469       goto CLEAN_UP;
470       }
471
472     (void)chmod(tempname, 0600);
473
474     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
475         lock_flock_timeout) >= 0)
476       {
477       if (!quiet)
478         {
479         if (use_fcntl)
480           printf("exim_lock: fcntl() lock successfully applied to mbx "
481             "lock file %s\n", tempname);
482         if (use_flock)
483           printf("exim_lock: flock() lock successfully applied to mbx "
484             "lock file %s\n", tempname);
485         }
486
487       /* This test checks for a race condition */
488
489       if (lstat(tempname, &statbuf) != 0 ||
490           fstat(md, &ostatbuf) != 0 ||
491           statbuf.st_dev != ostatbuf.st_dev ||
492           statbuf.st_ino != ostatbuf.st_ino)
493        {
494        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
495            "creation and locking\n", tempname);
496        goto RETRY;
497        }
498       else break;
499       }
500     else goto RETRY;   /* Message already output */
501     }
502
503   /* Clean up before retrying */
504
505   RETRY:
506
507   if (md >= 0)
508     {
509     if (close(md) < 0)
510       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
511     else
512       if (!quiet) printf("exim_lock: %s closed\n", tempname);
513     md = -1;
514     }
515
516   if (fd >= 0)
517     {
518     if (close(fd) < 0)
519       printf("exim_lock: close failed: %s\n", strerror(errno));
520     else
521       if (!quiet) printf("exim_lock: file closed\n");
522     fd = -1;
523     }
524
525   if (hd >= 0)
526     {
527     if (unlink(lockname) < 0)
528       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
529     else
530       if (!quiet) printf("exim_lock: lock file removed\n");
531     hd = -1;
532     }
533
534   /* If a blocking call timed out, break the retry loop if the total time
535   so far is not less than than retries * interval. */
536
537   if (sigalrm_seen &&
538       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
539         lock_fcntl_timeout : lock_flock_timeout) >=
540           lock_retries * lock_interval)
541     j = lock_retries;
542
543   /* Wait a bit before retrying, except when it was a blocked fcntl() that
544   caused the problem. */
545
546   if (j < lock_retries && sleep_before_retry)
547     {
548     printf(" ... waiting\n");
549     sleep(lock_interval);
550     }
551   }
552
553 if (j >= lock_retries)
554   {
555   printf("exim_lock: locking failed too many times\n");
556   yield = 1;
557   goto CLEAN_UP;
558   }
559
560 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
561
562 /* If there are no further arguments, run the user's shell; otherwise
563 the next argument is a command to run. */
564
565 if (i >= argc)
566   {
567   command = getenv("SHELL");
568   if (command == NULL || *command == 0) command = "/bin/sh";
569   if (!quiet) printf("running %s ...\n", command);
570   }
571 else
572   {
573   command = argv[i];
574   if (!quiet) printf("running the command ...\n");
575   }
576
577 /* Run the command, saving and restoring the times if required. */
578
579 if (restore_times)
580   {
581   struct stat strestore;
582   struct utimbuf ut;
583   stat(filename, &strestore);
584   (void)system(command);
585   ut.actime = strestore.st_atime;
586   ut.modtime = strestore.st_mtime;
587   utime(filename, &ut);
588   }
589 else (void)system(command);
590
591 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
592 lock on the mailbox. This should be a non-blocking lock call, as there is no
593 point in waiting. */
594
595 CLEAN_UP:
596
597 if (md >= 0)
598   {
599   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
600     {
601     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
602     unlink(tempname);
603     }
604   else if (!quiet)
605     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
606       tempname);
607   if (close(md) < 0)
608     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
609   else
610     if (!quiet) printf("exim_lock: %s closed\n", tempname);
611   }
612
613 if (fd >= 0)
614   {
615   if (close(fd) < 0)
616     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
617   else
618     if (!quiet) printf("exim_lock: %s closed\n", filename);
619   }
620
621 if (hd >= 0)
622   {
623   if (unlink(lockname) < 0)
624     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
625   else
626     if (!quiet) printf("exim_lock: lock file removed\n");
627   }
628
629 return yield;
630 }
631
632 /* End */