]> diplodocus.org Git - nmh/blob - sbr/lock_file.c
Don't `else' after return. Simplify control flow.
[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 /* 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 }
504
505 /*
506 * Abort locking, if we fail to lock after 5 attempts
507 * and are never able to stat the lock file. Or, if
508 * we can stat the lockfile but exceed LOCK_RETRIES
509 * seconds waiting for it (by falling out of the loop).
510 */
511 struct stat st;
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 *failed_to_lock = 1;
529 return -1;
530 }
531 #else
532 if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
533 timerON(lkinfo.curlock, fd);
534 return fd;
535 }
536
537 close(fd);
538 *failed_to_lock = 1;
539 return -1;
540 #endif /* HAVE_LIBLOCKFILE */
541 }
542
543 #if !defined(HAVE_LIBLOCKFILE)
544 /*
545 * Routine that actually tries to create
546 * the lock file.
547 */
548
549 static int
550 lockit (struct lockinfo *li)
551 {
552 int fd;
553 char *curlock, *tmpfile;
554
555 #if 0
556 char buffer[128];
557 #endif
558
559 curlock = li->curlock;
560
561 if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
562 advise(NULL, "unable to create temporary file in %s", li->tmplock);
563 return -1;
564 }
565
566 #if 0
567 /* write our process id into lock file */
568 snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
569 write(fd, buffer, strlen(buffer) + 1);
570 #endif
571
572 close (fd);
573
574 /*
575 * Now try to create the real lock file
576 * by linking to the temporary file.
577 */
578 fd = link(tmpfile, curlock);
579 (void) m_unlink(tmpfile);
580
581 return (fd == -1 ? -1 : 0);
582 }
583 #endif /* HAVE_LIBLOCKFILE */
584
585 /*
586 * Get name of lock file, and temporary lock file
587 */
588
589 static void
590 lockname (const char *file, struct lockinfo *li, int isnewlock)
591 {
592 int bplen, tmplen;
593 char *bp;
594 const char *cp;
595
596 #if 0
597 struct stat st;
598 #endif
599
600 if ((cp = strrchr (file, '/')) == NULL || *++cp == 0)
601 cp = file;
602
603 bp = li->curlock;
604 bplen = 0;
605 #ifdef LOCKDIR
606 snprintf (bp, sizeof(li->curlock), "%s/", lockdir);
607 tmplen = strlen (bp);
608 bp += tmplen;
609 bplen += tmplen;
610 #else
611 if (cp != file) {
612 snprintf (bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
613 tmplen = strlen (bp);
614 bp += tmplen;
615 bplen += tmplen;
616 }
617 #endif
618
619 #if 0
620 /*
621 * mmdf style dot locking. Currently not supported.
622 * If we start supporting mmdf style dot locking,
623 * we will need to change the return value of lockname
624 */
625 if (stat (file, &st) == -1)
626 return -1;
627
628 snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d",
629 st.st_dev, st.st_ino);
630 #endif
631
632 snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
633
634 #if !defined(HAVE_LIBLOCKFILE)
635 /*
636 * If this is for a new lock, create a name for
637 * the temporary lock file for lockit()
638 */
639 if (isnewlock) {
640 if ((cp = strrchr (li->curlock, '/')) == NULL || *++cp == 0)
641 strncpy (li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock));
642 else
643 snprintf (li->tmplock, sizeof(li->tmplock), "%.*s,LCK.XXXXXX",
644 (int)(cp - li->curlock), li->curlock);
645 }
646 #endif
647 }
648
649
650 /*
651 * Add new lockfile to the list of open lockfiles
652 * and start the lock file timer.
653 */
654
655 static void
656 timerON (char *curlock, int fd)
657 {
658 struct lock *lp;
659
660 NEW(lp);
661 lp->l_lock = mh_xstrdup(curlock);
662 lp->l_fd = fd;
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 l_top = lp;
671 }
672
673
674 /*
675 * Search through the list of lockfiles for the
676 * current lockfile, and remove it from the list.
677 */
678
679 static void
680 timerOFF (int fd)
681 {
682 struct lock *pp, *lp;
683
684 alarm(0);
685
686 if (l_top) {
687 for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) {
688 if (lp->l_fd == fd)
689 break;
690 }
691 if (lp) {
692 if (lp == l_top)
693 l_top = lp->l_next;
694 else
695 pp->l_next = lp->l_next;
696
697 free (lp->l_lock);
698 free (lp);
699 }
700 }
701
702 /* if there are locks left, restart timer */
703 if (l_top)
704 alarm (NSECS);
705 }
706
707
708 /*
709 * If timer goes off, we update the ctime of all open
710 * lockfiles, so another command doesn't remove them.
711 */
712
713 static void
714 alrmser (int sig)
715 {
716 char *lockfile;
717 struct lock *lp;
718 NMH_UNUSED (sig);
719
720 /* update the ctime of all the lock files */
721 for (lp = l_top; lp; lp = lp->l_next) {
722 lockfile = lp->l_lock;
723 #if !defined(HAVE_LIBLOCKFILE)
724 {
725 int j;
726 if (*lockfile && (j = creat (lockfile, 0600)) != -1)
727 close (j);
728 }
729 #else
730 lockfile_touch(lockfile);
731 #endif
732 }
733
734 /* restart the alarm */
735 alarm (NSECS);
736 }
737
738
739 /*
740 * Return a locking algorithm based on the string name
741 */
742
743 static enum locktype
744 init_locktype(const char *lockname)
745 {
746 if (strcasecmp(lockname, "fcntl") == 0) {
747 return FCNTL_LOCKING;
748 }
749 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 }
756 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 }
763 if (strcasecmp(lockname, "dot") == 0) {
764 return DOT_LOCKING;
765 }
766 adios(NULL, "Unknown lock type: \"%s\"", lockname);
767 /* NOTREACHED */
768 return 0;
769 }