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
49 static 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
65 static sig_atomic_t timeout_expired;
66
67 static __dead __printflike(1, 2) void
usage(const char * fmt,...)68 usage(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
85 static void
sigalrm(int sig)86 sigalrm(int sig)
87 {
88 timeout_expired++;
89 }
90
91 static const char *
lock2name(int l)92 lock2name(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
119 static char
lockchar(int l)120 lockchar(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
134 static char *
cmdline(char ** av)135 cmdline(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
149 int
main(int argc,char * argv[])150 main(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