]>
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" |
59dc9f28 | 46 | #include "monotonic.h" |
26e8964b | 47 | #include "timer.h" |
db1749cf | 48 | |
e8cea669 | 49 | static void __attribute__((__noreturn__)) usage(int ex) |
baf39af1 | 50 | { |
dc36ee2a | 51 | fprintf(stderr, USAGE_HEADER); |
75332f0f | 52 | fprintf(stderr, |
7a853ebc BS |
53 | _(" %1$s [options] <file>|<directory> <command> [<argument>...]\n" |
54 | " %1$s [options] <file>|<directory> -c <command>\n" | |
295dd902 | 55 | " %1$s [options] <file descriptor number>\n"), |
d4eafec4 | 56 | program_invocation_short_name); |
451dbcfa BS |
57 | |
58 | fputs(USAGE_SEPARATOR, stderr); | |
59 | fputs(_("Manage file locks from shell scripts.\n"), stderr); | |
60 | ||
dc36ee2a | 61 | fputs(USAGE_OPTIONS, stderr); |
7a853ebc BS |
62 | fputs(_( " -s, --shared get a shared lock\n"), stderr); |
63 | fputs(_( " -x, --exclusive get an exclusive lock (default)\n"), stderr); | |
64 | fputs(_( " -u, --unlock remove a lock\n"), stderr); | |
65 | fputs(_( " -n, --nonblock fail rather than wait\n"), stderr); | |
66 | fputs(_( " -w, --timeout <secs> wait for a limited amount of time\n"), stderr); | |
67 | fputs(_( " -E, --conflict-exit-code <number> exit code after conflict or timeout\n"), stderr); | |
68 | fputs(_( " -o, --close close file descriptor before running command\n"), stderr); | |
69 | fputs(_( " -c, --command <command> run a single command string through the shell\n"), stderr); | |
70325140 | 70 | fputs(_( " -F, --no-fork execute command without forking\n"), stderr); |
59dc9f28 | 71 | fputs(_( " --verbose increase verbosity\n"), stderr); |
dc36ee2a SK |
72 | fprintf(stderr, USAGE_SEPARATOR); |
73 | fprintf(stderr, USAGE_HELP); | |
74 | fprintf(stderr, USAGE_VERSION); | |
75 | fprintf(stderr, USAGE_MAN_TAIL("flock(1)")); | |
75332f0f | 76 | exit(ex); |
baf39af1 KZ |
77 | } |
78 | ||
baf39af1 KZ |
79 | static sig_atomic_t timeout_expired = 0; |
80 | ||
378543e1 SK |
81 | static void timeout_handler(int sig __attribute__((__unused__)), |
82 | siginfo_t *info, | |
83 | void *context __attribute__((__unused__))) | |
baf39af1 | 84 | { |
378543e1 SK |
85 | if (info->si_code == SI_TIMER) |
86 | timeout_expired = 1; | |
baf39af1 KZ |
87 | } |
88 | ||
eb742a1f KZ |
89 | static int open_file(const char *filename, int *flags) |
90 | { | |
91 | ||
92 | int fd; | |
93 | int fl = *flags == 0 ? O_RDONLY : *flags; | |
94 | ||
95 | errno = 0; | |
96 | fl |= O_NOCTTY | O_CREAT; | |
97 | fd = open(filename, fl, 0666); | |
98 | ||
99 | /* Linux doesn't like O_CREAT on a directory, even though it | |
100 | * should be a no-op; POSIX doesn't allow O_RDWR or O_WRONLY | |
101 | */ | |
102 | if (fd < 0 && errno == EISDIR) { | |
103 | fl = O_RDONLY | O_NOCTTY; | |
104 | fd = open(filename, fl); | |
105 | } | |
106 | if (fd < 0) { | |
107 | warn(_("cannot open lock file %s"), filename); | |
108 | if (errno == ENOMEM || errno == EMFILE || errno == ENFILE) | |
109 | exit(EX_OSERR); | |
110 | if (errno == EROFS || errno == ENOSPC) | |
111 | exit(EX_CANTCREAT); | |
112 | exit(EX_NOINPUT); | |
113 | } | |
114 | *flags = fl; | |
115 | return fd; | |
116 | } | |
117 | ||
93fea49f | 118 | static void __attribute__((__noreturn__)) run_program(char **cmd_argv) |
391e675c | 119 | { |
391e675c KZ |
120 | execvp(cmd_argv[0], cmd_argv); |
121 | ||
122 | warn(_("failed to execute %s"), cmd_argv[0]); | |
123 | _exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE); | |
124 | } | |
125 | ||
baf39af1 | 126 | int main(int argc, char *argv[]) |
d162fcb5 | 127 | { |
378543e1 SK |
128 | static timer_t t_id; |
129 | struct itimerval timeout; | |
75332f0f SK |
130 | int have_timeout = 0; |
131 | int type = LOCK_EX; | |
132 | int block = 0; | |
eb742a1f | 133 | int open_flags = 0; |
75332f0f SK |
134 | int fd = -1; |
135 | int opt, ix; | |
136 | int do_close = 0; | |
70325140 | 137 | int no_fork = 0; |
75332f0f | 138 | int status; |
59dc9f28 SK |
139 | int verbose = 0; |
140 | struct timeval time_start, time_done; | |
827b1cee JYK |
141 | /* |
142 | * The default exit code for lock conflict or timeout | |
143 | * is specified in man flock.1 | |
144 | */ | |
145 | int conflict_exit_code = 1; | |
75332f0f SK |
146 | char **cmd_argv = NULL, *sh_c_argv[4]; |
147 | const char *filename = NULL; | |
59dc9f28 SK |
148 | enum { |
149 | OPT_VERBOSE = CHAR_MAX + 1 | |
150 | }; | |
d89bfedf SK |
151 | static const struct option long_options[] = { |
152 | {"shared", no_argument, NULL, 's'}, | |
153 | {"exclusive", no_argument, NULL, 'x'}, | |
154 | {"unlock", no_argument, NULL, 'u'}, | |
155 | {"nonblocking", no_argument, NULL, 'n'}, | |
156 | {"nb", no_argument, NULL, 'n'}, | |
157 | {"timeout", required_argument, NULL, 'w'}, | |
158 | {"wait", required_argument, NULL, 'w'}, | |
827b1cee | 159 | {"conflict-exit-code", required_argument, NULL, 'E'}, |
d89bfedf | 160 | {"close", no_argument, NULL, 'o'}, |
70325140 | 161 | {"no-fork", no_argument, NULL, 'F'}, |
59dc9f28 | 162 | {"verbose", no_argument, NULL, OPT_VERBOSE}, |
d89bfedf SK |
163 | {"help", no_argument, NULL, 'h'}, |
164 | {"version", no_argument, NULL, 'V'}, | |
165 | {NULL, 0, NULL, 0} | |
166 | }; | |
167 | ||
75332f0f SK |
168 | setlocale(LC_ALL, ""); |
169 | bindtextdomain(PACKAGE, LOCALEDIR); | |
170 | textdomain(PACKAGE); | |
efb8854f | 171 | atexit(close_stdout); |
75332f0f | 172 | |
75332f0f SK |
173 | if (argc < 2) |
174 | usage(EX_USAGE); | |
175 | ||
176 | memset(&timeout, 0, sizeof timeout); | |
177 | ||
178 | optopt = 0; | |
179 | while ((opt = | |
70325140 | 180 | getopt_long(argc, argv, "+sexnoFuw:E:hV?", long_options, |
75332f0f SK |
181 | &ix)) != EOF) { |
182 | switch (opt) { | |
183 | case 's': | |
184 | type = LOCK_SH; | |
185 | break; | |
186 | case 'e': | |
187 | case 'x': | |
188 | type = LOCK_EX; | |
189 | break; | |
190 | case 'u': | |
191 | type = LOCK_UN; | |
192 | break; | |
193 | case 'o': | |
194 | do_close = 1; | |
195 | break; | |
70325140 TB |
196 | case 'F': |
197 | no_fork = 1; | |
198 | break; | |
75332f0f SK |
199 | case 'n': |
200 | block = LOCK_NB; | |
201 | break; | |
202 | case 'w': | |
203 | have_timeout = 1; | |
880c4045 KZ |
204 | strtotimeval_or_err(optarg, &timeout.it_value, |
205 | _("invalid timeout value")); | |
75332f0f | 206 | break; |
827b1cee JYK |
207 | case 'E': |
208 | conflict_exit_code = strtos32_or_err(optarg, | |
209 | _("invalid exit code")); | |
210 | break; | |
59dc9f28 SK |
211 | case OPT_VERBOSE: |
212 | verbose = 1; | |
213 | break; | |
75332f0f | 214 | case 'V': |
0c6625a1 | 215 | printf(UTIL_LINUX_VERSION); |
56d45cfa | 216 | exit(EX_OK); |
677ec86c KZ |
217 | case 'h': |
218 | usage(0); | |
75332f0f | 219 | default: |
677ec86c | 220 | errtryhelp(EX_USAGE); |
75332f0f SK |
221 | } |
222 | } | |
223 | ||
70325140 TB |
224 | if (no_fork && do_close) |
225 | errx(EX_USAGE, | |
226 | _("the --no-fork and --close options are incompatible")); | |
227 | ||
75332f0f SK |
228 | if (argc > optind + 1) { |
229 | /* Run command */ | |
230 | if (!strcmp(argv[optind + 1], "-c") || | |
231 | !strcmp(argv[optind + 1], "--command")) { | |
d4eafec4 SK |
232 | if (argc != optind + 3) |
233 | errx(EX_USAGE, | |
234 | _("%s requires exactly one command argument"), | |
235 | argv[optind + 1]); | |
75332f0f SK |
236 | cmd_argv = sh_c_argv; |
237 | cmd_argv[0] = getenv("SHELL"); | |
238 | if (!cmd_argv[0] || !*cmd_argv[0]) | |
239 | cmd_argv[0] = _PATH_BSHELL; | |
240 | cmd_argv[1] = "-c"; | |
241 | cmd_argv[2] = argv[optind + 2]; | |
87918040 | 242 | cmd_argv[3] = NULL; |
75332f0f SK |
243 | } else { |
244 | cmd_argv = &argv[optind + 1]; | |
245 | } | |
246 | ||
247 | filename = argv[optind]; | |
eb742a1f KZ |
248 | fd = open_file(filename, &open_flags); |
249 | ||
75332f0f SK |
250 | } else if (optind < argc) { |
251 | /* Use provided file descriptor */ | |
c5c056e7 | 252 | fd = strtos32_or_err(argv[optind], _("bad file descriptor")); |
75332f0f SK |
253 | } else { |
254 | /* Bad options */ | |
d4eafec4 | 255 | errx(EX_USAGE, _("requires file descriptor, file or directory")); |
75332f0f SK |
256 | } |
257 | ||
258 | if (have_timeout) { | |
259 | if (timeout.it_value.tv_sec == 0 && | |
260 | timeout.it_value.tv_usec == 0) { | |
261 | /* -w 0 is equivalent to -n; this has to be | |
262 | * special-cased because setting an itimer to zero | |
263 | * means disabled! | |
264 | */ | |
265 | have_timeout = 0; | |
266 | block = LOCK_NB; | |
bc3ae4c6 | 267 | } else |
378543e1 | 268 | if (setup_timer(&t_id, &timeout, &timeout_handler)) |
62eea9ce | 269 | err(EX_OSERR, _("cannot set up timer")); |
75332f0f SK |
270 | } |
271 | ||
59dc9f28 SK |
272 | if (verbose) |
273 | gettime_monotonic(&time_start); | |
75332f0f | 274 | while (flock(fd, type | block)) { |
d4eafec4 | 275 | switch (errno) { |
75332f0f | 276 | case EWOULDBLOCK: |
827b1cee | 277 | /* -n option set and failed to lock. */ |
59dc9f28 SK |
278 | if (verbose) |
279 | warnx(_("failed to get lock")); | |
827b1cee | 280 | exit(conflict_exit_code); |
75332f0f SK |
281 | case EINTR: |
282 | /* Signal received */ | |
59dc9f28 | 283 | if (timeout_expired) { |
827b1cee | 284 | /* -w option set and failed to lock. */ |
59dc9f28 SK |
285 | if (verbose) |
286 | warnx(_("timeout while waiting to get lock")); | |
827b1cee | 287 | exit(conflict_exit_code); |
59dc9f28 | 288 | } |
75332f0f SK |
289 | /* otherwise try again */ |
290 | continue; | |
eb742a1f | 291 | case EIO: |
caf1ba11 | 292 | case EBADF: /* since Linux 3.4 (commit 55725513) */ |
eb742a1f KZ |
293 | /* Probably NFSv4 where flock() is emulated by fcntl(). |
294 | * Let's try to reopen in read-write mode. | |
295 | */ | |
296 | if (!(open_flags & O_RDWR) && | |
297 | type != LOCK_SH && | |
3c7fbc82 | 298 | filename && |
eb742a1f KZ |
299 | access(filename, R_OK | W_OK) == 0) { |
300 | ||
301 | close(fd); | |
302 | open_flags = O_RDWR; | |
303 | fd = open_file(filename, &open_flags); | |
304 | ||
305 | if (open_flags & O_RDWR) | |
306 | break; | |
307 | } | |
308 | /* go through */ | |
75332f0f SK |
309 | default: |
310 | /* Other errors */ | |
311 | if (filename) | |
d4eafec4 | 312 | warn("%s", filename); |
75332f0f | 313 | else |
d4eafec4 SK |
314 | warn("%d", fd); |
315 | exit((errno == ENOLCK | |
316 | || errno == ENOMEM) ? EX_OSERR : EX_DATAERR); | |
75332f0f SK |
317 | } |
318 | } | |
319 | ||
bc3ae4c6 | 320 | if (have_timeout) |
378543e1 | 321 | cancel_timer(&t_id); |
59dc9f28 SK |
322 | if (verbose) { |
323 | struct timeval delta; | |
75332f0f | 324 | |
59dc9f28 SK |
325 | gettime_monotonic(&time_done); |
326 | timersub(&time_done, &time_start, &delta); | |
327 | printf(_("%s: getting lock took %ld.%06ld seconds\n"), | |
328 | program_invocation_short_name, delta.tv_sec, | |
329 | delta.tv_usec); | |
330 | } | |
56d45cfa | 331 | status = EX_OK; |
75332f0f SK |
332 | |
333 | if (cmd_argv) { | |
334 | pid_t w, f; | |
335 | /* Clear any inherited settings */ | |
336 | signal(SIGCHLD, SIG_DFL); | |
59dc9f28 | 337 | if (verbose) |
b5575bc0 | 338 | printf(_("%s: executing %s\n"), program_invocation_short_name, cmd_argv[0]); |
75332f0f | 339 | |
70325140 TB |
340 | if (!no_fork) { |
341 | f = fork(); | |
391e675c | 342 | if (f < 0) |
70325140 | 343 | err(EX_OSERR, _("fork failed")); |
391e675c KZ |
344 | |
345 | /* child */ | |
93fea49f KZ |
346 | else if (f == 0) { |
347 | if (do_close) | |
348 | close(fd); | |
349 | run_program(cmd_argv); | |
391e675c KZ |
350 | |
351 | /* parent */ | |
93fea49f | 352 | } else { |
391e675c KZ |
353 | do { |
354 | w = waitpid(f, &status, 0); | |
355 | if (w == -1 && errno != EINTR) | |
70325140 | 356 | break; |
391e675c | 357 | } while (w != f); |
75332f0f | 358 | |
391e675c | 359 | if (w == -1) { |
70325140 TB |
360 | status = EXIT_FAILURE; |
361 | warn(_("waitpid failed")); | |
362 | } else if (WIFEXITED(status)) | |
363 | status = WEXITSTATUS(status); | |
364 | else if (WIFSIGNALED(status)) | |
365 | status = WTERMSIG(status) + 128; | |
366 | else | |
367 | /* WTF? */ | |
368 | status = EX_OSERR; | |
70325140 | 369 | } |
391e675c KZ |
370 | |
371 | } else | |
372 | /* no-fork execution */ | |
93fea49f | 373 | run_program(cmd_argv); |
75332f0f SK |
374 | } |
375 | ||
376 | return status; | |
d162fcb5 | 377 | } |