diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/basic/lock-util.c | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/basic/lock-util.c')
-rw-r--r-- | src/basic/lock-util.c | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c new file mode 100644 index 0000000..047fd01 --- /dev/null +++ b/src/basic/lock-util.c @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#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 "fs-util.h" +#include "lock-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; + _cleanup_free_ char *t = NULL; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(p); + assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH)); + assert(ret); + + if (isempty(p)) + return -EINVAL; + + /* We use UNPOSIX locks as they have nice semantics, and are mostly compatible with NFS. */ + + dfd = fd_reopen(dir_fd, O_CLOEXEC|O_PATH|O_DIRECTORY); + if (dfd < 0) + return dfd; + + t = strdup(p); + if (!t) + return -ENOMEM; + + fd = xopenat_lock(dfd, + p, + O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, + /* xopen_flags = */ 0, + 0600, + LOCK_UNPOSIX, + operation); + if (fd < 0) + return fd == -EAGAIN ? -EBUSY : fd; + + *ret = (LockFile) { + .dir_fd = TAKE_FD(dfd), + .path = TAKE_PTR(t), + .fd = TAKE_FD(fd), + .operation = operation, + }; + + return 0; +} + +int make_lock_file_for(const char *p, int operation, LockFile *ret) { + _cleanup_free_ char *fn = NULL, *dn = NULL, *t = NULL; + int r; + + assert(p); + assert(ret); + + r = path_extract_filename(p, &fn); + if (r < 0) + return r; + + r = path_extract_directory(p, &dn); + if (r < 0) + return r; + + t = strjoin(dn, "/.#", fn, ".lck"); + if (!t) + return -ENOMEM; + + return make_lock_file(t, operation, ret); +} + +void release_lock_file(LockFile *f) { + if (!f) + return; + + if (f->path) { + + /* If we are the exclusive owner we can safely delete + * the lock file itself. If we are not the exclusive + * owner, we can try becoming it. */ + + if (f->fd >= 0 && + (f->operation & ~LOCK_NB) == LOCK_SH && + unposix_lock(f->fd, LOCK_EX|LOCK_NB) >= 0) + f->operation = LOCK_EX|LOCK_NB; + + if ((f->operation & ~LOCK_NB) == LOCK_EX) + (void) unlinkat(f->dir_fd, f->path, 0); + + f->path = mfree(f->path); + } + + f->dir_fd = safe_close(f->dir_fd); + f->fd = safe_close(f->fd); + f->operation = 0; +} + +static int fcntl_lock(int fd, int operation, bool ofd) { + int cmd, type, r; + + assert(fd >= 0); + + if (ofd) + cmd = (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW; + else + cmd = (operation & LOCK_NB) ? F_SETLK : F_SETLKW; + + switch (operation & ~LOCK_NB) { + case LOCK_EX: + type = F_WRLCK; + break; + case LOCK_SH: + type = F_RDLCK; + break; + case LOCK_UN: + type = F_UNLCK; + break; + default: + assert_not_reached(); + } + + r = RET_NERRNO(fcntl(fd, cmd, &(struct flock) { + .l_type = type, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + })); + + if (r == -EACCES) /* Treat EACCESS/EAGAIN the same as per man page. */ + r = -EAGAIN; + + return r; +} + +int posix_lock(int fd, int operation) { + return fcntl_lock(fd, operation, /*ofd=*/ false); +} + +int unposix_lock(int fd, int operation) { + return fcntl_lock(fd, operation, /*ofd=*/ true); +} + +void posix_unlockpp(int **fd) { + assert(fd); + + if (!*fd || **fd < 0) + return; + + (void) fcntl_lock(**fd, LOCK_UN, /*ofd=*/ false); + *fd = NULL; +} + +void unposix_unlockpp(int **fd) { + assert(fd); + + if (!*fd || **fd < 0) + return; + + (void) fcntl_lock(**fd, LOCK_UN, /*ofd=*/ true); + *fd = NULL; +} + +int lock_generic(int fd, LockType type, int operation) { + assert(fd >= 0); + + switch (type) { + case LOCK_NONE: + return 0; + case LOCK_BSD: + return RET_NERRNO(flock(fd, operation)); + case LOCK_POSIX: + return posix_lock(fd, operation); + case LOCK_UNPOSIX: + return unposix_lock(fd, operation); + default: + 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(); + } +} |