xref: /netbsd/usr.bin/flock/flock.c (revision ad6bb801)
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