1 /* SPDX-License-Identifier: GPL-2.0-or-later */
8 #include "blockdev-util.h"
9 #include "btrfs-util.h"
10 #include "device-util.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"
21 #include "time-util.h"
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;
30 STATIC_DESTRUCTOR_REGISTER(arg_devices
, strv_freep
);
31 STATIC_DESTRUCTOR_REGISTER(arg_backing
, strv_freep
);
32 STATIC_DESTRUCTOR_REGISTER(arg_cmdline
, strv_freep
);
34 static int help(void) {
35 _cleanup_free_
char *link
= NULL
;
38 r
= terminal_urlify_man("udevadm", "8", &link
);
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
,
61 static int parse_argv(int argc
, char *argv
[]) {
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' },
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). */
81 while ((c
= getopt_long(argc
, argv
, arg_print
? "hVd:b:t:p" : "+hVd:b:t:p", options
, NULL
)) >= 0)
89 return print_version();
93 _cleanup_free_
char *s
= NULL
;
94 char ***l
= c
== 'd' ? &arg_devices
: &arg_backing
;
96 r
= path_make_absolute_cwd(optarg
, &s
);
98 return log_error_errno(r
, "Failed to make path '%s' absolute: %m", optarg
);
102 if (strv_consume(l
, TAKE_PTR(s
)) < 0)
110 r
= parse_sec(optarg
, &arg_timeout_usec
);
112 return log_error_errno(r
, "Failed to parse --timeout= parameter: %s", optarg
);
123 assert_not_reached();
128 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No arguments expected");
130 if (optind
+ 1 > argc
)
131 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too few arguments, command to execute.");
133 arg_cmdline
= strv_copy(argv
+ optind
);
138 if (strv_isempty(arg_devices
) && strv_isempty(arg_backing
))
139 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No devices to lock specified, refusing.");
144 static int find_devno(
155 assert(*devnos
|| *n_devnos
== 0);
158 r
= path_get_whole_disk(device
, backing
, &devt
);
160 return log_error_errno(r
, "Failed to find whole block device for '%s': %m", device
);
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
));
167 if (!GREEDY_REALLOC(*devnos
, *n_devnos
+ 1))
170 (*devnos
)[(*n_devnos
)++] = devt
;
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
);
177 static int lock_device(
182 _cleanup_close_
int fd
= -EBADF
;
186 fd
= open(path
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
188 return log_error_errno(errno
, "Failed to open '%s': %m", path
);
190 if (fstat(fd
, &st
) < 0)
191 return log_error_errno(errno
, "Failed to stat '%s': %m", path
);
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
));
197 if (flock(fd
, LOCK_EX
|LOCK_NB
) < 0) {
200 return log_error_errno(errno
, "Failed to lock device '%s': %m", path
);
203 return log_error_errno(SYNTHETIC_ERRNO(EBUSY
), "Device '%s' is currently locked.", path
);
205 if (deadline
== USEC_INFINITY
) {
207 log_info("Device '%s' is currently locked, waiting%s", path
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
209 if (flock(fd
, LOCK_EX
) < 0)
210 return log_error_errno(errno
, "Failed to lock device '%s': %m", path
);
213 _cleanup_(sigkill_waitp
) pid_t flock_pid
= 0;
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. */
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
));
227 BLOCK_SIGNALS(SIGCHLD
);
229 r
= safe_fork("(timed-flock)", FORK_DEATHSIG
|FORK_LOG
, &flock_pid
);
235 if (flock(fd
, LOCK_EX
) < 0) {
236 log_error_errno(errno
, "Failed to lock device '%s': %m", path
);
248 assert(sigemptyset(&ss
) >= 0);
249 assert(sigaddset(&ss
, SIGCHLD
) >= 0);
251 n
= now(CLOCK_MONOTONIC
);
253 return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT
), "Timeout reached.");
255 r
= sigtimedwait(&ss
, NULL
, TIMESPEC_STORE(deadline
- n
));
258 return log_error_errno(errno
, "Failed to wait for SIGCHLD: %m");
260 return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT
), "Timeout reached.");
263 assert(r
== SIGCHLD
);
267 if (waitid(P_PID
, flock_pid
, &si
, WEXITED
|WNOHANG
|WNOWAIT
) < 0)
268 return log_error_errno(errno
, "Failed to wait for child: %m");
270 if (si
.si_pid
!= 0) {
271 assert(si
.si_pid
== flock_pid
);
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.");
279 log_debug("Got SIGCHLD for other child, continuing.");
284 log_debug("Successfully locked %s (%u:%u)%s", path
, major(devno
), minor(devno
), special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
289 int lock_main(int argc
, char *argv
[], void *userdata
) {
290 _cleanup_(fdset_freep
) FDSet
*fds
= NULL
;
291 _cleanup_free_ dev_t
*devnos
= NULL
;
297 r
= parse_argv(argc
, argv
);
301 STRV_FOREACH(i
, arg_devices
) {
302 r
= find_devno(&devnos
, &n_devnos
, *i
, /* backing= */ false);
307 STRV_FOREACH(i
, arg_backing
) {
308 r
= find_devno(&devnos
, &n_devnos
, *i
, /* backing= */ true);
313 assert(n_devnos
> 0);
319 if (IN_SET(arg_timeout_usec
, 0, USEC_INFINITY
))
320 deadline
= arg_timeout_usec
;
322 deadline
= usec_add(now(CLOCK_MONOTONIC
), arg_timeout_usec
);
324 for (size_t i
= 0; i
< n_devnos
; i
++) {
325 _cleanup_free_
char *node
= NULL
;
327 r
= devname_from_devnum(S_IFBLK
, devnos
[i
], &node
);
329 return log_error_errno(r
, "Failed to format block device path: %m");
332 printf("%s\n", node
);
334 _cleanup_close_
int fd
= -EBADF
;
336 fd
= lock_device(node
, devnos
[i
], deadline
);
340 r
= fdset_consume(fds
, TAKE_FD(fd
));
349 /* Ignore SIGINT and allow the forked process to receive it */
350 (void) ignore_signals(SIGINT
);
352 r
= safe_fork("(lock)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
358 execvp(arg_cmdline
[0], arg_cmdline
);
360 log_error_errno(errno
, "Failed to execute %s: %m", arg_cmdline
[0]);
364 return wait_for_terminate_and_check(arg_cmdline
[0], pid
, 0);