0c60ce107c5ae8c8095f4fc62185d788f1940b70
[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, *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, lstatbuf, statbuf2;
314 int mbx_tmp_oflags;
315
316 /* Try to build a lock file if so configured */
317
318 if (use_lockfile)
319 {
320 int rc;
321 if (verbose) printf("exim_lock: creating lock file\n");
322 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
323 if (hd < 0)
324 {
325 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
326 strerror(errno));
327 exit(1);
328 }
329
330 /* Apply hitching post algorithm. */
331
332 if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
333 (void)close(hd);
334 unlink(hitchname);
335
336 if (rc != 0 && statbuf.st_nlink != 2)
337 {
338 printf("exim_lock: failed to link hitching post to lock file\n");
339 hd = -1;
340 goto RETRY;
341 }
342
343 if (!quiet) printf("exim_lock: lock file successfully created\n");
344 }
345
346 /* We are done if no other locking required. */
347
348 if (!use_fcntl && !use_flock && !use_mbx) break;
349
350 /* Open the file for writing. */
351
352 fd = open(filename, O_RDWR + O_APPEND);
353 if (fd < 0)
354 {
355 printf("exim_lock: failed to open %s for writing: %s\n", filename,
356 strerror(errno));
357 yield = 1;
358 goto CLEAN_UP;
359 }
360
361 /* If there is a timeout, implying blocked locking, we don't want to
362 sleep before any retries after this. */
363
364 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
365 sleep_before_retry = FALSE;
366
367 /* Lock using fcntl. There are pros and cons to using a blocking call vs
368 a non-blocking call and retries. Exim is non-blocking by default, but setting
369 a timeout changes it to blocking. */
370
371 if (!use_mbx && (use_fcntl || use_flock))
372 {
373 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
374 lock_flock_timeout) >= 0)
375 {
376 if (!quiet)
377 {
378 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
379 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
380 }
381 break;
382 }
383 else goto RETRY; /* Message already output */
384 }
385
386 /* Lock using MBX rules. This is complicated and is documented with the
387 source of the c-client library that goes with Pine and IMAP. What has to
388 be done to interwork correctly is to take out a shared lock on the mailbox,
389 and an exclusive lock on a /tmp file. */
390
391 else
392 {
393 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
394 lock_flock_timeout) >= 0)
395 {
396 if (!quiet)
397 {
398 if (use_fcntl)
399 printf("exim_lock: fcntl() read lock successfully applied\n");
400 if (use_flock)
401 printf("exim_lock: fcntl() read lock successfully applied\n");
402 }
403 }
404 else goto RETRY; /* Message already output */
405
406 if (fstat(fd, &statbuf) < 0)
407 {
408 printf("exim_lock: fstat() of %s failed: %s\n", filename,
409 strerror(errno));
410 yield = 1;
411 goto CLEAN_UP;
412 }
413
414 /* Set up file in /tmp and check its state if already existing. */
415
416 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
417 (long)statbuf.st_ino);
418
419 if (lstat(tempname, &statbuf) >= 0)
420 {
421 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
422 {
423 printf("exim_lock: symbolic link on lock name %s\n", tempname);
424 yield = 1;
425 goto CLEAN_UP;
426 }
427 if (statbuf.st_nlink > 1)
428 {
429 printf("exim_lock: hard link to lock name %s\n", tempname);
430 yield = 1;
431 goto CLEAN_UP;
432 }
433 }
434
435 mbx_tmp_oflags = O_RDWR | O_CREAT;
436 #ifdef O_NOFOLLOW
437 mbx_tmp_oflags |= O_NOFOLLOW;
438 #endif
439 md = open(tempname, mbx_tmp_oflags, 0600);
440 if (md < 0)
441 {
442 printf("exim_lock: failed to create mbx lock file %s: %s\n",
443 tempname, strerror(errno));
444 goto CLEAN_UP;
445 }
446
447 /* security fixes from 2010-05 */
448 if (lstat(tempname, &lstatbuf) < 0)
449 {
450 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
451 tempname, strerror(errno));
452 goto CLEAN_UP;
453 }
454 if (fstat(md, &statbuf2) < 0)
455 {
456 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
457 tempname, strerror(errno));
458 goto CLEAN_UP;
459 }
460 if ((statbuf2.st_nlink > 1) ||
461 (lstatbuf.st_nlink > 1) ||
462 (!S_ISREG(lstatbuf.st_mode)) ||
463 (lstatbuf.st_dev != statbuf2.st_dev) ||
464 (lstatbuf.st_ino != statbuf2.st_ino))
465 {
466 printf("exim_lock: race condition exploited against us when "
467 "locking \"%s\"\n", tempname);
468 goto CLEAN_UP;
469 }
470
471 (void)chmod(tempname, 0600);
472
473 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
474 lock_flock_timeout) >= 0)
475 {
476 if (!quiet)
477 {
478 if (use_fcntl)
479 printf("exim_lock: fcntl() lock successfully applied to mbx "
480 "lock file %s\n", tempname);
481 if (use_flock)
482 printf("exim_lock: flock() lock successfully applied to mbx "
483 "lock file %s\n", tempname);
484 }
485
486 /* This test checks for a race condition */
487
488 if (lstat(tempname, &statbuf) != 0 ||
489 fstat(md, &ostatbuf) != 0 ||
490 statbuf.st_dev != ostatbuf.st_dev ||
491 statbuf.st_ino != ostatbuf.st_ino)
492 {
493 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
494 "creation and locking\n", tempname);
495 goto RETRY;
496 }
497 else break;
498 }
499 else goto RETRY; /* Message already output */
500 }
501
502 /* Clean up before retrying */
503
504 RETRY:
505
506 if (md >= 0)
507 {
508 if (close(md) < 0)
509 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
510 else
511 if (!quiet) printf("exim_lock: %s closed\n", tempname);
512 md = -1;
513 }
514
515 if (fd >= 0)
516 {
517 if (close(fd) < 0)
518 printf("exim_lock: close failed: %s\n", strerror(errno));
519 else
520 if (!quiet) printf("exim_lock: file closed\n");
521 fd = -1;
522 }
523
524 if (hd >= 0)
525 {
526 if (unlink(lockname) < 0)
527 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
528 else
529 if (!quiet) printf("exim_lock: lock file removed\n");
530 hd = -1;
531 }
532
533 /* If a blocking call timed out, break the retry loop if the total time
534 so far is not less than than retries * interval. */
535
536 if (sigalrm_seen &&
537 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
538 lock_fcntl_timeout : lock_flock_timeout) >=
539 lock_retries * lock_interval)
540 j = lock_retries;
541
542 /* Wait a bit before retrying, except when it was a blocked fcntl() that
543 caused the problem. */
544
545 if (j < lock_retries && sleep_before_retry)
546 {
547 printf(" ... waiting\n");
548 sleep(lock_interval);
549 }
550 }
551
552 if (j >= lock_retries)
553 {
554 printf("exim_lock: locking failed too many times\n");
555 yield = 1;
556 goto CLEAN_UP;
557 }
558
559 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
560
561 /* If there are no further arguments, run the user's shell; otherwise
562 the next argument is a command to run. */
563
564 if (i >= argc)
565 {
566 command = getenv("SHELL");
567 if (command == NULL || *command == 0) command = "/bin/sh";
568 if (!quiet) printf("running %s ...\n", command);
569 }
570 else
571 {
572 command = argv[i];
573 if (!quiet) printf("running the command ...\n");
574 }
575
576 /* Run the command, saving and restoring the times if required. */
577
578 if (restore_times)
579 {
580 struct stat strestore;
581 struct utimbuf ut;
582 stat(filename, &strestore);
583 (void)system(command);
584 ut.actime = strestore.st_atime;
585 ut.modtime = strestore.st_mtime;
586 utime(filename, &ut);
587 }
588 else (void)system(command);
589
590 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
591 lock on the mailbox. This should be a non-blocking lock call, as there is no
592 point in waiting. */
593
594 CLEAN_UP:
595
596 if (md >= 0)
597 {
598 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
599 {
600 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
601 unlink(tempname);
602 }
603 else if (!quiet)
604 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
605 tempname);
606 if (close(md) < 0)
607 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
608 else
609 if (!quiet) printf("exim_lock: %s closed\n", tempname);
610 }
611
612 if (fd >= 0)
613 {
614 if (close(fd) < 0)
615 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
616 else
617 if (!quiet) printf("exim_lock: %s closed\n", filename);
618 }
619
620 if (hd >= 0)
621 {
622 if (unlink(lockname) < 0)
623 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
624 else
625 if (!quiet) printf("exim_lock: lock file removed\n");
626 }
627
628 return yield;
629 }
630
631 /* End */