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