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