11de7b4b8SPedro F. Giffuni /*- 24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause 31de7b4b8SPedro F. Giffuni * 4c8929a49SJohn Polstra * Copyright (C) 1997 John D. Polstra. All rights reserved. 5c8929a49SJohn Polstra * 6c8929a49SJohn Polstra * Redistribution and use in source and binary forms, with or without 7c8929a49SJohn Polstra * modification, are permitted provided that the following conditions 8c8929a49SJohn Polstra * are met: 9c8929a49SJohn Polstra * 1. Redistributions of source code must retain the above copyright 10c8929a49SJohn Polstra * notice, this list of conditions and the following disclaimer. 11c8929a49SJohn Polstra * 2. Redistributions in binary form must reproduce the above copyright 12c8929a49SJohn Polstra * notice, this list of conditions and the following disclaimer in the 13c8929a49SJohn Polstra * documentation and/or other materials provided with the distribution. 14c8929a49SJohn Polstra * 15c8929a49SJohn Polstra * THIS SOFTWARE IS PROVIDED BY JOHN D. POLSTRA AND CONTRIBUTORS ``AS IS'' AND 16c8929a49SJohn Polstra * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17c8929a49SJohn Polstra * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18c8929a49SJohn Polstra * ARE DISCLAIMED. IN NO EVENT SHALL JOHN D. POLSTRA OR CONTRIBUTORS BE LIABLE 19c8929a49SJohn Polstra * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20c8929a49SJohn Polstra * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21c8929a49SJohn Polstra * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22c8929a49SJohn Polstra * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23c8929a49SJohn Polstra * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24c8929a49SJohn Polstra * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25c8929a49SJohn Polstra * SUCH DAMAGE. 26c8929a49SJohn Polstra */ 27c8929a49SJohn Polstra 28c8929a49SJohn Polstra #include <sys/types.h> 29c8929a49SJohn Polstra #include <sys/wait.h> 30c8929a49SJohn Polstra 3109a7fe0aSKyle Evans #include <assert.h> 32c8929a49SJohn Polstra #include <err.h> 33c8929a49SJohn Polstra #include <errno.h> 34c8929a49SJohn Polstra #include <fcntl.h> 3509a7fe0aSKyle Evans #include <limits.h> 36c8929a49SJohn Polstra #include <signal.h> 37c8929a49SJohn Polstra #include <stdio.h> 38c8929a49SJohn Polstra #include <stdlib.h> 3909a7fe0aSKyle Evans #include <string.h> 40ae06cb4dSJohn Polstra #include <sysexits.h> 41c8929a49SJohn Polstra #include <unistd.h> 42c8929a49SJohn Polstra 4309a7fe0aSKyle Evans #define FDLOCK_PREFIX "/dev/fd/" 4409a7fe0aSKyle Evans 4509a7fe0aSKyle Evans union lock_subject { 4609a7fe0aSKyle Evans long subj_fd; 4709a7fe0aSKyle Evans const char *subj_name; 4809a7fe0aSKyle Evans }; 4909a7fe0aSKyle Evans 5009a7fe0aSKyle Evans static int acquire_lock(union lock_subject *subj, int flags, int silent); 51c8929a49SJohn Polstra static void cleanup(void); 52c8929a49SJohn Polstra static void killed(int sig); 53c8929a49SJohn Polstra static void timeout(int sig); 54a1b6427aSAlfonso Gregory static void usage(void) __dead2; 556a53f0a5SChristian S.J. Peron static void wait_for_lock(const char *name); 56c8929a49SJohn Polstra 57c8929a49SJohn Polstra static const char *lockname; 58031469ebSChristian S.J. Peron static int lockfd = -1; 592f278eacSJohn Polstra static int keep; 6009a7fe0aSKyle Evans static int fdlock; 61c8929a49SJohn Polstra static volatile sig_atomic_t timed_out; 62c8929a49SJohn Polstra 63c8929a49SJohn Polstra /* 6409a7fe0aSKyle Evans * Check if fdlock is implied by the given `lockname`. We'll write the fd that 6509a7fe0aSKyle Evans * is represented by it out to ofd, and the caller is expected to do any 6609a7fe0aSKyle Evans * necessary validation on it. 6709a7fe0aSKyle Evans */ 6809a7fe0aSKyle Evans static int 6909a7fe0aSKyle Evans fdlock_implied(const char *name, long *ofd) 7009a7fe0aSKyle Evans { 7109a7fe0aSKyle Evans char *endp; 7209a7fe0aSKyle Evans long fd; 7309a7fe0aSKyle Evans 7409a7fe0aSKyle Evans if (strncmp(name, FDLOCK_PREFIX, sizeof(FDLOCK_PREFIX) - 1) != 0) 7509a7fe0aSKyle Evans return (0); 7609a7fe0aSKyle Evans 7709a7fe0aSKyle Evans /* Skip past the prefix. */ 7809a7fe0aSKyle Evans name += sizeof(FDLOCK_PREFIX) - 1; 7909a7fe0aSKyle Evans errno = 0; 8009a7fe0aSKyle Evans fd = strtol(name, &endp, 10); 8109a7fe0aSKyle Evans if (errno != 0 || *endp != '\0') 8209a7fe0aSKyle Evans return (0); 8309a7fe0aSKyle Evans 8409a7fe0aSKyle Evans *ofd = fd; 8509a7fe0aSKyle Evans return (1); 8609a7fe0aSKyle Evans } 8709a7fe0aSKyle Evans 8809a7fe0aSKyle Evans /* 89c8929a49SJohn Polstra * Execute an arbitrary command while holding a file lock. 90c8929a49SJohn Polstra */ 91c8929a49SJohn Polstra int 92c8929a49SJohn Polstra main(int argc, char **argv) 93c8929a49SJohn Polstra { 94*e4967d4dSKyle Evans int ch, flags, silent, status; 95*e4967d4dSKyle Evans long long waitsec; 96c8929a49SJohn Polstra pid_t child; 9709a7fe0aSKyle Evans union lock_subject subj; 98c8929a49SJohn Polstra 9945edbdccSChristian S.J. Peron silent = keep = 0; 100437bab48SColin Percival flags = O_CREAT | O_RDONLY; 101c8929a49SJohn Polstra waitsec = -1; /* Infinite. */ 10235095fd2SKyle Evans while ((ch = getopt(argc, argv, "knst:w")) != -1) { 103c8929a49SJohn Polstra switch (ch) { 1042f278eacSJohn Polstra case 'k': 1052f278eacSJohn Polstra keep = 1; 1062f278eacSJohn Polstra break; 107b5be420aSEitan Adler case 'n': 108b5be420aSEitan Adler flags &= ~O_CREAT; 109b5be420aSEitan Adler break; 110c8929a49SJohn Polstra case 's': 111c8929a49SJohn Polstra silent = 1; 112c8929a49SJohn Polstra break; 113c8929a49SJohn Polstra case 't': 114c8929a49SJohn Polstra { 115*e4967d4dSKyle Evans const char *errstr; 116*e4967d4dSKyle Evans 117*e4967d4dSKyle Evans waitsec = strtonum(optarg, 0, UINT_MAX, &errstr); 118*e4967d4dSKyle Evans if (errstr != NULL) 11945edbdccSChristian S.J. Peron errx(EX_USAGE, 12045edbdccSChristian S.J. Peron "invalid timeout \"%s\"", optarg); 121c8929a49SJohn Polstra } 122c8929a49SJohn Polstra break; 123437bab48SColin Percival case 'w': 124437bab48SColin Percival flags = (flags & ~O_RDONLY) | O_WRONLY; 125437bab48SColin Percival break; 126c8929a49SJohn Polstra default: 127c8929a49SJohn Polstra usage(); 128c8929a49SJohn Polstra } 129c8929a49SJohn Polstra } 13009a7fe0aSKyle Evans 131c8929a49SJohn Polstra argc -= optind; 132c8929a49SJohn Polstra argv += optind; 13309a7fe0aSKyle Evans 13409a7fe0aSKyle Evans if (argc == 0) 13509a7fe0aSKyle Evans usage(); 13609a7fe0aSKyle Evans 13709a7fe0aSKyle Evans lockname = argv[0]; 13809a7fe0aSKyle Evans 13909a7fe0aSKyle Evans argc--; 14009a7fe0aSKyle Evans argv++; 14109a7fe0aSKyle Evans 14209a7fe0aSKyle Evans /* 14309a7fe0aSKyle Evans * If there aren't any arguments left, then we must be in fdlock mode. 14409a7fe0aSKyle Evans */ 14509a7fe0aSKyle Evans if (argc == 0 && *lockname != '/') { 14609a7fe0aSKyle Evans fdlock = 1; 14709a7fe0aSKyle Evans subj.subj_fd = -1; 14809a7fe0aSKyle Evans } else { 14909a7fe0aSKyle Evans fdlock = fdlock_implied(lockname, &subj.subj_fd); 15009a7fe0aSKyle Evans if (argc == 0 && !fdlock) { 15109a7fe0aSKyle Evans fprintf(stderr, "Expected fd, got '%s'\n", lockname); 15209a7fe0aSKyle Evans usage(); 15309a7fe0aSKyle Evans } 15409a7fe0aSKyle Evans } 15509a7fe0aSKyle Evans 15609a7fe0aSKyle Evans if (fdlock) { 15709a7fe0aSKyle Evans if (subj.subj_fd < 0) { 15809a7fe0aSKyle Evans char *endp; 15909a7fe0aSKyle Evans 16009a7fe0aSKyle Evans errno = 0; 16109a7fe0aSKyle Evans subj.subj_fd = strtol(lockname, &endp, 10); 16209a7fe0aSKyle Evans if (errno != 0 || *endp != '\0') { 16309a7fe0aSKyle Evans fprintf(stderr, "Expected fd, got '%s'\n", 16409a7fe0aSKyle Evans lockname); 16509a7fe0aSKyle Evans usage(); 16609a7fe0aSKyle Evans } 16709a7fe0aSKyle Evans } 16809a7fe0aSKyle Evans 16909a7fe0aSKyle Evans if (subj.subj_fd < 0 || subj.subj_fd > INT_MAX) { 17009a7fe0aSKyle Evans fprintf(stderr, "fd '%ld' out of range\n", 17109a7fe0aSKyle Evans subj.subj_fd); 17209a7fe0aSKyle Evans usage(); 17309a7fe0aSKyle Evans } 17409a7fe0aSKyle Evans } else { 17509a7fe0aSKyle Evans subj.subj_name = lockname; 17609a7fe0aSKyle Evans } 17709a7fe0aSKyle Evans 178c8929a49SJohn Polstra if (waitsec > 0) { /* Set up a timeout. */ 179c8929a49SJohn Polstra struct sigaction act; 180c8929a49SJohn Polstra 181c8929a49SJohn Polstra act.sa_handler = timeout; 182c8929a49SJohn Polstra sigemptyset(&act.sa_mask); 183c8929a49SJohn Polstra act.sa_flags = 0; /* Note that we do not set SA_RESTART. */ 184c8929a49SJohn Polstra sigaction(SIGALRM, &act, NULL); 185*e4967d4dSKyle Evans alarm((unsigned int)waitsec); 186c8929a49SJohn Polstra } 1876a53f0a5SChristian S.J. Peron /* 1886a53f0a5SChristian S.J. Peron * If the "-k" option is not given, then we must not block when 1896a53f0a5SChristian S.J. Peron * acquiring the lock. If we did, then the lock holder would 1906a53f0a5SChristian S.J. Peron * unlink the file upon releasing the lock, and we would acquire 1916a53f0a5SChristian S.J. Peron * a lock on a file with no directory entry. Then another 1926a53f0a5SChristian S.J. Peron * process could come along and acquire the same lock. To avoid 1936a53f0a5SChristian S.J. Peron * this problem, we separate out the actions of waiting for the 1946a53f0a5SChristian S.J. Peron * lock to be available and of actually acquiring the lock. 1956a53f0a5SChristian S.J. Peron * 1966a53f0a5SChristian S.J. Peron * That approach produces behavior that is technically correct; 1976a53f0a5SChristian S.J. Peron * however, it causes some performance & ordering problems for 1986a53f0a5SChristian S.J. Peron * locks that have a lot of contention. First, it is unfair in 1996a53f0a5SChristian S.J. Peron * the sense that a released lock isn't necessarily granted to 2006a53f0a5SChristian S.J. Peron * the process that has been waiting the longest. A waiter may 2016a53f0a5SChristian S.J. Peron * be starved out indefinitely. Second, it creates a thundering 2026a53f0a5SChristian S.J. Peron * herd situation each time the lock is released. 2036a53f0a5SChristian S.J. Peron * 2046a53f0a5SChristian S.J. Peron * When the "-k" option is used, the unlink race no longer 2056a53f0a5SChristian S.J. Peron * exists. In that case we can block while acquiring the lock, 2066a53f0a5SChristian S.J. Peron * avoiding the separate step of waiting for the lock. This 2076a53f0a5SChristian S.J. Peron * yields fairness and improved performance. 2086a53f0a5SChristian S.J. Peron */ 20909a7fe0aSKyle Evans lockfd = acquire_lock(&subj, flags | O_NONBLOCK, silent); 2106a53f0a5SChristian S.J. Peron while (lockfd == -1 && !timed_out && waitsec != 0) { 21109a7fe0aSKyle Evans if (keep || fdlock) 21209a7fe0aSKyle Evans lockfd = acquire_lock(&subj, flags, silent); 2136a53f0a5SChristian S.J. Peron else { 2146a53f0a5SChristian S.J. Peron wait_for_lock(lockname); 21509a7fe0aSKyle Evans lockfd = acquire_lock(&subj, flags | O_NONBLOCK, 2163041e695SKyle Evans silent); 2176a53f0a5SChristian S.J. Peron } 2186a53f0a5SChristian S.J. Peron } 219c8929a49SJohn Polstra if (waitsec > 0) 220c8929a49SJohn Polstra alarm(0); 221c8929a49SJohn Polstra if (lockfd == -1) { /* We failed to acquire the lock. */ 222c8929a49SJohn Polstra if (silent) 223ae06cb4dSJohn Polstra exit(EX_TEMPFAIL); 224ae06cb4dSJohn Polstra errx(EX_TEMPFAIL, "%s: already locked", lockname); 225c8929a49SJohn Polstra } 22609a7fe0aSKyle Evans 227c8929a49SJohn Polstra /* At this point, we own the lock. */ 22809a7fe0aSKyle Evans 22909a7fe0aSKyle Evans /* Nothing else to do for FD lock, just exit */ 23009a7fe0aSKyle Evans if (argc == 0) { 23109a7fe0aSKyle Evans assert(fdlock); 23209a7fe0aSKyle Evans return 0; 23309a7fe0aSKyle Evans } 23409a7fe0aSKyle Evans 235c8929a49SJohn Polstra if (atexit(cleanup) == -1) 236ae06cb4dSJohn Polstra err(EX_OSERR, "atexit failed"); 237c8929a49SJohn Polstra if ((child = fork()) == -1) 238ae06cb4dSJohn Polstra err(EX_OSERR, "cannot fork"); 239c8929a49SJohn Polstra if (child == 0) { /* The child process. */ 240c8929a49SJohn Polstra close(lockfd); 241c8929a49SJohn Polstra execvp(argv[0], argv); 242208f2fd1STim J. Robbins warn("%s", argv[0]); 243c8929a49SJohn Polstra _exit(1); 244c8929a49SJohn Polstra } 245c8929a49SJohn Polstra /* This is the parent process. */ 246c8929a49SJohn Polstra signal(SIGINT, SIG_IGN); 247c8929a49SJohn Polstra signal(SIGQUIT, SIG_IGN); 248c8929a49SJohn Polstra signal(SIGTERM, killed); 24918425c19SAlexander Melkov fclose(stdin); 25018425c19SAlexander Melkov fclose(stdout); 25118425c19SAlexander Melkov fclose(stderr); 252c8929a49SJohn Polstra if (waitpid(child, &status, 0) == -1) 25318425c19SAlexander Melkov exit(EX_OSERR); 2547f15c32dSDavid Malone return (WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE); 255c8929a49SJohn Polstra } 256c8929a49SJohn Polstra 257c8929a49SJohn Polstra /* 25809a7fe0aSKyle Evans * Try to acquire a lock on the given file/fd, creating the file if 2596a53f0a5SChristian S.J. Peron * necessary. The flags argument is O_NONBLOCK or 0, depending on 2606a53f0a5SChristian S.J. Peron * whether we should wait for the lock. Returns an open file descriptor 2616a53f0a5SChristian S.J. Peron * on success, or -1 on failure. 2626a53f0a5SChristian S.J. Peron */ 2636a53f0a5SChristian S.J. Peron static int 26409a7fe0aSKyle Evans acquire_lock(union lock_subject *subj, int flags, int silent) 2656a53f0a5SChristian S.J. Peron { 2666a53f0a5SChristian S.J. Peron int fd; 2676a53f0a5SChristian S.J. Peron 26809a7fe0aSKyle Evans if (fdlock) { 26909a7fe0aSKyle Evans assert(subj->subj_fd >= 0 && subj->subj_fd <= INT_MAX); 27009a7fe0aSKyle Evans fd = (int)subj->subj_fd; 27109a7fe0aSKyle Evans 27209a7fe0aSKyle Evans if (flock(fd, LOCK_EX | LOCK_NB) == -1) { 27309a7fe0aSKyle Evans if (errno == EAGAIN || errno == EINTR) 27409a7fe0aSKyle Evans return (-1); 27509a7fe0aSKyle Evans err(EX_CANTCREAT, "cannot lock fd %d", fd); 27609a7fe0aSKyle Evans } 27709a7fe0aSKyle Evans } else if ((fd = open(subj->subj_name, O_EXLOCK|flags, 0666)) == -1) { 2786a53f0a5SChristian S.J. Peron if (errno == EAGAIN || errno == EINTR) 2796a53f0a5SChristian S.J. Peron return (-1); 2803041e695SKyle Evans else if (errno == ENOENT && (flags & O_CREAT) == 0) { 2813041e695SKyle Evans if (!silent) 28209a7fe0aSKyle Evans warn("%s", subj->subj_name); 2833041e695SKyle Evans exit(EX_UNAVAILABLE); 2843041e695SKyle Evans } 28509a7fe0aSKyle Evans err(EX_CANTCREAT, "cannot open %s", subj->subj_name); 2866a53f0a5SChristian S.J. Peron } 2876a53f0a5SChristian S.J. Peron return (fd); 2886a53f0a5SChristian S.J. Peron } 2896a53f0a5SChristian S.J. Peron 2906a53f0a5SChristian S.J. Peron /* 291c8929a49SJohn Polstra * Remove the lock file. 292c8929a49SJohn Polstra */ 293c8929a49SJohn Polstra static void 294c8929a49SJohn Polstra cleanup(void) 295c8929a49SJohn Polstra { 296cc2a9f52SChristian S.J. Peron 29709a7fe0aSKyle Evans if (keep || fdlock) 2982f278eacSJohn Polstra flock(lockfd, LOCK_UN); 2992f278eacSJohn Polstra else 300ae06cb4dSJohn Polstra unlink(lockname); 301c8929a49SJohn Polstra } 302c8929a49SJohn Polstra 303c8929a49SJohn Polstra /* 304c8929a49SJohn Polstra * Signal handler for SIGTERM. Cleans up the lock file, then re-raises 305c8929a49SJohn Polstra * the signal. 306c8929a49SJohn Polstra */ 307c8929a49SJohn Polstra static void 308c8929a49SJohn Polstra killed(int sig) 309c8929a49SJohn Polstra { 310cc2a9f52SChristian S.J. Peron 311c8929a49SJohn Polstra cleanup(); 312c8929a49SJohn Polstra signal(sig, SIG_DFL); 313c8929a49SJohn Polstra if (kill(getpid(), sig) == -1) 31418425c19SAlexander Melkov _Exit(EX_OSERR); 315c8929a49SJohn Polstra } 316c8929a49SJohn Polstra 317c8929a49SJohn Polstra /* 318c8929a49SJohn Polstra * Signal handler for SIGALRM. 319c8929a49SJohn Polstra */ 320c8929a49SJohn Polstra static void 3219ff5e898SDavid Malone timeout(int sig __unused) 322c8929a49SJohn Polstra { 323cc2a9f52SChristian S.J. Peron 324c8929a49SJohn Polstra timed_out = 1; 325c8929a49SJohn Polstra } 326c8929a49SJohn Polstra 327c8929a49SJohn Polstra static void 328c8929a49SJohn Polstra usage(void) 329c8929a49SJohn Polstra { 330cc2a9f52SChristian S.J. Peron 3313ec665d4SPhilippe Charnier fprintf(stderr, 33209a7fe0aSKyle Evans "usage: lockf [-knsw] [-t seconds] file command [arguments]\n" 33309a7fe0aSKyle Evans " lockf [-s] [-t seconds] fd\n"); 3343ec665d4SPhilippe Charnier exit(EX_USAGE); 335c8929a49SJohn Polstra } 336c8929a49SJohn Polstra 337c8929a49SJohn Polstra /* 338c8929a49SJohn Polstra * Wait until it might be possible to acquire a lock on the given file. 3396a53f0a5SChristian S.J. Peron * If the file does not exist, return immediately without creating it. 340c8929a49SJohn Polstra */ 3416a53f0a5SChristian S.J. Peron static void 3426a53f0a5SChristian S.J. Peron wait_for_lock(const char *name) 343c8929a49SJohn Polstra { 344c8929a49SJohn Polstra int fd; 345c8929a49SJohn Polstra 3466a53f0a5SChristian S.J. Peron if ((fd = open(name, O_RDONLY|O_EXLOCK, 0666)) == -1) { 3476a53f0a5SChristian S.J. Peron if (errno == ENOENT || errno == EINTR) 3486a53f0a5SChristian S.J. Peron return; 349ae06cb4dSJohn Polstra err(EX_CANTCREAT, "cannot open %s", name); 350c8929a49SJohn Polstra } 3516a53f0a5SChristian S.J. Peron close(fd); 352c8929a49SJohn Polstra } 353