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