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