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 /*
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 <unistd.h>
19 #include <h/mh.h>
20 #include <h/signals.h>
21 #include <h/utils.h>
22 #include <sys/stat.h>
23
24 #ifdef HAVE_SYS_TIME_H
25 # include <sys/time.h>
26 #endif
27 #include <time.h>
28
29 #include <errno.h>
30
31 #ifdef HAVE_FCNTL_H
32 # include <fcntl.h>
33 #else
34 # include <sys/file.h>
35 #endif
36
37 #if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING)
38 # include <sys/file.h>
39 #endif
40
41 #include <signal.h>
42
43 #if defined(HAVE_LIBLOCKFILE)
44 #include <lockfile.h>
45 #endif
46
47 #ifdef DOT_LOCKING
48 # ifdef LOCKDIR
49 char *lockdir = LOCKDIR;
50 # endif
51 #endif
52
53 /* Are we using any kernel locking? */
54 #if defined (FLOCK_LOCKING) || defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
55 # define KERNEL_LOCKING
56 #endif
57
58 #ifdef DOT_LOCKING
59
60 /* struct for getting name of lock file to create */
61 struct lockinfo {
62 char curlock[BUFSIZ];
63 #if !defined(HAVE_LIBLOCKFILE)
64 char tmplock[BUFSIZ];
65 #endif
66 };
67
68 /*
69 ** Amount of time to wait before
70 ** updating ctime of lock file.
71 */
72 #define NSECS 20
73
74 #if !defined(HAVE_LIBLOCKFILE)
75 /*
76 ** How old does a lock file need to be
77 ** before we remove it.
78 */
79 #define RSECS 180
80 #endif /* HAVE_LIBLOCKFILE */
81
82 /* struct for recording and updating locks */
83 struct lock {
84 int l_fd;
85 char *l_lock;
86 struct lock *l_next;
87 };
88
89 /* top of list containing all open locks */
90 static struct lock *l_top = NULL;
91 #endif /* DOT_LOCKING */
92
93 /*
94 ** static prototypes
95 */
96 #ifdef KERNEL_LOCKING
97 static int lkopen_kernel(char *, int, mode_t);
98 #endif
99
100 #ifdef DOT_LOCKING
101 static int lkopen_dot(char *, int, mode_t);
102 static void lockname(char *, struct lockinfo *, int);
103 static void timerON(char *, int);
104 static void timerOFF(int);
105 static void alrmser(int);
106
107 #if !defined(HAVE_LIBLOCKFILE)
108 static int lockit(struct lockinfo *);
109 #endif
110 #endif
111
112 /*
113 ** Base routine to open and lock a file,
114 ** and return a file descriptor.
115 */
116
117 int
lkopen(char * file,int access,mode_t mode)118 lkopen(char *file, int access, mode_t mode)
119 {
120 #ifdef KERNEL_LOCKING
121 return lkopen_kernel(file, access, mode);
122 #endif
123
124 #ifdef DOT_LOCKING
125 return lkopen_dot(file, access, mode);
126 #endif
127 }
128
129
130 /*
131 ** Base routine to close and unlock a file,
132 ** given a file descriptor.
133 */
134
135 int
lkclose(int fd,char * file)136 lkclose(int fd, char *file)
137 {
138 #ifdef FCNTL_LOCKING
139 struct flock buf;
140 #endif
141
142 #ifdef DOT_LOCKING
143 struct lockinfo lkinfo;
144 #endif
145
146 if (fd == -1)
147 return 0;
148
149 #ifdef FCNTL_LOCKING
150 buf.l_type = F_UNLCK;
151 buf.l_whence = SEEK_SET;
152 buf.l_start = 0;
153 buf.l_len = 0;
154 fcntl(fd, F_SETLK, &buf);
155 #endif
156
157 #ifdef FLOCK_LOCKING
158 flock(fd, LOCK_UN);
159 #endif
160
161 #ifdef LOCKF_LOCKING
162 /* make sure we unlock the whole thing */
163 lseek(fd, (off_t) 0, SEEK_SET);
164 lockf(fd, F_ULOCK, 0L);
165 #endif
166
167 #ifdef DOT_LOCKING
168 lockname(file, &lkinfo, 0); /* get name of lock file */
169 #if !defined(HAVE_LIBLOCKFILE)
170 unlink(lkinfo.curlock); /* remove lock file */
171 #else
172 lockfile_remove(lkinfo.curlock);
173 #endif /* HAVE_LIBLOCKFILE */
174 timerOFF(fd); /* turn off lock timer */
175 #endif /* DOT_LOCKING */
176
177 return (close(fd));
178 }
179
180
181 /*
182 ** Base routine to open and lock a file,
183 ** and return a FILE pointer
184 */
185
186 FILE *
lkfopen(char * file,char * mode)187 lkfopen(char *file, char *mode)
188 {
189 int fd, access;
190 FILE *fp;
191
192 if (strcmp(mode, "r") == 0)
193 access = O_RDONLY;
194 else if (strcmp(mode, "r+") == 0)
195 access = O_RDWR;
196 else if (strcmp(mode, "w") == 0)
197 access = O_WRONLY | O_CREAT | O_TRUNC;
198 else if (strcmp(mode, "w+") == 0)
199 access = O_RDWR | O_CREAT | O_TRUNC;
200 else if (strcmp(mode, "a") == 0)
201 access = O_WRONLY | O_CREAT | O_APPEND;
202 else if (strcmp(mode, "a+") == 0)
203 access = O_RDWR | O_CREAT | O_APPEND;
204 else {
205 errno = EINVAL;
206 return NULL;
207 }
208
209 if ((fd = lkopen(file, access, 0666)) == -1)
210 return NULL;
211
212 if ((fp = fdopen(fd, mode)) == NULL) {
213 close(fd);
214 return NULL;
215 }
216
217 return fp;
218 }
219
220
221 /*
222 ** Base routine to close and unlock a file,
223 ** given a FILE pointer
224 */
225
226 int
lkfclose(FILE * fp,char * file)227 lkfclose(FILE *fp, char *file)
228 {
229 #ifdef FCNTL_LOCKING
230 struct flock buf;
231 #endif
232
233 #ifdef DOT_LOCKING
234 struct lockinfo lkinfo;
235 #endif
236
237 if (fp == NULL)
238 return 0;
239
240 #ifdef FCNTL_LOCKING
241 buf.l_type = F_UNLCK;
242 buf.l_whence = SEEK_SET;
243 buf.l_start = 0;
244 buf.l_len = 0;
245 fcntl(fileno(fp), F_SETLK, &buf);
246 #endif
247
248 #ifdef FLOCK_LOCKING
249 flock(fileno(fp), LOCK_UN);
250 #endif
251
252 #ifdef LOCKF_LOCKING
253 /* make sure we unlock the whole thing */
254 fseek(fp, 0L, SEEK_SET);
255 lockf(fileno(fp), F_ULOCK, 0L);
256 #endif
257
258 #ifdef DOT_LOCKING
259 lockname(file, &lkinfo, 0); /* get name of lock file */
260 #if !defined(HAVE_LIBLOCKFILE)
261 unlink(lkinfo.curlock); /* remove lock file */
262 #else
263 lockfile_remove(lkinfo.curlock);
264 #endif /* HAVE_LIBLOCKFILE */
265 timerOFF(fileno(fp)); /* turn off lock timer */
266 #endif /* DOT_LOCKING */
267
268 return (fclose(fp));
269 }
270
271
272 #ifdef KERNEL_LOCKING
273
274 /*
275 ** open and lock a file, using kernel locking
276 */
277
278 static int
lkopen_kernel(char * file,int access,mode_t mode)279 lkopen_kernel(char *file, int access, mode_t mode)
280 {
281 int fd, i, j;
282
283 # ifdef FCNTL_LOCKING
284 struct flock buf;
285 # endif /* FCNTL_LOCKING */
286
287 for (i = 0; i < 5; i++) {
288
289 # if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
290 /* remember the original mode */
291 j = access;
292
293 /* make sure we open at the beginning */
294 access &= ~O_APPEND;
295
296 /*
297 ** We MUST have write permission or
298 ** lockf/fcntl() won't work
299 */
300 if ((access & 03) == O_RDONLY) {
301 access &= ~O_RDONLY;
302 access |= O_RDWR;
303 }
304 # endif /* LOCKF_LOCKING || FCNTL_LOCKING */
305
306 if ((fd = open(file, access | O_NDELAY, mode)) == -1)
307 return -1;
308
309 # ifdef FCNTL_LOCKING
310 buf.l_type = F_WRLCK;
311 buf.l_whence = SEEK_SET;
312 buf.l_start = 0;
313 buf.l_len = 0;
314 if (fcntl(fd, F_SETLK, &buf) != -1)
315 return fd;
316 # endif
317
318 # ifdef FLOCK_LOCKING
319 if (flock(fd, (((access & 03) == O_RDONLY) ? LOCK_SH :
320 LOCK_EX) | LOCK_NB) != -1)
321 return fd;
322 # endif
323
324 # ifdef LOCKF_LOCKING
325 if (lockf(fd, F_TLOCK, 0L) != -1) {
326 /* see if we should be at the end */
327 if (j & O_APPEND)
328 lseek(fd, (off_t) 0, SEEK_END);
329 return fd;
330 }
331 # endif
332
333 j = errno;
334 close(fd);
335 sleep(1);
336 }
337
338 close(fd);
339 errno = j;
340 return -1;
341 }
342
343 #endif /* KERNEL_LOCKING */
344
345
346 #ifdef DOT_LOCKING
347
348 /*
349 ** open and lock a file, using dot locking
350 */
351
352 static int
lkopen_dot(char * file,int access,mode_t mode)353 lkopen_dot(char *file, int access, mode_t mode)
354 {
355 int fd;
356 struct lockinfo lkinfo;
357
358 /* open the file */
359 if ((fd = open(file, access, mode)) == -1)
360 return -1;
361
362 /*
363 ** Get the name of the eventual lock file, as well
364 ** as a name for a temporary lock file.
365 */
366 lockname(file, &lkinfo, 1);
367
368 #if !defined(HAVE_LIBLOCKFILE)
369 {
370 int i;
371 for (i = 0;;) {
372 /* attempt to create lock file */
373 if (lockit(&lkinfo) == 0) {
374 /* if successful, turn on timer and return */
375 timerON(lkinfo.curlock, fd);
376 return fd;
377 } else {
378 /*
379 ** Abort locking, if we fail to lock after 5
380 ** attempts and are never able to stat the
381 ** lock file.
382 */
383 struct stat st;
384 if (stat(lkinfo.curlock, &st) == -1) {
385 if (i++ > 5)
386 return -1;
387 sleep(1);
388 } else {
389 time_t curtime;
390 i = 0;
391 time(&curtime);
392
393 /*
394 ** check for stale lockfile,
395 ** else sleep
396 */
397 if (curtime > st.st_ctime + RSECS)
398 unlink(lkinfo.curlock);
399 else
400 sleep(1);
401 }
402 lockname(file, &lkinfo, 1);
403 }
404 }
405 }
406 #else
407 if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
408 timerON(lkinfo.curlock, fd);
409 return fd;
410 } else {
411 close(fd);
412 return -1;
413 }
414 #endif /* HAVE_LIBLOCKFILE */
415 }
416
417 #if !defined(HAVE_LIBLOCKFILE)
418 /*
419 ** Routine that actually tries to create
420 ** the lock file.
421 */
422
423 static int
lockit(struct lockinfo * li)424 lockit(struct lockinfo *li)
425 {
426 int fd;
427 char *curlock, *tmplock;
428
429 #if 0
430 char buffer[128];
431 #endif
432
433 curlock = li->curlock;
434 tmplock = li->tmplock;
435
436 if ((fd = mkstemp(tmplock)) == -1) {
437 advise(NULL, "unable to create temporary file in %s", tmplock);
438 return -1;
439 }
440
441 #if 0
442 /* write our process id into lock file */
443 snprintf(buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid());
444 write(fd, buffer, strlen(buffer) + 1);
445 #endif
446
447 close(fd);
448
449 /*
450 ** Now try to create the real lock file
451 ** by linking to the temporary file.
452 */
453 fd = link(tmplock, curlock);
454 unlink(tmplock);
455
456 return (fd == -1 ? -1 : 0);
457 }
458 #endif /* HAVE_LIBLOCKFILE */
459
460 /*
461 ** Get name of lock file, and temporary lock file
462 */
463
464 static void
lockname(char * file,struct lockinfo * li,int isnewlock)465 lockname(char *file, struct lockinfo *li, int isnewlock)
466 {
467 int bplen, tmplen;
468 char *bp, *cp;
469
470 if ((cp = strrchr(file, '/')) == NULL || *++cp == 0)
471 cp = file;
472
473 bp = li->curlock;
474 bplen = 0;
475 #ifdef LOCKDIR
476 snprintf(bp, sizeof(li->curlock), "%s/", lockdir);
477 tmplen = strlen(bp);
478 bp += tmplen;
479 bplen += tmplen;
480 #else
481 if (cp != file) {
482 snprintf(bp, sizeof(li->curlock), "%.*s", (int)(cp - file), file);
483 tmplen = strlen(bp);
484 bp += tmplen;
485 bplen += tmplen;
486 }
487 #endif
488
489 snprintf(bp, sizeof(li->curlock) - bplen, "%s.lock", cp);
490
491 #if !defined(HAVE_LIBLOCKFILE)
492 /*
493 ** If this is for a new lock, create a name for
494 ** the temporary lock file for lockit()
495 */
496 if (isnewlock) {
497 if ((cp = strrchr(li->curlock, '/')) == NULL || *++cp == 0)
498 strncpy(li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock));
499 else
500 snprintf(li->tmplock, sizeof(li->tmplock),
501 "%.*s,LCK.XXXXXX",
502 (int)(cp - li->curlock), li->curlock);
503 }
504 #endif
505 }
506
507
508 /*
509 ** Add new lockfile to the list of open lockfiles
510 ** and start the lock file timer.
511 */
512
513 static void
timerON(char * curlock,int fd)514 timerON(char *curlock, int fd)
515 {
516 struct lock *lp;
517 size_t len;
518
519 lp = mh_xcalloc(1, sizeof(*lp));
520
521 len = strlen(curlock) + 1;
522 lp->l_fd = fd;
523 lp->l_lock = mh_xcalloc(len, sizeof(char));
524 memcpy(lp->l_lock, curlock, len);
525 lp->l_next = l_top;
526
527 if (!l_top) {
528 /* perhaps SIGT{STP,TIN,TOU} ? */
529 SIGNAL(SIGALRM, alrmser);
530 alarm(NSECS);
531 }
532
533 l_top = lp;
534 }
535
536
537 /*
538 ** Search through the list of lockfiles for the
539 ** current lockfile, and remove it from the list.
540 */
541
542 static void
timerOFF(int fd)543 timerOFF(int fd)
544 {
545 struct lock *pp, *lp;
546
547 alarm(0);
548
549 if (l_top) {
550 for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) {
551 if (lp->l_fd == fd)
552 break;
553 }
554 if (lp) {
555 if (lp == l_top)
556 l_top = lp->l_next;
557 else
558 pp->l_next = lp->l_next;
559
560 mh_free0(&(lp->l_lock));
561 mh_free0(&lp);
562 }
563 }
564
565 /* if there are locks left, restart timer */
566 if (l_top)
567 alarm(NSECS);
568 }
569
570
571 /*
572 ** If timer goes off, we update the ctime of all open
573 ** lockfiles, so another command doesn't remove them.
574 */
575
576 static void
alrmser(int sig)577 alrmser(int sig)
578 {
579 char *lockfile;
580 struct lock *lp;
581
582 /* update the ctime of all the lock files */
583 for (lp = l_top; lp; lp = lp->l_next) {
584 lockfile = lp->l_lock;
585 #if !defined(HAVE_LIBLOCKFILE)
586 {
587 int j;
588 if (*lockfile && (j = creat(lockfile, 0600)) != -1)
589 close(j);
590 }
591 #else
592 lockfile_touch(lockfile);
593 #endif
594 }
595
596 /* restart the alarm */
597 alarm(NSECS);
598 }
599
600 #endif /* DOT_LOCKING */
601