udevadm-lock: switch things over to lock_generic_with_timeout()

This replaces the local implementation of a timeout file lock with our
new generic one.

Note that a comment in the old code claimed we couldn't use alarm()-like timeouts,
but htat's not entirely true: we can if we use SIGKILL, and thus know
for sure that the process will be dead in case the timer is hit before
we actually enter the file lock syscall. But we also know it will be
delivered if we hit after.
This commit is contained in:
Lennart Poettering
2023-11-02 14:19:32 +01:00
parent e5f1e8b894
commit de7e033286
+13 -72
View File
@@ -10,6 +10,7 @@
#include "device-util.h"
#include "fd-util.h"
#include "fdset.h"
#include "lock-util.h"
#include "main-func.h"
#include "parse-util.h"
#include "path-util.h"
@@ -194,91 +195,31 @@ static int lock_device(
if (!S_ISBLK(st.st_mode) || st.st_rdev != devno)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Path '%s' no longer refers to specified block device %u:%u: %m", path, major(devno), minor(devno));
if (flock(fd, LOCK_EX|LOCK_NB) < 0) {
if (errno != EAGAIN)
return log_error_errno(errno, "Failed to lock device '%s': %m", path);
r = lock_generic(fd, LOCK_BSD, LOCK_EX|LOCK_NB);
if (r < 0) {
if (r != -EAGAIN)
return log_error_errno(r, "Failed to lock device '%s': %m", path);
if (deadline == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Device '%s' is currently locked.", path);
if (deadline == USEC_INFINITY) {
log_info("Device '%s' is currently locked, waiting%s", path, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
if (flock(fd, LOCK_EX) < 0)
return log_error_errno(errno, "Failed to lock device '%s': %m", path);
r = lock_generic(fd, LOCK_BSD, LOCK_EX);
} else {
_cleanup_(sigkill_waitp) pid_t flock_pid = 0;
/* flock() doesn't support a time-out. Let's fake one then. The traditional way to do
* this is via alarm()/setitimer()/timer_create(), but that's racy, given that the
* SIGALRM might already fire between the alarm() and the flock() in which case the
* flock() is never cancelled and we lock up (this is a short time window, but with
* short timeouts on a loaded machine we might run into it, who knows?). Let's
* instead do the lock out-of-process: fork off a child that does the locking, and
* that we'll wait on and kill if it takes too long. */
usec_t left = usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC));
log_info("Device '%s' is currently locked, waiting %s%s",
path, FORMAT_TIMESPAN(usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC)), 0),
path, FORMAT_TIMESPAN(left, 0),
special_glyph(SPECIAL_GLYPH_ELLIPSIS));
BLOCK_SIGNALS(SIGCHLD);
r = safe_fork("(timed-flock)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &flock_pid);
if (r < 0)
return r;
if (r == 0) {
/* Child */
if (flock(fd, LOCK_EX) < 0) {
log_error_errno(errno, "Failed to lock device '%s': %m", path);
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
for (;;) {
siginfo_t si;
sigset_t ss;
usec_t n;
assert_se(sigemptyset(&ss) >= 0);
assert_se(sigaddset(&ss, SIGCHLD) >= 0);
n = now(CLOCK_MONOTONIC);
if (n >= deadline)
return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Timeout reached.");
r = sigtimedwait(&ss, NULL, TIMESPEC_STORE(deadline - n));
if (r < 0) {
if (errno != EAGAIN)
return log_error_errno(errno, "Failed to wait for SIGCHLD: %m");
return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Timeout reached.");
}
assert(r == SIGCHLD);
zero(si);
if (waitid(P_PID, flock_pid, &si, WEXITED|WNOHANG|WNOWAIT) < 0)
return log_error_errno(errno, "Failed to wait for child: %m");
if (si.si_pid != 0) {
assert(si.si_pid == flock_pid);
if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpected exit status of file lock child.");
break;
}
log_debug("Got SIGCHLD for other child, continuing.");
}
r = lock_generic_with_timeout(fd, LOCK_BSD, LOCK_EX, left);
if (r == -ETIMEDOUT)
return log_error_errno(r, "Timeout reached.");
}
if (r < 0)
return log_error_errno(r, "Failed to lock device '%s': %m", path);
}
log_debug("Successfully locked %s (%u:%u)%s", path, major(devno), minor(devno), special_glyph(SPECIAL_GLYPH_ELLIPSIS));