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 *);
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
lkopendata(const char * file,int access,mode_t mode,int * failed_to_lock)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
lkopenspool(const char * file,int access,mode_t mode,int * failed_to_lock)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 *
lkfopendata(const char * file,const char * mode,int * failed_to_lock)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 *
lkfopenspool(const char * file,const char * mode)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
lkclosedata(int fd,const char * name)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
lkfclosedata(FILE * f,const char * name)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
lkclosespool(int fd,const char * name)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
lkfclosespool(FILE * f,const char * name)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
str2accbits(const char * mode)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
lkopen(const char * file,int access,mode_t mode,enum locktype ltype,int * failed_to_lock)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
lkclose_dot(int fd,const char * file)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
lkopen_fcntl(const char * file,int access,mode_t mode,int * failed_to_lock)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
lkopen_flock(const char * file,int access,mode_t mode,int * failed_to_lock)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
lkopen_lockf(const char * file,int access,mode_t mode,int * failed_to_lock)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
lkopen_dot(const char * file,int access,mode_t mode,int * failed_to_lock)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 *failed_to_lock = 1;
526 return -1;
527 }
528 #else
529 if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
530 timerON(lkinfo.curlock, fd);
531 return fd;
532 }
533
534 close(fd);
535 *failed_to_lock = 1;
536 return -1;
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
lockit(struct lockinfo * li)547 lockit (struct lockinfo *li)
548 {
549 int fd;
550 char *curlock, *tmpfile;
551
552 #if 0
553 char buffer[128];
554 #endif
555
556 curlock = li->curlock;
557
558 if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
559 inform("unable to create temporary file in %s", li->tmplock);
560 return -1;
561 }
562
563 #if 0
564 /* write our process id into lock file */
565 snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
566 write(fd, buffer, strlen(buffer) + 1);
567 #endif
568
569 close (fd);
570
571 /*
572 * Now try to create the real lock file
573 * by linking to the temporary file.
574 */
575 fd = link(tmpfile, curlock);
576 (void) m_unlink(tmpfile);
577
578 return (fd == -1 ? -1 : 0);
579 }
580 #endif /* HAVE_LIBLOCKFILE */
581
582 /*
583 * Get name of lock file, and temporary lock file
584 */
585
586 static void
lockname(const char * file,struct lockinfo * li,int isnewlock)587 lockname (const char *file, struct lockinfo *li, int isnewlock)
588 {
589 int bplen, tmplen;
590 char *bp;
591 const char *cp;
592
593 #if 0
594 struct stat st;
595 #endif
596
597 if ((cp = strrchr (file, '/')) == NULL || *++cp == 0)
598 cp = file;
599
600 bp = li->curlock;
601 bplen = 0;
602 #ifdef LOCKDIR
603 snprintf (bp, sizeof(li->curlock), "%s/", lockdir);
604 tmplen = strlen (bp);
605 bp += tmplen;
606 bplen += tmplen;
607 #else
608 if (cp != file) {
609 snprintf (bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
610 tmplen = strlen (bp);
611 bp += tmplen;
612 bplen += tmplen;
613 }
614 #endif
615
616 #if 0
617 /*
618 * mmdf style dot locking. Currently not supported.
619 * If we start supporting mmdf style dot locking,
620 * we will need to change the return value of lockname
621 */
622 if (stat (file, &st) == -1)
623 return -1;
624
625 snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d",
626 st.st_dev, st.st_ino);
627 #endif
628
629 snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
630
631 #if defined(HAVE_LIBLOCKFILE)
632 NMH_UNUSED(isnewlock);
633 #else
634 /*
635 * If this is for a new lock, create a name for
636 * the temporary lock file for lockit()
637 */
638 if (isnewlock) {
639 if ((cp = strrchr (li->curlock, '/')) == NULL || *++cp == 0)
640 strncpy (li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock));
641 else
642 snprintf (li->tmplock, sizeof(li->tmplock), "%.*s,LCK.XXXXXX",
643 (int)(cp - li->curlock), li->curlock);
644 }
645 #endif
646 }
647
648
649 /*
650 * Add new lockfile to the list of open lockfiles
651 * and start the lock file timer.
652 */
653
654 static void
timerON(char * curlock,int fd)655 timerON (char *curlock, int fd)
656 {
657 struct lock *lp;
658
659 NEW(lp);
660 lp->l_lock = mh_xstrdup(curlock);
661 lp->l_fd = fd;
662 lp->l_next = l_top;
663
664 if (!l_top) {
665 /* perhaps SIGT{STP,TIN,TOU} ? */
666 SIGNAL (SIGALRM, alrmser);
667 alarm (NSECS);
668 }
669 l_top = lp;
670 }
671
672
673 /*
674 * Search through the list of lockfiles for the
675 * current lockfile, and remove it from the list.
676 */
677
678 static void
timerOFF(int fd)679 timerOFF (int fd)
680 {
681 struct lock *pp, *lp;
682
683 alarm(0);
684
685 if (l_top) {
686 for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) {
687 if (lp->l_fd == fd)
688 break;
689 }
690 if (lp) {
691 if (lp == l_top)
692 l_top = lp->l_next;
693 else
694 pp->l_next = lp->l_next;
695
696 free (lp->l_lock);
697 free (lp);
698 }
699 }
700
701 /* if there are locks left, restart timer */
702 if (l_top)
703 alarm (NSECS);
704 }
705
706
707 /*
708 * If timer goes off, we update the ctime of all open
709 * lockfiles, so another command doesn't remove them.
710 */
711
712 static void
alrmser(int sig)713 alrmser (int sig)
714 {
715 char *lockfile;
716 struct lock *lp;
717 NMH_UNUSED (sig);
718
719 /* update the ctime of all the lock files */
720 for (lp = l_top; lp; lp = lp->l_next) {
721 lockfile = lp->l_lock;
722 #if !defined(HAVE_LIBLOCKFILE)
723 {
724 int j;
725 if (*lockfile && (j = creat (lockfile, 0600)) != -1)
726 close (j);
727 }
728 #else
729 lockfile_touch(lockfile);
730 #endif
731 }
732
733 /* restart the alarm */
734 alarm (NSECS);
735 }
736
737
738 /*
739 * Return a locking algorithm based on the string name
740 */
741
742 static enum locktype
init_locktype(const char * lockname)743 init_locktype(const char *lockname)
744 {
745 if (strcasecmp(lockname, "fcntl") == 0) {
746 return FCNTL_LOCKING;
747 }
748 if (strcasecmp(lockname, "lockf") == 0) {
749 #ifdef HAVE_LOCKF
750 return LOCKF_LOCKING;
751 #else /* ! HAVE_LOCKF */
752 adios(NULL, "lockf not supported on this system");
753 #endif /* HAVE_LOCKF */
754 }
755 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 }
762 if (strcasecmp(lockname, "dot") == 0) {
763 return DOT_LOCKING;
764 }
765 adios(NULL, "Unknown lock type: \"%s\"", lockname);
766 /* NOTREACHED */
767 return 0;
768 }
769