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