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