Home | History | Annotate | Line # | Download | only in shlock
      1 /*	$NetBSD: shlock.c,v 1.16 2024/08/17 14:26:53 riastradh 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 (at) 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 (at) 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 (at) clock.org>, May 20, 1997
     62 */
     63 
     64 #include <sys/cdefs.h>
     65 
     66 #ifndef lint
     67 __RCSID("$NetBSD: shlock.c,v 1.16 2024/08/17 14:26:53 riastradh 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