Update test 0357 following the retry fix.
[users/jgh/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 #ifndef OS_GET_DNS_RESOLVER_RES
74   #define OS_GET_DNS_RESOLVER_RES
75 #endif
76
77 #include "../src/os.c"
78
79
80
81 /*************************************************
82 *             Timeout handler                    *
83 *************************************************/
84
85 static void
86 sigalrm_handler(int sig)
87 {
88 sig = sig;      /* Keep picky compilers happy */
89 sigalrm_seen = TRUE;
90 }
91
92
93
94 /*************************************************
95 *           Give usage and die                   *
96 *************************************************/
97
98 static void
99 usage(void)
100 {
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");
104 exit(1);
105 }
106
107
108
109 /*************************************************
110 *         Apply a lock to a file descriptor      *
111 *************************************************/
112
113 static int
114 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
115     int flocktime)
116 {
117 int yield = 0;
118 int save_errno;
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;
122
123 sigalrm_seen = FALSE;
124
125 if (dofcntl)
126   {
127   if (fcntltime > 0)
128     {
129     os_non_restarting_signal(SIGALRM, sigalrm_handler);
130     alarm(fcntltime);
131     yield = fcntl(fd, F_SETLKW, &lock_data);
132     save_errno = errno;
133     alarm(0);
134     errno = save_errno;
135     }
136   else yield = fcntl(fd, F_SETLK, &lock_data);
137   if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
138   }
139
140 #ifndef NO_FLOCK
141 if (doflock && (yield >= 0))
142   {
143   int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
144   if (flocktime > 0)
145     {
146     os_non_restarting_signal(SIGALRM, sigalrm_handler);
147     alarm(flocktime);
148     yield = flock(fd, flocktype);
149     save_errno = errno;
150     alarm(0);
151     errno = save_errno;
152     }
153   else yield = flock(fd, flocktype | LOCK_NB);
154   if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
155   }
156 #endif
157
158 return yield;
159 }
160
161
162
163 /*************************************************
164 *           The exim_lock program                *
165 *************************************************/
166
167 int main(int argc, char **argv)
168 {
169 int  lock_retries = 10;
170 int  lock_interval = 3;
171 int  lock_fcntl_timeout = 0;
172 int  lock_flock_timeout = 0;
173 int  i, j, len;
174 int  fd = -1;
175 int  hd = -1;
176 int  md = -1;
177 int  yield = 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;
184 BOOL quiet = FALSE;
185 BOOL restore_times = FALSE;
186 char *filename;
187 char *lockname = NULL, *hitchname = NULL;
188 char *primary_hostname;
189 const char *command;
190 struct utsname s;
191 char buffer[256];
192 char tempname[256];
193
194 /* Decode options */
195
196 for (i = 1; i < argc; i++)
197   {
198   char *arg = argv[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;
207   else if (++i < argc)
208     {
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;
214     else usage();
215     }
216   else usage();
217   }
218
219 if (quiet) verbose = 0;
220
221 /* Can't use flock() if the OS doesn't provide it */
222
223 #ifdef NO_FLOCK
224 if (use_flock)
225   {
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");
228   exit(1);
229   }
230 #endif
231
232 /* Default is to use lockfiles and fcntl(). */
233
234 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
235   use_lockfile = use_fcntl = TRUE;
236
237 /* Default fcntl() for use with mbx */
238
239 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
240
241 /* Unset unused timeouts */
242
243 if (!use_fcntl) lock_fcntl_timeout = 0;
244 if (!use_flock) lock_flock_timeout = 0;
245
246 /* A file name is required */
247
248 if (i >= argc) usage();
249
250 filename = argv[i++];
251
252 /* Expand file names starting with ~ */
253
254 if (*filename == '~')
255   {
256   struct passwd *pw;
257
258   if (*(++filename) == '/')
259     pw = getpwuid(getuid());
260   else
261     {
262     char *s = buffer;
263     while (*filename != 0 && *filename != '/')
264       *s++ = *filename++;
265     *s = 0;
266     pw = getpwnam(buffer);
267     }
268
269   if (pw == NULL)
270     {
271     printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
272     exit(1);
273     }
274
275   if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
276     {
277     printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
278       filename);
279     exit(1);
280     }
281
282   strcpy(buffer, pw->pw_dir);
283   strcat(buffer, filename);
284   filename = buffer;
285   }
286
287 /* If using a lock file, prepare by creating the lock file name and
288 the hitching post name. */
289
290 if (use_lockfile)
291   {
292   if (uname(&s) < 0)
293     {
294     printf("exim_lock: failed to find host name using uname()\n");
295     exit(1);
296     }
297   primary_hostname = s.nodename;
298
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,
304     now, (int)getpid());
305
306   if (verbose)
307     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
308       hitchname);
309   }
310
311 /* Locking retry loop */
312
313 for (j = 0; j < lock_retries; j++)
314   {
315   int sleep_before_retry = TRUE;
316   struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
317   int mbx_tmp_oflags;
318
319   /* Try to build a lock file if so configured */
320
321   if (use_lockfile)
322     {
323     int rc;
324     if (verbose) printf("exim_lock: creating lock file\n");
325     hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
326     if (hd < 0)
327       {
328       printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
329         strerror(errno));
330       exit(1);
331       }
332
333     /* Apply hitching post algorithm. */
334
335     if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
336     (void)close(hd);
337     unlink(hitchname);
338
339     if (rc != 0 && statbuf.st_nlink != 2)
340       {
341       printf("exim_lock: failed to link hitching post to lock file\n");
342       hd = -1;
343       goto RETRY;
344       }
345
346     if (!quiet) printf("exim_lock: lock file successfully created\n");
347     }
348
349   /* We are done if no other locking required. */
350
351   if (!use_fcntl && !use_flock && !use_mbx) break;
352
353   /* Open the file for writing. */
354
355   fd = open(filename, O_RDWR + O_APPEND);
356   if (fd < 0)
357     {
358     printf("exim_lock: failed to open %s for writing: %s\n", filename,
359       strerror(errno));
360     yield = 1;
361     goto CLEAN_UP;
362     }
363
364   /* If there is a timeout, implying blocked locking, we don't want to
365   sleep before any retries after this. */
366
367   if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
368     sleep_before_retry = FALSE;
369
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. */
373
374   if (!use_mbx && (use_fcntl || use_flock))
375     {
376     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
377         lock_flock_timeout) >= 0)
378       {
379       if (!quiet)
380         {
381         if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
382         if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
383         }
384       break;
385       }
386     else goto RETRY;   /* Message already output */
387     }
388
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. */
393
394   else
395     {
396     if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
397         lock_flock_timeout) >= 0)
398       {
399       if (!quiet)
400         {
401         if (use_fcntl)
402           printf("exim_lock: fcntl() read lock successfully applied\n");
403         if (use_flock)
404           printf("exim_lock: fcntl() read lock successfully applied\n");
405         }
406       }
407     else goto RETRY;   /* Message already output */
408
409     if (fstat(fd, &statbuf) < 0)
410       {
411       printf("exim_lock: fstat() of %s failed: %s\n", filename,
412         strerror(errno));
413       yield = 1;
414       goto CLEAN_UP;
415       }
416
417     /* Set up file in /tmp and check its state if already existing. */
418
419     sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
420       (long)statbuf.st_ino);
421
422     if (lstat(tempname, &statbuf) >= 0)
423       {
424       if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
425         {
426         printf("exim_lock: symbolic link on lock name %s\n", tempname);
427         yield = 1;
428         goto CLEAN_UP;
429         }
430       if (statbuf.st_nlink > 1)
431         {
432         printf("exim_lock: hard link to lock name %s\n", tempname);
433         yield = 1;
434         goto CLEAN_UP;
435         }
436       }
437
438     mbx_tmp_oflags = O_RDWR | O_CREAT;
439 #ifdef O_NOFOLLOW
440     mbx_tmp_oflags |= O_NOFOLLOW;
441 #endif
442     md = open(tempname, mbx_tmp_oflags, 0600);
443     if (md < 0)
444       {
445       printf("exim_lock: failed to create mbx lock file %s: %s\n",
446         tempname, strerror(errno));
447       goto CLEAN_UP;
448       }
449
450     /* security fixes from 2010-05 */
451     if (lstat(tempname, &lstatbuf) < 0)
452       {
453       printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
454           tempname, strerror(errno));
455       goto CLEAN_UP;
456       }
457     if (fstat(md, &statbuf2) < 0)
458       {
459       printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
460           tempname, strerror(errno));
461       goto CLEAN_UP;
462       }
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))
468       {
469       printf("exim_lock: race condition exploited against us when "
470           "locking \"%s\"\n", tempname);
471       goto CLEAN_UP;
472       }
473
474     (void)chmod(tempname, 0600);
475
476     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
477         lock_flock_timeout) >= 0)
478       {
479       if (!quiet)
480         {
481         if (use_fcntl)
482           printf("exim_lock: fcntl() lock successfully applied to mbx "
483             "lock file %s\n", tempname);
484         if (use_flock)
485           printf("exim_lock: flock() lock successfully applied to mbx "
486             "lock file %s\n", tempname);
487         }
488
489       /* This test checks for a race condition */
490
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)
495        {
496        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
497            "creation and locking\n", tempname);
498        goto RETRY;
499        }
500       else break;
501       }
502     else goto RETRY;   /* Message already output */
503     }
504
505   /* Clean up before retrying */
506
507   RETRY:
508
509   if (md >= 0)
510     {
511     if (close(md) < 0)
512       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
513     else
514       if (!quiet) printf("exim_lock: %s closed\n", tempname);
515     md = -1;
516     }
517
518   if (fd >= 0)
519     {
520     if (close(fd) < 0)
521       printf("exim_lock: close failed: %s\n", strerror(errno));
522     else
523       if (!quiet) printf("exim_lock: file closed\n");
524     fd = -1;
525     }
526
527   if (hd >= 0)
528     {
529     if (unlink(lockname) < 0)
530       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
531     else
532       if (!quiet) printf("exim_lock: lock file removed\n");
533     hd = -1;
534     }
535
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. */
538
539   if (sigalrm_seen &&
540       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
541         lock_fcntl_timeout : lock_flock_timeout) >=
542           lock_retries * lock_interval)
543     j = lock_retries;
544
545   /* Wait a bit before retrying, except when it was a blocked fcntl() that
546   caused the problem. */
547
548   if (j < lock_retries && sleep_before_retry)
549     {
550     printf(" ... waiting\n");
551     sleep(lock_interval);
552     }
553   }
554
555 if (j >= lock_retries)
556   {
557   printf("exim_lock: locking failed too many times\n");
558   yield = 1;
559   goto CLEAN_UP;
560   }
561
562 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
563
564 /* If there are no further arguments, run the user's shell; otherwise
565 the next argument is a command to run. */
566
567 if (i >= argc)
568   {
569   command = getenv("SHELL");
570   if (command == NULL || *command == 0) command = "/bin/sh";
571   if (!quiet) printf("running %s ...\n", command);
572   }
573 else
574   {
575   command = argv[i];
576   if (!quiet) printf("running the command ...\n");
577   }
578
579 /* Run the command, saving and restoring the times if required. */
580
581 if (restore_times)
582   {
583   struct stat strestore;
584   struct utimbuf ut;
585   stat(filename, &strestore);
586   i = system(command);
587   ut.actime = strestore.st_atime;
588   ut.modtime = strestore.st_mtime;
589   utime(filename, &ut);
590   }
591 else i = system(command);
592
593 if(i && !quiet) printf("warning: nonzero status %d\n", i);
594
595 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
596 lock on the mailbox. This should be a non-blocking lock call, as there is no
597 point in waiting. */
598
599 CLEAN_UP:
600
601 if (md >= 0)
602   {
603   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
604     {
605     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
606     unlink(tempname);
607     }
608   else if (!quiet)
609     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
610       tempname);
611   if (close(md) < 0)
612     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
613   else
614     if (!quiet) printf("exim_lock: %s closed\n", tempname);
615   }
616
617 if (fd >= 0)
618   {
619   if (close(fd) < 0)
620     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
621   else
622     if (!quiet) printf("exim_lock: %s closed\n", filename);
623   }
624
625 if (hd >= 0)
626   {
627   if (unlink(lockname) < 0)
628     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
629   else
630     if (!quiet) printf("exim_lock: lock file removed\n");
631   }
632
633 return yield;
634 }
635
636 /* End */