121439Sjdp/* 221439Sjdp * Copyright (C) 1997 John D. Polstra. All rights reserved. 321439Sjdp * 421439Sjdp * Redistribution and use in source and binary forms, with or without 521439Sjdp * modification, are permitted provided that the following conditions 621439Sjdp * are met: 721439Sjdp * 1. Redistributions of source code must retain the above copyright 821439Sjdp * notice, this list of conditions and the following disclaimer. 921439Sjdp * 2. Redistributions in binary form must reproduce the above copyright 1021439Sjdp * notice, this list of conditions and the following disclaimer in the 1121439Sjdp * documentation and/or other materials provided with the distribution. 1221439Sjdp * 1321439Sjdp * THIS SOFTWARE IS PROVIDED BY JOHN D. POLSTRA AND CONTRIBUTORS ``AS IS'' AND 1421439Sjdp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1521439Sjdp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1621439Sjdp * ARE DISCLAIMED. IN NO EVENT SHALL JOHN D. POLSTRA OR CONTRIBUTORS BE LIABLE 1721439Sjdp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1821439Sjdp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 1921439Sjdp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2021439Sjdp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2121439Sjdp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2221439Sjdp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2321439Sjdp * SUCH DAMAGE. 2421439Sjdp */ 2521439Sjdp 2699112Sobrien#include <sys/cdefs.h> 2799112Sobrien__FBSDID("$FreeBSD$"); 2899112Sobrien 2921439Sjdp#include <sys/types.h> 3021439Sjdp#include <sys/wait.h> 3121439Sjdp 3221439Sjdp#include <err.h> 3321439Sjdp#include <errno.h> 3421439Sjdp#include <fcntl.h> 3521439Sjdp#include <signal.h> 3621439Sjdp#include <stdio.h> 3721439Sjdp#include <stdlib.h> 3821462Sjdp#include <sysexits.h> 3921439Sjdp#include <unistd.h> 4021439Sjdp 41172580Scsjpstatic int acquire_lock(const char *name, int flags); 4221439Sjdpstatic void cleanup(void); 4321439Sjdpstatic void killed(int sig); 4421439Sjdpstatic void timeout(int sig); 4521439Sjdpstatic void usage(void); 46172580Scsjpstatic void wait_for_lock(const char *name); 4721439Sjdp 4821439Sjdpstatic const char *lockname; 49150977Scsjpstatic int lockfd = -1; 5037493Sjdpstatic int keep; 5121439Sjdpstatic volatile sig_atomic_t timed_out; 5221439Sjdp 5321439Sjdp/* 5421439Sjdp * Execute an arbitrary command while holding a file lock. 5521439Sjdp */ 5621439Sjdpint 5721439Sjdpmain(int argc, char **argv) 5821439Sjdp{ 59250462Seadler int ch, flags, silent, status, waitsec; 60151056Scsjp pid_t child; 6121439Sjdp 62151056Scsjp silent = keep = 0; 63250462Seadler flags = O_CREAT; 64151056Scsjp waitsec = -1; /* Infinite. */ 65250462Seadler while ((ch = getopt(argc, argv, "sknt:")) != -1) { 66151056Scsjp switch (ch) { 67151056Scsjp case 'k': 68151056Scsjp keep = 1; 69151056Scsjp break; 70250462Seadler case 'n': 71250462Seadler flags &= ~O_CREAT; 72250462Seadler break; 73151056Scsjp case 's': 74151056Scsjp silent = 1; 75151056Scsjp break; 76151056Scsjp case 't': 77151056Scsjp { 78151056Scsjp char *endptr; 79151056Scsjp waitsec = strtol(optarg, &endptr, 0); 80151056Scsjp if (*optarg == '\0' || *endptr != '\0' || waitsec < 0) 81151056Scsjp errx(EX_USAGE, 82151056Scsjp "invalid timeout \"%s\"", optarg); 83151056Scsjp } 84151056Scsjp break; 85151056Scsjp default: 86151056Scsjp usage(); 87151056Scsjp } 88151056Scsjp } 89151056Scsjp if (argc - optind < 2) 90151056Scsjp usage(); 91151056Scsjp lockname = argv[optind++]; 92151056Scsjp argc -= optind; 93151056Scsjp argv += optind; 94151056Scsjp if (waitsec > 0) { /* Set up a timeout. */ 95151056Scsjp struct sigaction act; 9621439Sjdp 97151056Scsjp act.sa_handler = timeout; 98151056Scsjp sigemptyset(&act.sa_mask); 99151056Scsjp act.sa_flags = 0; /* Note that we do not set SA_RESTART. */ 100151056Scsjp sigaction(SIGALRM, &act, NULL); 101151056Scsjp alarm(waitsec); 10221439Sjdp } 103172580Scsjp /* 104172580Scsjp * If the "-k" option is not given, then we must not block when 105172580Scsjp * acquiring the lock. If we did, then the lock holder would 106172580Scsjp * unlink the file upon releasing the lock, and we would acquire 107172580Scsjp * a lock on a file with no directory entry. Then another 108172580Scsjp * process could come along and acquire the same lock. To avoid 109172580Scsjp * this problem, we separate out the actions of waiting for the 110172580Scsjp * lock to be available and of actually acquiring the lock. 111172580Scsjp * 112172580Scsjp * That approach produces behavior that is technically correct; 113172580Scsjp * however, it causes some performance & ordering problems for 114172580Scsjp * locks that have a lot of contention. First, it is unfair in 115172580Scsjp * the sense that a released lock isn't necessarily granted to 116172580Scsjp * the process that has been waiting the longest. A waiter may 117172580Scsjp * be starved out indefinitely. Second, it creates a thundering 118172580Scsjp * herd situation each time the lock is released. 119172580Scsjp * 120172580Scsjp * When the "-k" option is used, the unlink race no longer 121172580Scsjp * exists. In that case we can block while acquiring the lock, 122172580Scsjp * avoiding the separate step of waiting for the lock. This 123172580Scsjp * yields fairness and improved performance. 124172580Scsjp */ 125250462Seadler lockfd = acquire_lock(lockname, flags | O_NONBLOCK); 126172580Scsjp while (lockfd == -1 && !timed_out && waitsec != 0) { 127172580Scsjp if (keep) 128250462Seadler lockfd = acquire_lock(lockname, flags); 129172580Scsjp else { 130172580Scsjp wait_for_lock(lockname); 131250462Seadler lockfd = acquire_lock(lockname, flags | O_NONBLOCK); 132172580Scsjp } 133172580Scsjp } 134151056Scsjp if (waitsec > 0) 135151056Scsjp alarm(0); 136151056Scsjp if (lockfd == -1) { /* We failed to acquire the lock. */ 137151056Scsjp if (silent) 138151056Scsjp exit(EX_TEMPFAIL); 139151056Scsjp errx(EX_TEMPFAIL, "%s: already locked", lockname); 140151056Scsjp } 141151056Scsjp /* At this point, we own the lock. */ 142151056Scsjp if (atexit(cleanup) == -1) 143151056Scsjp err(EX_OSERR, "atexit failed"); 144151056Scsjp if ((child = fork()) == -1) 145151056Scsjp err(EX_OSERR, "cannot fork"); 146151056Scsjp if (child == 0) { /* The child process. */ 147151056Scsjp close(lockfd); 148151056Scsjp execvp(argv[0], argv); 149151056Scsjp warn("%s", argv[0]); 150151056Scsjp _exit(1); 151151056Scsjp } 152151056Scsjp /* This is the parent process. */ 153151056Scsjp signal(SIGINT, SIG_IGN); 154151056Scsjp signal(SIGQUIT, SIG_IGN); 155151056Scsjp signal(SIGTERM, killed); 156151056Scsjp if (waitpid(child, &status, 0) == -1) 157151056Scsjp err(EX_OSERR, "waitpid failed"); 158181960Sdwmalone return (WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE); 15921439Sjdp} 16021439Sjdp 16121439Sjdp/* 162172580Scsjp * Try to acquire a lock on the given file, creating the file if 163172580Scsjp * necessary. The flags argument is O_NONBLOCK or 0, depending on 164172580Scsjp * whether we should wait for the lock. Returns an open file descriptor 165172580Scsjp * on success, or -1 on failure. 166172580Scsjp */ 167172580Scsjpstatic int 168172580Scsjpacquire_lock(const char *name, int flags) 169172580Scsjp{ 170172580Scsjp int fd; 171172580Scsjp 172250462Seadler if ((fd = open(name, flags|O_RDONLY|O_EXLOCK|flags, 0666)) == -1) { 173172580Scsjp if (errno == EAGAIN || errno == EINTR) 174172580Scsjp return (-1); 175172580Scsjp err(EX_CANTCREAT, "cannot open %s", name); 176172580Scsjp } 177172580Scsjp return (fd); 178172580Scsjp} 179172580Scsjp 180172580Scsjp/* 18121439Sjdp * Remove the lock file. 18221439Sjdp */ 18321439Sjdpstatic void 18421439Sjdpcleanup(void) 18521439Sjdp{ 186151158Scsjp 187151158Scsjp if (keep) 188151158Scsjp flock(lockfd, LOCK_UN); 189151158Scsjp else 190151158Scsjp unlink(lockname); 19121439Sjdp} 19221439Sjdp 19321439Sjdp/* 19421439Sjdp * Signal handler for SIGTERM. Cleans up the lock file, then re-raises 19521439Sjdp * the signal. 19621439Sjdp */ 19721439Sjdpstatic void 19821439Sjdpkilled(int sig) 19921439Sjdp{ 200151158Scsjp 201151158Scsjp cleanup(); 202151158Scsjp signal(sig, SIG_DFL); 203151158Scsjp if (kill(getpid(), sig) == -1) 204151158Scsjp err(EX_OSERR, "kill failed"); 20521439Sjdp} 20621439Sjdp 20721439Sjdp/* 20821439Sjdp * Signal handler for SIGALRM. 20921439Sjdp */ 21021439Sjdpstatic void 21187290Sdwmalonetimeout(int sig __unused) 21221439Sjdp{ 213151158Scsjp 214151158Scsjp timed_out = 1; 21521439Sjdp} 21621439Sjdp 21721439Sjdpstatic void 21821439Sjdpusage(void) 21921439Sjdp{ 220151158Scsjp 221151158Scsjp fprintf(stderr, 222250462Seadler "usage: lockf [-kns] [-t seconds] file command [arguments]\n"); 223151158Scsjp exit(EX_USAGE); 22421439Sjdp} 22521439Sjdp 22621439Sjdp/* 22721439Sjdp * Wait until it might be possible to acquire a lock on the given file. 228172580Scsjp * If the file does not exist, return immediately without creating it. 22921439Sjdp */ 230172580Scsjpstatic void 231172580Scsjpwait_for_lock(const char *name) 23221439Sjdp{ 233151158Scsjp int fd; 23421439Sjdp 235172580Scsjp if ((fd = open(name, O_RDONLY|O_EXLOCK, 0666)) == -1) { 236172580Scsjp if (errno == ENOENT || errno == EINTR) 237172580Scsjp return; 238151158Scsjp err(EX_CANTCREAT, "cannot open %s", name); 239151158Scsjp } 240172580Scsjp close(fd); 24121439Sjdp} 242