xref: /netbsd-src/usr.bin/shlock/shlock.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: shlock.c,v 1.15 2021/04/17 00:02:19 christos 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.15 2021/04/17 00:02:19 christos Exp $");
68 #endif
69 
70 #include <sys/types.h>
71 #include <sys/stat.h>
72 #include <sys/file.h>
73 #include <err.h>
74 #include <fcntl.h>			/* Needed on hpux */
75 #include <stdio.h>
76 #include <signal.h>
77 #include <errno.h>
78 #include <string.h>
79 #include <unistd.h>
80 #include <stdlib.h>
81 
82 #define	LOCK_SET	0
83 #define	LOCK_FAIL	1
84 
85 #define	LOCK_GOOD	0
86 #define	LOCK_BAD	1
87 
88 #define	FAIL		(-1)
89 
90 #define	TRUE	1
91 #define	FALSE	0
92 
93 static int	Debug = FALSE;
94 static char	*Pname;
95 static const char USAGE[] = "%s: USAGE: %s [-du] [-p PID] -f file\n";
96 static const char E_unlk[] = "unlink(%s)";
97 static const char E_link[] = "link(%s, %s)";
98 static const char E_open[] = "open(%s)";
99 static const char E_stat[] = "stat(%s)";
100 
101 #define	dprintf	if (Debug) printf
102 
103 /*
104 ** Prototypes to make the ANSI compilers happy
105 ** Didn't lint used to do type and argument checking?
106 ** (and wasn't that sufficient?)
107 */
108 
109 /* the following is in case you need to make the prototypes go away. */
110 static char	*xtmpfile(const char *, pid_t, int);
111 static int	p_exists(pid_t);
112 static int	cklock(const char *, struct stat *, int);
113 static int	mklock(const char *, pid_t, int);
114 __dead static void	bad_usage(void);
115 
116 /*
117 ** Create a temporary file, all ready to lock with.
118 ** The file arg is so we get the filename right, if he
119 ** gave us a full path, instead of using the current directory
120 ** which might not be in the same filesystem.
121 */
122 static char *
123 xtmpfile(const char *file, pid_t pid, int uucpstyle)
124 {
125 	int	fd;
126 	size_t	len;
127 	char	*cp, buf[BUFSIZ];
128 	static char	tempname[BUFSIZ];
129 
130 	snprintf(buf, sizeof(buf), "shlock%jd", (intmax_t)getpid());
131 	if ((cp = strrchr(strcpy(tempname, file), '/')) != NULL) {
132 		*++cp = '\0';
133 		(void) strcat(tempname, buf);
134 	} else
135 		(void) strcpy(tempname, buf);
136 	dprintf("%s: temporary filename: %s\n", Pname, tempname);
137 
138 	snprintf(buf, sizeof(buf), "%jd\n", (intmax_t)pid);
139 	len = strlen(buf);
140 	while ((fd = open(tempname, O_RDWR|O_CREAT|O_TRUNC|O_SYNC|O_EXCL, 0644))
141 	    == -1)
142 	{
143 		switch(errno) {
144 		case EEXIST:
145 			if (unlink(tempname) == -1) {
146 				warn(E_unlk, tempname);
147 				return NULL;
148 			}
149 			break;
150 		default:
151 			warn(E_open, tempname);
152 			return NULL;
153 		}
154 	}
155 
156 	/*
157 	** Write the PID into the temporary file before attempting to link
158 	** to the actual lock file. That way we have a valid lock the instant
159 	** the link succeeds.
160 	*/
161 	if (uucpstyle ?
162 		(write(fd, &pid, sizeof(pid)) != sizeof(pid)) :
163 		(write(fd, buf, len) != (ssize_t)len))
164 	{
165 		warn("write(%s,%jd)", tempname, (intmax_t)pid);
166 		(void) close(fd);
167 		if (unlink(tempname) == -1) {
168 			warn(E_unlk, tempname);
169 		}
170 		return NULL;
171 	}
172 	(void) close(fd);
173 	return tempname;
174 }
175 
176 /*
177 ** Does the PID exist?
178 ** Send null signal to find out.
179 */
180 static int
181 p_exists(pid_t pid)
182 {
183 	dprintf("%s: process %jd is ", Pname, (intmax_t)pid);
184 	if (pid <= 0) {
185 		dprintf("invalid\n");
186 		return FALSE;
187 	}
188 	if (kill(pid, 0) == -1) {
189 		switch(errno) {
190 		case ESRCH:
191 			dprintf("dead %jd\n", (intmax_t)pid);
192 			return FALSE;	/* pid does not exist */
193 		case EPERM:
194 			dprintf("alive\n");
195 			return TRUE;	/* pid exists */
196 		default:
197 			dprintf("state unknown: %s\n", strerror(errno));
198 			return TRUE;	/* be conservative */
199 		}
200 	}
201 	dprintf("alive\n");
202 	return TRUE;	/* pid exists */
203 }
204 
205 /*
206 ** Check the validity of an existing lock file.
207 **
208 **	Read the PID out of the lock
209 **	Send a null signal to determine whether that PID still exists
210 **	Existence (or not) determines the validity of the lock.
211 **
212 **	Two bigs wins to this algorithm:
213 **
214 **	o	Locks do not survive crashes of either the system or the
215 **			application by any appreciable period of time.
216 **
217 **	o	No clean up to do if the system or application crashes.
218 **
219 */
220 static int
221 cklock(const char *file, struct stat *st, int uucpstyle)
222 {
223 	int	fd = open(file, O_RDONLY);
224 	ssize_t len;
225 	pid_t	pid;
226 	char	buf[BUFSIZ];
227 
228 	dprintf("%s: checking extant lock <%s>\n", Pname, file);
229 	if (fd == -1) {
230 		if (errno != ENOENT)
231 			warn(E_open, file);
232 		return TRUE;	/* might or might not; conservatism */
233 	}
234 
235 	if (st != NULL && fstat(fd, st) == -1) {
236 		warn(E_stat, file);
237 		close(fd);
238 		return TRUE;
239 	}
240 
241 	if (uucpstyle ?
242 		((len = read(fd, &pid, sizeof(pid))) != sizeof(pid)) :
243 		((len = read(fd, buf, sizeof(buf))) <= 0))
244 	{
245 		close(fd);
246 		dprintf("%s: lock file format error\n", Pname);
247 		return FALSE;
248 	}
249 	close(fd);
250 	buf[len + 1] = '\0';
251 	return p_exists(uucpstyle ? pid : atoi(buf));
252 }
253 
254 static int
255 mklock(const char *file, pid_t pid, int uucpstyle)
256 {
257 	char	*tmp, tmp2[BUFSIZ + 2];
258 	int	retcode = FALSE;
259 	struct stat stlock, sttmp, stlock2;
260 
261 	dprintf("%s: trying lock <%s> for process %jd\n", Pname, file,
262 	    (intmax_t)pid);
263 	if ((tmp = xtmpfile(file, pid, uucpstyle)) == NULL)
264 		return FALSE;
265 
266 	while (link(tmp, file) == -1) {
267 		switch(errno) {
268 		case EEXIST:
269 			dprintf("%s: lock <%s> already exists\n", Pname, file);
270 			if (cklock(file, &stlock, uucpstyle)) {
271 				dprintf("%s: extant lock is valid\n", Pname);
272 				goto out;
273 			}
274 			if (stat(tmp, &sttmp) == -1) {
275 				warn(E_stat, tmp);
276 				goto out;
277 			}
278 			/*
279 			 * We need to take another lock against the lock
280 			 * file to protect it against multiple removals
281 			 */
282 			snprintf(tmp2, sizeof(tmp2), "%s-2", tmp);
283 			if (unlink(tmp2) == -1 && errno != ENOENT) {
284 				warn(E_unlk, tmp2);
285 				goto out;
286 			}
287 			if (stlock.st_ctime >= sttmp.st_ctime) {
288 				dprintf("%s: lock time changed %jd >= %jd\n",
289 				    Pname, (intmax_t)stlock.st_ctime,
290 				    (intmax_t)stlock2.st_ctime);
291 				goto out;
292 			}
293 			if (stlock.st_nlink != 1) {
294 				dprintf("%s: someone else linked to it %d\n",
295 				    Pname, stlock.st_nlink);
296 				goto out;
297 			}
298 			if (link(file, tmp2) == -1) {
299 				/* someone took our temp name! */
300 				warn(E_link, file, tmp2);
301 				goto out2;
302 			}
303 			if (stat(file, &stlock2) == -1) {
304 				warn(E_stat, file);
305 				goto out2;
306 			}
307 			if (stlock.st_ino != stlock2.st_ino) {
308 				dprintf("%s: lock inode changed %jd != %jd\n",
309 				    Pname, (intmax_t)stlock.st_ino,
310 				    (intmax_t)stlock2.st_ino);
311 				goto out2;
312 			}
313 			if (stlock2.st_nlink != 2) {
314 				dprintf("%s: someone else linked to it %d\n",
315 				    Pname, stlock2.st_nlink);
316 				goto out2;
317 			}
318 
319 			dprintf("%s: lock is invalid, removing\n", Pname);
320 			if (unlink(file) == -1) {
321 				warn(E_unlk, file);
322 				goto out2;
323 			}
324 			if (unlink(tmp2) == -1)
325 				goto out2;
326 			break;
327 		default:
328 			warn(E_link, tmp, file);
329 			goto out;
330 		}
331 	}
332 
333 	if (stat(tmp, &sttmp) == -1) {
334 		warn(E_stat, tmp);
335 		goto out;
336 	}
337 
338 	if (sttmp.st_nlink == 2) {
339 		dprintf("%s: got lock <%s>\n", Pname, file);
340 		retcode = TRUE;
341 		goto out;
342 
343 	}
344 
345 out2:
346 	if (unlink(tmp2) == -1) {
347 		warn(E_unlk, tmp2);
348 	}
349 out:
350 	if (unlink(tmp) == -1) {
351 		warn(E_unlk, tmp);
352 	}
353 
354 	return retcode;
355 }
356 
357 static void
358 bad_usage(void)
359 {
360 	fprintf(stderr, USAGE, Pname, Pname);
361 	exit(LOCK_FAIL);
362 }
363 
364 int
365 main(int ac, char **av)
366 {
367 	int	x;
368 	char	*file = NULL;
369 	pid_t	pid = 0;
370 	int	uucpstyle = FALSE;	/* indicating UUCP style locks */
371 	int	only_check = TRUE;	/* don't make a lock */
372 
373 	Pname = ((Pname = strrchr(av[0], '/')) ? Pname + 1 : av[0]);
374 
375 	for(x = 1; x < ac; x++) {
376 		if (av[x][0] == '-') {
377 			switch(av[x][1]) {
378 			case 'u':
379 				uucpstyle = TRUE;
380 				break;
381 			case 'd':
382 				Debug = TRUE;
383 				break;
384 			case 'p':
385 				if (strlen(av[x]) > 2) {
386 					pid = atoi(&av[x][2]);
387 				} else {
388 					if (++x >= ac) {
389 						bad_usage();
390 					}
391 					pid = atoi(av[x]);
392 				}
393 				only_check = FALSE;	/* wants one */
394 				break;
395 			case 'f':
396 				if (strlen(av[x]) > 2) {
397 					file = &av[x][2];
398 				} else {
399 					if (++x >= ac) {
400 						bad_usage();
401 					}
402 					file = av[x];
403 				}
404 				break;
405 			default:
406 				bad_usage();
407 			}
408 		}
409 	}
410 
411 	if (file == NULL || (!only_check && pid <= 0)) {
412 		bad_usage();
413 	}
414 
415 	if (only_check) {
416 		exit(cklock(file, NULL, uucpstyle) ? LOCK_GOOD : LOCK_BAD);
417 	}
418 
419 	exit(mklock(file, pid, uucpstyle) ? LOCK_SET : LOCK_FAIL);
420 }
421