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