1/*	$NetBSD: shlock.c,v 1.10 2008/04/28 20:24:14 martin Exp $	*/
2
3/*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Erik E. Fair.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33** Program to produce reliable locks for shell scripts.
34** Algorithm suggested by Peter Honeyman, January 1984,
35** in connection with HoneyDanBer UUCP.
36**
37** I tried extending this to handle shared locks in November 1987,
38** and ran into to some fundamental problems:
39**
40**	Neither 4.3 BSD nor System V have an open(2) with locking,
41**	so that you can open a file and have it locked as soon as
42**	it's real; you have to make two system calls, and there's
43**	a race...
44**
45**	When removing dead process id's from a list in a file,
46**	you need to truncate the file (you don't want to create a
47**	new one; see above); unfortunately for the portability of
48**	this program, only 4.3 BSD has ftruncate(2).
49**
50** Erik E. Fair <fair@ucbarpa.berkeley.edu>, November 8, 1987
51**
52** Extensions for UUCP style locks (i.e. pid is an int in the file,
53** rather than an ASCII string). Also fix long standing bug with
54** full file systems and temporary files.
55**
56** Erik E. Fair <fair@apple.com>, November 12, 1989
57**
58** ANSIfy the code somewhat to make gcc -Wall happy with the code.
59** Submit to NetBSD
60**
61** Erik E. Fair <fair@clock.org>, May 20, 1997
62*/
63
64#include <sys/cdefs.h>
65
66#ifndef lint
67__RCSID("$NetBSD: shlock.c,v 1.10 2008/04/28 20:24:14 martin Exp $");
68#endif
69
70#include <sys/types.h>
71#include <sys/file.h>
72#include <fcntl.h>			/* Needed on hpux */
73#include <stdio.h>
74#include <signal.h>
75#include <errno.h>
76#include <string.h>
77#include <unistd.h>
78#include <stdlib.h>
79
80#define	LOCK_SET	0
81#define	LOCK_FAIL	1
82
83#define	LOCK_GOOD	0
84#define	LOCK_BAD	1
85
86#define	FAIL		(-1)
87
88#define	TRUE	1
89#define	FALSE	0
90
91int	Debug = FALSE;
92char	*Pname;
93const char USAGE[] = "%s: USAGE: %s [-du] [-p PID] -f file\n";
94const char E_unlk[] = "%s: unlink(%s): %s\n";
95const char E_open[] = "%s: open(%s): %s\n";
96
97#define	dprintf	if (Debug) printf
98
99/*
100** Prototypes to make the ANSI compilers happy
101** Didn't lint used to do type and argument checking?
102** (and wasn't that sufficient?)
103*/
104
105/* the following is in case you need to make the prototypes go away. */
106char	*xtmpfile(char *, pid_t, int);
107int	p_exists(pid_t);
108int	cklock(char *, int);
109int	mklock(char *, pid_t, int);
110void	bad_usage(void);
111int	main(int, char **);
112
113/*
114** Create a temporary file, all ready to lock with.
115** The file arg is so we get the filename right, if he
116** gave us a full path, instead of using the current directory
117** which might not be in the same filesystem.
118*/
119char *
120xtmpfile(char *file, pid_t pid, int uucpstyle)
121{
122	int	fd;
123	int	len;
124	char	*cp, buf[BUFSIZ];
125	static char	tempname[BUFSIZ];
126
127	sprintf(buf, "shlock%ld", (u_long)getpid());
128	if ((cp = strrchr(strcpy(tempname, file), '/')) != (char *)NULL) {
129		*++cp = '\0';
130		(void) strcat(tempname, buf);
131	} else
132		(void) strcpy(tempname, buf);
133	dprintf("%s: temporary filename: %s\n", Pname, tempname);
134
135	sprintf(buf, "%ld\n", (u_long)pid);
136	len = strlen(buf);
137openloop:
138	if ((fd = open(tempname, O_RDWR|O_CREAT|O_EXCL, 0644)) < 0) {
139		switch(errno) {
140		case EEXIST:
141			dprintf("%s: file %s exists already.\n",
142				Pname, tempname);
143			if (unlink(tempname) < 0) {
144				fprintf(stderr, E_unlk,
145					Pname, tempname, strerror(errno));
146				return((char *)NULL);
147			}
148			/*
149			** Further profanity
150			*/
151			goto openloop;
152		default:
153			fprintf(stderr, E_open,
154				Pname, tempname, strerror(errno));
155			return((char *)NULL);
156		}
157	}
158
159	/*
160	** Write the PID into the temporary file before attempting to link
161	** to the actual lock file. That way we have a valid lock the instant
162	** the link succeeds.
163	*/
164	if (uucpstyle ?
165		(write(fd, &pid, sizeof(pid)) != sizeof(pid)) :
166		(write(fd, buf, len) < 0))
167	{
168		fprintf(stderr, "%s: write(%s,%ld): %s\n",
169			Pname, tempname, (u_long)pid, strerror(errno));
170		(void) close(fd);
171		if (unlink(tempname) < 0) {
172			fprintf(stderr, E_unlk,
173				Pname, tempname, strerror(errno));
174		}
175		return((char *)NULL);
176	}
177	(void) close(fd);
178	return(tempname);
179}
180
181/*
182** Does the PID exist?
183** Send null signal to find out.
184*/
185int
186p_exists(pid_t pid)
187{
188	dprintf("%s: process %ld is ", Pname, (u_long)pid);
189	if (pid <= 0) {
190		dprintf("invalid\n");
191		return(FALSE);
192	}
193	if (kill(pid, 0) < 0) {
194		switch(errno) {
195		case ESRCH:
196			dprintf("dead\n");
197			return(FALSE);	/* pid does not exist */
198		case EPERM:
199			dprintf("alive\n");
200			return(TRUE);	/* pid exists */
201		default:
202			dprintf("state unknown: %s\n", strerror(errno));
203			return(TRUE);	/* be conservative */
204		}
205	}
206	dprintf("alive\n");
207	return(TRUE);	/* pid exists */
208}
209
210/*
211** Check the validity of an existing lock file.
212**
213**	Read the PID out of the lock
214**	Send a null signal to determine whether that PID still exists
215**	Existence (or not) determines the validity of the lock.
216**
217**	Two bigs wins to this algorithm:
218**
219**	o	Locks do not survive crashes of either the system or the
220**			application by any appreciable period of time.
221**
222**	o	No clean up to do if the system or application crashes.
223**
224*/
225int
226cklock(char *file, int uucpstyle)
227{
228	int	fd = open(file, O_RDONLY);
229	ssize_t len;
230	pid_t	pid;
231	char	buf[BUFSIZ];
232
233	dprintf("%s: checking extant lock <%s>\n", Pname, file);
234	if (fd < 0) {
235		if (errno != ENOENT)
236			fprintf(stderr, E_open, Pname, file, strerror(errno));
237		return(TRUE);	/* might or might not; conservatism */
238	}
239
240	if (uucpstyle ?
241		((len = read(fd, &pid, sizeof(pid))) != sizeof(pid)) :
242		((len = read(fd, buf, sizeof(buf))) <= 0))
243	{
244		close(fd);
245		dprintf("%s: lock file format error\n", Pname);
246		return(FALSE);
247	}
248	close(fd);
249	buf[len + 1] = '\0';
250	return(p_exists(uucpstyle ? pid : atoi(buf)));
251}
252
253int
254mklock(char *file, pid_t pid, int uucpstyle)
255{
256	char	*tmp;
257	int	retcode = FALSE;
258
259	dprintf("%s: trying lock <%s> for process %ld\n", Pname, file,
260	    (u_long)pid);
261	if ((tmp = xtmpfile(file, pid, uucpstyle)) == (char *)NULL)
262		return(FALSE);
263
264linkloop:
265	if (link(tmp, file) < 0) {
266		switch(errno) {
267		case EEXIST:
268			dprintf("%s: lock <%s> already exists\n", Pname, file);
269			if (cklock(file, uucpstyle)) {
270				dprintf("%s: extant lock is valid\n", Pname);
271				break;
272			} else {
273				dprintf("%s: lock is invalid, removing\n",
274					Pname);
275				if (unlink(file) < 0) {
276					fprintf(stderr, E_unlk,
277						Pname, file, strerror(errno));
278					break;
279				}
280			}
281			/*
282			** I hereby profane the god of structured programming,
283			** Edsgar Dijkstra
284			*/
285			goto linkloop;
286		default:
287			fprintf(stderr, "%s: link(%s, %s): %s\n",
288				Pname, tmp, file, strerror(errno));
289			break;
290		}
291	} else {
292		dprintf("%s: got lock <%s>\n", Pname, file);
293		retcode = TRUE;
294	}
295	if (unlink(tmp) < 0) {
296		fprintf(stderr, E_unlk, Pname, tmp, strerror(errno));
297	}
298	return(retcode);
299}
300
301void
302bad_usage(void)
303{
304	fprintf(stderr, USAGE, Pname, Pname);
305	exit(LOCK_FAIL);
306}
307
308int
309main(int ac, char **av)
310{
311	int	x;
312	char	*file = (char *)NULL;
313	pid_t	pid = 0;
314	int	uucpstyle = FALSE;	/* indicating UUCP style locks */
315	int	only_check = TRUE;	/* don't make a lock */
316
317	Pname = ((Pname = strrchr(av[0], '/')) ? Pname + 1 : av[0]);
318
319	for(x = 1; x < ac; x++) {
320		if (av[x][0] == '-') {
321			switch(av[x][1]) {
322			case 'u':
323				uucpstyle = TRUE;
324				break;
325			case 'd':
326				Debug = TRUE;
327				break;
328			case 'p':
329				if (strlen(av[x]) > 2) {
330					pid = atoi(&av[x][2]);
331				} else {
332					if (++x >= ac) {
333						bad_usage();
334					}
335					pid = atoi(av[x]);
336				}
337				only_check = FALSE;	/* wants one */
338				break;
339			case 'f':
340				if (strlen(av[x]) > 2) {
341					file = &av[x][2];
342				} else {
343					if (++x >= ac) {
344						bad_usage();
345					}
346					file = av[x];
347				}
348				break;
349			default:
350				bad_usage();
351			}
352		}
353	}
354
355	if (file == (char *)NULL || (!only_check && pid <= 0)) {
356		bad_usage();
357	}
358
359	if (only_check) {
360		exit(cklock(file, uucpstyle) ? LOCK_GOOD : LOCK_BAD);
361	}
362
363	exit(mklock(file, pid, uucpstyle) ? LOCK_SET : LOCK_FAIL);
364}
365