flock.c revision 1.7
1/*	$NetBSD: flock.c,v 1.7 2013/02/07 13:57:40 tron 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.7 2013/02/07 13:57:40 tron 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
48static 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
64static sig_atomic_t timeout_expired;
65
66static __dead void
67usage(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
84static void
85sigalrm(int sig)
86{
87	timeout_expired++;
88}
89
90static const char *
91lock2name(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
118static char
119lockchar(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
133static char *
134cmdline(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
148int
149main(int argc, char *argv[])
150{
151	int c;
152	int lock = LOCK_EX;
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			if (lock & ~LOCK_NB)
174				goto badlock;
175			lock |= LOCK_EX;
176			break;
177		case 'n':
178			lock |= LOCK_NB;
179			break;
180		case 's':
181			if (lock & ~LOCK_NB)
182				goto badlock;
183			lock |= LOCK_SH;
184			break;
185		case 'u':
186			if (lock & ~LOCK_NB)
187				goto badlock;
188			lock |= LOCK_UN;
189			break;
190		case 'w':
191			timeout = strtod(optarg, NULL);
192			break;
193		case 'v':
194			verbose = 1;
195			break;
196		case 'o':
197			cls = 1;
198			break;
199		default:
200			usage("Invalid option '%c'", c);
201		badlock:
202			usage("-%c can't be used with -%c", c, lockchar(lock));
203		}
204
205	argc -= optind;
206	argv += optind;
207
208	switch (argc) {
209	case 0:
210		usage("Missing lock file argument");
211	case 1:
212		if (cls)
213			usage("Close is valid only for descriptors");
214		fd = strtol(argv[0], NULL, 0);	// XXX: error checking
215		if (debug) {
216			fprintf(stderr, "descriptor %s lock %s\n",
217			    argv[0], lock2name(lock));
218		}
219		break;
220
221	default:
222		if ((lock & LOCK_NB) == LOCK_UN)
223			usage("Unlock is only valid for descriptors");
224		if (strcmp(argv[1], "-c") == 0 ||
225		    strcmp(argv[1], "--command") == 0) {
226			if (argc == 2)
227				usage("Missing argument to %s", strcmp(argv[1],
228				    "-c") == 0 ? "-c" : "--command");
229			mcargv[2] = argv[2];
230			cmdargv = mcargv;
231		} else
232			cmdargv = argv + 1;
233
234		if ((fd = open(argv[0], O_RDONLY)) == -1) {
235			if (errno != ENOENT ||
236			    (fd = open(argv[0], O_RDWR|O_CREAT, 0600)) == -1)
237				err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
238		}
239		if (debug) {
240			fprintf(stderr, "file %s lock %s command %s ...\n",
241			    argv[0], lock2name(lock), v = cmdline(cmdargv));
242			free(v);
243		}
244		break;
245	}
246
247	if (timeout) {
248		struct sigevent ev;
249		struct itimerspec it;
250		struct sigaction sa;
251
252		timespecclear(&it.it_interval);
253		it.it_value.tv_sec = timeout;
254		it.it_value.tv_nsec = (timeout - it.it_value.tv_sec) *
255			1000000000;
256
257		memset(&ev, 0, sizeof(ev));
258		ev.sigev_notify = SIGEV_SIGNAL;
259		ev.sigev_signo = SIGALRM;
260
261		if (timer_create(CLOCK_REALTIME, &ev, &tm) == -1)
262			err(EXIT_FAILURE, "timer_create");
263
264		if (timer_settime(tm, TIMER_RELTIME, &it, NULL) == -1)
265			err(EXIT_FAILURE, "timer_settime");
266
267		memset(&sa, 0, sizeof(sa));
268		sa.sa_handler = sigalrm;
269		sigemptyset(&sa.sa_mask);
270		sa.sa_flags = 0;
271		if (sigaction(SIGALRM, &sa, NULL) == -1)
272			err(EXIT_FAILURE, "sigaction");
273
274		if (debug)
275			fprintf(stderr, "alarm %g\n", timeout);
276	}
277
278	while (flock(fd, lock) == -1) {
279		if (errno == EINTR && timeout_expired == 0)
280			continue;
281		if (verbose)
282			err(EXIT_FAILURE, "flock(%d, %s)", fd, lock2name(lock));
283		else
284			return EXIT_FAILURE;
285	}
286
287	if (timeout)
288		timer_delete(tm);
289
290	if (cls)
291		(void)close(fd);
292
293	if (cmdargv != NULL) {
294		execvp(cmdargv[0], cmdargv);
295		err(EXIT_FAILURE, "execvp '%s'", v = cmdline(cmdargv));
296		free(v);
297	}
298	return 0;
299}
300