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