From: Lennart Poettering Date: Thu, 2 Nov 2023 10:07:14 +0000 (+0100) Subject: lock-util: add a new lock_generic_with_timeout() helper X-Git-Tag: v255-rc1~43^2~9 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e5f1e8b89432724ba5c4772d1394eefe799c5504;p=thirdparty%2Fsystemd.git lock-util: add a new lock_generic_with_timeout() helper This is just like lock_generic(), but applies the lock with a timeout. This requires jumping through some hoops by executing things in a child process, so that we can abort if necessary via a timer. Linux after all has no native way to take file locks with a timeout. --- diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index 7e67c2d5838..047fd0184d6 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -2,10 +2,12 @@ #include #include +#include #include #include #include #include +#include #include "alloc-util.h" #include "fd-util.h" @@ -14,6 +16,7 @@ #include "macro.h" #include "missing_fcntl.h" #include "path-util.h" +#include "process-util.h" int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret) { _cleanup_close_ int fd = -EBADF, dfd = -EBADF; @@ -186,3 +189,89 @@ int lock_generic(int fd, LockType type, int operation) { assert_not_reached(); } } + +int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeout) { + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert(fd >= 0); + + /* A version of lock_generic(), but with a time-out. We do this in a child process, since the kernel + * APIs natively don't support a timeout. We set a SIGALRM timer that will kill the child after the + * timeout is hit. Returns -ETIMEDOUT if the time-out is hit, and 0 on success. + * + * This only works for BSD and UNPOSIX locks, as only those are fd-bound, and hence can be acquired + * from any process that has access to the fd. POSIX locks OTOH are process-bound, and hence if we'd + * acquire them in a child process they'd remain unlocked in the parent. */ + + if (type == LOCK_NONE) + return 0; + if (!IN_SET(type, LOCK_BSD, LOCK_UNPOSIX)) /* Not for POSIX locks, see above. */ + return -EOPNOTSUPP; + + /* First, try without forking anything off */ + r = lock_generic(fd, type, operation | (timeout == USEC_INFINITY ? 0 : LOCK_NB)); + if (r != -EAGAIN || timeout == 0 || FLAGS_SET(operation, LOCK_NB)) + return r; + + /* If that didn't work, try with a child */ + + r = safe_fork("(sd-flock)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pid); + if (r < 0) + return log_error_errno(r, "Failed to flock block device in child process: %m"); + if (r == 0) { + struct sigevent sev = { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = SIGALRM, + }; + timer_t id = 0; + + if (timer_create(CLOCK_MONOTONIC, &sev, &id) < 0) { + log_error_errno(errno, "Failed to allocate CLOCK_MONOTONIC timer: %m"); + _exit(EXIT_FAILURE); + } + + struct itimerspec its = {}; + timespec_store(&its.it_value, timeout); + + if (timer_settime(id, /* flags= */ 0, &its, NULL) < 0) { + log_error_errno(errno, "Failed to start CLOCK_MONOTONIC timer: %m"); + _exit(EXIT_FAILURE); + } + + if (lock_generic(fd, type, operation) < 0) { + log_error_errno(errno, "Unable to get an exclusive lock on the device: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + siginfo_t status; + r = wait_for_terminate(pid, &status); + if (r < 0) + return r; + + TAKE_PID(pid); + + switch (status.si_code) { + + case CLD_EXITED: + if (status.si_status != EXIT_SUCCESS) + return -EPROTO; + + return 0; + + case CLD_KILLED: + if (status.si_status == SIGALRM) + return -ETIMEDOUT; + + _fallthrough_; + + case CLD_DUMPED: + return -EPROTO; + + default: + assert_not_reached(); + } +} diff --git a/src/basic/lock-util.h b/src/basic/lock-util.h index b96ad85e210..91b332f803c 100644 --- a/src/basic/lock-util.h +++ b/src/basic/lock-util.h @@ -41,3 +41,5 @@ typedef enum LockType { } LockType; int lock_generic(int fd, LockType type, int operation); + +int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeout); diff --git a/src/basic/meson.build b/src/basic/meson.build index 438e8493b82..d7450d8b445 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -273,6 +273,7 @@ libbasic = static_library( include_directories : basic_includes, dependencies : [libcap, libm, + librt, threads, userspace], c_args : ['-fvisibility=default'], diff --git a/src/test/test-lock-util.c b/src/test/test-lock-util.c index 28fc54a5d6e..5edd0875822 100644 --- a/src/test/test-lock-util.c +++ b/src/test/test-lock-util.c @@ -35,4 +35,31 @@ TEST(make_lock_file) { assert_se(make_lock_file("lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY); } +static void test_lock_generic_with_timeout_for_type(LockType type) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, tfd2 = -EBADF; + + tfd = mkdtemp_open(NULL, 0, &t); + assert_se(tfd >= 0); + + tfd2 = fd_reopen(tfd, O_CLOEXEC|O_DIRECTORY); + assert_se(tfd2 >= 0); + + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_EX) >= 0); + assert_se(lock_generic(tfd2, LOCK_BSD, LOCK_EX|LOCK_NB) == -EWOULDBLOCK); + + usec_t start = now(CLOCK_MONOTONIC); + assert_se(lock_generic_with_timeout(tfd2, LOCK_BSD, LOCK_EX, 200 * USEC_PER_MSEC) == -ETIMEDOUT); + assert_se(usec_sub_unsigned(now(CLOCK_MONOTONIC), start) >= 200 * USEC_PER_MSEC); + + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_UN) >= 0); + assert_se(lock_generic_with_timeout(tfd2, LOCK_BSD, LOCK_EX, 200 * USEC_PER_MSEC) == 0); + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_EX|LOCK_NB) == -EWOULDBLOCK); +} + +TEST(lock_generic_with_timeout) { + test_lock_generic_with_timeout_for_type(LOCK_BSD); + test_lock_generic_with_timeout_for_type(LOCK_UNPOSIX); +} + DEFINE_TEST_MAIN(LOG_INFO);