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