]> diplodocus.org Git - nmh/blob - sbr/lock_file.c
forwsbr.c: Move interface declaration to own forwsbr.h.
[nmh] / sbr / lock_file.c
1 /* lock_file.c -- routines to lock/unlock files
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 /* Modified by Ruud de Rooij to support Miquel van Smoorenburg's liblockfile
9 *
10 * Since liblockfile locking shares most of its code with dot locking, it
11 * is enabled by defining both DOT_LOCKING and HAVE_LIBLOCKFILE.
12 *
13 * Ruud de Rooij <ruud@debian.org> Sun, 28 Mar 1999 15:34:03 +0200
14 */
15
16 #include <h/mh.h>
17 #include <h/signals.h>
18 #include <h/utils.h>
19 #include <h/mts.h>
20 #include "lock_file.h"
21 #include "m_mktemp.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 /* Our saved lock types. */
77 static enum locktype datalocktype, spoollocktype;
78
79 /* top of list containing all open locks */
80 static struct lock *l_top = NULL;
81
82 static int lkopen(const char *, int, mode_t, enum locktype, int *);
83 static int str2accbits(const char *);
84
85 static int lkopen_fcntl (const char *, int, mode_t, int *);
86 #ifdef HAVE_LOCKF
87 static int lkopen_lockf (const char *, int, mode_t, int *);
88 #endif /* HAVE_LOCKF */
89 #ifdef HAVE_FLOCK
90 static int lkopen_flock (const char *, int, mode_t, int *);
91 #endif /* HAVE_FLOCK */
92
93 static enum locktype init_locktype(const char *) PURE;
94
95 static int lkopen_dot (const char *, int, mode_t, int *);
96 static void lkclose_dot (int, const char *);
97 static void lockname (const char *, struct lockinfo *, int);
98 static void timerON (char *, int);
99 static void timerOFF (int);
100 static void alrmser (int);
101
102 #if !defined(HAVE_LIBLOCKFILE)
103 static int lockit (struct lockinfo *);
104 #endif
105
106 /*
107 * Base functions: determine the data type used to lock files and
108 * call the underlying function.
109 */
110
111 int
112 lkopendata(const char *file, int access, mode_t mode, int *failed_to_lock)
113 {
114 static bool deja_vu;
115
116 if (!deja_vu) {
117 char *dl;
118
119 deja_vu = true;
120 if ((dl = context_find("datalocking"))) {
121 datalocktype = init_locktype(dl);
122 } else {
123 /* We default to fcntl locking for data files */
124 datalocktype = FCNTL_LOCKING;
125 }
126 }
127
128 return lkopen(file, access, mode, datalocktype, failed_to_lock);
129 }
130
131
132 /*
133 * Locking using the spool locking algorithm
134 */
135
136 int
137 lkopenspool(const char *file, int access, mode_t mode, int *failed_to_lock)
138 {
139 static bool deja_vu;
140
141 if (!deja_vu) {
142 deja_vu = true;
143 spoollocktype = init_locktype(spoollocking);
144 }
145
146 return lkopen(file, access, mode, spoollocktype, failed_to_lock);
147 }
148
149
150 /*
151 * Versions of lkopen that return a FILE *
152 */
153
154 FILE *
155 lkfopendata(const char *file, const char *mode, int *failed_to_lock)
156 {
157 FILE *fp;
158 int oflags = str2accbits(mode);
159 int fd;
160
161 if (oflags == -1) {
162 errno = EINVAL;
163 return NULL;
164 }
165
166 if ((fd = lkopendata(file, oflags, 0666, failed_to_lock)) == -1)
167 return NULL;
168
169 if ((fp = fdopen (fd, mode)) == NULL) {
170 close (fd);
171 return NULL;
172 }
173
174 return fp;
175 }
176
177 FILE *
178 lkfopenspool(const char *file, const char *mode)
179 {
180 FILE *fp;
181 int oflags = str2accbits(mode);
182 int failed_to_lock = 0;
183 int fd;
184
185 if (oflags == -1) {
186 errno = EINVAL;
187 return NULL;
188 }
189
190 if ((fd = lkopenspool(file, oflags, 0666, &failed_to_lock)) == -1)
191 return NULL;
192
193 if ((fp = fdopen (fd, mode)) == NULL) {
194 close (fd);
195 return NULL;
196 }
197
198 return fp;
199 }
200
201
202 /*
203 * Corresponding close functions.
204 *
205 * A note here: All of the kernel locking functions terminate the lock
206 * when the descriptor is closed, so why write the code to explicitly
207 * unlock the file? We only need to do this in the dot-locking case.
208 */
209
210 int
211 lkclosedata(int fd, const char *name)
212 {
213 int rc = close(fd);
214
215 if (datalocktype == DOT_LOCKING)
216 lkclose_dot(fd, name);
217
218 return rc;
219 }
220
221 int
222 lkfclosedata(FILE *f, const char *name)
223 {
224 int fd, rc;
225
226 if (f == NULL)
227 return 0;
228
229 fd = fileno(f);
230 rc = fclose(f);
231
232 if (datalocktype == DOT_LOCKING)
233 lkclose_dot(fd, name);
234
235 return rc;
236 }
237
238 int
239 lkclosespool(int fd, const char *name)
240 {
241 int rc = close(fd);
242
243 if (spoollocktype == DOT_LOCKING)
244 lkclose_dot(fd, name);
245
246 return rc;
247 }
248
249 int
250 lkfclosespool(FILE *f, const char *name)
251 {
252 int fd, rc;
253
254 if (f == NULL)
255 return 0;
256
257 fd = fileno(f);
258 rc = fclose(f);
259
260 if (spoollocktype == DOT_LOCKING)
261 lkclose_dot(fd, name);
262
263 return rc;
264 }
265
266
267 /*
268 * Convert fopen() mode argument to open() bits
269 */
270
271 static int
272 str2accbits(const char *mode)
273 {
274 if (strcmp (mode, "r") == 0)
275 return O_RDONLY;
276 if (strcmp (mode, "r+") == 0)
277 return O_RDWR;
278 if (strcmp (mode, "w") == 0)
279 return O_WRONLY | O_CREAT | O_TRUNC;
280 if (strcmp (mode, "w+") == 0)
281 return O_RDWR | O_CREAT | O_TRUNC;
282 if (strcmp (mode, "a") == 0)
283 return O_WRONLY | O_CREAT | O_APPEND;
284 if (strcmp (mode, "a+") == 0)
285 return O_RDWR | O_CREAT | O_APPEND;
286
287 errno = EINVAL;
288 return -1;
289 }
290
291 /*
292 * Internal routine to switch between different locking types.
293 */
294
295 static int
296 lkopen (const char *file, int access, mode_t mode, enum locktype ltype,
297 int *failed_to_lock)
298 {
299 switch (ltype) {
300
301 case FCNTL_LOCKING:
302 return lkopen_fcntl(file, access, mode, failed_to_lock);
303
304 case DOT_LOCKING:
305 return lkopen_dot(file, access, mode, failed_to_lock);
306
307 #ifdef HAVE_FLOCK
308 case FLOCK_LOCKING:
309 return lkopen_flock(file, access, mode, failed_to_lock);
310 #endif /* HAVE_FLOCK */
311
312 #ifdef HAVE_LOCKF
313 case LOCKF_LOCKING:
314 return lkopen_lockf(file, access, mode, failed_to_lock);
315 #endif /* HAVE_FLOCK */
316
317 default:
318 die("Internal locking error: unsupported lock type used!");
319 }
320
321 return -1;
322 }
323
324
325 /*
326 * Routine to clean up the dot locking file
327 */
328
329 static void
330 lkclose_dot (int fd, const char *file)
331 {
332 struct lockinfo lkinfo;
333
334 lockname (file, &lkinfo, 0); /* get name of lock file */
335 #if !defined(HAVE_LIBLOCKFILE)
336 (void) m_unlink (lkinfo.curlock); /* remove lock file */
337 #else
338 lockfile_remove(lkinfo.curlock);
339 #endif /* HAVE_LIBLOCKFILE */
340 timerOFF (fd); /* turn off lock timer */
341 }
342
343
344 /*
345 * Open and lock a file, using fcntl locking
346 */
347
348 static int
349 lkopen_fcntl(const char *file, int access, mode_t mode, int *failed_to_lock)
350 {
351 int fd, i, saved_errno;
352 struct flock flk;
353
354 /*
355 * The assumption here is that if you open the file for writing, you
356 * need an exclusive lock.
357 */
358
359 for (i = 0; i < LOCK_RETRIES; i++) {
360 if ((fd = open(file, access, mode)) == -1)
361 return -1;
362
363 flk.l_start = 0;
364 flk.l_len = 0;
365 flk.l_type = (access & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK;
366 flk.l_whence = SEEK_SET;
367
368 if (fcntl(fd, F_SETLK, &flk) != -1)
369 return fd;
370
371 saved_errno = errno;
372 close(fd);
373 sleep(1);
374 }
375
376 *failed_to_lock = 1;
377 errno = saved_errno;
378 return -1;
379 }
380
381
382 #ifdef HAVE_FLOCK
383 /*
384 * Open and lock a file, using flock locking
385 */
386
387 static int
388 lkopen_flock(const char *file, int access, mode_t mode, int *failed_to_lock)
389 {
390 int fd, i, saved_errno, locktype;
391
392 /*
393 * The assumption here is that if you open the file for writing, you
394 * need an exclusive lock.
395 */
396
397 locktype = (((access & O_ACCMODE) == O_RDONLY) ? LOCK_SH : LOCK_EX) |
398 LOCK_NB;
399
400 for (i = 0; i < LOCK_RETRIES; i++) {
401 if ((fd = open(file, access, mode)) == -1)
402 return -1;
403
404 if (flock(fd, locktype) != -1)
405 return fd;
406
407 saved_errno = errno;
408 close(fd);
409 sleep(1);
410 }
411
412 *failed_to_lock = 1;
413 errno = saved_errno;
414 return -1;
415 }
416 #endif /* HAVE_FLOCK */
417
418 /*
419 * Open and lock a file, using lockf locking
420 */
421
422 static int
423 lkopen_lockf(const char *file, int access, mode_t mode, int *failed_to_lock)
424 {
425 int fd, i, saved_errno, saved_access;
426
427 /*
428 * Two notes:
429 *
430 * Because lockf locks start from the current offset, mask off O_APPEND
431 * and seek to the end of the file later if it was requested.
432 *
433 * lockf locks require write access to the file, so always add it
434 * even if it wasn't requested.
435 */
436
437 saved_access = access;
438
439 access &= ~O_APPEND;
440
441 if ((access & O_ACCMODE) == O_RDONLY) {
442 access &= ~O_RDONLY;
443 access |= O_RDWR;
444 }
445
446 for (i = 0; i < LOCK_RETRIES; i++) {
447 if ((fd = open(file, access, mode)) == -1)
448 return -1;
449
450 if (lockf(fd, F_TLOCK, 0) != -1) {
451 /*
452 * Seek to end if requested
453 */
454 if (saved_access & O_APPEND) {
455 lseek(fd, 0, SEEK_END);
456 }
457 return fd;
458 }
459
460 saved_errno = errno;
461 close(fd);
462 sleep(1);
463 }
464
465 *failed_to_lock = 1;
466 errno = saved_errno;
467 return -1;
468 }
469
470
471 /*
472 * open and lock a file, using dot locking
473 */
474
475 static int
476 lkopen_dot (const char *file, int access, mode_t mode, int *failed_to_lock)
477 {
478 int fd;
479 struct lockinfo lkinfo;
480
481 /* open the file */
482 if ((fd = open (file, access, mode)) == -1)
483 return -1;
484
485 /*
486 * Get the name of the eventual lock file, as well
487 * as a name for a temporary lock file.
488 */
489 lockname (file, &lkinfo, 1);
490
491 #if !defined(HAVE_LIBLOCKFILE)
492 {
493 int i;
494 for (i = 0; i < LOCK_RETRIES; ++i) {
495 struct stat st;
496
497 /* attempt to create lock file */
498 if (lockit (&lkinfo) == 0) {
499 /* if successful, turn on timer and return */
500 timerON (lkinfo.curlock, fd);
501 return fd;
502 }
503
504 /*
505 * Abort locking, if we fail to lock after 5 attempts
506 * and are never able to stat the lock file. Or, if
507 * we can stat the lockfile but exceed LOCK_RETRIES
508 * seconds waiting for it (by falling out of the loop).
509 */
510 if (stat (lkinfo.curlock, &st) == -1) {
511 if (i++ > 5) break;
512 sleep (1);
513 } else {
514 time_t curtime;
515 time (&curtime);
516
517 /* check for stale lockfile, else sleep */
518 if (curtime > st.st_ctime + RSECS)
519 (void) m_unlink (lkinfo.curlock);
520 else
521 sleep (1);
522 }
523 lockname (file, &lkinfo, 1);
524 }
525
526 close(fd);
527 *failed_to_lock = 1;
528 return -1;
529 }
530 #else
531 if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
532 timerON(lkinfo.curlock, fd);
533 return fd;
534 }
535
536 close(fd);
537 *failed_to_lock = 1;
538 return -1;
539 #endif /* HAVE_LIBLOCKFILE */
540 }
541
542 #if !defined(HAVE_LIBLOCKFILE)
543 /*
544 * Routine that actually tries to create
545 * the lock file.
546 */
547
548 static int
549 lockit (struct lockinfo *li)
550 {
551 int fd;
552 char *curlock, *tmpfile;
553
554 #if 0
555 char buffer[128];
556 #endif
557
558 curlock = li->curlock;
559
560 if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
561 inform("unable to create temporary file in %s", li->tmplock);
562 return -1;
563 }
564
565 #if 0
566 /* write our process id into lock file */
567 snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
568 write(fd, buffer, strlen(buffer) + 1);
569 #endif
570
571 close (fd);
572
573 /*
574 * Now try to create the real lock file
575 * by linking to the temporary file.
576 */
577 fd = link(tmpfile, curlock);
578 (void) m_unlink(tmpfile);
579
580 return fd == -1 ? -1 : 0;
581 }
582 #endif /* HAVE_LIBLOCKFILE */
583
584 /*
585 * Get name of lock file, and temporary lock file
586 */
587
588 static void
589 lockname (const char *file, struct lockinfo *li, int isnewlock)
590 {
591 int bplen, tmplen;
592 char *bp;
593 const char *cp;
594
595 #if 0
596 struct stat st;
597 #endif
598
599 if ((cp = strrchr (file, '/')) == NULL || *++cp == 0)
600 cp = file;
601
602 bp = li->curlock;
603 bplen = 0;
604 #ifdef LOCKDIR
605 snprintf (bp, sizeof(li->curlock), "%s/", lockdir);
606 tmplen = strlen (bp);
607 bp += tmplen;
608 bplen += tmplen;
609 #else
610 if (cp != file) {
611 snprintf (bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
612 tmplen = strlen (bp);
613 bp += tmplen;
614 bplen += tmplen;
615 }
616 #endif
617
618 #if 0
619 /*
620 * mmdf style dot locking. Currently not supported.
621 * If we start supporting mmdf style dot locking,
622 * we will need to change the return value of lockname
623 */
624 if (stat (file, &st) == -1)
625 return -1;
626
627 snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d",
628 st.st_dev, st.st_ino);
629 #endif
630
631 snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
632
633 #if defined(HAVE_LIBLOCKFILE)
634 NMH_UNUSED(isnewlock);
635 #else
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 die("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 die("flock not supported on this system");
762 #endif /* HAVE_FLOCK */
763 }
764 if (strcasecmp(lockname, "dot") == 0) {
765 return DOT_LOCKING;
766 }
767 die("Unknown lock type: \"%s\"", lockname);
768 /* NOTREACHED */
769 return 0;
770 }