027a55831d1e106d09ad336ed51e5fe7b395e6e5
[users/heiko/exim.git] / src / src / exim_lock.c
1 /* A program to lock a file exactly as Exim would, for investigation of
2 interlocking problems.
3
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()
8
9 Default is -fcntl -lockfile.
10
11 Argument: the name of the lock file
12 */
13
14 #include "os.h"
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <signal.h>
20 #include <errno.h>
21 #include <time.h>
22 #include <netdb.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <utime.h>
26 #include <sys/utsname.h>
27 #include <sys/stat.h>
28 #include <sys/file.h>
29 #include <pwd.h>
30
31 /* Not all systems have flock() available. Those that do must define LOCK_SH
32 in sys/file.h. */
33
34 #ifndef LOCK_SH
35 #define NO_FLOCK
36 #endif
37
38
39 typedef int BOOL;
40 #define FALSE 0
41 #define TRUE  1
42
43
44 /* Flag for timeout signal handler */
45
46 static int sigalrm_seen = FALSE;
47
48
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. */
52
53 #ifndef OS_RESTARTING_SIGNAL
54   #define OS_RESTARTING_SIGNAL
55 #endif
56
57 #ifndef OS_STRSIGNAL
58   #define OS_STRSIGNAL
59 #endif
60
61 #ifndef OS_STREXIT
62   #define OS_STREXIT
63 #endif
64
65 #ifndef OS_LOAD_AVERAGE
66   #define OS_LOAD_AVERAGE
67 #endif
68
69 #ifndef FIND_RUNNING_INTERFACES
70   #define FIND_RUNNING_INTERFACES
71 #endif
72
73 #include "../src/os.c"
74
75
76
77 /*************************************************
78 *             Timeout handler                    *
79 *************************************************/
80
81 static void
82 sigalrm_handler(int sig)
83 {
84 sig = sig;      /* Keep picky compilers happy */
85 sigalrm_seen = TRUE;
86 }
87
88
89
90 /*************************************************
91 *           Give usage and die                   *
92 *************************************************/
93
94 static void
95 usage(void)
96 {
97 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
98        "       [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
99        "       <file name> [command]\n");
100 exit(1);
101 }
102
103
104
105 /*************************************************
106 *         Apply a lock to a file descriptor      *
107 *************************************************/
108
109 static int
110 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
111     int flocktime)
112 {
113 int yield = 0;
114 int save_errno;
115 struct flock lock_data;
116 lock_data.l_type = fcntltype;
117 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
118
119 sigalrm_seen = FALSE;
120
121 if (dofcntl)
122   {
123   if (fcntltime > 0)
124     {
125     os_non_restarting_signal(SIGALRM, sigalrm_handler);
126     alarm(fcntltime);
127     yield = fcntl(fd, F_SETLKW, &lock_data);
128     save_errno = errno;
129     alarm(0);
130     errno = save_errno;
131     }
132   else yield = fcntl(fd, F_SETLK, &lock_data);
133   if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
134   }
135
136 #ifndef NO_FLOCK
137 if (doflock && (yield >= 0))
138   {
139   int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
140   if (flocktime > 0)
141     {
142     os_non_restarting_signal(SIGALRM, sigalrm_handler);
143     alarm(flocktime);
144     yield = flock(fd, flocktype);
145     save_errno = errno;
146     alarm(0);
147     errno = save_errno;
148     }
149   else yield = flock(fd, flocktype | LOCK_NB);
150   if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
151   }
152 #endif
153
154 return yield;
155 }
156
157
158
159 /*************************************************
160 *           The exim_lock program                *
161 *************************************************/
162
163 int main(int argc, char **argv)
164 {
165 int  lock_retries = 10;
166 int  lock_interval = 3;
167 int  lock_fcntl_timeout = 0;
168 int  lock_flock_timeout = 0;
169 int  i, j, len;
170 int  fd = -1;
171 int  hd = -1;
172 int  md = -1;
173 int  yield = 0;
174 int  now = time(NULL);
175 BOOL use_lockfile = FALSE;
176 BOOL use_fcntl = FALSE;
177 BOOL use_flock = FALSE;
178 BOOL use_mbx = FALSE;
179 BOOL verbose = FALSE;
180 BOOL quiet = FALSE;
181 BOOL restore_times = FALSE;
182 char *filename;
183 char *lockname = NULL, *hitchname = NULL;
184 char *primary_hostname;
185 const char *command;
186 struct utsname s;
187 char buffer[256];
188 char tempname[256];
189
190 /* Decode options */
191
192 for (i = 1; i < argc; i++)
193   {
194   char *arg = argv[i];
195   if (*arg != '-') break;
196   if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
197   else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
198   else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
199   else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
200   else if (strcmp(arg, "-v") == 0) verbose = TRUE;
201   else if (strcmp(arg, "-q") == 0) quiet = TRUE;
202   else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
203   else if (++i < argc)
204     {
205     int value = atoi(argv[i]);
206     if (strcmp(arg, "-retries") == 0) lock_retries = value;
207     else if (strcmp(arg, "-interval") == 0) lock_interval = value;
208     else if (strcmp(arg, "-timeout") == 0)
209       lock_fcntl_timeout = lock_flock_timeout = value;
210     else usage();
211     }
212   else usage();
213   }
214
215 if (quiet) verbose = 0;
216
217 /* Can't use flock() if the OS doesn't provide it */
218
219 #ifdef NO_FLOCK
220 if (use_flock)
221   {
222   printf("exim_lock: can't use flock() because it was not available in the\n"
223          "           operating system when exim_lock was compiled\n");
224   exit(1);
225   }
226 #endif
227
228 /* Default is to use lockfiles and fcntl(). */
229
230 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
231   use_lockfile = use_fcntl = TRUE;
232
233 /* Default fcntl() for use with mbx */
234
235 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
236
237 /* Unset unused timeouts */
238
239 if (!use_fcntl) lock_fcntl_timeout = 0;
240 if (!use_flock) lock_flock_timeout = 0;
241
242 /* A file name is required */
243
244 if (i >= argc) usage();
245
246 filename = argv[i++];
247
248 /* Expand file names starting with ~ */
249
250 if (*filename == '~')
251   {
252   struct passwd *pw;
253
254   if (*(++filename) == '/')
255     pw = getpwuid(getuid());
256   else
257     {
258     char *s = buffer;
259     while (*filename != 0 && *filename != '/')
260       *s++ = *filename++;
261     *s = 0;
262     pw = getpwnam(buffer);
263     }
264
265   if (pw == NULL)
266     {
267     printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
268     exit(1);
269     }
270
271   if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
272     {
273     printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
274       filename);
275     exit(1);
276     }
277
278   strcpy(buffer, pw->pw_dir);
279   strcat(buffer, filename);
280   filename = buffer;
281   }
282
283 /* If using a lock file, prepare by creating the lock file name and
284 the hitching post name. */
285
286 if (use_lockfile)
287   {
288   if (uname(&s) < 0)
289     {
290     printf("exim_lock: failed to find host name using uname()\n");
291     exit(1);
292     }
293   primary_hostname = s.nodename;
294
295   len = (int)strlen(filename);
296   lockname = malloc(len + 8);
297   sprintf(lockname, "%s.lock", filename);
298   hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
299   sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
300     now, (int)getpid());
301
302   if (verbose)
303     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
304       hitchname);
305   }
306
307 /* Locking retry loop */
308
309 for (j = 0; j < lock_retries; j++)
310   {
311   int sleep_before_retry = TRUE;
312   struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
313   int mbx_tmp_oflags;
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     (void)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     mbx_tmp_oflags = O_RDWR | O_CREAT;
435 #ifdef O_NOFOLLOW
436     mbx_tmp_oflags |= O_NOFOLLOW;
437 #endif
438     md = open(tempname, mbx_tmp_oflags, 0600);
439     if (md < 0)
440       {
441       printf("exim_lock: failed to create mbx lock file %s: %s\n",
442         tempname, strerror(errno));
443       goto CLEAN_UP;
444       }
445
446     /* security fixes from 2010-05 */
447     if (lstat(tempname, &lstatbuf) < 0)
448       {
449       printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
450           tempname, strerror(errno));
451       goto CLEAN_UP;
452       }
453     if (fstat(md, &statbuf2) < 0)
454       {
455       printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
456           tempname, strerror(errno));
457       goto CLEAN_UP;
458       }
459     if ((statbuf2.st_nlink > 1) ||
460         (lstatbuf.st_nlink > 1) ||
461         (!S_ISREG(lstatbuf.st_mode)) ||
462         (lstatbuf.st_dev != statbuf2.st_dev) ||
463         (lstatbuf.st_ino != statbuf2.st_ino))
464       {
465       printf("exim_lock: race condition exploited against us when "
466           "locking \"%s\"\n", tempname);
467       goto CLEAN_UP;
468       }
469
470     (void)chmod(tempname, 0600);
471
472     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
473         lock_flock_timeout) >= 0)
474       {
475       if (!quiet)
476         {
477         if (use_fcntl)
478           printf("exim_lock: fcntl() lock successfully applied to mbx "
479             "lock file %s\n", tempname);
480         if (use_flock)
481           printf("exim_lock: flock() lock successfully applied to mbx "
482             "lock file %s\n", tempname);
483         }
484
485       /* This test checks for a race condition */
486
487       if (lstat(tempname, &statbuf) != 0 ||
488           fstat(md, &ostatbuf) != 0 ||
489           statbuf.st_dev != ostatbuf.st_dev ||
490           statbuf.st_ino != ostatbuf.st_ino)
491        {
492        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
493            "creation and locking\n", tempname);
494        goto RETRY;
495        }
496       else break;
497       }
498     else goto RETRY;   /* Message already output */
499     }
500
501   /* Clean up before retrying */
502
503   RETRY:
504
505   if (md >= 0)
506     {
507     if (close(md) < 0)
508       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
509     else
510       if (!quiet) printf("exim_lock: %s closed\n", tempname);
511     md = -1;
512     }
513
514   if (fd >= 0)
515     {
516     if (close(fd) < 0)
517       printf("exim_lock: close failed: %s\n", strerror(errno));
518     else
519       if (!quiet) printf("exim_lock: file closed\n");
520     fd = -1;
521     }
522
523   if (hd >= 0)
524     {
525     if (unlink(lockname) < 0)
526       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
527     else
528       if (!quiet) printf("exim_lock: lock file removed\n");
529     hd = -1;
530     }
531
532   /* If a blocking call timed out, break the retry loop if the total time
533   so far is not less than than retries * interval. */
534
535   if (sigalrm_seen &&
536       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
537         lock_fcntl_timeout : lock_flock_timeout) >=
538           lock_retries * lock_interval)
539     j = lock_retries;
540
541   /* Wait a bit before retrying, except when it was a blocked fcntl() that
542   caused the problem. */
543
544   if (j < lock_retries && sleep_before_retry)
545     {
546     printf(" ... waiting\n");
547     sleep(lock_interval);
548     }
549   }
550
551 if (j >= lock_retries)
552   {
553   printf("exim_lock: locking failed too many times\n");
554   yield = 1;
555   goto CLEAN_UP;
556   }
557
558 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
559
560 /* If there are no further arguments, run the user's shell; otherwise
561 the next argument is a command to run. */
562
563 if (i >= argc)
564   {
565   command = getenv("SHELL");
566   if (command == NULL || *command == 0) command = "/bin/sh";
567   if (!quiet) printf("running %s ...\n", command);
568   }
569 else
570   {
571   command = argv[i];
572   if (!quiet) printf("running the command ...\n");
573   }
574
575 /* Run the command, saving and restoring the times if required. */
576
577 if (restore_times)
578   {
579   struct stat strestore;
580   struct utimbuf ut;
581   stat(filename, &strestore);
582   (void)system(command);
583   ut.actime = strestore.st_atime;
584   ut.modtime = strestore.st_mtime;
585   utime(filename, &ut);
586   }
587 else (void)system(command);
588
589 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
590 lock on the mailbox. This should be a non-blocking lock call, as there is no
591 point in waiting. */
592
593 CLEAN_UP:
594
595 if (md >= 0)
596   {
597   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
598     {
599     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
600     unlink(tempname);
601     }
602   else if (!quiet)
603     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
604       tempname);
605   if (close(md) < 0)
606     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
607   else
608     if (!quiet) printf("exim_lock: %s closed\n", tempname);
609   }
610
611 if (fd >= 0)
612   {
613   if (close(fd) < 0)
614     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
615   else
616     if (!quiet) printf("exim_lock: %s closed\n", filename);
617   }
618
619 if (hd >= 0)
620   {
621   if (unlink(lockname) < 0)
622     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
623   else
624     if (!quiet) printf("exim_lock: lock file removed\n");
625   }
626
627 return yield;
628 }
629
630 /* End */