We are making POSIX signal support a requirement.
[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 <h/mh.h>
19 #include <h/signals.h>
20 #include <h/utils.h>
21
22 #ifdef TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # ifdef TM_IN_SYS_TIME
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <errno.h>
34
35 #ifdef HAVE_FCNTL_H
36 # include <fcntl.h>
37 #else
38 # include <sys/file.h>
39 #endif
40
41 #if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING)
42 # include <sys/file.h>
43 #endif
44
45 #include <signal.h>
46
47 #if defined(HAVE_LIBLOCKFILE)
48 #include <lockfile.h>
49 #endif
50
51 #ifdef LOCKDIR
52 char *lockdir = LOCKDIR;
53 #endif
54
55 /* Are we using any kernel locking? */
56 #if defined (FLOCK_LOCKING) || defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
57 # define KERNEL_LOCKING
58 #endif
59
60 #ifdef DOT_LOCKING
61
62 /* struct for getting name of lock file to create */
63 struct lockinfo {
64         char curlock[BUFSIZ];
65 #if !defined(HAVE_LIBLOCKFILE)
66         char tmplock[BUFSIZ];
67 #endif
68 };
69
70 /*
71 ** Amount of time to wait before
72 ** updating ctime of lock file.
73 */
74 #define NSECS 20
75
76 #if !defined(HAVE_LIBLOCKFILE)
77 /*
78 ** How old does a lock file need to be
79 ** before we remove it.
80 */
81 #define RSECS 180
82 #endif /* HAVE_LIBLOCKFILE */
83
84 /* struct for recording and updating locks */
85 struct lock {
86         int l_fd;
87         char *l_lock;
88         struct lock *l_next;
89 };
90
91 /* top of list containing all open locks */
92 static struct lock *l_top = NULL;
93 #endif /* DOT_LOCKING */
94
95 /*
96 ** static prototypes
97 */
98 #ifdef KERNEL_LOCKING
99 static int lkopen_kernel(char *, int, mode_t);
100 #endif
101
102 #ifdef DOT_LOCKING
103 static int lkopen_dot(char *, int, mode_t);
104 static void lockname(char *, struct lockinfo *, int);
105 static void timerON(char *, int);
106 static void timerOFF(int);
107 static void alrmser(int);
108
109 #if !defined(HAVE_LIBLOCKFILE)
110 static int lockit(struct lockinfo *);
111 #endif
112 #endif
113
114 /*
115 ** Base routine to open and lock a file,
116 ** and return a file descriptor.
117 */
118
119 int
120 lkopen(char *file, int access, mode_t mode)
121 {
122 #ifdef KERNEL_LOCKING
123         return lkopen_kernel(file, access, mode);
124 #endif
125
126 #ifdef DOT_LOCKING
127         return lkopen_dot(file, access, mode);
128 #endif
129 }
130
131
132 /*
133 ** Base routine to close and unlock a file,
134 ** given a file descriptor.
135 */
136
137 int
138 lkclose(int fd, char *file)
139 {
140 #ifdef FCNTL_LOCKING
141         struct flock buf;
142 #endif
143
144 #ifdef DOT_LOCKING
145         struct lockinfo lkinfo;
146 #endif
147
148         if (fd == -1)
149                 return 0;
150
151 #ifdef FCNTL_LOCKING
152         buf.l_type   = F_UNLCK;
153         buf.l_whence = SEEK_SET;
154         buf.l_start  = 0;
155         buf.l_len = 0;
156         fcntl(fd, F_SETLK, &buf);
157 #endif
158
159 #ifdef FLOCK_LOCKING
160         flock(fd, LOCK_UN);
161 #endif
162
163 #ifdef LOCKF_LOCKING
164         /* make sure we unlock the whole thing */
165         lseek(fd, (off_t) 0, SEEK_SET);
166         lockf(fd, F_ULOCK, 0L);
167 #endif
168
169 #ifdef DOT_LOCKING
170         lockname(file, &lkinfo, 0);  /* get name of lock file */
171 #if !defined(HAVE_LIBLOCKFILE)
172         unlink(lkinfo.curlock);  /* remove lock file */
173 #else
174         lockfile_remove(lkinfo.curlock);
175 #endif /* HAVE_LIBLOCKFILE */
176         timerOFF(fd);  /* turn off lock timer   */
177 #endif /* DOT_LOCKING */
178
179         return (close(fd));
180 }
181
182
183 /*
184 ** Base routine to open and lock a file,
185 ** and return a FILE pointer
186 */
187
188 FILE *
189 lkfopen(char *file, char *mode)
190 {
191         int fd, access;
192         FILE *fp;
193
194         if (strcmp(mode, "r") == 0)
195                 access = O_RDONLY;
196         else if (strcmp(mode, "r+") == 0)
197                 access = O_RDWR;
198         else if (strcmp(mode, "w") == 0)
199                 access = O_WRONLY | O_CREAT | O_TRUNC;
200         else if (strcmp(mode, "w+") == 0)
201                 access = O_RDWR | O_CREAT | O_TRUNC;
202         else if (strcmp(mode, "a") == 0)
203                 access = O_WRONLY | O_CREAT | O_APPEND;
204         else if (strcmp(mode, "a+") == 0)
205                 access = O_RDWR | O_CREAT | O_APPEND;
206         else {
207                 errno = EINVAL;
208                 return NULL;
209         }
210
211         if ((fd = lkopen(file, access, 0666)) == -1)
212                 return NULL;
213
214         if ((fp = fdopen(fd, mode)) == NULL) {
215                 close(fd);
216                 return NULL;
217         }
218
219         return fp;
220 }
221
222
223 /*
224 ** Base routine to close and unlock a file,
225 ** given a FILE pointer
226 */
227
228 int
229 lkfclose(FILE *fp, char *file)
230 {
231 #ifdef FCNTL_LOCKING
232         struct flock buf;
233 #endif
234
235 #ifdef DOT_LOCKING
236         struct lockinfo lkinfo;
237 #endif
238
239         if (fp == NULL)
240                 return 0;
241
242 #ifdef FCNTL_LOCKING
243         buf.l_type   = F_UNLCK;
244         buf.l_whence = SEEK_SET;
245         buf.l_start  = 0;
246         buf.l_len = 0;
247         fcntl(fileno(fp), F_SETLK, &buf);
248 #endif
249
250 #ifdef FLOCK_LOCKING
251         flock(fileno(fp), LOCK_UN);
252 #endif
253
254 #ifdef LOCKF_LOCKING
255         /* make sure we unlock the whole thing */
256         fseek(fp, 0L, SEEK_SET);
257         lockf(fileno(fp), F_ULOCK, 0L);
258 #endif
259
260 #ifdef DOT_LOCKING
261         lockname(file, &lkinfo, 0);  /* get name of lock file */
262 #if !defined(HAVE_LIBLOCKFILE)
263         unlink(lkinfo.curlock);  /* remove lock file */
264 #else
265         lockfile_remove(lkinfo.curlock);
266 #endif /* HAVE_LIBLOCKFILE */
267         timerOFF(fileno(fp));  /* turn off lock timer   */
268 #endif /* DOT_LOCKING */
269
270         return (fclose(fp));
271 }
272
273
274 #ifdef KERNEL_LOCKING
275
276 /*
277 ** open and lock a file, using kernel locking
278 */
279
280 static int
281 lkopen_kernel(char *file, int access, mode_t mode)
282 {
283         int fd, i, j;
284
285 # ifdef FCNTL_LOCKING
286         struct flock buf;
287 # endif /* FCNTL_LOCKING */
288
289         for (i = 0; i < 5; i++) {
290
291 # if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
292                 /* remember the original mode */
293                 j = access;
294
295                 /* make sure we open at the beginning */
296                 access &= ~O_APPEND;
297
298                 /*
299                 ** We MUST have write permission or
300                 ** lockf/fcntl() won't work
301                 */
302                 if ((access & 03) == O_RDONLY) {
303                         access &= ~O_RDONLY;
304                         access |= O_RDWR;
305                 }
306 # endif /* LOCKF_LOCKING || FCNTL_LOCKING */
307
308                 if ((fd = open(file, access | O_NDELAY, mode)) == -1)
309                         return -1;
310
311 # ifdef FCNTL_LOCKING
312                 buf.l_type   = F_WRLCK;
313                 buf.l_whence = SEEK_SET;
314                 buf.l_start  = 0;
315                 buf.l_len = 0;
316                 if (fcntl(fd, F_SETLK, &buf) != -1)
317                         return fd;
318 # endif
319
320 # ifdef FLOCK_LOCKING
321                 if (flock(fd, (((access & 03) == O_RDONLY) ? LOCK_SH :
322                         LOCK_EX) | LOCK_NB) != -1)
323                         return fd;
324 # endif
325
326 # ifdef LOCKF_LOCKING
327                 if (lockf(fd, F_TLOCK, 0L) != -1) {
328                         /* see if we should be at the end */
329                         if (j & O_APPEND)
330                                 lseek(fd, (off_t) 0, SEEK_END);
331                         return fd;
332                 }
333 # endif
334
335                 j = errno;
336                 close(fd);
337                 sleep(5);
338         }
339
340         close(fd);
341         errno = j;
342         return -1;
343 }
344
345 #endif /* KERNEL_LOCKING */
346
347
348 #ifdef DOT_LOCKING
349
350 /*
351 ** open and lock a file, using dot locking
352 */
353
354 static int
355 lkopen_dot(char *file, int access, mode_t mode)
356 {
357         int fd;
358         struct lockinfo lkinfo;
359
360         /* open the file */
361         if ((fd = open(file, access, mode)) == -1)
362                 return -1;
363
364         /*
365         ** Get the name of the eventual lock file, as well
366         ** as a name for a temporary lock file.
367         */
368         lockname(file, &lkinfo, 1);
369
370 #if !defined(HAVE_LIBLOCKFILE)
371         {
372                 int i;
373                 for (i = 0;;) {
374                         /* attempt to create lock file */
375                         if (lockit(&lkinfo) == 0) {
376                                 /* if successful, turn on timer and return */
377                                 timerON(lkinfo.curlock, fd);
378                                 return fd;
379                         } else {
380                                 /*
381                                 ** Abort locking, if we fail to lock after 5
382                                 ** attempts and are never able to stat the
383                                 ** 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                                         /*
396                                         ** check for stale lockfile,
397                                         ** else sleep
398                                         */
399                                         if (curtime > st.st_ctime + RSECS)
400                                                 unlink(lkinfo.curlock);
401                                         else
402                                                 sleep(5);
403                                 }
404                                 lockname(file, &lkinfo, 1);
405                         }
406                 }
407         }
408 #else
409         if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
410                 timerON(lkinfo.curlock, fd);
411                 return fd;
412         } else {
413                 close(fd);
414                 return -1;
415         }
416 #endif /* HAVE_LIBLOCKFILE */
417 }
418
419 #if !defined(HAVE_LIBLOCKFILE)
420 /*
421 ** Routine that actually tries to create
422 ** the lock file.
423 */
424
425 static int
426 lockit(struct lockinfo *li)
427 {
428         int fd;
429         char *curlock, *tmplock;
430
431 #if 0
432         char buffer[128];
433 #endif
434
435         curlock = li->curlock;
436         tmplock = li->tmplock;
437
438         if ((fd = mkstemp(tmplock)) == -1)
439                 return -1;
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 = (struct lock *) mh_xmalloc(sizeof(*lp));
520
521         len = strlen(curlock) + 1;
522         lp->l_fd = fd;
523         lp->l_lock = mh_xmalloc(len);
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 */