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.
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
|
||||
#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"
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -273,6 +273,7 @@ libbasic = static_library(
|
||||
include_directories : basic_includes,
|
||||
dependencies : [libcap,
|
||||
libm,
|
||||
librt,
|
||||
threads,
|
||||
userspace],
|
||||
c_args : ['-fvisibility=default'],
|
||||
|
||||
Reference in New Issue
Block a user