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