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