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