xref: /netbsd-src/usr.bin/shlock/shlock.c (revision 274254cdae52594c1aa480a736aef78313d15c9c)
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 
91 int	Debug = FALSE;
92 char	*Pname;
93 const char USAGE[] = "%s: USAGE: %s [-du] [-p PID] -f file\n";
94 const char E_unlk[] = "%s: unlink(%s): %s\n";
95 const 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. */
106 char	*xtmpfile(char *, pid_t, int);
107 int	p_exists(pid_t);
108 int	cklock(char *, int);
109 int	mklock(char *, pid_t, int);
110 void	bad_usage(void);
111 int	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 */
119 char *
120 xtmpfile(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);
137 openloop:
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 */
185 int
186 p_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 */
225 int
226 cklock(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 
253 int
254 mklock(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 
264 linkloop:
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 
301 void
302 bad_usage(void)
303 {
304 	fprintf(stderr, USAGE, Pname, Pname);
305 	exit(LOCK_FAIL);
306 }
307 
308 int
309 main(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