#include <errno.h>
#include <fcntl.h>
+#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/stat.h>
+#include <sys/wait.h>
#include "alloc-util.h"
#include "fd-util.h"
#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;
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();
+ }
+}
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);