f783a2bcf781cbcb8067105f1437ff955824767f
[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 */