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