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