1 /* $NetBSD: flock.c,v 1.8 2013/10/29 16:02:15 christos 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.8 2013/10/29 16:02:15 christos 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 <time.h> 47 48 static struct option flock_longopts[] = { 49 { "debug", no_argument, 0, 'd' }, 50 { "help", no_argument, 0, 'h' }, 51 { "nonblock", no_argument, 0, 'n' }, 52 { "nb", no_argument, 0, 'n' }, 53 { "close", no_argument, 0, 'o' }, 54 { "shared", no_argument, 0, 's' }, 55 { "exclusive", no_argument, 0, 'x' }, 56 { "unlock", no_argument, 0, 'u' }, 57 { "verbose", no_argument, 0, 'v' }, 58 { "command", required_argument, 0, 'c' }, 59 { "wait", required_argument, 0, 'w' }, 60 { "timeout", required_argument, 0, 'w' }, 61 { NULL, 0, 0, 0 }, 62 }; 63 64 static sig_atomic_t timeout_expired; 65 66 static __dead void 67 usage(const char *fmt, ...) 68 { 69 if (fmt) { 70 va_list ap; 71 va_start(ap, fmt); 72 fprintf(stderr, "%s: ", getprogname()); 73 vfprintf(stderr, fmt, ap); 74 fputc('\n', stderr); 75 va_end(ap); 76 } 77 78 fprintf(stderr, "Usage: %s [-dnosvx] [-w timeout] lockfile|lockdir " 79 "[-c command]|command ...\n\t%s [-dnsuvx] [-w timeout] lockfd\n", 80 getprogname(), getprogname()); 81 exit(EXIT_FAILURE); 82 } 83 84 static void 85 sigalrm(int sig) 86 { 87 timeout_expired++; 88 } 89 90 static const char * 91 lock2name(int l) 92 { 93 static char buf[1024]; 94 int nb = l & LOCK_NB; 95 96 l &= ~LOCK_NB; 97 if (nb) 98 strlcpy(buf, "LOCK_NB|", sizeof(buf)); 99 else 100 buf[0] = '\0'; 101 102 switch (l) { 103 case LOCK_SH: 104 strlcat(buf, "LOCK_SH", sizeof(buf)); 105 return buf; 106 case LOCK_EX: 107 strlcat(buf, "LOCK_EX", sizeof(buf)); 108 return buf; 109 case LOCK_UN: 110 strlcat(buf, "LOCK_UN", sizeof(buf)); 111 return buf; 112 default: 113 snprintf(buf, sizeof(buf), "*%d*", l | nb); 114 return buf; 115 } 116 } 117 118 static char 119 lockchar(int l) 120 { 121 switch (l & ~LOCK_NB) { 122 case LOCK_SH: 123 return 's'; 124 case LOCK_EX: 125 return 'x'; 126 case LOCK_UN: 127 return 'u'; 128 default: 129 return '*'; 130 } 131 } 132 133 static char * 134 cmdline(char **av) 135 { 136 char *v = NULL; 137 while (*av) 138 if (v) { 139 if (asprintf(&v, "%s %s", v, *av++) < 0) 140 err(EXIT_FAILURE, "malloc"); 141 } else { 142 if ((v = strdup(*av++)) == NULL) 143 err(EXIT_FAILURE, "strdup"); 144 } 145 return v; 146 } 147 148 int 149 main(int argc, char *argv[]) 150 { 151 int c; 152 int lock = 0; 153 double timeout = 0; 154 int cls = 0; 155 int fd = -1; 156 int debug = 0; 157 int verbose = 0; 158 char *mcargv[] = { 159 __UNCONST(_PATH_BSHELL), __UNCONST("-c"), NULL, NULL 160 }; 161 char **cmdargv = NULL, *v; 162 timer_t tm; 163 164 setprogname(argv[0]); 165 166 while ((c = getopt_long(argc, argv, "+dnosuvw:x", flock_longopts, NULL)) 167 != -1) 168 switch (c) { 169 case 'd': 170 debug++; 171 break; 172 case 'x': 173 #define T(l) (lock & ~LOCK_NB) != (l) && (lock & ~LOCK_NB) != 0 174 if (T(LOCK_EX)) 175 goto badlock; 176 lock |= LOCK_EX; 177 break; 178 case 'n': 179 lock |= LOCK_NB; 180 break; 181 case 's': 182 if (T(LOCK_SH)) 183 goto badlock; 184 lock |= LOCK_SH; 185 break; 186 case 'u': 187 if (T(LOCK_UN)) 188 goto badlock; 189 lock |= LOCK_UN; 190 break; 191 case 'w': 192 timeout = strtod(optarg, NULL); 193 break; 194 case 'v': 195 verbose = 1; 196 break; 197 case 'o': 198 cls = 1; 199 break; 200 default: 201 usage("Invalid option '%c'", c); 202 badlock: 203 usage("-%c can't be used with -%c", c, lockchar(lock)); 204 } 205 206 argc -= optind; 207 argv += optind; 208 209 if ((lock & ~LOCK_NB) == 0) 210 usage("Missing lock type flag"); 211 212 switch (argc) { 213 case 0: 214 usage("Missing lock file argument"); 215 case 1: 216 if (cls) 217 usage("Close is valid only for descriptors"); 218 fd = strtol(argv[0], NULL, 0); // XXX: error checking 219 if (debug) { 220 fprintf(stderr, "descriptor %s lock %s\n", 221 argv[0], lock2name(lock)); 222 } 223 break; 224 225 default: 226 if ((lock & LOCK_NB) == LOCK_UN) 227 usage("Unlock is only valid for descriptors"); 228 if (strcmp(argv[1], "-c") == 0 || 229 strcmp(argv[1], "--command") == 0) { 230 if (argc == 2) 231 usage("Missing argument to %s", strcmp(argv[1], 232 "-c") == 0 ? "-c" : "--command"); 233 mcargv[2] = argv[2]; 234 cmdargv = mcargv; 235 } else 236 cmdargv = argv + 1; 237 238 if ((fd = open(argv[0], O_RDONLY)) == -1) { 239 if (errno != ENOENT || 240 (fd = open(argv[0], O_RDWR|O_CREAT, 0600)) == -1) 241 err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); 242 } 243 if (debug) { 244 fprintf(stderr, "file %s lock %s command %s ...\n", 245 argv[0], lock2name(lock), v = cmdline(cmdargv)); 246 free(v); 247 } 248 break; 249 } 250 251 if (timeout) { 252 struct sigevent ev; 253 struct itimerspec it; 254 struct sigaction sa; 255 256 timespecclear(&it.it_interval); 257 it.it_value.tv_sec = timeout; 258 it.it_value.tv_nsec = (timeout - it.it_value.tv_sec) * 259 1000000000; 260 261 memset(&ev, 0, sizeof(ev)); 262 ev.sigev_notify = SIGEV_SIGNAL; 263 ev.sigev_signo = SIGALRM; 264 265 if (timer_create(CLOCK_REALTIME, &ev, &tm) == -1) 266 err(EXIT_FAILURE, "timer_create"); 267 268 if (timer_settime(tm, TIMER_RELTIME, &it, NULL) == -1) 269 err(EXIT_FAILURE, "timer_settime"); 270 271 memset(&sa, 0, sizeof(sa)); 272 sa.sa_handler = sigalrm; 273 sigemptyset(&sa.sa_mask); 274 sa.sa_flags = 0; 275 if (sigaction(SIGALRM, &sa, NULL) == -1) 276 err(EXIT_FAILURE, "sigaction"); 277 278 if (debug) 279 fprintf(stderr, "alarm %g\n", timeout); 280 } 281 282 while (flock(fd, lock) == -1) { 283 if (errno == EINTR && timeout_expired == 0) 284 continue; 285 if (verbose) 286 err(EXIT_FAILURE, "flock(%d, %s)", fd, lock2name(lock)); 287 else 288 return EXIT_FAILURE; 289 } 290 291 if (timeout) 292 timer_delete(tm); 293 294 if (cls) 295 (void)close(fd); 296 297 if (cmdargv != NULL) { 298 execvp(cmdargv[0], cmdargv); 299 err(EXIT_FAILURE, "execvp '%s'", v = cmdline(cmdargv)); 300 free(v); 301 } 302 return 0; 303 } 304