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