1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (C) 1997 John D. Polstra. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY JOHN D. POLSTRA AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL JOHN D. POLSTRA OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 #include <sys/types.h> 30 #include <sys/wait.h> 31 32 #include <assert.h> 33 #include <err.h> 34 #include <errno.h> 35 #include <fcntl.h> 36 #include <limits.h> 37 #include <signal.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <sysexits.h> 42 #include <unistd.h> 43 44 #define FDLOCK_PREFIX "/dev/fd/" 45 46 union lock_subject { 47 long subj_fd; 48 const char *subj_name; 49 }; 50 51 static int acquire_lock(union lock_subject *subj, int flags, int silent); 52 static void cleanup(void); 53 static void killed(int sig); 54 static void timeout(int sig); 55 static void usage(void) __dead2; 56 static void wait_for_lock(const char *name); 57 58 static const char *lockname; 59 static int lockfd = -1; 60 static int keep; 61 static int fdlock; 62 static volatile sig_atomic_t timed_out; 63 64 /* 65 * Check if fdlock is implied by the given `lockname`. We'll write the fd that 66 * is represented by it out to ofd, and the caller is expected to do any 67 * necessary validation on it. 68 */ 69 static int 70 fdlock_implied(const char *name, long *ofd) 71 { 72 char *endp; 73 long fd; 74 75 if (strncmp(name, FDLOCK_PREFIX, sizeof(FDLOCK_PREFIX) - 1) != 0) 76 return (0); 77 78 /* Skip past the prefix. */ 79 name += sizeof(FDLOCK_PREFIX) - 1; 80 errno = 0; 81 fd = strtol(name, &endp, 10); 82 if (errno != 0 || *endp != '\0') 83 return (0); 84 85 *ofd = fd; 86 return (1); 87 } 88 89 /* 90 * Execute an arbitrary command while holding a file lock. 91 */ 92 int 93 main(int argc, char **argv) 94 { 95 int ch, flags, silent, status; 96 long long waitsec; 97 pid_t child; 98 union lock_subject subj; 99 100 silent = keep = 0; 101 flags = O_CREAT | O_RDONLY; 102 waitsec = -1; /* Infinite. */ 103 while ((ch = getopt(argc, argv, "knst:w")) != -1) { 104 switch (ch) { 105 case 'k': 106 keep = 1; 107 break; 108 case 'n': 109 flags &= ~O_CREAT; 110 break; 111 case 's': 112 silent = 1; 113 break; 114 case 't': 115 { 116 const char *errstr; 117 118 waitsec = strtonum(optarg, 0, UINT_MAX, &errstr); 119 if (errstr != NULL) 120 errx(EX_USAGE, 121 "invalid timeout \"%s\"", optarg); 122 } 123 break; 124 case 'w': 125 flags = (flags & ~O_RDONLY) | O_WRONLY; 126 break; 127 default: 128 usage(); 129 } 130 } 131 132 argc -= optind; 133 argv += optind; 134 135 if (argc == 0) 136 usage(); 137 138 lockname = argv[0]; 139 140 argc--; 141 argv++; 142 143 /* 144 * If there aren't any arguments left, then we must be in fdlock mode. 145 */ 146 if (argc == 0 && *lockname != '/') { 147 fdlock = 1; 148 subj.subj_fd = -1; 149 } else { 150 fdlock = fdlock_implied(lockname, &subj.subj_fd); 151 if (argc == 0 && !fdlock) { 152 fprintf(stderr, "Expected fd, got '%s'\n", lockname); 153 usage(); 154 } 155 } 156 157 if (fdlock) { 158 if (subj.subj_fd < 0) { 159 char *endp; 160 161 errno = 0; 162 subj.subj_fd = strtol(lockname, &endp, 10); 163 if (errno != 0 || *endp != '\0') { 164 fprintf(stderr, "Expected fd, got '%s'\n", 165 lockname); 166 usage(); 167 } 168 } 169 170 if (subj.subj_fd < 0 || subj.subj_fd > INT_MAX) { 171 fprintf(stderr, "fd '%ld' out of range\n", 172 subj.subj_fd); 173 usage(); 174 } 175 } else { 176 subj.subj_name = lockname; 177 } 178 179 if (waitsec > 0) { /* Set up a timeout. */ 180 struct sigaction act; 181 182 act.sa_handler = timeout; 183 sigemptyset(&act.sa_mask); 184 act.sa_flags = 0; /* Note that we do not set SA_RESTART. */ 185 sigaction(SIGALRM, &act, NULL); 186 alarm((unsigned int)waitsec); 187 } 188 /* 189 * If the "-k" option is not given, then we must not block when 190 * acquiring the lock. If we did, then the lock holder would 191 * unlink the file upon releasing the lock, and we would acquire 192 * a lock on a file with no directory entry. Then another 193 * process could come along and acquire the same lock. To avoid 194 * this problem, we separate out the actions of waiting for the 195 * lock to be available and of actually acquiring the lock. 196 * 197 * That approach produces behavior that is technically correct; 198 * however, it causes some performance & ordering problems for 199 * locks that have a lot of contention. First, it is unfair in 200 * the sense that a released lock isn't necessarily granted to 201 * the process that has been waiting the longest. A waiter may 202 * be starved out indefinitely. Second, it creates a thundering 203 * herd situation each time the lock is released. 204 * 205 * When the "-k" option is used, the unlink race no longer 206 * exists. In that case we can block while acquiring the lock, 207 * avoiding the separate step of waiting for the lock. This 208 * yields fairness and improved performance. 209 */ 210 lockfd = acquire_lock(&subj, flags | O_NONBLOCK, silent); 211 while (lockfd == -1 && !timed_out && waitsec != 0) { 212 if (keep || fdlock) 213 lockfd = acquire_lock(&subj, flags, silent); 214 else { 215 wait_for_lock(lockname); 216 lockfd = acquire_lock(&subj, flags | O_NONBLOCK, 217 silent); 218 } 219 } 220 if (waitsec > 0) 221 alarm(0); 222 if (lockfd == -1) { /* We failed to acquire the lock. */ 223 if (silent) 224 exit(EX_TEMPFAIL); 225 errx(EX_TEMPFAIL, "%s: already locked", lockname); 226 } 227 228 /* At this point, we own the lock. */ 229 230 /* Nothing else to do for FD lock, just exit */ 231 if (argc == 0) { 232 assert(fdlock); 233 return 0; 234 } 235 236 if (atexit(cleanup) == -1) 237 err(EX_OSERR, "atexit failed"); 238 if ((child = fork()) == -1) 239 err(EX_OSERR, "cannot fork"); 240 if (child == 0) { /* The child process. */ 241 close(lockfd); 242 execvp(argv[0], argv); 243 warn("%s", argv[0]); 244 _exit(1); 245 } 246 /* This is the parent process. */ 247 signal(SIGINT, SIG_IGN); 248 signal(SIGQUIT, SIG_IGN); 249 signal(SIGTERM, killed); 250 fclose(stdin); 251 fclose(stdout); 252 fclose(stderr); 253 if (waitpid(child, &status, 0) == -1) 254 exit(EX_OSERR); 255 return (WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE); 256 } 257 258 /* 259 * Try to acquire a lock on the given file/fd, creating the file if 260 * necessary. The flags argument is O_NONBLOCK or 0, depending on 261 * whether we should wait for the lock. Returns an open file descriptor 262 * on success, or -1 on failure. 263 */ 264 static int 265 acquire_lock(union lock_subject *subj, int flags, int silent) 266 { 267 int fd; 268 269 if (fdlock) { 270 assert(subj->subj_fd >= 0 && subj->subj_fd <= INT_MAX); 271 fd = (int)subj->subj_fd; 272 273 if (flock(fd, LOCK_EX | LOCK_NB) == -1) { 274 if (errno == EAGAIN || errno == EINTR) 275 return (-1); 276 err(EX_CANTCREAT, "cannot lock fd %d", fd); 277 } 278 } else if ((fd = open(subj->subj_name, O_EXLOCK|flags, 0666)) == -1) { 279 if (errno == EAGAIN || errno == EINTR) 280 return (-1); 281 else if (errno == ENOENT && (flags & O_CREAT) == 0) { 282 if (!silent) 283 warn("%s", subj->subj_name); 284 exit(EX_UNAVAILABLE); 285 } 286 err(EX_CANTCREAT, "cannot open %s", subj->subj_name); 287 } 288 return (fd); 289 } 290 291 /* 292 * Remove the lock file. 293 */ 294 static void 295 cleanup(void) 296 { 297 298 if (keep || fdlock) 299 flock(lockfd, LOCK_UN); 300 else 301 unlink(lockname); 302 } 303 304 /* 305 * Signal handler for SIGTERM. Cleans up the lock file, then re-raises 306 * the signal. 307 */ 308 static void 309 killed(int sig) 310 { 311 312 cleanup(); 313 signal(sig, SIG_DFL); 314 if (kill(getpid(), sig) == -1) 315 _Exit(EX_OSERR); 316 } 317 318 /* 319 * Signal handler for SIGALRM. 320 */ 321 static void 322 timeout(int sig __unused) 323 { 324 325 timed_out = 1; 326 } 327 328 static void 329 usage(void) 330 { 331 332 fprintf(stderr, 333 "usage: lockf [-knsw] [-t seconds] file command [arguments]\n" 334 " lockf [-s] [-t seconds] fd\n"); 335 exit(EX_USAGE); 336 } 337 338 /* 339 * Wait until it might be possible to acquire a lock on the given file. 340 * If the file does not exist, return immediately without creating it. 341 */ 342 static void 343 wait_for_lock(const char *name) 344 { 345 int fd; 346 347 if ((fd = open(name, O_RDONLY|O_EXLOCK, 0666)) == -1) { 348 if (errno == ENOENT || errno == EINTR) 349 return; 350 err(EX_CANTCREAT, "cannot open %s", name); 351 } 352 close(fd); 353 } 354