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