Fix merge problem.
[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 */