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