]>
Commit | Line | Data |
---|---|---|
75332f0f | 1 | /* Copyright 2003-2005 H. Peter Anvin - All Rights Reserved |
baf39af1 KZ |
2 | * |
3 | * Permission is hereby granted, free of charge, to any person | |
4 | * obtaining a copy of this software and associated documentation | |
5 | * files (the "Software"), to deal in the Software without | |
6 | * restriction, including without limitation the rights to use, | |
7 | * copy, modify, merge, publish, distribute, sublicense, and/or | |
8 | * sell copies of the Software, and to permit persons to whom | |
9 | * the Software is furnished to do so, subject to the following | |
10 | * conditions: | |
db1749cf | 11 | * |
baf39af1 KZ |
12 | * The above copyright notice and this permission notice shall |
13 | * be included in all copies or substantial portions of the Software. | |
db1749cf | 14 | * |
baf39af1 KZ |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
17 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
19 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
20 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
22 | * OTHER DEALINGS IN THE SOFTWARE. | |
75332f0f | 23 | */ |
baf39af1 | 24 | |
75332f0f | 25 | #include <ctype.h> |
baf39af1 | 26 | #include <errno.h> |
75332f0f | 27 | #include <fcntl.h> |
baf39af1 | 28 | #include <getopt.h> |
75332f0f | 29 | #include <paths.h> |
baf39af1 | 30 | #include <signal.h> |
75332f0f SK |
31 | #include <stdio.h> |
32 | #include <stdlib.h> | |
baf39af1 | 33 | #include <string.h> |
baf39af1 | 34 | #include <sysexits.h> |
baf39af1 | 35 | #include <sys/file.h> |
75332f0f | 36 | #include <sys/stat.h> |
baf39af1 | 37 | #include <sys/time.h> |
75332f0f | 38 | #include <sys/types.h> |
d162fcb5 | 39 | #include <sys/wait.h> |
75332f0f | 40 | #include <unistd.h> |
d162fcb5 | 41 | |
d4eafec4 | 42 | #include "c.h" |
db1749cf | 43 | #include "nls.h" |
37bb0ce8 | 44 | #include "strutils.h" |
efb8854f | 45 | #include "closestream.h" |
db1749cf | 46 | |
e8cea669 | 47 | static void __attribute__((__noreturn__)) usage(int ex) |
baf39af1 | 48 | { |
dc36ee2a | 49 | fprintf(stderr, USAGE_HEADER); |
75332f0f | 50 | fprintf(stderr, |
dc36ee2a SK |
51 | _(" %1$s [options] <file descriptor number>\n" |
52 | " %1$s [options] <file> -c <command>\n" | |
53 | " %1$s [options} <directory> -c <command>\n"), | |
d4eafec4 | 54 | program_invocation_short_name); |
dc36ee2a SK |
55 | fputs(USAGE_OPTIONS, stderr); |
56 | fputs(_( " -s --shared get a shared lock\n"), stderr); | |
57 | fputs(_( " -x --exclusive get an exclusive lock (default)\n"), stderr); | |
58 | fputs(_( " -u --unlock remove a lock\n"), stderr); | |
59 | fputs(_( " -n --nonblock fail rather than wait\n"), stderr); | |
60 | fputs(_( " -w --timeout <secs> wait for a limited amount of time\n"), stderr); | |
61 | fputs(_( " -o --close close file descriptor before running command\n"), stderr); | |
62 | fputs(_( " -c --command <command> run a single command string through the shell\n"), stderr); | |
63 | fprintf(stderr, USAGE_SEPARATOR); | |
64 | fprintf(stderr, USAGE_HELP); | |
65 | fprintf(stderr, USAGE_VERSION); | |
66 | fprintf(stderr, USAGE_MAN_TAIL("flock(1)")); | |
75332f0f | 67 | exit(ex); |
baf39af1 KZ |
68 | } |
69 | ||
baf39af1 KZ |
70 | static sig_atomic_t timeout_expired = 0; |
71 | ||
e8cea669 | 72 | static void timeout_handler(int sig __attribute__((__unused__))) |
baf39af1 | 73 | { |
75332f0f | 74 | timeout_expired = 1; |
baf39af1 KZ |
75 | } |
76 | ||
605325b2 | 77 | static void strtotimeval(const char *str, struct timeval *tv) |
baf39af1 | 78 | { |
605325b2 | 79 | double user_input; |
75332f0f | 80 | |
605325b2 SK |
81 | user_input = strtod_or_err(str, "bad number"); |
82 | tv->tv_sec = (time_t) user_input; | |
83 | tv->tv_usec = (long)((user_input - tv->tv_sec) * 1000000); | |
84 | if ((tv->tv_sec + tv->tv_usec) == 0) | |
85 | errx(EX_USAGE, _("timeout cannot be zero")); | |
baf39af1 KZ |
86 | } |
87 | ||
bc3ae4c6 KZ |
88 | static void setup_timer(struct itimerval *timer, struct itimerval *old_timer, |
89 | struct sigaction *sa, struct sigaction *old_sa) | |
90 | { | |
1bbf8203 | 91 | memset(sa, 0, sizeof *sa); |
bc3ae4c6 KZ |
92 | sa->sa_handler = timeout_handler; |
93 | sa->sa_flags = SA_RESETHAND; | |
94 | sigaction(SIGALRM, sa, old_sa); | |
95 | setitimer(ITIMER_REAL, timer, old_timer); | |
96 | } | |
97 | ||
98 | static void cancel_timer(struct itimerval *old_timer, struct sigaction *old_sa) | |
99 | { | |
100 | setitimer(ITIMER_REAL, old_timer, NULL); | |
101 | sigaction(SIGALRM, old_sa, NULL); | |
102 | } | |
103 | ||
eb742a1f KZ |
104 | static int open_file(const char *filename, int *flags) |
105 | { | |
106 | ||
107 | int fd; | |
108 | int fl = *flags == 0 ? O_RDONLY : *flags; | |
109 | ||
110 | errno = 0; | |
111 | fl |= O_NOCTTY | O_CREAT; | |
112 | fd = open(filename, fl, 0666); | |
113 | ||
114 | /* Linux doesn't like O_CREAT on a directory, even though it | |
115 | * should be a no-op; POSIX doesn't allow O_RDWR or O_WRONLY | |
116 | */ | |
117 | if (fd < 0 && errno == EISDIR) { | |
118 | fl = O_RDONLY | O_NOCTTY; | |
119 | fd = open(filename, fl); | |
120 | } | |
121 | if (fd < 0) { | |
122 | warn(_("cannot open lock file %s"), filename); | |
123 | if (errno == ENOMEM || errno == EMFILE || errno == ENFILE) | |
124 | exit(EX_OSERR); | |
125 | if (errno == EROFS || errno == ENOSPC) | |
126 | exit(EX_CANTCREAT); | |
127 | exit(EX_NOINPUT); | |
128 | } | |
129 | *flags = fl; | |
130 | return fd; | |
131 | } | |
132 | ||
baf39af1 | 133 | int main(int argc, char *argv[]) |
d162fcb5 | 134 | { |
75332f0f SK |
135 | struct itimerval timeout, old_timer; |
136 | int have_timeout = 0; | |
137 | int type = LOCK_EX; | |
138 | int block = 0; | |
eb742a1f | 139 | int open_flags = 0; |
75332f0f SK |
140 | int fd = -1; |
141 | int opt, ix; | |
142 | int do_close = 0; | |
75332f0f | 143 | int status; |
75332f0f SK |
144 | char **cmd_argv = NULL, *sh_c_argv[4]; |
145 | const char *filename = NULL; | |
146 | struct sigaction sa, old_sa; | |
147 | ||
d89bfedf SK |
148 | static const struct option long_options[] = { |
149 | {"shared", no_argument, NULL, 's'}, | |
150 | {"exclusive", no_argument, NULL, 'x'}, | |
151 | {"unlock", no_argument, NULL, 'u'}, | |
152 | {"nonblocking", no_argument, NULL, 'n'}, | |
153 | {"nb", no_argument, NULL, 'n'}, | |
154 | {"timeout", required_argument, NULL, 'w'}, | |
155 | {"wait", required_argument, NULL, 'w'}, | |
156 | {"close", no_argument, NULL, 'o'}, | |
157 | {"help", no_argument, NULL, 'h'}, | |
158 | {"version", no_argument, NULL, 'V'}, | |
159 | {NULL, 0, NULL, 0} | |
160 | }; | |
161 | ||
75332f0f SK |
162 | setlocale(LC_ALL, ""); |
163 | bindtextdomain(PACKAGE, LOCALEDIR); | |
164 | textdomain(PACKAGE); | |
efb8854f | 165 | atexit(close_stdout); |
75332f0f | 166 | |
75332f0f SK |
167 | if (argc < 2) |
168 | usage(EX_USAGE); | |
169 | ||
170 | memset(&timeout, 0, sizeof timeout); | |
171 | ||
172 | optopt = 0; | |
173 | while ((opt = | |
174 | getopt_long(argc, argv, "+sexnouw:hV?", long_options, | |
175 | &ix)) != EOF) { | |
176 | switch (opt) { | |
177 | case 's': | |
178 | type = LOCK_SH; | |
179 | break; | |
180 | case 'e': | |
181 | case 'x': | |
182 | type = LOCK_EX; | |
183 | break; | |
184 | case 'u': | |
185 | type = LOCK_UN; | |
186 | break; | |
187 | case 'o': | |
188 | do_close = 1; | |
189 | break; | |
190 | case 'n': | |
191 | block = LOCK_NB; | |
192 | break; | |
193 | case 'w': | |
194 | have_timeout = 1; | |
605325b2 | 195 | strtotimeval(optarg, &timeout.it_value); |
75332f0f SK |
196 | break; |
197 | case 'V': | |
198 | printf("flock (%s)\n", PACKAGE_STRING); | |
56d45cfa | 199 | exit(EX_OK); |
75332f0f SK |
200 | default: |
201 | /* optopt will be set if this was an unrecognized | |
202 | * option, i.e. *not* 'h' or '? | |
203 | */ | |
204 | usage(optopt ? EX_USAGE : 0); | |
205 | break; | |
206 | } | |
207 | } | |
208 | ||
209 | if (argc > optind + 1) { | |
210 | /* Run command */ | |
211 | if (!strcmp(argv[optind + 1], "-c") || | |
212 | !strcmp(argv[optind + 1], "--command")) { | |
d4eafec4 SK |
213 | if (argc != optind + 3) |
214 | errx(EX_USAGE, | |
215 | _("%s requires exactly one command argument"), | |
216 | argv[optind + 1]); | |
75332f0f SK |
217 | cmd_argv = sh_c_argv; |
218 | cmd_argv[0] = getenv("SHELL"); | |
219 | if (!cmd_argv[0] || !*cmd_argv[0]) | |
220 | cmd_argv[0] = _PATH_BSHELL; | |
221 | cmd_argv[1] = "-c"; | |
222 | cmd_argv[2] = argv[optind + 2]; | |
223 | cmd_argv[3] = 0; | |
224 | } else { | |
225 | cmd_argv = &argv[optind + 1]; | |
226 | } | |
227 | ||
228 | filename = argv[optind]; | |
eb742a1f KZ |
229 | fd = open_file(filename, &open_flags); |
230 | ||
75332f0f SK |
231 | } else if (optind < argc) { |
232 | /* Use provided file descriptor */ | |
37bb0ce8 | 233 | fd = (int)strtol_or_err(argv[optind], "bad number"); |
75332f0f SK |
234 | } else { |
235 | /* Bad options */ | |
d4eafec4 | 236 | errx(EX_USAGE, _("requires file descriptor, file or directory")); |
75332f0f SK |
237 | } |
238 | ||
239 | if (have_timeout) { | |
240 | if (timeout.it_value.tv_sec == 0 && | |
241 | timeout.it_value.tv_usec == 0) { | |
242 | /* -w 0 is equivalent to -n; this has to be | |
243 | * special-cased because setting an itimer to zero | |
244 | * means disabled! | |
245 | */ | |
246 | have_timeout = 0; | |
247 | block = LOCK_NB; | |
bc3ae4c6 KZ |
248 | } else |
249 | setup_timer(&timeout, &old_timer, &sa, &old_sa); | |
75332f0f SK |
250 | } |
251 | ||
252 | while (flock(fd, type | block)) { | |
d4eafec4 | 253 | switch (errno) { |
75332f0f | 254 | case EWOULDBLOCK: |
56d45cfa SK |
255 | /* -n option set and failed to lock. The numeric |
256 | * exit value is specified in man flock.1 | |
257 | */ | |
75332f0f SK |
258 | exit(1); |
259 | case EINTR: | |
260 | /* Signal received */ | |
261 | if (timeout_expired) | |
56d45cfa SK |
262 | /* -w option set and failed to lock. The |
263 | * numeric exit value is specified in man | |
264 | * flock.1 | |
265 | */ | |
75332f0f SK |
266 | exit(1); |
267 | /* otherwise try again */ | |
268 | continue; | |
eb742a1f KZ |
269 | case EIO: |
270 | /* Probably NFSv4 where flock() is emulated by fcntl(). | |
271 | * Let's try to reopen in read-write mode. | |
272 | */ | |
273 | if (!(open_flags & O_RDWR) && | |
274 | type != LOCK_SH && | |
275 | access(filename, R_OK | W_OK) == 0) { | |
276 | ||
277 | close(fd); | |
278 | open_flags = O_RDWR; | |
279 | fd = open_file(filename, &open_flags); | |
280 | ||
281 | if (open_flags & O_RDWR) | |
282 | break; | |
283 | } | |
284 | /* go through */ | |
75332f0f SK |
285 | default: |
286 | /* Other errors */ | |
287 | if (filename) | |
d4eafec4 | 288 | warn("%s", filename); |
75332f0f | 289 | else |
d4eafec4 SK |
290 | warn("%d", fd); |
291 | exit((errno == ENOLCK | |
292 | || errno == ENOMEM) ? EX_OSERR : EX_DATAERR); | |
75332f0f SK |
293 | } |
294 | } | |
295 | ||
bc3ae4c6 KZ |
296 | if (have_timeout) |
297 | cancel_timer(&old_timer, &old_sa); | |
75332f0f | 298 | |
56d45cfa | 299 | status = EX_OK; |
75332f0f SK |
300 | |
301 | if (cmd_argv) { | |
302 | pid_t w, f; | |
303 | /* Clear any inherited settings */ | |
304 | signal(SIGCHLD, SIG_DFL); | |
305 | f = fork(); | |
306 | ||
307 | if (f < 0) { | |
d4eafec4 | 308 | err(EX_OSERR, _("fork failed")); |
75332f0f SK |
309 | } else if (f == 0) { |
310 | if (do_close) | |
311 | close(fd); | |
312 | execvp(cmd_argv[0], cmd_argv); | |
75332f0f | 313 | /* execvp() failed */ |
d4eafec4 SK |
314 | warn("%s", cmd_argv[0]); |
315 | _exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE); | |
75332f0f SK |
316 | } else { |
317 | do { | |
318 | w = waitpid(f, &status, 0); | |
319 | if (w == -1 && errno != EINTR) | |
320 | break; | |
321 | } while (w != f); | |
322 | ||
323 | if (w == -1) { | |
75332f0f | 324 | status = EXIT_FAILURE; |
d4eafec4 | 325 | warn(_("waitpid failed")); |
75332f0f SK |
326 | } else if (WIFEXITED(status)) |
327 | status = WEXITSTATUS(status); | |
328 | else if (WIFSIGNALED(status)) | |
329 | status = WTERMSIG(status) + 128; | |
330 | else | |
331 | /* WTF? */ | |
332 | status = EX_OSERR; | |
333 | } | |
334 | } | |
335 | ||
336 | return status; | |
d162fcb5 | 337 | } |