xref: /minix/usr.bin/flock/flock.c (revision 0a6a1f1d)
1 /*	$NetBSD: flock.c,v 1.11 2014/08/18 09:16:35 christos 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.11 2014/08/18 09:16:35 christos 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 #if !defined(__minix)
165 	timer_t tm;
166 #else
167 	struct itimerval it;
168 #endif /* !defined(__minix) */
169 
170 	setprogname(argv[0]);
171 
172 	while ((c = getopt_long(argc, argv, "+dnosuvw:x", flock_longopts, NULL))
173 	    != -1)
174 		switch (c) {
175 		case 'd':
176 			debug++;
177 			break;
178 		case 'x':
179 #define T(l)	(lock & ~LOCK_NB) != (l) && (lock & ~LOCK_NB) != 0
180 			if (T(LOCK_EX))
181 				goto badlock;
182 			lock |= LOCK_EX;
183 			break;
184 		case 'n':
185 			lock |= LOCK_NB;
186 			break;
187 		case 's':
188 			if (T(LOCK_SH))
189 				goto badlock;
190 			lock |= LOCK_SH;
191 			break;
192 		case 'u':
193 			if (T(LOCK_UN))
194 				goto badlock;
195 			lock |= LOCK_UN;
196 			break;
197 		case 'w':
198 			timeout = strtod(optarg, NULL);
199 			break;
200 		case 'v':
201 			verbose = 1;
202 			break;
203 		case 'o':
204 			cls = 1;
205 			break;
206 		default:
207 			usage("Invalid option '%c'", c);
208 		badlock:
209 			usage("-%c can't be used with -%c", c, lockchar(lock));
210 		}
211 
212 	argc -= optind;
213 	argv += optind;
214 
215 	if ((lock & ~LOCK_NB) == 0)
216 		lock |= LOCK_EX;	/* default to exclusive like linux */
217 
218 	switch (argc) {
219 	case 0:
220 		usage("Missing lock file argument");
221 	case 1:
222 		if (cls)
223 			usage("Close is not valid for descriptors");
224 		errno = 0;
225 		l = strtol(argv[0], &v, 0);
226 		if ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE)
227 			err(EXIT_FAILURE, "Bad file descriptor `%s'", argv[0]);
228 		if (l > INT_MAX || l < 0 || *v)
229 			errx(EXIT_FAILURE, "Bad file descriptor `%s'", argv[0]);
230 		fd = (int)l;
231 		if (debug) {
232 			fprintf(stderr, "descriptor %s lock %s\n",
233 			    argv[0], lock2name(lock));
234 		}
235 		break;
236 
237 	default:
238 		if ((lock & LOCK_NB) == LOCK_UN)
239 			usage("Unlock is only valid for descriptors");
240 		if (strcmp(argv[1], "-c") == 0 ||
241 		    strcmp(argv[1], "--command") == 0) {
242 			if (argc == 2)
243 				usage("Missing argument to %s", strcmp(argv[1],
244 				    "-c") == 0 ? "-c" : "--command");
245 			mcargv[2] = argv[2];
246 			cmdargv = mcargv;
247 		} else
248 			cmdargv = argv + 1;
249 
250 		if ((fd = open(argv[0], O_RDONLY)) == -1) {
251 			if (errno != ENOENT ||
252 			    (fd = open(argv[0], O_RDWR|O_CREAT, 0600)) == -1)
253 				err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
254 		}
255 		if (debug) {
256 			fprintf(stderr, "file %s lock %s command %s ...\n",
257 			    argv[0], lock2name(lock), v = cmdline(cmdargv));
258 			free(v);
259 		}
260 		break;
261 	}
262 
263 	if (timeout) {
264 #if !defined(__minix)
265 		struct sigevent ev;
266 		struct itimerspec it;
267 #endif /* !defined(__minix) */
268 		struct sigaction sa;
269 
270 #if !defined(__minix)
271 		timespecclear(&it.it_interval);
272 		it.it_value.tv_sec = timeout;
273 		it.it_value.tv_nsec = (timeout - it.it_value.tv_sec) *
274 			1000000000;
275 
276 		memset(&ev, 0, sizeof(ev));
277 		ev.sigev_notify = SIGEV_SIGNAL;
278 		ev.sigev_signo = SIGALRM;
279 
280 		if (timer_create(CLOCK_REALTIME, &ev, &tm) == -1)
281 			err(EXIT_FAILURE, "timer_create");
282 
283 		if (timer_settime(tm, TIMER_RELTIME, &it, NULL) == -1)
284 			err(EXIT_FAILURE, "timer_settime");
285 #else
286 		memset(&it.it_interval, 0, sizeof(it.it_interval));
287 		it.it_value.tv_sec = timeout;
288 		it.it_value.tv_usec = (timeout - it.it_value.tv_sec) * 1000000;
289 
290 		if (setitimer(ITIMER_REAL, &it, NULL) == -1)
291 			err(EXIT_FAILURE, "setitimer");
292 
293 		memset(&it, 0, sizeof(it)); /* for the reset later */
294 #endif /* !defined(__minix) */
295 
296 		memset(&sa, 0, sizeof(sa));
297 		sa.sa_handler = sigalrm;
298 		sigemptyset(&sa.sa_mask);
299 		sa.sa_flags = 0;
300 		if (sigaction(SIGALRM, &sa, NULL) == -1)
301 			err(EXIT_FAILURE, "sigaction");
302 
303 		if (debug)
304 			fprintf(stderr, "alarm %g\n", timeout);
305 	}
306 
307 	while (flock(fd, lock) == -1) {
308 		if (errno == EINTR && timeout_expired == 0)
309 			continue;
310 		if (verbose)
311 			err(EXIT_FAILURE, "flock(%d, %s)", fd, lock2name(lock));
312 		else
313 			return EXIT_FAILURE;
314 	}
315 
316 	if (timeout)
317 #if !defined(__minix)
318 		timer_delete(tm);
319 #else
320 		setitimer(ITIMER_REAL, &it, NULL);
321 #endif /* !defined(__minix) */
322 
323 	if (cls)
324 		(void)close(fd);
325 
326 	if (cmdargv != NULL) {
327 		execvp(cmdargv[0], cmdargv);
328 		err(EXIT_FAILURE, "execvp '%s'", v = cmdline(cmdargv));
329 		free(v);
330 	}
331 	return 0;
332 }
333