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