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