1/* $NetBSD: flock.c,v 1.12 2019/10/04 16:27:00 mrg Exp $ */ 2 3/*- 4 * Copyright (c) 2012 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Christos Zoulas. 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 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33#include <sys/cdefs.h> 34__RCSID("$NetBSD: flock.c,v 1.12 2019/10/04 16:27:00 mrg Exp $"); 35 36#include <stdio.h> 37#include <string.h> 38#include <fcntl.h> 39#include <stdlib.h> 40#include <signal.h> 41#include <unistd.h> 42#include <err.h> 43#include <errno.h> 44#include <getopt.h> 45#include <paths.h> 46#include <limits.h> 47#include <time.h> 48 49static struct option flock_longopts[] = { 50 { "debug", no_argument, 0, 'd' }, 51 { "help", no_argument, 0, 'h' }, 52 { "nonblock", no_argument, 0, 'n' }, 53 { "nb", no_argument, 0, 'n' }, 54 { "close", no_argument, 0, 'o' }, 55 { "shared", no_argument, 0, 's' }, 56 { "exclusive", no_argument, 0, 'x' }, 57 { "unlock", no_argument, 0, 'u' }, 58 { "verbose", no_argument, 0, 'v' }, 59 { "command", required_argument, 0, 'c' }, 60 { "wait", required_argument, 0, 'w' }, 61 { "timeout", required_argument, 0, 'w' }, 62 { NULL, 0, 0, 0 }, 63}; 64 65static sig_atomic_t timeout_expired; 66 67static __dead __printflike(1, 2) void 68usage(const char *fmt, ...) 69{ 70 if (fmt) { 71 va_list ap; 72 va_start(ap, fmt); 73 fprintf(stderr, "%s: ", getprogname()); 74 vfprintf(stderr, fmt, ap); 75 fputc('\n', stderr); 76 va_end(ap); 77 } 78 79 fprintf(stderr, "Usage: %s [-dnosvx] [-w timeout] lockfile|lockdir " 80 "[-c command]|command ...\n\t%s [-dnsuvx] [-w timeout] lockfd\n", 81 getprogname(), getprogname()); 82 exit(EXIT_FAILURE); 83} 84 85static void 86sigalrm(int sig) 87{ 88 timeout_expired++; 89} 90 91static const char * 92lock2name(int l) 93{ 94 static char buf[1024]; 95 int nb = l & LOCK_NB; 96 97 l &= ~LOCK_NB; 98 if (nb) 99 strlcpy(buf, "LOCK_NB|", sizeof(buf)); 100 else 101 buf[0] = '\0'; 102 103 switch (l) { 104 case LOCK_SH: 105 strlcat(buf, "LOCK_SH", sizeof(buf)); 106 return buf; 107 case LOCK_EX: 108 strlcat(buf, "LOCK_EX", sizeof(buf)); 109 return buf; 110 case LOCK_UN: 111 strlcat(buf, "LOCK_UN", sizeof(buf)); 112 return buf; 113 default: 114 snprintf(buf, sizeof(buf), "*%d*", l | nb); 115 return buf; 116 } 117} 118 119static char 120lockchar(int l) 121{ 122 switch (l & ~LOCK_NB) { 123 case LOCK_SH: 124 return 's'; 125 case LOCK_EX: 126 return 'x'; 127 case LOCK_UN: 128 return 'u'; 129 default: 130 return '*'; 131 } 132} 133 134static char * 135cmdline(char **av) 136{ 137 char *v = NULL; 138 while (*av) 139 if (v) { 140 if (asprintf(&v, "%s %s", v, *av++) < 0) 141 err(EXIT_FAILURE, "malloc"); 142 } else { 143 if ((v = strdup(*av++)) == NULL) 144 err(EXIT_FAILURE, "strdup"); 145 } 146 return v; 147} 148 149int 150main(int argc, char *argv[]) 151{ 152 int c; 153 int lock = 0; 154 double timeout = 0; 155 int cls = 0; 156 int fd = -1; 157 int debug = 0; 158 int verbose = 0; 159 long l; 160 char *mcargv[] = { 161 __UNCONST(_PATH_BSHELL), __UNCONST("-c"), NULL, NULL 162 }; 163 char **cmdargv = NULL, *v; 164 timer_t tm; 165 166 setprogname(argv[0]); 167 168 while ((c = getopt_long(argc, argv, "+dnosuvw:x", flock_longopts, NULL)) 169 != -1) 170 switch (c) { 171 case 'd': 172 debug++; 173 break; 174 case 'x': 175#define T(l) (lock & ~LOCK_NB) != (l) && (lock & ~LOCK_NB) != 0 176 if (T(LOCK_EX)) 177 goto badlock; 178 lock |= LOCK_EX; 179 break; 180 case 'n': 181 lock |= LOCK_NB; 182 break; 183 case 's': 184 if (T(LOCK_SH)) 185 goto badlock; 186 lock |= LOCK_SH; 187 break; 188 case 'u': 189 if (T(LOCK_UN)) 190 goto badlock; 191 lock |= LOCK_UN; 192 break; 193 case 'w': 194 timeout = strtod(optarg, NULL); 195 break; 196 case 'v': 197 verbose = 1; 198 break; 199 case 'o': 200 cls = 1; 201 break; 202 default: 203 usage("Invalid option '%c'", c); 204 badlock: 205 usage("-%c can't be used with -%c", c, lockchar(lock)); 206 } 207 208 argc -= optind; 209 argv += optind; 210 211 if ((lock & ~LOCK_NB) == 0) 212 lock |= LOCK_EX; /* default to exclusive like linux */ 213 214 switch (argc) { 215 case 0: 216 usage("Missing lock file argument"); 217 case 1: 218 if (cls) 219 usage("Close is not valid for descriptors"); 220 errno = 0; 221 l = strtol(argv[0], &v, 0); 222 if ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) 223 err(EXIT_FAILURE, "Bad file descriptor `%s'", argv[0]); 224 if (l > INT_MAX || l < 0 || *v) 225 errx(EXIT_FAILURE, "Bad file descriptor `%s'", argv[0]); 226 fd = (int)l; 227 if (debug) { 228 fprintf(stderr, "descriptor %s lock %s\n", 229 argv[0], lock2name(lock)); 230 } 231 break; 232 233 default: 234 if ((lock & ~LOCK_NB) == LOCK_UN) 235 usage("Unlock is only valid for descriptors"); 236 if (strcmp(argv[1], "-c") == 0 || 237 strcmp(argv[1], "--command") == 0) { 238 if (argc == 2) 239 usage("Missing argument to %s", strcmp(argv[1], 240 "-c") == 0 ? "-c" : "--command"); 241 mcargv[2] = argv[2]; 242 cmdargv = mcargv; 243 } else 244 cmdargv = argv + 1; 245 246 if ((fd = open(argv[0], O_RDONLY)) == -1) { 247 if (errno != ENOENT || 248 (fd = open(argv[0], O_RDWR|O_CREAT, 0600)) == -1) 249 err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); 250 } 251 if (debug) { 252 fprintf(stderr, "file %s lock %s command %s ...\n", 253 argv[0], lock2name(lock), v = cmdline(cmdargv)); 254 free(v); 255 } 256 break; 257 } 258 259 if (timeout) { 260 struct sigevent ev; 261 struct itimerspec it; 262 struct sigaction sa; 263 264 timespecclear(&it.it_interval); 265 it.it_value.tv_sec = timeout; 266 it.it_value.tv_nsec = (timeout - it.it_value.tv_sec) * 267 1000000000; 268 269 memset(&ev, 0, sizeof(ev)); 270 ev.sigev_notify = SIGEV_SIGNAL; 271 ev.sigev_signo = SIGALRM; 272 273 if (timer_create(CLOCK_REALTIME, &ev, &tm) == -1) 274 err(EXIT_FAILURE, "timer_create"); 275 276 if (timer_settime(tm, TIMER_RELTIME, &it, NULL) == -1) 277 err(EXIT_FAILURE, "timer_settime"); 278 279 memset(&sa, 0, sizeof(sa)); 280 sa.sa_handler = sigalrm; 281 sigemptyset(&sa.sa_mask); 282 sa.sa_flags = 0; 283 if (sigaction(SIGALRM, &sa, NULL) == -1) 284 err(EXIT_FAILURE, "sigaction"); 285 286 if (debug) 287 fprintf(stderr, "alarm %g\n", timeout); 288 } 289 290 while (flock(fd, lock) == -1) { 291 if (errno == EINTR && timeout_expired == 0) 292 continue; 293 if (verbose) 294 err(EXIT_FAILURE, "flock(%d, %s)", fd, lock2name(lock)); 295 else 296 return EXIT_FAILURE; 297 } 298 299 if (timeout) 300 timer_delete(tm); 301 302 if (cls) 303 (void)close(fd); 304 305 if (cmdargv != NULL) { 306 execvp(cmdargv[0], cmdargv); 307 err(EXIT_FAILURE, "execvp '%s'", v = cmdline(cmdargv)); 308 free(v); 309 } 310 return 0; 311} 312