Ensure we log the error message when unlink() fails.
[exim.git] / src / src / exim_lock.c
CommitLineData
bf83d8d3 1/* $Cambridge: exim/src/src/exim_lock.c,v 1.4 2010/05/29 12:11:48 pdp Exp $ */
059ec3d9
PH
2
3/* A program to lock a file exactly as Exim would, for investigation of
4interlocking problems.
5
6Options: -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
11Default is -fcntl -lockfile.
12
13Argument: 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
34in sys/file.h. */
35
36#ifndef LOCK_SH
37#define NO_FLOCK
38#endif
39
40
41typedef int BOOL;
42#define FALSE 0
43#define TRUE 1
44
45
46/* Flag for timeout signal handler */
47
48static int sigalrm_seen = FALSE;
49
50
51/* We need to pull in strerror() and os_non_restarting_signal() from the
52os.c source, if they are required for this OS. However, we don't need any of
53the 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
83static void
84sigalrm_handler(int sig)
85{
86sig = sig; /* Keep picky compilers happy */
87sigalrm_seen = TRUE;
88}
89
90
91
92/*************************************************
93* Give usage and die *
94*************************************************/
95
96static void
97usage(void)
98{
99printf("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");
102exit(1);
103}
104
105
106
107/*************************************************
108* Apply a lock to a file descriptor *
109*************************************************/
110
111static int
112apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
113 int flocktime)
114{
115int yield = 0;
116int save_errno;
117struct flock lock_data;
118lock_data.l_type = fcntltype;
119lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
120
121sigalrm_seen = FALSE;
122
123if (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
139if (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
156return yield;
157}
158
159
160
161/*************************************************
162* The exim_lock program *
163*************************************************/
164
165int main(int argc, char **argv)
166{
167int lock_retries = 10;
168int lock_interval = 3;
169int lock_fcntl_timeout = 0;
170int lock_flock_timeout = 0;
171int i, j, len;
172int fd = -1;
173int hd = -1;
174int md = -1;
175int yield = 0;
176int now = time(NULL);
177BOOL use_lockfile = FALSE;
178BOOL use_fcntl = FALSE;
179BOOL use_flock = FALSE;
180BOOL use_mbx = FALSE;
181BOOL verbose = FALSE;
182BOOL quiet = FALSE;
183BOOL restore_times = FALSE;
184char *filename;
185char *lockname = NULL, *hitchname = NULL;
1ba28e2b
PP
186char *primary_hostname;
187const char *command;
059ec3d9
PH
188struct utsname s;
189char buffer[256];
190char tempname[256];
191
192/* Decode options */
193
194for (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
217if (quiet) verbose = 0;
218
219/* Can't use flock() if the OS doesn't provide it */
220
221#ifdef NO_FLOCK
222if (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
232if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
233 use_lockfile = use_fcntl = TRUE;
234
235/* Default fcntl() for use with mbx */
236
237if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
238
239/* Unset unused timeouts */
240
241if (!use_fcntl) lock_fcntl_timeout = 0;
242if (!use_flock) lock_flock_timeout = 0;
243
244/* A file name is required */
245
246if (i >= argc) usage();
247
248filename = argv[i++];
249
250/* Expand file names starting with ~ */
251
252if (*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
286the hitching post name. */
287
288if (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
311for (j = 0; j < lock_retries; j++)
312 {
313 int sleep_before_retry = TRUE;
bf83d8d3
PP
314 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
315 int mbx_tmp_oflags;
059ec3d9
PH
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);
f1e894f3 334 (void)close(hd);
059ec3d9
PH
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
bf83d8d3
PP
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);
059ec3d9
PH
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;
bf83d8d3
PP
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;
059ec3d9
PH
470 }
471
ff790e47 472 (void)chmod(tempname, 0600);
059ec3d9
PH
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
553if (j >= lock_retries)
554 {
555 printf("exim_lock: locking failed too many times\n");
556 yield = 1;
557 goto CLEAN_UP;
558 }
559
560if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
561
562/* If there are no further arguments, run the user's shell; otherwise
563the next argument is a command to run. */
564
565if (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 }
571else
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
579if (restore_times)
580 {
581 struct stat strestore;
582 struct utimbuf ut;
583 stat(filename, &strestore);
ff790e47 584 (void)system(command);
059ec3d9
PH
585 ut.actime = strestore.st_atime;
586 ut.modtime = strestore.st_mtime;
587 utime(filename, &ut);
588 }
ff790e47 589else (void)system(command);
059ec3d9
PH
590
591/* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
592lock on the mailbox. This should be a non-blocking lock call, as there is no
593point in waiting. */
594
595CLEAN_UP:
596
597if (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
613if (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
621if (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
629return yield;
630}
631
632/* End */