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