Eliminate '#ifdef HAVE_FOO' for Posix-mandated FOOs.
[mmh] / sbr / lock_file.c
1 /*
2 ** lock.c -- routines to lock/unlock files
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 /*
10 ** Modified by Ruud de Rooij to support Miquel van Smoorenburg's liblockfile
11 **
12 ** Since liblockfile locking shares most of its code with dot locking, it
13 ** is enabled by defining both DOT_LOCKING and HAVE_LIBLOCKFILE.
14 **
15 ** Ruud de Rooij <ruud@debian.org>  Sun, 28 Mar 1999 15:34:03 +0200
16 */
17
18 #include <h/mh.h>
19 #include <h/signals.h>
20 #include <h/utils.h>
21
22 #ifdef TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # ifdef TM_IN_SYS_TIME
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <errno.h>
34
35 #ifdef HAVE_FCNTL_H
36 # include <fcntl.h>
37 #else
38 # include <sys/file.h>
39 #endif
40
41 #if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING)
42 # include <sys/file.h>
43 #endif
44
45 #include <signal.h>
46
47 #if defined(HAVE_LIBLOCKFILE)
48 #include <lockfile.h>
49 #endif
50
51 #ifdef LOCKDIR
52 char *lockdir = LOCKDIR;
53 #endif
54
55 /* Are we using any kernel locking? */
56 #if defined (FLOCK_LOCKING) || defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
57 # define KERNEL_LOCKING
58 #endif
59
60 #ifdef DOT_LOCKING
61
62 /* struct for getting name of lock file to create */
63 struct lockinfo {
64         char curlock[BUFSIZ];
65 #if !defined(HAVE_LIBLOCKFILE)
66         char tmplock[BUFSIZ];
67 #endif
68 };
69
70 /*
71 ** Amount of time to wait before
72 ** updating ctime of lock file.
73 */
74 #define NSECS 20
75
76 #if !defined(HAVE_LIBLOCKFILE)
77 /*
78 ** How old does a lock file need to be
79 ** before we remove it.
80 */
81 #define RSECS 180
82 #endif /* HAVE_LIBLOCKFILE */
83
84 /* struct for recording and updating locks */
85 struct lock {
86         int l_fd;
87         char *l_lock;
88         struct lock *l_next;
89 };
90
91 /* top of list containing all open locks */
92 static struct lock *l_top = NULL;
93 #endif /* DOT_LOCKING */
94
95 /*
96 ** static prototypes
97 */
98 #ifdef KERNEL_LOCKING
99 static int lkopen_kernel(char *, int, mode_t);
100 #endif
101
102 #ifdef DOT_LOCKING
103 static int lkopen_dot(char *, int, mode_t);
104 static void lockname(char *, struct lockinfo *, int);
105 static void timerON(char *, int);
106 static void timerOFF(int);
107 static RETSIGTYPE alrmser(int);
108
109 #if !defined(HAVE_LIBLOCKFILE)
110 static int lockit(struct lockinfo *);
111 #endif
112 #endif
113
114 /*
115 ** Base routine to open and lock a file,
116 ** and return a file descriptor.
117 */
118
119 int
120 lkopen(char *file, int access, mode_t mode)
121 {
122 #ifdef KERNEL_LOCKING
123         return lkopen_kernel(file, access, mode);
124 #endif
125
126 #ifdef DOT_LOCKING
127         return lkopen_dot(file, access, mode);
128 #endif
129 }
130
131
132 /*
133 ** Base routine to close and unlock a file,
134 ** given a file descriptor.
135 */
136
137 int
138 lkclose(int fd, char *file)
139 {
140 #ifdef FCNTL_LOCKING
141         struct flock buf;
142 #endif
143
144 #ifdef DOT_LOCKING
145         struct lockinfo lkinfo;
146 #endif
147
148         if (fd == -1)
149                 return 0;
150
151 #ifdef FCNTL_LOCKING
152         buf.l_type   = F_UNLCK;
153         buf.l_whence = SEEK_SET;
154         buf.l_start  = 0;
155         buf.l_len = 0;
156         fcntl(fd, F_SETLK, &buf);
157 #endif
158
159 #ifdef FLOCK_LOCKING
160         flock(fd, LOCK_UN);
161 #endif
162
163 #ifdef LOCKF_LOCKING
164         /* make sure we unlock the whole thing */
165         lseek(fd, (off_t) 0, SEEK_SET);
166         lockf(fd, F_ULOCK, 0L);
167 #endif
168
169 #ifdef DOT_LOCKING
170         lockname(file, &lkinfo, 0);  /* get name of lock file */
171 #if !defined(HAVE_LIBLOCKFILE)
172         unlink(lkinfo.curlock);  /* remove lock file */
173 #else
174         lockfile_remove(lkinfo.curlock);
175 #endif /* HAVE_LIBLOCKFILE */
176         timerOFF(fd);  /* turn off lock timer   */
177 #endif /* DOT_LOCKING */
178
179         return (close(fd));
180 }
181
182
183 /*
184 ** Base routine to open and lock a file,
185 ** and return a FILE pointer
186 */
187
188 FILE *
189 lkfopen(char *file, char *mode)
190 {
191         int fd, access;
192         FILE *fp;
193
194         if (strcmp(mode, "r") == 0)
195                 access = O_RDONLY;
196         else if (strcmp(mode, "r+") == 0)
197                 access = O_RDWR;
198         else if (strcmp(mode, "w") == 0)
199                 access = O_WRONLY | O_CREAT | O_TRUNC;
200         else if (strcmp(mode, "w+") == 0)
201                 access = O_RDWR | O_CREAT | O_TRUNC;
202         else if (strcmp(mode, "a") == 0)
203                 access = O_WRONLY | O_CREAT | O_APPEND;
204         else if (strcmp(mode, "a+") == 0)
205                 access = O_RDWR | O_CREAT | O_APPEND;
206         else {
207                 errno = EINVAL;
208                 return NULL;
209         }
210
211         if ((fd = lkopen(file, access, 0666)) == -1)
212                 return NULL;
213
214         if ((fp = fdopen(fd, mode)) == NULL) {
215                 close(fd);
216                 return NULL;
217         }
218
219         return fp;
220 }
221
222
223 /*
224 ** Base routine to close and unlock a file,
225 ** given a FILE pointer
226 */
227
228 int
229 lkfclose(FILE *fp, char *file)
230 {
231 #ifdef FCNTL_LOCKING
232         struct flock buf;
233 #endif
234
235 #ifdef DOT_LOCKING
236         struct lockinfo lkinfo;
237 #endif
238
239         if (fp == NULL)
240                 return 0;
241
242 #ifdef FCNTL_LOCKING
243         buf.l_type   = F_UNLCK;
244         buf.l_whence = SEEK_SET;
245         buf.l_start  = 0;
246         buf.l_len = 0;
247         fcntl(fileno(fp), F_SETLK, &buf);
248 #endif
249
250 #ifdef FLOCK_LOCKING
251         flock(fileno(fp), LOCK_UN);
252 #endif
253
254 #ifdef LOCKF_LOCKING
255         /* make sure we unlock the whole thing */
256         fseek(fp, 0L, SEEK_SET);
257         lockf(fileno(fp), F_ULOCK, 0L);
258 #endif
259
260 #ifdef DOT_LOCKING
261         lockname(file, &lkinfo, 0);  /* get name of lock file */
262 #if !defined(HAVE_LIBLOCKFILE)
263         unlink(lkinfo.curlock);  /* remove lock file */
264 #else
265         lockfile_remove(lkinfo.curlock);
266 #endif /* HAVE_LIBLOCKFILE */
267         timerOFF(fileno(fp));  /* turn off lock timer   */
268 #endif /* DOT_LOCKING */
269
270         return (fclose(fp));
271 }
272
273
274 #ifdef KERNEL_LOCKING
275
276 /*
277 ** open and lock a file, using kernel locking
278 */
279
280 static int
281 lkopen_kernel(char *file, int access, mode_t mode)
282 {
283         int fd, i, j;
284
285 # ifdef FCNTL_LOCKING
286         struct flock buf;
287 # endif /* FCNTL_LOCKING */
288
289         for (i = 0; i < 5; i++) {
290
291 # if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
292                 /* remember the original mode */
293                 j = access;
294
295                 /* make sure we open at the beginning */
296                 access &= ~O_APPEND;
297
298                 /*
299                 ** We MUST have write permission or
300                 ** lockf/fcntl() won't work
301                 */
302                 if ((access & 03) == O_RDONLY) {
303                         access &= ~O_RDONLY;
304                         access |= O_RDWR;
305                 }
306 # endif /* LOCKF_LOCKING || FCNTL_LOCKING */
307
308                 if ((fd = open(file, access | O_NDELAY, mode)) == -1)
309                         return -1;
310
311 # ifdef FCNTL_LOCKING
312                 buf.l_type   = F_WRLCK;
313                 buf.l_whence = SEEK_SET;
314                 buf.l_start  = 0;
315                 buf.l_len = 0;
316                 if (fcntl(fd, F_SETLK, &buf) != -1)
317                         return fd;
318 # endif
319
320 # ifdef FLOCK_LOCKING
321                 if (flock(fd, (((access & 03) == O_RDONLY) ? LOCK_SH :
322                         LOCK_EX) | LOCK_NB) != -1)
323                         return fd;
324 # endif
325
326 # ifdef LOCKF_LOCKING
327                 if (lockf(fd, F_TLOCK, 0L) != -1) {
328                         /* see if we should be at the end */
329                         if (j & O_APPEND)
330                                 lseek(fd, (off_t) 0, SEEK_END);
331                         return fd;
332                 }
333 # endif
334
335                 j = errno;
336                 close(fd);
337                 sleep(5);
338         }
339
340         close(fd);
341         errno = j;
342         return -1;
343 }
344
345 #endif /* KERNEL_LOCKING */
346
347
348 #ifdef DOT_LOCKING
349
350 /*
351 ** open and lock a file, using dot locking
352 */
353
354 static int
355 lkopen_dot(char *file, int access, mode_t mode)
356 {
357         int fd;
358         struct lockinfo lkinfo;
359
360         /* open the file */
361         if ((fd = open(file, access, mode)) == -1)
362                 return -1;
363
364         /*
365         ** Get the name of the eventual lock file, as well
366         ** as a name for a temporary lock file.
367         */
368         lockname(file, &lkinfo, 1);
369
370 #if !defined(HAVE_LIBLOCKFILE)
371         {
372                 int i;
373                 for (i = 0;;) {
374                         /* attempt to create lock file */
375                         if (lockit(&lkinfo) == 0) {
376                                 /* if successful, turn on timer and return */
377                                 timerON(lkinfo.curlock, fd);
378                                 return fd;
379                         } else {
380                                 /*
381                                 ** Abort locking, if we fail to lock after 5
382                                 ** attempts and are never able to stat the
383                                 ** lock file.
384                                 */
385                                 struct stat st;
386                                 if (stat(lkinfo.curlock, &st) == -1) {
387                                         if (i++ > 5)
388                                                 return -1;
389                                         sleep(5);
390                                 } else {
391                                         time_t curtime;
392                                         i = 0;
393                                         time(&curtime);
394
395                                         /*
396                                         ** check for stale lockfile,
397                                         ** else sleep
398                                         */
399                                         if (curtime > st.st_ctime + RSECS)
400                                                 unlink(lkinfo.curlock);
401                                         else
402                                                 sleep(5);
403                                 }
404                                 lockname(file, &lkinfo, 1);
405                         }
406                 }
407         }
408 #else
409         if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
410                 timerON(lkinfo.curlock, fd);
411                 return fd;
412         } else {
413                 close(fd);
414                 return -1;
415         }
416 #endif /* HAVE_LIBLOCKFILE */
417 }
418
419 #if !defined(HAVE_LIBLOCKFILE)
420 /*
421 ** Routine that actually tries to create
422 ** the lock file.
423 */
424
425 static int
426 lockit(struct lockinfo *li)
427 {
428         int fd;
429         char *curlock, *tmplock;
430
431 #if 0
432         char buffer[128];
433 #endif
434
435         curlock = li->curlock;
436         tmplock = li->tmplock;
437
438 #ifdef HAVE_MKSTEMP
439         if ((fd = mkstemp(tmplock)) == -1)
440                 return -1;
441 #else
442         if (mktemp(tmplock) == NULL)
443                 return -1;
444         if (unlink(tmplock) == -1 && errno != ENOENT)
445                 return -1;
446         /* create the temporary lock file */
447         if ((fd = creat(tmplock, 0600)) == -1)
448                 return -1;
449 #endif
450
451 #if 0
452         /* write our process id into lock file */
453         snprintf(buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
454         write(fd, buffer, strlen(buffer) + 1);
455 #endif
456
457         close(fd);
458
459         /*
460         ** Now try to create the real lock file
461         ** by linking to the temporary file.
462         */
463         fd = link(tmplock, curlock);
464         unlink(tmplock);
465
466         return (fd == -1 ? -1 : 0);
467 }
468 #endif /* HAVE_LIBLOCKFILE */
469
470 /*
471 ** Get name of lock file, and temporary lock file
472 */
473
474 static void
475 lockname(char *file, struct lockinfo *li, int isnewlock)
476 {
477         int bplen, tmplen;
478         char *bp, *cp;
479
480         if ((cp = strrchr(file, '/')) == NULL || *++cp == 0)
481                 cp = file;
482
483         bp = li->curlock;
484         bplen = 0;
485 #ifdef LOCKDIR
486         snprintf(bp, sizeof(li->curlock), "%s/", lockdir);
487         tmplen = strlen(bp);
488         bp += tmplen;
489         bplen += tmplen;
490 #else
491         if (cp != file) {
492                 snprintf(bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
493                 tmplen = strlen(bp);
494                 bp += tmplen;
495                 bplen += tmplen;
496         }
497 #endif
498
499         snprintf(bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
500
501 #if !defined(HAVE_LIBLOCKFILE)
502         /*
503         ** If this is for a new lock, create a name for
504         ** the temporary lock file for lockit()
505         */
506         if (isnewlock) {
507                 if ((cp = strrchr(li->curlock, '/')) == NULL || *++cp == 0)
508                         strncpy(li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock));
509                 else
510                         snprintf(li->tmplock, sizeof(li->tmplock),
511                                         "%.*s,LCK.XXXXXX",
512                                         (int)(cp - li->curlock), li->curlock);
513         }
514 #endif
515 }
516
517
518 /*
519 ** Add new lockfile to the list of open lockfiles
520 ** and start the lock file timer.
521 */
522
523 static void
524 timerON(char *curlock, int fd)
525 {
526         struct lock *lp;
527         size_t len;
528
529         lp = (struct lock *) mh_xmalloc(sizeof(*lp));
530
531         len = strlen(curlock) + 1;
532         lp->l_fd = fd;
533         lp->l_lock = mh_xmalloc(len);
534         memcpy(lp->l_lock, curlock, len);
535         lp->l_next = l_top;
536
537         if (!l_top) {
538                 /* perhaps SIGT{STP,TIN,TOU} ? */
539                 SIGNAL(SIGALRM, alrmser);
540                 alarm(NSECS);
541         }
542
543         l_top = lp;
544 }
545
546
547 /*
548 ** Search through the list of lockfiles for the
549 ** current lockfile, and remove it from the list.
550 */
551
552 static void
553 timerOFF(int fd)
554 {
555         struct lock *pp, *lp;
556
557         alarm(0);
558
559         if (l_top) {
560                 for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) {
561                         if (lp->l_fd == fd)
562                                 break;
563                 }
564                 if (lp) {
565                         if (lp == l_top)
566                                 l_top = lp->l_next;
567                         else
568                                 pp->l_next = lp->l_next;
569
570                         free(lp->l_lock);
571                         free(lp);
572                 }
573         }
574
575         /* if there are locks left, restart timer */
576         if (l_top)
577                 alarm(NSECS);
578 }
579
580
581 /*
582 ** If timer goes off, we update the ctime of all open
583 ** lockfiles, so another command doesn't remove them.
584 */
585
586 static RETSIGTYPE
587 alrmser(int sig)
588 {
589         char *lockfile;
590         struct lock *lp;
591
592 #ifndef RELIABLE_SIGNALS
593         SIGNAL(SIGALRM, alrmser);
594 #endif
595
596         /* update the ctime of all the lock files */
597         for (lp = l_top; lp; lp = lp->l_next) {
598                 lockfile = lp->l_lock;
599 #if !defined(HAVE_LIBLOCKFILE)
600                 {
601                         int j;
602                         if (*lockfile && (j = creat(lockfile, 0600)) != -1)
603                                 close(j);
604                 }
605 #else
606         lockfile_touch(lockfile);
607 #endif
608         }
609
610         /* restart the alarm */
611         alarm(NSECS);
612 }
613
614 #endif /* DOT_LOCKING */