xref: /minix3/usr.bin/shlock/shlock.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1 /*	$NetBSD: shlock.c,v 1.13 2015/04/10 09:34:43 tron 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.13 2015/04/10 09:34:43 tron 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 static int	Debug = FALSE;
92 static char	*Pname;
93 static const char USAGE[] = "%s: USAGE: %s [-du] [-p PID] -f file\n";
94 static const char E_unlk[] = "%s: unlink(%s): %s\n";
95 static 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 static char	*xtmpfile(char *, pid_t, int);
107 static int	p_exists(pid_t);
108 static int	cklock(char *, int);
109 static int	mklock(char *, pid_t, int);
110 __dead static void	bad_usage(void);
111 
112 /*
113 ** Create a temporary file, all ready to lock with.
114 ** The file arg is so we get the filename right, if he
115 ** gave us a full path, instead of using the current directory
116 ** which might not be in the same filesystem.
117 */
118 static char *
xtmpfile(char * file,pid_t pid,int uucpstyle)119 xtmpfile(char *file, pid_t pid, int uucpstyle)
120 {
121 	int	fd;
122 	int	len;
123 	char	*cp, buf[BUFSIZ];
124 	static char	tempname[BUFSIZ];
125 
126 	sprintf(buf, "shlock%ld", (long)getpid());
127 	if ((cp = strrchr(strcpy(tempname, file), '/')) != NULL) {
128 		*++cp = '\0';
129 		(void) strcat(tempname, buf);
130 	} else
131 		(void) strcpy(tempname, buf);
132 	dprintf("%s: temporary filename: %s\n", Pname, tempname);
133 
134 	sprintf(buf, "%ld\n", (long)pid);
135 	len = strlen(buf);
136 openloop:
137 	if ((fd = open(tempname, O_RDWR|O_CREAT|O_EXCL, 0644)) < 0) {
138 		switch(errno) {
139 		case EEXIST:
140 			dprintf("%s: file %s exists already.\n",
141 				Pname, tempname);
142 			if (unlink(tempname) < 0) {
143 				fprintf(stderr, E_unlk,
144 					Pname, tempname, strerror(errno));
145 				return (NULL);
146 			}
147 			/*
148 			** Further profanity
149 			*/
150 			goto openloop;
151 		default:
152 			fprintf(stderr, E_open,
153 				Pname, tempname, strerror(errno));
154 			return (NULL);
155 		}
156 	}
157 
158 	/*
159 	** Write the PID into the temporary file before attempting to link
160 	** to the actual lock file. That way we have a valid lock the instant
161 	** the link succeeds.
162 	*/
163 	if (uucpstyle ?
164 		(write(fd, &pid, sizeof(pid)) != sizeof(pid)) :
165 		(write(fd, buf, len) < 0))
166 	{
167 		fprintf(stderr, "%s: write(%s,%ld): %s\n",
168 			Pname, tempname, (long)pid, strerror(errno));
169 		(void) close(fd);
170 		if (unlink(tempname) < 0) {
171 			fprintf(stderr, E_unlk,
172 				Pname, tempname, strerror(errno));
173 		}
174 		return (NULL);
175 	}
176 	(void) close(fd);
177 	return(tempname);
178 }
179 
180 /*
181 ** Does the PID exist?
182 ** Send null signal to find out.
183 */
184 static int
p_exists(pid_t pid)185 p_exists(pid_t pid)
186 {
187 	dprintf("%s: process %ld is ", Pname, (long)pid);
188 	if (pid <= 0) {
189 		dprintf("invalid\n");
190 		return(FALSE);
191 	}
192 	if (kill(pid, 0) < 0) {
193 		switch(errno) {
194 		case ESRCH:
195 			dprintf("dead\n");
196 			return(FALSE);	/* pid does not exist */
197 		case EPERM:
198 			dprintf("alive\n");
199 			return(TRUE);	/* pid exists */
200 		default:
201 			dprintf("state unknown: %s\n", strerror(errno));
202 			return(TRUE);	/* be conservative */
203 		}
204 	}
205 	dprintf("alive\n");
206 	return(TRUE);	/* pid exists */
207 }
208 
209 /*
210 ** Check the validity of an existing lock file.
211 **
212 **	Read the PID out of the lock
213 **	Send a null signal to determine whether that PID still exists
214 **	Existence (or not) determines the validity of the lock.
215 **
216 **	Two bigs wins to this algorithm:
217 **
218 **	o	Locks do not survive crashes of either the system or the
219 **			application by any appreciable period of time.
220 **
221 **	o	No clean up to do if the system or application crashes.
222 **
223 */
224 static int
cklock(char * file,int uucpstyle)225 cklock(char *file, int uucpstyle)
226 {
227 	int	fd = open(file, O_RDONLY);
228 	ssize_t len;
229 	pid_t	pid;
230 	char	buf[BUFSIZ];
231 
232 	dprintf("%s: checking extant lock <%s>\n", Pname, file);
233 	if (fd < 0) {
234 		if (errno != ENOENT)
235 			fprintf(stderr, E_open, Pname, file, strerror(errno));
236 		return(TRUE);	/* might or might not; conservatism */
237 	}
238 
239 	if (uucpstyle ?
240 		((len = read(fd, &pid, sizeof(pid))) != sizeof(pid)) :
241 		((len = read(fd, buf, sizeof(buf))) <= 0))
242 	{
243 		close(fd);
244 		dprintf("%s: lock file format error\n", Pname);
245 		return(FALSE);
246 	}
247 	close(fd);
248 	buf[len + 1] = '\0';
249 	return(p_exists(uucpstyle ? pid : atoi(buf)));
250 }
251 
252 static int
mklock(char * file,pid_t pid,int uucpstyle)253 mklock(char *file, pid_t pid, int uucpstyle)
254 {
255 	char	*tmp;
256 	int	retcode = FALSE;
257 
258 	dprintf("%s: trying lock <%s> for process %ld\n", Pname, file,
259 	    (long)pid);
260 	if ((tmp = xtmpfile(file, pid, uucpstyle)) == NULL)
261 		return(FALSE);
262 
263 linkloop:
264 	if (link(tmp, file) < 0) {
265 		switch(errno) {
266 		case EEXIST:
267 			dprintf("%s: lock <%s> already exists\n", Pname, file);
268 			if (cklock(file, uucpstyle)) {
269 				dprintf("%s: extant lock is valid\n", Pname);
270 				break;
271 			} else {
272 				dprintf("%s: lock is invalid, removing\n",
273 					Pname);
274 				if (unlink(file) < 0) {
275 					fprintf(stderr, E_unlk,
276 						Pname, file, strerror(errno));
277 					break;
278 				}
279 			}
280 			/*
281 			** I hereby profane the god of structured programming,
282 			** Edsgar Dijkstra
283 			*/
284 			goto linkloop;
285 		default:
286 			fprintf(stderr, "%s: link(%s, %s): %s\n",
287 				Pname, tmp, file, strerror(errno));
288 			break;
289 		}
290 	} else {
291 		dprintf("%s: got lock <%s>\n", Pname, file);
292 		retcode = TRUE;
293 	}
294 	if (unlink(tmp) < 0) {
295 		fprintf(stderr, E_unlk, Pname, tmp, strerror(errno));
296 	}
297 	return(retcode);
298 }
299 
300 static void
bad_usage(void)301 bad_usage(void)
302 {
303 	fprintf(stderr, USAGE, Pname, Pname);
304 	exit(LOCK_FAIL);
305 }
306 
307 int
main(int ac,char ** av)308 main(int ac, char **av)
309 {
310 	int	x;
311 	char	*file = NULL;
312 	pid_t	pid = 0;
313 	int	uucpstyle = FALSE;	/* indicating UUCP style locks */
314 	int	only_check = TRUE;	/* don't make a lock */
315 
316 	Pname = ((Pname = strrchr(av[0], '/')) ? Pname + 1 : av[0]);
317 
318 	for(x = 1; x < ac; x++) {
319 		if (av[x][0] == '-') {
320 			switch(av[x][1]) {
321 			case 'u':
322 				uucpstyle = TRUE;
323 				break;
324 			case 'd':
325 				Debug = TRUE;
326 				break;
327 			case 'p':
328 				if (strlen(av[x]) > 2) {
329 					pid = atoi(&av[x][2]);
330 				} else {
331 					if (++x >= ac) {
332 						bad_usage();
333 					}
334 					pid = atoi(av[x]);
335 				}
336 				only_check = FALSE;	/* wants one */
337 				break;
338 			case 'f':
339 				if (strlen(av[x]) > 2) {
340 					file = &av[x][2];
341 				} else {
342 					if (++x >= ac) {
343 						bad_usage();
344 					}
345 					file = av[x];
346 				}
347 				break;
348 			default:
349 				bad_usage();
350 			}
351 		}
352 	}
353 
354 	if (file == NULL || (!only_check && pid <= 0)) {
355 		bad_usage();
356 	}
357 
358 	if (only_check) {
359 		exit(cklock(file, uucpstyle) ? LOCK_GOOD : LOCK_BAD);
360 	}
361 
362 	exit(mklock(file, pid, uucpstyle) ? LOCK_SET : LOCK_FAIL);
363 }
364