]>
Commit | Line | Data |
---|---|---|
8b12a516 LP |
1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
2 | ||
3 | #include <getopt.h> | |
4 | #include <stdlib.h> | |
5 | #include <sys/file.h> | |
6 | #include <unistd.h> | |
7 | ||
8 | #include "blockdev-util.h" | |
9 | #include "btrfs-util.h" | |
ca822829 | 10 | #include "device-util.h" |
8b12a516 LP |
11 | #include "fd-util.h" |
12 | #include "fdset.h" | |
13 | #include "main-func.h" | |
14 | #include "parse-util.h" | |
15 | #include "path-util.h" | |
16 | #include "pretty-print.h" | |
17 | #include "process-util.h" | |
18 | #include "signal-util.h" | |
19 | #include "sort-util.h" | |
8b12a516 LP |
20 | #include "strv.h" |
21 | #include "time-util.h" | |
22 | #include "udevadm.h" | |
23 | ||
24 | static usec_t arg_timeout_usec = USEC_INFINITY; | |
25 | static char **arg_devices = NULL; | |
26 | static char **arg_backing = NULL; | |
27 | static char **arg_cmdline = NULL; | |
28 | static bool arg_print = false; | |
29 | ||
30 | STATIC_DESTRUCTOR_REGISTER(arg_devices, strv_freep); | |
31 | STATIC_DESTRUCTOR_REGISTER(arg_backing, strv_freep); | |
32 | STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep); | |
33 | ||
34 | static int help(void) { | |
35 | _cleanup_free_ char *link = NULL; | |
36 | int r; | |
37 | ||
38 | r = terminal_urlify_man("udevadm", "8", &link); | |
39 | if (r < 0) | |
40 | return log_oom(); | |
41 | ||
42 | printf("%s [OPTIONS...] COMMAND\n" | |
43 | "%s [OPTIONS...] --print\n" | |
e7e25c21 | 44 | "\n%sLock a block device and run a command.%s\n\n" |
8b12a516 LP |
45 | " -h --help Print this message\n" |
46 | " -V --version Print version of the program\n" | |
47 | " -d --device=DEVICE Block device to lock\n" | |
48 | " -b --backing=FILE File whose backing block device to lock\n" | |
49 | " -t --timeout=SECS Block at most the specified time waiting for lock\n" | |
50 | " -p --print Only show which block device the lock would be taken on\n" | |
51 | "\nSee the %s for details.\n", | |
52 | program_invocation_short_name, | |
53 | program_invocation_short_name, | |
54 | ansi_highlight(), | |
55 | ansi_normal(), | |
56 | link); | |
57 | ||
58 | return 0; | |
59 | } | |
60 | ||
61 | static int parse_argv(int argc, char *argv[]) { | |
62 | ||
63 | static const struct option options[] = { | |
64 | { "help", no_argument, NULL, 'h' }, | |
65 | { "version", no_argument, NULL, 'V' }, | |
66 | { "device", required_argument, NULL, 'd' }, | |
67 | { "backing", required_argument, NULL, 'b' }, | |
68 | { "timeout", required_argument, NULL, 't' }, | |
69 | { "print", no_argument, NULL, 'p' }, | |
70 | {} | |
71 | }; | |
72 | ||
73 | int c, r; | |
74 | ||
75 | assert(argc >= 0); | |
76 | assert(argv); | |
77 | ||
ef9c12b1 YW |
78 | /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() |
79 | * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ | |
80 | optind = 0; | |
8b12a516 LP |
81 | while ((c = getopt_long(argc, argv, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0) |
82 | ||
83 | switch (c) { | |
84 | ||
85 | case 'h': | |
86 | return help(); | |
87 | ||
88 | case 'V': | |
89 | return print_version(); | |
90 | ||
91 | case 'd': | |
92 | case 'b': { | |
93 | _cleanup_free_ char *s = NULL; | |
94 | char ***l = c == 'd' ? &arg_devices : &arg_backing; | |
95 | ||
96 | r = path_make_absolute_cwd(optarg, &s); | |
97 | if (r < 0) | |
98 | return log_error_errno(r, "Failed to make path '%s' absolute: %m", optarg); | |
99 | ||
100 | path_simplify(s); | |
101 | ||
102 | if (strv_consume(l, TAKE_PTR(s)) < 0) | |
103 | return log_oom(); | |
104 | ||
105 | strv_uniq(*l); | |
106 | break; | |
107 | } | |
108 | ||
109 | case 't': | |
110 | r = parse_sec(optarg, &arg_timeout_usec); | |
111 | if (r < 0) | |
112 | return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg); | |
113 | break; | |
114 | ||
115 | case 'p': | |
116 | arg_print = true; | |
117 | break; | |
118 | ||
119 | case '?': | |
120 | return -EINVAL; | |
121 | ||
122 | default: | |
123 | assert_not_reached(); | |
124 | } | |
125 | ||
126 | if (arg_print) { | |
127 | if (optind != argc) | |
128 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected"); | |
129 | } else { | |
130 | if (optind + 1 > argc) | |
131 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, command to execute."); | |
132 | ||
133 | arg_cmdline = strv_copy(argv + optind); | |
134 | if (!arg_cmdline) | |
135 | return log_oom(); | |
136 | } | |
137 | ||
138 | if (strv_isempty(arg_devices) && strv_isempty(arg_backing)) | |
139 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No devices to lock specified, refusing."); | |
140 | ||
141 | return 1; | |
142 | } | |
143 | ||
144 | static int find_devno( | |
145 | dev_t **devnos, | |
146 | size_t *n_devnos, | |
147 | const char *device, | |
148 | bool backing) { | |
149 | ||
26aa4800 | 150 | dev_t devt; |
8b12a516 LP |
151 | int r; |
152 | ||
153 | assert(devnos); | |
154 | assert(n_devnos); | |
155 | assert(*devnos || *n_devnos == 0); | |
156 | assert(device); | |
157 | ||
26aa4800 | 158 | r = path_get_whole_disk(device, backing, &devt); |
8b12a516 LP |
159 | if (r < 0) |
160 | return log_error_errno(r, "Failed to find whole block device for '%s': %m", device); | |
161 | ||
26aa4800 DDM |
162 | if (typesafe_bsearch(&devt, *devnos, *n_devnos, devt_compare_func)) { |
163 | log_debug("Device %u:%u already listed for locking, ignoring.", major(devt), minor(devt)); | |
8b12a516 LP |
164 | return 0; |
165 | } | |
166 | ||
167 | if (!GREEDY_REALLOC(*devnos, *n_devnos + 1)) | |
168 | return log_oom(); | |
169 | ||
26aa4800 | 170 | (*devnos)[(*n_devnos)++] = devt; |
8b12a516 LP |
171 | |
172 | /* Immediately sort again, to ensure the binary search above will work for the next device we add */ | |
173 | typesafe_qsort(*devnos, *n_devnos, devt_compare_func); | |
174 | return 1; | |
175 | } | |
176 | ||
177 | static int lock_device( | |
178 | const char *path, | |
179 | dev_t devno, | |
180 | usec_t deadline) { | |
181 | ||
254d1313 | 182 | _cleanup_close_ int fd = -EBADF; |
8b12a516 LP |
183 | struct stat st; |
184 | int r; | |
185 | ||
32703bd1 | 186 | fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); |
8b12a516 LP |
187 | if (fd < 0) |
188 | return log_error_errno(errno, "Failed to open '%s': %m", path); | |
189 | ||
190 | if (fstat(fd, &st) < 0) | |
191 | return log_error_errno(errno, "Failed to stat '%s': %m", path); | |
192 | ||
193 | /* Extra safety: check that the device still refers to what we think it refers to */ | |
194 | if (!S_ISBLK(st.st_mode) || st.st_rdev != devno) | |
195 | return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Path '%s' no longer refers to specified block device %u:%u: %m", path, major(devno), minor(devno)); | |
196 | ||
197 | if (flock(fd, LOCK_EX|LOCK_NB) < 0) { | |
198 | ||
199 | if (errno != EAGAIN) | |
200 | return log_error_errno(errno, "Failed to lock device '%s': %m", path); | |
201 | ||
202 | if (deadline == 0) | |
203 | return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Device '%s' is currently locked.", path); | |
204 | ||
205 | if (deadline == USEC_INFINITY) { | |
206 | ||
28e5e1e9 | 207 | log_info("Device '%s' is currently locked, waiting%s", path, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); |
8b12a516 LP |
208 | |
209 | if (flock(fd, LOCK_EX) < 0) | |
210 | return log_error_errno(errno, "Failed to lock device '%s': %m", path); | |
211 | ||
212 | } else { | |
213 | _cleanup_(sigkill_waitp) pid_t flock_pid = 0; | |
214 | ||
215 | /* flock() doesn't support a time-out. Let's fake one then. The traditional way to do | |
216 | * this is via alarm()/setitimer()/timer_create(), but that's racy, given that the | |
e7e25c21 | 217 | * SIGALRM might already fire between the alarm() and the flock() in which case the |
8b12a516 LP |
218 | * flock() is never cancelled and we lock up (this is a short time window, but with |
219 | * short timeouts on a loaded machine we might run into it, who knows?). Let's | |
220 | * instead do the lock out-of-process: fork off a child that does the locking, and | |
221 | * that we'll wait on and kill if it takes too long. */ | |
222 | ||
28e5e1e9 DT |
223 | log_info("Device '%s' is currently locked, waiting %s%s", |
224 | path, FORMAT_TIMESPAN(usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC)), 0), | |
225 | special_glyph(SPECIAL_GLYPH_ELLIPSIS)); | |
8b12a516 LP |
226 | |
227 | BLOCK_SIGNALS(SIGCHLD); | |
228 | ||
e9ccae31 | 229 | r = safe_fork("(timed-flock)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &flock_pid); |
8b12a516 LP |
230 | if (r < 0) |
231 | return r; | |
232 | if (r == 0) { | |
233 | /* Child */ | |
234 | ||
235 | if (flock(fd, LOCK_EX) < 0) { | |
236 | log_error_errno(errno, "Failed to lock device '%s': %m", path); | |
237 | _exit(EXIT_FAILURE); | |
238 | } | |
239 | ||
240 | _exit(EXIT_SUCCESS); | |
241 | } | |
242 | ||
243 | for (;;) { | |
244 | siginfo_t si; | |
245 | sigset_t ss; | |
246 | usec_t n; | |
247 | ||
6c7b1ea1 N |
248 | assert_se(sigemptyset(&ss) >= 0); |
249 | assert_se(sigaddset(&ss, SIGCHLD) >= 0); | |
8b12a516 LP |
250 | |
251 | n = now(CLOCK_MONOTONIC); | |
252 | if (n >= deadline) | |
253 | return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Timeout reached."); | |
254 | ||
255 | r = sigtimedwait(&ss, NULL, TIMESPEC_STORE(deadline - n)); | |
256 | if (r < 0) { | |
257 | if (errno != EAGAIN) | |
258 | return log_error_errno(errno, "Failed to wait for SIGCHLD: %m"); | |
259 | ||
260 | return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Timeout reached."); | |
261 | } | |
262 | ||
263 | assert(r == SIGCHLD); | |
264 | ||
265 | zero(si); | |
266 | ||
267 | if (waitid(P_PID, flock_pid, &si, WEXITED|WNOHANG|WNOWAIT) < 0) | |
268 | return log_error_errno(errno, "Failed to wait for child: %m"); | |
269 | ||
270 | if (si.si_pid != 0) { | |
271 | assert(si.si_pid == flock_pid); | |
272 | ||
273 | if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) | |
274 | return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpected exit status of file lock child."); | |
275 | ||
276 | break; | |
277 | } | |
278 | ||
279 | log_debug("Got SIGCHLD for other child, continuing."); | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
28e5e1e9 | 284 | log_debug("Successfully locked %s (%u:%u)%s", path, major(devno), minor(devno), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); |
8b12a516 LP |
285 | |
286 | return TAKE_FD(fd); | |
287 | } | |
288 | ||
289 | int lock_main(int argc, char *argv[], void *userdata) { | |
5d2a48da | 290 | _cleanup_fdset_free_ FDSet *fds = NULL; |
8b12a516 LP |
291 | _cleanup_free_ dev_t *devnos = NULL; |
292 | size_t n_devnos = 0; | |
293 | usec_t deadline; | |
294 | pid_t pid; | |
295 | int r; | |
296 | ||
297 | r = parse_argv(argc, argv); | |
298 | if (r <= 0) | |
299 | return r; | |
300 | ||
301 | STRV_FOREACH(i, arg_devices) { | |
302 | r = find_devno(&devnos, &n_devnos, *i, /* backing= */ false); | |
303 | if (r < 0) | |
304 | return r; | |
305 | } | |
306 | ||
307 | STRV_FOREACH(i, arg_backing) { | |
308 | r = find_devno(&devnos, &n_devnos, *i, /* backing= */ true); | |
309 | if (r < 0) | |
310 | return r; | |
311 | } | |
312 | ||
313 | assert(n_devnos > 0); | |
314 | ||
315 | fds = fdset_new(); | |
316 | if (!fds) | |
317 | return log_oom(); | |
318 | ||
8df68749 | 319 | if (!timestamp_is_set(arg_timeout_usec)) |
8b12a516 LP |
320 | deadline = arg_timeout_usec; |
321 | else | |
322 | deadline = usec_add(now(CLOCK_MONOTONIC), arg_timeout_usec); | |
323 | ||
324 | for (size_t i = 0; i < n_devnos; i++) { | |
325 | _cleanup_free_ char *node = NULL; | |
326 | ||
4fe46c34 | 327 | r = devname_from_devnum(S_IFBLK, devnos[i], &node); |
8b12a516 LP |
328 | if (r < 0) |
329 | return log_error_errno(r, "Failed to format block device path: %m"); | |
330 | ||
331 | if (arg_print) | |
332 | printf("%s\n", node); | |
333 | else { | |
254d1313 | 334 | _cleanup_close_ int fd = -EBADF; |
8b12a516 LP |
335 | |
336 | fd = lock_device(node, devnos[i], deadline); | |
337 | if (fd < 0) | |
338 | return fd; | |
339 | ||
e829f28c | 340 | r = fdset_consume(fds, TAKE_FD(fd)); |
8b12a516 LP |
341 | if (r < 0) |
342 | return log_oom(); | |
8b12a516 LP |
343 | } |
344 | } | |
345 | ||
346 | if (arg_print) | |
347 | return EXIT_SUCCESS; | |
348 | ||
349 | /* Ignore SIGINT and allow the forked process to receive it */ | |
350 | (void) ignore_signals(SIGINT); | |
351 | ||
e9ccae31 | 352 | r = safe_fork("(lock)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); |
8b12a516 LP |
353 | if (r < 0) |
354 | return r; | |
355 | if (r == 0) { | |
356 | /* Child */ | |
357 | ||
358 | execvp(arg_cmdline[0], arg_cmdline); | |
359 | log_open(); | |
360 | log_error_errno(errno, "Failed to execute %s: %m", arg_cmdline[0]); | |
361 | _exit(EXIT_FAILURE); | |
362 | } | |
363 | ||
364 | return wait_for_terminate_and_check(arg_cmdline[0], pid, 0); | |
365 | } |