]> diplodocus.org Git - nmh/blob - sbr/lock_file.c
mhlsbr.c: Don't strchr(3) non-string NUL-less buffer.
[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 #include "lock_file.h"
21 #include "m_mktemp.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 /* Our saved lock types. */
77 static enum locktype datalocktype, spoollocktype;
78
79 /* top of list containing all open locks */
80 static struct lock *l_top = NULL;
81
82 static int lkopen(const char *, int, mode_t, enum locktype, int *);
83 static int str2accbits(const char *);
84
85 static int lkopen_fcntl (const char *, int, mode_t, int *);
86 #ifdef HAVE_LOCKF
87 static int lkopen_lockf (const char *, int, mode_t, int *);
88 #endif /* HAVE_LOCKF */
89 #ifdef HAVE_FLOCK
90 static int lkopen_flock (const char *, int, mode_t, int *);
91 #endif /* HAVE_FLOCK */
92
93 static enum locktype init_locktype(const char *) PURE;
94
95 static int lkopen_dot (const char *, int, mode_t, int *);
96 static void lkclose_dot (int, const char *);
97 static void lockname (const char *, struct lockinfo *, int);
98 static void timerON (char *, int);
99 static void timerOFF (int);
100 static void alrmser (int);
101
102 #if !defined(HAVE_LIBLOCKFILE)
103 static int lockit (struct lockinfo *);
104 #endif
105
106 /*
107 * Base functions: determine the data type used to lock files and
108 * call the underlying function.
109 */
110
111 int
112 lkopendata(const char *file, int access, mode_t mode, int *failed_to_lock)
113 {
114 static bool deja_vu;
115
116 if (!deja_vu) {
117 char *dl;
118
119 deja_vu = true;
120 if ((dl = context_find("datalocking"))) {
121 datalocktype = init_locktype(dl);
122 } else {
123 /* We default to fcntl locking for data files */
124 datalocktype = FCNTL_LOCKING;
125 }
126 }
127
128 return lkopen(file, access, mode, datalocktype, failed_to_lock);
129 }
130
131
132 /*
133 * Locking using the spool locking algorithm
134 */
135
136 int lkopenspool(const char *file, int access, mode_t mode, int *failed_to_lock)
137 {
138 static bool deja_vu;
139
140 if (!deja_vu) {
141 deja_vu = true;
142 spoollocktype = init_locktype(spoollocking);
143 }
144
145 return lkopen(file, access, mode, spoollocktype, failed_to_lock);
146 }
147
148
149 /*
150 * Versions of lkopen that return a FILE *
151 */
152
153 FILE *
154 lkfopendata(const char *file, const char *mode, int *failed_to_lock)
155 {
156 FILE *fp;
157 int oflags = str2accbits(mode);
158 int fd;
159
160 if (oflags == -1) {
161 errno = EINVAL;
162 return NULL;
163 }
164
165 if ((fd = lkopendata(file, oflags, 0666, failed_to_lock)) == -1)
166 return NULL;
167
168 if ((fp = fdopen (fd, mode)) == NULL) {
169 close (fd);
170 return NULL;
171 }
172
173 return fp;
174 }
175
176 FILE *
177 lkfopenspool(const char *file, const char *mode)
178 {
179 FILE *fp;
180 int oflags = str2accbits(mode);
181 int failed_to_lock = 0;
182 int fd;
183
184 if (oflags == -1) {
185 errno = EINVAL;
186 return NULL;
187 }
188
189 if ((fd = lkopenspool(file, oflags, 0666, &failed_to_lock)) == -1)
190 return NULL;
191
192 if ((fp = fdopen (fd, mode)) == NULL) {
193 close (fd);
194 return NULL;
195 }
196
197 return fp;
198 }
199
200
201 /*
202 * Corresponding close functions.
203 *
204 * A note here: All of the kernel locking functions terminate the lock
205 * when the descriptor is closed, so why write the code to explicitly
206 * unlock the file? We only need to do this in the dot-locking case.
207 */
208
209 int
210 lkclosedata(int fd, const char *name)
211 {
212 int rc = close(fd);
213
214 if (datalocktype == DOT_LOCKING)
215 lkclose_dot(fd, name);
216
217 return rc;
218 }
219
220 int
221 lkfclosedata(FILE *f, const char *name)
222 {
223 int fd, rc;
224
225 if (f == NULL)
226 return 0;
227
228 fd = fileno(f);
229 rc = fclose(f);
230
231 if (datalocktype == DOT_LOCKING)
232 lkclose_dot(fd, name);
233
234 return rc;
235 }
236
237 int
238 lkclosespool(int fd, const char *name)
239 {
240 int rc = close(fd);
241
242 if (spoollocktype == DOT_LOCKING)
243 lkclose_dot(fd, name);
244
245 return rc;
246 }
247
248 int
249 lkfclosespool(FILE *f, const char *name)
250 {
251 int fd, rc;
252
253 if (f == NULL)
254 return 0;
255
256 fd = fileno(f);
257 rc = fclose(f);
258
259 if (spoollocktype == DOT_LOCKING)
260 lkclose_dot(fd, name);
261
262 return rc;
263 }
264
265
266 /*
267 * Convert fopen() mode argument to open() bits
268 */
269
270 static int
271 str2accbits(const char *mode)
272 {
273 if (strcmp (mode, "r") == 0)
274 return O_RDONLY;
275 if (strcmp (mode, "r+") == 0)
276 return O_RDWR;
277 if (strcmp (mode, "w") == 0)
278 return O_WRONLY | O_CREAT | O_TRUNC;
279 if (strcmp (mode, "w+") == 0)
280 return O_RDWR | O_CREAT | O_TRUNC;
281 if (strcmp (mode, "a") == 0)
282 return O_WRONLY | O_CREAT | O_APPEND;
283 if (strcmp (mode, "a+") == 0)
284 return O_RDWR | O_CREAT | O_APPEND;
285
286 errno = EINVAL;
287 return -1;
288 }
289
290 /*
291 * Internal routine to switch between different locking types.
292 */
293
294 static int
295 lkopen (const char *file, int access, mode_t mode, enum locktype ltype,
296 int *failed_to_lock)
297 {
298 switch (ltype) {
299
300 case FCNTL_LOCKING:
301 return lkopen_fcntl(file, access, mode, failed_to_lock);
302
303 case DOT_LOCKING:
304 return lkopen_dot(file, access, mode, failed_to_lock);
305
306 #ifdef HAVE_FLOCK
307 case FLOCK_LOCKING:
308 return lkopen_flock(file, access, mode, failed_to_lock);
309 #endif /* HAVE_FLOCK */
310
311 #ifdef HAVE_LOCKF
312 case LOCKF_LOCKING:
313 return lkopen_lockf(file, access, mode, failed_to_lock);
314 #endif /* HAVE_FLOCK */
315
316 default:
317 adios(NULL, "Internal locking error: unsupported lock type used!");
318 }
319
320 return -1;
321 }
322
323
324 /*
325 * Routine to clean up the dot locking file
326 */
327
328 static void
329 lkclose_dot (int fd, const char *file)
330 {
331 struct lockinfo lkinfo;
332
333 lockname (file, &lkinfo, 0); /* get name of lock file */
334 #if !defined(HAVE_LIBLOCKFILE)
335 (void) m_unlink (lkinfo.curlock); /* remove lock file */
336 #else
337 lockfile_remove(lkinfo.curlock);
338 #endif /* HAVE_LIBLOCKFILE */
339 timerOFF (fd); /* turn off lock timer */
340 }
341
342
343 /*
344 * Open and lock a file, using fcntl locking
345 */
346
347 static int
348 lkopen_fcntl(const char *file, int access, mode_t mode, int *failed_to_lock)
349 {
350 int fd, i, saved_errno;
351 struct flock flk;
352
353 /*
354 * The assumption here is that if you open the file for writing, you
355 * need an exclusive lock.
356 */
357
358 for (i = 0; i < LOCK_RETRIES; i++) {
359 if ((fd = open(file, access, mode)) == -1)
360 return -1;
361
362 flk.l_start = 0;
363 flk.l_len = 0;
364 flk.l_type = (access & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK;
365 flk.l_whence = SEEK_SET;
366
367 if (fcntl(fd, F_SETLK, &flk) != -1)
368 return fd;
369
370 saved_errno = errno;
371 close(fd);
372 sleep(1);
373 }
374
375 *failed_to_lock = 1;
376 errno = saved_errno;
377 return -1;
378 }
379
380
381 #ifdef HAVE_FLOCK
382 /*
383 * Open and lock a file, using flock locking
384 */
385
386 static int
387 lkopen_flock(const char *file, int access, mode_t mode, int *failed_to_lock)
388 {
389 int fd, i, saved_errno, locktype;
390
391 /*
392 * The assumption here is that if you open the file for writing, you
393 * need an exclusive lock.
394 */
395
396 locktype = (((access & O_ACCMODE) == O_RDONLY) ? LOCK_SH : LOCK_EX) |
397 LOCK_NB;
398
399 for (i = 0; i < LOCK_RETRIES; i++) {
400 if ((fd = open(file, access, mode)) == -1)
401 return -1;
402
403 if (flock(fd, locktype) != -1)
404 return fd;
405
406 saved_errno = errno;
407 close(fd);
408 sleep(1);
409 }
410
411 *failed_to_lock = 1;
412 errno = saved_errno;
413 return -1;
414 }
415 #endif /* HAVE_FLOCK */
416
417 /*
418 * Open and lock a file, using lockf locking
419 */
420
421 static int
422 lkopen_lockf(const char *file, int access, mode_t mode, int *failed_to_lock)
423 {
424 int fd, i, saved_errno, saved_access;
425
426 /*
427 * Two notes:
428 *
429 * Because lockf locks start from the current offset, mask off O_APPEND
430 * and seek to the end of the file later if it was requested.
431 *
432 * lockf locks require write access to the file, so always add it
433 * even if it wasn't requested.
434 */
435
436 saved_access = access;
437
438 access &= ~O_APPEND;
439
440 if ((access & O_ACCMODE) == O_RDONLY) {
441 access &= ~O_RDONLY;
442 access |= O_RDWR;
443 }
444
445 for (i = 0; i < LOCK_RETRIES; i++) {
446 if ((fd = open(file, access, mode)) == -1)
447 return -1;
448
449 if (lockf(fd, F_TLOCK, 0) != -1) {
450 /*
451 * Seek to end if requested
452 */
453 if (saved_access & O_APPEND) {
454 lseek(fd, 0, SEEK_END);
455 }
456 return fd;
457 }
458
459 saved_errno = errno;
460 close(fd);
461 sleep(1);
462 }
463
464 *failed_to_lock = 1;
465 errno = saved_errno;
466 return -1;
467 }
468
469
470 /*
471 * open and lock a file, using dot locking
472 */
473
474 static int
475 lkopen_dot (const char *file, int access, mode_t mode, int *failed_to_lock)
476 {
477 int fd;
478 struct lockinfo lkinfo;
479
480 /* open the file */
481 if ((fd = open (file, access, mode)) == -1)
482 return -1;
483
484 /*
485 * Get the name of the eventual lock file, as well
486 * as a name for a temporary lock file.
487 */
488 lockname (file, &lkinfo, 1);
489
490 #if !defined(HAVE_LIBLOCKFILE)
491 {
492 int i;
493 for (i = 0; i < LOCK_RETRIES; ++i) {
494 struct stat st;
495
496 /* attempt to create lock file */
497 if (lockit (&lkinfo) == 0) {
498 /* if successful, turn on timer and return */
499 timerON (lkinfo.curlock, fd);
500 return fd;
501 }
502
503 /*
504 * Abort locking, if we fail to lock after 5 attempts
505 * and are never able to stat the lock file. Or, if
506 * we can stat the lockfile but exceed LOCK_RETRIES
507 * seconds waiting for it (by falling out of the loop).
508 */
509 if (stat (lkinfo.curlock, &st) == -1) {
510 if (i++ > 5) break;
511 sleep (1);
512 } else {
513 time_t curtime;
514 time (&curtime);
515
516 /* check for stale lockfile, else sleep */
517 if (curtime > st.st_ctime + RSECS)
518 (void) m_unlink (lkinfo.curlock);
519 else
520 sleep (1);
521 }
522 lockname (file, &lkinfo, 1);
523 }
524
525 close(fd);
526 *failed_to_lock = 1;
527 return -1;
528 }
529 #else
530 if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
531 timerON(lkinfo.curlock, fd);
532 return fd;
533 }
534
535 close(fd);
536 *failed_to_lock = 1;
537 return -1;
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, *tmpfile;
552
553 #if 0
554 char buffer[128];
555 #endif
556
557 curlock = li->curlock;
558
559 if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
560 inform("unable to create temporary file in %s", li->tmplock);
561 return -1;
562 }
563
564 #if 0
565 /* write our process id into lock file */
566 snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
567 write(fd, buffer, strlen(buffer) + 1);
568 #endif
569
570 close (fd);
571
572 /*
573 * Now try to create the real lock file
574 * by linking to the temporary file.
575 */
576 fd = link(tmpfile, curlock);
577 (void) m_unlink(tmpfile);
578
579 return fd == -1 ? -1 : 0;
580 }
581 #endif /* HAVE_LIBLOCKFILE */
582
583 /*
584 * Get name of lock file, and temporary lock file
585 */
586
587 static void
588 lockname (const char *file, struct lockinfo *li, int isnewlock)
589 {
590 int bplen, tmplen;
591 char *bp;
592 const char *cp;
593
594 #if 0
595 struct stat st;
596 #endif
597
598 if ((cp = strrchr (file, '/')) == NULL || *++cp == 0)
599 cp = file;
600
601 bp = li->curlock;
602 bplen = 0;
603 #ifdef LOCKDIR
604 snprintf (bp, sizeof(li->curlock), "%s/", lockdir);
605 tmplen = strlen (bp);
606 bp += tmplen;
607 bplen += tmplen;
608 #else
609 if (cp != file) {
610 snprintf (bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
611 tmplen = strlen (bp);
612 bp += tmplen;
613 bplen += tmplen;
614 }
615 #endif
616
617 #if 0
618 /*
619 * mmdf style dot locking. Currently not supported.
620 * If we start supporting mmdf style dot locking,
621 * we will need to change the return value of lockname
622 */
623 if (stat (file, &st) == -1)
624 return -1;
625
626 snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d",
627 st.st_dev, st.st_ino);
628 #endif
629
630 snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
631
632 #if defined(HAVE_LIBLOCKFILE)
633 NMH_UNUSED(isnewlock);
634 #else
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 }