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