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