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