]> diplodocus.org Git - nmh/blob - sbr/lock_file.c
Alter mh-chart(7)'s NAME to be lowercase.
[nmh] / sbr / lock_file.c
1
2 /*
3 * lock.c -- routines to lock/unlock files
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
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 #include <h/mts.h>
22
23 #ifdef HAVE_SYS_TIME_H
24 # include <sys/time.h>
25 #endif
26 #include <time.h>
27 #include <fcntl.h>
28 #ifdef HAVE_FLOCK
29 # include <sys/file.h>
30 #endif
31
32 #if defined(HAVE_LIBLOCKFILE)
33 # include <lockfile.h>
34 #endif
35
36 #ifdef LOCKDIR
37 char *lockdir = LOCKDIR;
38 #endif
39
40 /* struct for getting name of lock file to create */
41 struct lockinfo {
42 char curlock[BUFSIZ];
43 #if !defined(HAVE_LIBLOCKFILE)
44 char tmplock[BUFSIZ];
45 #endif
46 };
47
48 /*
49 * Number of tries to retry locking
50 */
51 #define LOCK_RETRIES 60
52
53 /*
54 * Amount of time to wait before
55 * updating ctime of lock file.
56 */
57 #define NSECS 20
58
59 #if !defined(HAVE_LIBLOCKFILE)
60 /*
61 * How old does a lock file need to be
62 * before we remove it.
63 */
64 #define RSECS 180
65 #endif /* HAVE_LIBLOCKFILE */
66
67 /* struct for recording and updating locks */
68 struct lock {
69 int l_fd;
70 char *l_lock;
71 struct lock *l_next;
72 };
73
74 enum locktype { FCNTL_LOCKING, FLOCK_LOCKING, LOCKF_LOCKING, DOT_LOCKING };
75
76 /*
77 * Flags to indicate whether we've initialized the lock types, and
78 * our saved lock types
79 */
80 static int datalockinit = 0;
81 static int spoollockinit = 0;
82 static enum locktype datalocktype, spoollocktype;
83
84
85 /* top of list containing all open locks */
86 static struct lock *l_top = NULL;
87
88 static int lkopen(const char *, int, mode_t, enum locktype, int *);
89 static int str2accbits(const char *);
90
91 static int lkopen_fcntl (const char *, int, mode_t, int *);
92 #ifdef HAVE_LOCKF
93 static int lkopen_lockf (const char *, int, mode_t, int *);
94 #endif /* HAVE_LOCKF */
95 #ifdef HAVE_FLOCK
96 static int lkopen_flock (const char *, int, mode_t, int *);
97 #endif /* HAVE_FLOCK */
98
99 static enum locktype init_locktype(const char *);
100
101 static int lkopen_dot (const char *, int, mode_t, int *);
102 static void lkclose_dot (int, const char *);
103 static void lockname (const char *, struct lockinfo *, int);
104 static void timerON (char *, int);
105 static void timerOFF (int);
106 static void alrmser (int);
107
108 #if !defined(HAVE_LIBLOCKFILE)
109 static int lockit (struct lockinfo *);
110 #endif
111
112 /*
113 * Base functions: determine the data type used to lock files and
114 * call the underlying function.
115 */
116
117 int
118 lkopendata(const char *file, int access, mode_t mode, int *failed_to_lock)
119 {
120 if (! datalockinit) {
121 char *cp = context_find("datalocking");
122
123 if (cp) {
124 datalocktype = init_locktype(cp);
125 } else {
126 /* We default to fcntl locking for data files */
127 datalocktype = FCNTL_LOCKING;
128 }
129
130 datalockinit = 1;
131 }
132
133 return lkopen(file, access, mode, datalocktype, failed_to_lock);
134 }
135
136
137 /*
138 * Locking using the spool locking algorithm
139 */
140
141 int lkopenspool(const char *file, int access, mode_t mode, int *failed_to_lock)
142 {
143 if (! spoollockinit) {
144 spoollocktype = init_locktype(spoollocking);
145
146 spoollockinit = 1;
147 }
148
149 return lkopen(file, access, mode, spoollocktype, failed_to_lock);
150 }
151
152
153 /*
154 * Versions of lkopen that return a FILE *
155 */
156
157 FILE *
158 lkfopendata(const char *file, const char *mode, int *failed_to_lock)
159 {
160 FILE *fp;
161 int oflags = str2accbits(mode);
162 int fd;
163
164 if (oflags == -1) {
165 errno = EINVAL;
166 return NULL;
167 }
168
169 if ((fd = lkopendata(file, oflags, 0666, failed_to_lock)) == -1)
170 return NULL;
171
172 if ((fp = fdopen (fd, mode)) == NULL) {
173 close (fd);
174 return NULL;
175 }
176
177 return fp;
178 }
179
180 FILE *
181 lkfopenspool(const char *file, const char *mode)
182 {
183 FILE *fp;
184 int oflags = str2accbits(mode);
185 int failed_to_lock = 0;
186 int fd;
187
188 if (oflags == -1) {
189 errno = EINVAL;
190 return NULL;
191 }
192
193 if ((fd = lkopenspool(file, oflags, 0666, &failed_to_lock)) == -1)
194 return NULL;
195
196 if ((fp = fdopen (fd, mode)) == NULL) {
197 close (fd);
198 return NULL;
199 }
200
201 return fp;
202 }
203
204
205 /*
206 * Corresponding close functions.
207 *
208 * A note here: All of the kernel locking functions terminate the lock
209 * when the descriptor is closed, so why write the code to explicitly
210 * unlock the file? We only need to do this in the dot-locking case.
211 */
212
213 int
214 lkclosedata(int fd, const char *name)
215 {
216 int rc = close(fd);
217
218 if (datalocktype == DOT_LOCKING)
219 lkclose_dot(fd, name);
220
221 return rc;
222 }
223
224 int
225 lkfclosedata(FILE *f, const char *name)
226 {
227 int fd, rc;
228
229 if (f == NULL)
230 return 0;
231
232 fd = fileno(f);
233 rc = fclose(f);
234
235 if (datalocktype == DOT_LOCKING)
236 lkclose_dot(fd, name);
237
238 return rc;
239 }
240
241 int
242 lkclosespool(int fd, const char *name)
243 {
244 int rc = close(fd);
245
246 if (spoollocktype == DOT_LOCKING)
247 lkclose_dot(fd, name);
248
249 return rc;
250 }
251
252 int
253 lkfclosespool(FILE *f, const char *name)
254 {
255 int fd, rc;
256
257 if (f == NULL)
258 return 0;
259
260 fd = fileno(f);
261 rc = fclose(f);
262
263 if (spoollocktype == DOT_LOCKING)
264 lkclose_dot(fd, name);
265
266 return rc;
267 }
268
269
270 /*
271 * Convert fopen() mode argument to open() bits
272 */
273
274 static int
275 str2accbits(const char *mode)
276 {
277 if (strcmp (mode, "r") == 0)
278 return O_RDONLY;
279 if (strcmp (mode, "r+") == 0)
280 return O_RDWR;
281 if (strcmp (mode, "w") == 0)
282 return O_WRONLY | O_CREAT | O_TRUNC;
283 if (strcmp (mode, "w+") == 0)
284 return O_RDWR | O_CREAT | O_TRUNC;
285 if (strcmp (mode, "a") == 0)
286 return O_WRONLY | O_CREAT | O_APPEND;
287 if (strcmp (mode, "a+") == 0)
288 return O_RDWR | O_CREAT | O_APPEND;
289
290 errno = EINVAL;
291 return -1;
292 }
293
294 /*
295 * Internal routine to switch between different locking types.
296 */
297
298 static int
299 lkopen (const char *file, int access, mode_t mode, enum locktype ltype,
300 int *failed_to_lock)
301 {
302 switch (ltype) {
303
304 case FCNTL_LOCKING:
305 return lkopen_fcntl(file, access, mode, failed_to_lock);
306
307 case DOT_LOCKING:
308 return lkopen_dot(file, access, mode, failed_to_lock);
309
310 #ifdef HAVE_FLOCK
311 case FLOCK_LOCKING:
312 return lkopen_flock(file, access, mode, failed_to_lock);
313 #endif /* HAVE_FLOCK */
314
315 #ifdef HAVE_LOCKF
316 case LOCKF_LOCKING:
317 return lkopen_lockf(file, access, mode, failed_to_lock);
318 #endif /* HAVE_FLOCK */
319
320 default:
321 adios(NULL, "Internal locking error: unsupported lock type used!");
322 }
323
324 return -1;
325 }
326
327
328 /*
329 * Routine to clean up the dot locking file
330 */
331
332 static void
333 lkclose_dot (int fd, const char *file)
334 {
335 struct lockinfo lkinfo;
336
337 lockname (file, &lkinfo, 0); /* get name of lock file */
338 #if !defined(HAVE_LIBLOCKFILE)
339 (void) m_unlink (lkinfo.curlock); /* remove lock file */
340 #else
341 lockfile_remove(lkinfo.curlock);
342 #endif /* HAVE_LIBLOCKFILE */
343 timerOFF (fd); /* turn off lock timer */
344 }
345
346
347 /*
348 * Open and lock a file, using fcntl locking
349 */
350
351 static int
352 lkopen_fcntl(const char *file, int access, mode_t mode, int *failed_to_lock)
353 {
354 int fd, i, saved_errno;
355 struct flock flk;
356
357 /*
358 * The assumption here is that if you open the file for writing, you
359 * need an exclusive lock.
360 */
361
362 for (i = 0; i < LOCK_RETRIES; i++) {
363 if ((fd = open(file, access, mode)) == -1)
364 return -1;
365
366 flk.l_start = 0;
367 flk.l_len = 0;
368 flk.l_type = (access & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK;
369 flk.l_whence = SEEK_SET;
370
371 if (fcntl(fd, F_SETLK, &flk) != -1)
372 return fd;
373
374 saved_errno = errno;
375 close(fd);
376 sleep(1);
377 }
378
379 *failed_to_lock = 1;
380 errno = saved_errno;
381 return -1;
382 }
383
384
385 #ifdef HAVE_FLOCK
386 /*
387 * Open and lock a file, using flock locking
388 */
389
390 static int
391 lkopen_flock(const char *file, int access, mode_t mode, int *failed_to_lock)
392 {
393 int fd, i, saved_errno, locktype;
394
395 /*
396 * The assumption here is that if you open the file for writing, you
397 * need an exclusive lock.
398 */
399
400 locktype = (((access & O_ACCMODE) == O_RDONLY) ? LOCK_SH : LOCK_EX) |
401 LOCK_NB;
402
403 for (i = 0; i < LOCK_RETRIES; i++) {
404 if ((fd = open(file, access, mode)) == -1)
405 return -1;
406
407 if (flock(fd, locktype) != -1)
408 return fd;
409
410 saved_errno = errno;
411 close(fd);
412 sleep(1);
413 }
414
415 *failed_to_lock = 1;
416 errno = saved_errno;
417 return -1;
418 }
419 #endif /* HAVE_FLOCK */
420
421 /*
422 * Open and lock a file, using lockf locking
423 */
424
425 static int
426 lkopen_lockf(const char *file, int access, mode_t mode, int *failed_to_lock)
427 {
428 int fd, i, saved_errno, saved_access;
429
430 /*
431 * Two notes:
432 *
433 * Because lockf locks start from the current offset, mask off O_APPEND
434 * and seek to the end of the file later if it was requested.
435 *
436 * lockf locks require write access to the file, so always add it
437 * even if it wasn't requested.
438 */
439
440 saved_access = access;
441
442 access &= ~O_APPEND;
443
444 if ((access & O_ACCMODE) == O_RDONLY) {
445 access &= ~O_RDONLY;
446 access |= O_RDWR;
447 }
448
449 for (i = 0; i < LOCK_RETRIES; i++) {
450 if ((fd = open(file, access, mode)) == -1)
451 return -1;
452
453 if (lockf(fd, F_TLOCK, 0) != -1) {
454 /*
455 * Seek to end if requested
456 */
457 if (saved_access & O_APPEND) {
458 lseek(fd, 0, SEEK_END);
459 }
460 return fd;
461 }
462
463 saved_errno = errno;
464 close(fd);
465 sleep(1);
466 }
467
468 *failed_to_lock = 1;
469 errno = saved_errno;
470 return -1;
471 }
472
473
474 /*
475 * open and lock a file, using dot locking
476 */
477
478 static int
479 lkopen_dot (const char *file, int access, mode_t mode, int *failed_to_lock)
480 {
481 int fd;
482 struct lockinfo lkinfo;
483
484 /* open the file */
485 if ((fd = open (file, access, mode)) == -1)
486 return -1;
487
488 /*
489 * Get the name of the eventual lock file, as well
490 * as a name for a temporary lock file.
491 */
492 lockname (file, &lkinfo, 1);
493
494 #if !defined(HAVE_LIBLOCKFILE)
495 {
496 int i;
497 for (i = 0; i < LOCK_RETRIES; ++i) {
498 struct stat st;
499
500 /* attempt to create lock file */
501 if (lockit (&lkinfo) == 0) {
502 /* if successful, turn on timer and return */
503 timerON (lkinfo.curlock, fd);
504 return fd;
505 }
506
507 /*
508 * Abort locking, if we fail to lock after 5 attempts
509 * and are never able to stat the lock file. Or, if
510 * we can stat the lockfile but exceed LOCK_RETRIES
511 * seconds waiting for it (by falling out of the loop).
512 */
513 if (stat (lkinfo.curlock, &st) == -1) {
514 if (i++ > 5) break;
515 sleep (1);
516 } else {
517 time_t curtime;
518 time (&curtime);
519
520 /* check for stale lockfile, else sleep */
521 if (curtime > st.st_ctime + RSECS)
522 (void) m_unlink (lkinfo.curlock);
523 else
524 sleep (1);
525 }
526 lockname (file, &lkinfo, 1);
527 }
528
529 *failed_to_lock = 1;
530 return -1;
531 }
532 #else
533 if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
534 timerON(lkinfo.curlock, fd);
535 return fd;
536 }
537
538 close(fd);
539 *failed_to_lock = 1;
540 return -1;
541 #endif /* HAVE_LIBLOCKFILE */
542 }
543
544 #if !defined(HAVE_LIBLOCKFILE)
545 /*
546 * Routine that actually tries to create
547 * the lock file.
548 */
549
550 static int
551 lockit (struct lockinfo *li)
552 {
553 int fd;
554 char *curlock, *tmpfile;
555
556 #if 0
557 char buffer[128];
558 #endif
559
560 curlock = li->curlock;
561
562 if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
563 advise(NULL, "unable to create temporary file in %s", li->tmplock);
564 return -1;
565 }
566
567 #if 0
568 /* write our process id into lock file */
569 snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
570 write(fd, buffer, strlen(buffer) + 1);
571 #endif
572
573 close (fd);
574
575 /*
576 * Now try to create the real lock file
577 * by linking to the temporary file.
578 */
579 fd = link(tmpfile, curlock);
580 (void) m_unlink(tmpfile);
581
582 return (fd == -1 ? -1 : 0);
583 }
584 #endif /* HAVE_LIBLOCKFILE */
585
586 /*
587 * Get name of lock file, and temporary lock file
588 */
589
590 static void
591 lockname (const char *file, struct lockinfo *li, int isnewlock)
592 {
593 int bplen, tmplen;
594 char *bp;
595 const char *cp;
596
597 #if 0
598 struct stat st;
599 #endif
600
601 if ((cp = strrchr (file, '/')) == NULL || *++cp == 0)
602 cp = file;
603
604 bp = li->curlock;
605 bplen = 0;
606 #ifdef LOCKDIR
607 snprintf (bp, sizeof(li->curlock), "%s/", lockdir);
608 tmplen = strlen (bp);
609 bp += tmplen;
610 bplen += tmplen;
611 #else
612 if (cp != file) {
613 snprintf (bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
614 tmplen = strlen (bp);
615 bp += tmplen;
616 bplen += tmplen;
617 }
618 #endif
619
620 #if 0
621 /*
622 * mmdf style dot locking. Currently not supported.
623 * If we start supporting mmdf style dot locking,
624 * we will need to change the return value of lockname
625 */
626 if (stat (file, &st) == -1)
627 return -1;
628
629 snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d",
630 st.st_dev, st.st_ino);
631 #endif
632
633 snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
634
635 #if !defined(HAVE_LIBLOCKFILE)
636 /*
637 * If this is for a new lock, create a name for
638 * the temporary lock file for lockit()
639 */
640 if (isnewlock) {
641 if ((cp = strrchr (li->curlock, '/')) == NULL || *++cp == 0)
642 strncpy (li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock));
643 else
644 snprintf (li->tmplock, sizeof(li->tmplock), "%.*s,LCK.XXXXXX",
645 (int)(cp - li->curlock), li->curlock);
646 }
647 #endif
648 }
649
650
651 /*
652 * Add new lockfile to the list of open lockfiles
653 * and start the lock file timer.
654 */
655
656 static void
657 timerON (char *curlock, int fd)
658 {
659 struct lock *lp;
660
661 NEW(lp);
662 lp->l_lock = mh_xstrdup(curlock);
663 lp->l_fd = fd;
664 lp->l_next = l_top;
665
666 if (!l_top) {
667 /* perhaps SIGT{STP,TIN,TOU} ? */
668 SIGNAL (SIGALRM, alrmser);
669 alarm (NSECS);
670 }
671 l_top = lp;
672 }
673
674
675 /*
676 * Search through the list of lockfiles for the
677 * current lockfile, and remove it from the list.
678 */
679
680 static void
681 timerOFF (int fd)
682 {
683 struct lock *pp, *lp;
684
685 alarm(0);
686
687 if (l_top) {
688 for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) {
689 if (lp->l_fd == fd)
690 break;
691 }
692 if (lp) {
693 if (lp == l_top)
694 l_top = lp->l_next;
695 else
696 pp->l_next = lp->l_next;
697
698 free (lp->l_lock);
699 free (lp);
700 }
701 }
702
703 /* if there are locks left, restart timer */
704 if (l_top)
705 alarm (NSECS);
706 }
707
708
709 /*
710 * If timer goes off, we update the ctime of all open
711 * lockfiles, so another command doesn't remove them.
712 */
713
714 static void
715 alrmser (int sig)
716 {
717 char *lockfile;
718 struct lock *lp;
719 NMH_UNUSED (sig);
720
721 /* update the ctime of all the lock files */
722 for (lp = l_top; lp; lp = lp->l_next) {
723 lockfile = lp->l_lock;
724 #if !defined(HAVE_LIBLOCKFILE)
725 {
726 int j;
727 if (*lockfile && (j = creat (lockfile, 0600)) != -1)
728 close (j);
729 }
730 #else
731 lockfile_touch(lockfile);
732 #endif
733 }
734
735 /* restart the alarm */
736 alarm (NSECS);
737 }
738
739
740 /*
741 * Return a locking algorithm based on the string name
742 */
743
744 static enum locktype
745 init_locktype(const char *lockname)
746 {
747 if (strcasecmp(lockname, "fcntl") == 0) {
748 return FCNTL_LOCKING;
749 }
750 if (strcasecmp(lockname, "lockf") == 0) {
751 #ifdef HAVE_LOCKF
752 return LOCKF_LOCKING;
753 #else /* ! HAVE_LOCKF */
754 adios(NULL, "lockf not supported on this system");
755 #endif /* HAVE_LOCKF */
756 }
757 if (strcasecmp(lockname, "flock") == 0) {
758 #ifdef HAVE_FLOCK
759 return FLOCK_LOCKING;
760 #else /* ! HAVE_FLOCK */
761 adios(NULL, "flock not supported on this system");
762 #endif /* HAVE_FLOCK */
763 }
764 if (strcasecmp(lockname, "dot") == 0) {
765 return DOT_LOCKING;
766 }
767 adios(NULL, "Unknown lock type: \"%s\"", lockname);
768 /* NOTREACHED */
769 return 0;
770 }