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