]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-lock.c
Merge pull request #26887 from yuwata/proc-cmdline-filter-arguments
[thirdparty/systemd.git] / src / udev / udevadm-lock.c
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"
10 #include "device-util.h"
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"
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"
44 "\n%sLock a block device and run a command.%s\n\n"
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
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;
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
150 dev_t devt;
151 int r;
152
153 assert(devnos);
154 assert(n_devnos);
155 assert(*devnos || *n_devnos == 0);
156 assert(device);
157
158 r = path_get_whole_disk(device, backing, &devt);
159 if (r < 0)
160 return log_error_errno(r, "Failed to find whole block device for '%s': %m", device);
161
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));
164 return 0;
165 }
166
167 if (!GREEDY_REALLOC(*devnos, *n_devnos + 1))
168 return log_oom();
169
170 (*devnos)[(*n_devnos)++] = devt;
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
182 _cleanup_close_ int fd = -EBADF;
183 struct stat st;
184 int r;
185
186 fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
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
207 log_info("Device '%s' is currently locked, waiting%s", path, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
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
217 * SIGALRM might already fire between the alarm() and the flock() in which case the
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
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));
226
227 BLOCK_SIGNALS(SIGCHLD);
228
229 r = safe_fork("(timed-flock)", FORK_DEATHSIG|FORK_LOG, &flock_pid);
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
248 assert(sigemptyset(&ss) >= 0);
249 assert(sigaddset(&ss, SIGCHLD) >= 0);
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
284 log_debug("Successfully locked %s (%u:%u)%s", path, major(devno), minor(devno), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
285
286 return TAKE_FD(fd);
287 }
288
289 int lock_main(int argc, char *argv[], void *userdata) {
290 _cleanup_(fdset_freep) FDSet *fds = NULL;
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
319 if (IN_SET(arg_timeout_usec, 0, USEC_INFINITY))
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
327 r = devname_from_devnum(S_IFBLK, devnos[i], &node);
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 {
334 _cleanup_close_ int fd = -EBADF;
335
336 fd = lock_device(node, devnos[i], deadline);
337 if (fd < 0)
338 return fd;
339
340 r = fdset_consume(fds, TAKE_FD(fd));
341 if (r < 0)
342 return log_oom();
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
352 r = safe_fork("(lock)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
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 }