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 #ifndef __minix 163 timer_t tm; 164 #else /* __minix */ 165 struct itimerval it; 166 #endif /* __minix */ 167 168 setprogname(argv[0]); 169 170 while ((c = getopt_long(argc, argv, "+dnosuvw:x", flock_longopts, NULL)) 171 != -1) 172 switch (c) { 173 case 'd': 174 debug++; 175 break; 176 case 'x': 177 #define T(l) (lock & ~LOCK_NB) != (l) && (lock & ~LOCK_NB) != 0 178 if (T(LOCK_EX)) 179 goto badlock; 180 lock |= LOCK_EX; 181 break; 182 case 'n': 183 lock |= LOCK_NB; 184 break; 185 case 's': 186 if (T(LOCK_SH)) 187 goto badlock; 188 lock |= LOCK_SH; 189 break; 190 case 'u': 191 if (T(LOCK_UN)) 192 goto badlock; 193 lock |= LOCK_UN; 194 break; 195 case 'w': 196 timeout = strtod(optarg, NULL); 197 break; 198 case 'v': 199 verbose = 1; 200 break; 201 case 'o': 202 cls = 1; 203 break; 204 default: 205 usage("Invalid option '%c'", c); 206 badlock: 207 usage("-%c can't be used with -%c", c, lockchar(lock)); 208 } 209 210 argc -= optind; 211 argv += optind; 212 213 if ((lock & ~LOCK_NB) == 0) 214 usage("Missing lock type flag"); 215 216 switch (argc) { 217 case 0: 218 usage("Missing lock file argument"); 219 case 1: 220 if (cls) 221 usage("Close is valid only for descriptors"); 222 fd = strtol(argv[0], NULL, 0); // XXX: error checking 223 if (debug) { 224 fprintf(stderr, "descriptor %s lock %s\n", 225 argv[0], lock2name(lock)); 226 } 227 break; 228 229 default: 230 if ((lock & LOCK_NB) == LOCK_UN) 231 usage("Unlock is only valid for descriptors"); 232 if (strcmp(argv[1], "-c") == 0 || 233 strcmp(argv[1], "--command") == 0) { 234 if (argc == 2) 235 usage("Missing argument to %s", strcmp(argv[1], 236 "-c") == 0 ? "-c" : "--command"); 237 mcargv[2] = argv[2]; 238 cmdargv = mcargv; 239 } else 240 cmdargv = argv + 1; 241 242 if ((fd = open(argv[0], O_RDONLY)) == -1) { 243 if (errno != ENOENT || 244 (fd = open(argv[0], O_RDWR|O_CREAT, 0600)) == -1) 245 err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); 246 } 247 if (debug) { 248 fprintf(stderr, "file %s lock %s command %s ...\n", 249 argv[0], lock2name(lock), v = cmdline(cmdargv)); 250 free(v); 251 } 252 break; 253 } 254 255 if (timeout) { 256 #ifndef __minix 257 struct sigevent ev; 258 struct itimerspec it; 259 #endif /* !__minix */ 260 struct sigaction sa; 261 262 #ifndef __minix 263 timespecclear(&it.it_interval); 264 it.it_value.tv_sec = timeout; 265 it.it_value.tv_nsec = (timeout - it.it_value.tv_sec) * 266 1000000000; 267 268 memset(&ev, 0, sizeof(ev)); 269 ev.sigev_notify = SIGEV_SIGNAL; 270 ev.sigev_signo = SIGALRM; 271 272 if (timer_create(CLOCK_REALTIME, &ev, &tm) == -1) 273 err(EXIT_FAILURE, "timer_create"); 274 275 if (timer_settime(tm, TIMER_RELTIME, &it, NULL) == -1) 276 err(EXIT_FAILURE, "timer_settime"); 277 #else /* __minix */ 278 memset(&it.it_interval, 0, sizeof(it.it_interval)); 279 it.it_value.tv_sec = timeout; 280 it.it_value.tv_usec = (timeout - it.it_value.tv_sec) * 1000000; 281 282 if (setitimer(ITIMER_REAL, &it, NULL) == -1) 283 err(EXIT_FAILURE, "setitimer"); 284 285 memset(&it, 0, sizeof(it)); /* for the reset later */ 286 #endif /* __minix */ 287 288 memset(&sa, 0, sizeof(sa)); 289 sa.sa_handler = sigalrm; 290 sigemptyset(&sa.sa_mask); 291 sa.sa_flags = 0; 292 if (sigaction(SIGALRM, &sa, NULL) == -1) 293 err(EXIT_FAILURE, "sigaction"); 294 295 if (debug) 296 fprintf(stderr, "alarm %g\n", timeout); 297 } 298 299 while (flock(fd, lock) == -1) { 300 if (errno == EINTR && timeout_expired == 0) 301 continue; 302 if (verbose) 303 err(EXIT_FAILURE, "flock(%d, %s)", fd, lock2name(lock)); 304 else 305 return EXIT_FAILURE; 306 } 307 308 if (timeout) 309 #ifndef __minix 310 timer_delete(tm); 311 #else /* __minix */ 312 setitimer(ITIMER_REAL, &it, NULL); 313 #endif /* __minix */ 314 315 if (cls) 316 (void)close(fd); 317 318 if (cmdargv != NULL) { 319 execvp(cmdargv[0], cmdargv); 320 err(EXIT_FAILURE, "execvp '%s'", v = cmdline(cmdargv)); 321 free(v); 322 } 323 return 0; 324 } 325