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