Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP4
s390-tools.28248
s390-tools-sles15sp4-07-libutil-introduce-util_...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File s390-tools-sles15sp4-07-libutil-introduce-util_lockfile.patch of Package s390-tools.28248
Subject: [PATCH] [FEAT VS1822] libutil: introduce util_lockfile From: Matthew Rosato <mjrosato@linux.ibm.com> Summary: ap_tools: add ap-check and the ap device type to zdev Description: This feature adds multiple components in support of persistent configuration of vfio-ap mediated devices. The ap-check utility is added as a callout function for the mdevctl utility. This allows for meaningful error messages to be presented to end-users when vfio-ap configuration errors are detected while using mdevctl to create/modify vfio-ap mediated devices. Additionally, the 'ap' device type is added to zdev, providing a command-line interface for managing the apmask and aqmask, which determine what AP resources are available for vfio-ap usage. 'chzdev' is updated to allow for modifying the active masks as well as to specify persistent mask values via a udev rule. 'lszdev' is updated to allow for querying of the active and persistent mask values. Upstream-ID: e1aec24e843689524b470081fd46e67593161edd Problem-ID: VS1822 Upstream-Description: libutil: introduce util_lockfile Implement simple file-locking routines that use process PIDs for stale lock detection. The implementation is meant to be a simplified subset of what liblockfile was previously being used for by libap, allowing the external dependency to be removed. This initial implementation provides a series of functions that allow for creating/release file locks using either the current process PID or the PID of the current process parent. When creating a file lock, first a temporary file is created and the appropriate PID (either this process PID or the parent process PID) is placed in the file to specify the owner of the lock. Then an attempt is made to link that file to the desired file location; if this succeeds, the lock is now held on behalf of the specified PID. If it fails, this implies the file already exists (meaning the lock is already held). In this case, stale lock detection is performed by reading the PID from the file and ensuring that the associated process still exists -- if it does not, then the lock is presumed stale and destroyed. If the process still exists, then either the lock request fails or the caller will sleep and retry, depending on an optional retry setting. A lock remains valid until either 1) it is released via the corresponding util_lockfile function, which will delete the corresponding file 2) the associated PID no longer exists, which leaves the file in-place but will cause it to be destroyed the next time a different process attempts to lock that file or 3) the file is directly removed (e.g. rm). A typical usecase for such support would be to provide a means for multiple invocations of the same (or different) tools to ensure that they do not access the same shared resource simultaneously. For example, ap-check, chzdev and lszdev all have a need to view and/or modify the AP and vfio-ap configuration files; util_lockfile can be used to ensure that only one instance of any of these utilities do that at a time by ensuring they all use the same lockfile. Additionally, providing the ability to specify the parent PID rather than the current PID allows for a general purpose tool (like mdevctl) to invoke a sub-program (ap-check) to acquire and release a lockfile as necssary while allowing stale lock detection to be controlled by that parent PID, allowing the lock to remain held over multiple sub-program invocations. Note that this implementation is sufficient for our current usage (e.g. lockfiles placed in tmpfs) but does not take into consideration things like NFS, which a more complete lockfile solution like liblockfile does. GitHub-ID: https://github.com/ibm-s390-linux/s390-tools/issues/142 Suggested-by: Luca BRUNO <luca.bruno@coreos.com> Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com> Reviewed-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Jan Hoeppner <hoeppner@linux.ibm.com> Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com> --- include/lib/util_lockfile.h | 26 +++ libutil/util_lockfile.c | 300 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 326 insertions(+) --- /dev/null +++ b/include/lib/util_lockfile.h @@ -0,0 +1,26 @@ +/** + * @defgroup util_lockfile_h util_lockfile: File locking utility + * @{ + * @brief Create file-based locks + * + * Copyright IBM Corp. 2022 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ +#ifndef LIB_UTIL_LOCKFILE_H +#define LIB_UTIL_LOCKFILE_H + +#define UTIL_LOCKFILE_OK 0 /* Lock acquired/released successfully */ +#define UTIL_LOCKFILE_LOCK_FAIL 1 /* Lock already held, ran out of retries */ +#define UTIL_LOCKFILE_RELEASE_NONE 2 /* Lock not held */ +#define UTIL_LOCKFILE_RELEASE_FAIL 3 /* Lock not held by specified pid */ +#define UTIL_LOCKFILE_ERR 4 /* Other, unexpected error conditions */ + +int util_lockfile_lock(char *lockfile, int retries); +int util_lockfile_parent_lock(char *lockfile, int retries); + +int util_lockfile_release(char *lockfile); +int util_lockfile_parent_release(char *lockfile); + +#endif /** LIB_UTIL_LOCKFILE_H @} */ --- /dev/null +++ b/libutil/util_lockfile.c @@ -0,0 +1,300 @@ +/* + * util - Utility function library + * + * Created file-based locks + * + * Copyright IBM Corp. 2022 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> + +#include "lib/util_libc.h" +#include "lib/util_lockfile.h" +#include "lib/util_panic.h" + +#define WAITPID 120 /* Time to wait for pid to be written */ +#define WAITINC 5 /* Additional time to wait each retry */ +#define MAXWAIT 60 /* Maximum wait between retries */ +#define BUFSIZE 40 /* Buffer must be large enough to fit pid string */ + +/** + * Check if there is a process that exists with the specified identifier. + * + * @param[in] pid Process Identifier to check + * + * @retval true Process exists or we lack privelege to check + * @retval false Process does not exist + */ +static bool pid_exists(int pid) +{ + int rc; + + /* Use sig 0 to determine if the owner is still alive */ + rc = kill(pid, 0); + if (rc != 0) { + switch (errno) { + case EPERM: + /* Privilege issue, just assume PID exists */ + break; + case ESRCH: + /* PID does not exist, this lock is stale */ + return false; + default: + util_assert(false, "Unexpected return from kill: %d\n", rc); + break; + } + } + + return true; +} + +/** + * Check for an existing lock that is deemed stale (either the associated PID + * is gone or no PID has been written to the file in a reasonable timeframe). + * In the case a stale lock is found, remove it. + * + * @param[in] lockfile Path to the lock file + * + * @retval 0 Either no lock or stale lock was found and removed + * @retval 1 Lock is held by another PID and is not stale + */ +static int handle_stale_lock(char *lockfile) +{ + int fd, rc, pid, len; + struct stat info; + char buf[BUFSIZE]; + time_t curr; + + fd = open(lockfile, O_RDONLY); + + if (fd >= 0) { + /* Lock exists, see who owns it */ + len = read(fd, buf, sizeof(buf)); + if (len > 0) { + buf[len] = 0; /* Ensure null terminated string */ + pid = atoi(buf); + if (!pid_exists(pid)) { + /* Stale lock detected unlink and retry now */ + close(fd); + unlink(lockfile); + return 0; + } else if (pid != 0) { + /* Lock is held by an active pid, delay */ + close(fd); + return 1; + } + /* + * If we reach this point, the PID was 0 which is + * unexpected. Proceed under the assumption that the + * proper PID hasn't been written yet. + */ + } + /* + * PID hasn't been written yet? Either a bad lock or a very + * new one. + */ + time(&curr); + rc = fstat(fd, &info); + close(fd); + if (rc != 0) { + /* Can't read file anymore, retry now */ + return 0; + } + if (curr > info.st_mtime + WAITPID) { + /* + * PID should be in the file within 2 minutes, + * something went wrong. Treat as stale. + */ + unlink(lockfile); + return 0; + } + /* Otherwise, assume file was newly created and delay */ + return 1; + } + + /* Couldn't open, try again immediately */ + return 0; +} + +/** + * Attempt to create a lockfile at the specified path. + * + * @param[in] lockfile Path to the lock file + * @param[in] retries Number of times to retry if lock fails initially + * @param[in] pid PID to use for lock ownership + * + * @retval 0 Lock created with PID as owner + * @retval !=0 Lock was not created + */ +static int do_lockfile_lock(char *lockfile, unsigned int retries, int pid) +{ + int fd, plen, len, rc = 0, snooze = 0; + unsigned int tries = retries + 1; + char buf[BUFSIZE]; + char *tpath; + + if (!lockfile) + return UTIL_LOCKFILE_ERR; + + plen = snprintf(buf, sizeof(buf), "%d\n", pid); + if (plen < 0 || (plen > ((int)sizeof(buf) - 1))) + return UTIL_LOCKFILE_ERR; + + /* Allocate temporary lock file with a sufficiently unique path */ + len = util_asprintf(&tpath, "%s%05d%02x", lockfile, getpid(), + (unsigned int)time(NULL) & 255); + if (len < 0) + return UTIL_LOCKFILE_ERR; + + /* Open the temporary lockfile, write the specified pid */ + fd = open(tpath, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd < 0) { + rc = UTIL_LOCKFILE_ERR; + goto out; + } + + len = write(fd, buf, plen); + if (close(fd) != 0) { + rc = UTIL_LOCKFILE_ERR; + goto cleanup; + } + if (len != plen) { + /* Failed to write the temp lockfile, bail out */ + rc = UTIL_LOCKFILE_ERR; + goto cleanup; + } + + /* Link the temprorary file to the real path */ + do { + rc = link(tpath, lockfile); + if (rc == 0) { + /* Lock successfully acquired */ + rc = UTIL_LOCKFILE_OK; + goto cleanup; + } + /* Lock already held - check for stale lock */ + rc = handle_stale_lock(lockfile); + /* Only wait if the lock was not stale */ + if (rc != 0) { + tries--; + if (tries > 0) { + snooze += WAITINC; + snooze = (snooze > MAXWAIT) ? MAXWAIT : snooze; + sleep(snooze); + } + } + } while (tries > 0); + + /* Exhausted specified number of retries, exit on failure */ + rc = UTIL_LOCKFILE_LOCK_FAIL; + +cleanup: + unlink(tpath); + free(tpath); +out: + return rc; +} + +/** + * Attempt to release a lockfile at the specified path. + * + * @param[in] lockfile Path to the lock file + * @param[in] pid PID that should own the lock + * + * @retval 0 Lock released + * @retval !=0 Lock was not released or did not exist + */ +static int do_lockfile_release(char *lockfile, int pid) +{ + int fd, len, lpid; + char buf[BUFSIZE]; + + if (!lockfile) + return UTIL_LOCKFILE_ERR; + + /* Open lockfile, read the owning pid if it exists */ + fd = open(lockfile, O_RDONLY); + if (fd < 0) + return UTIL_LOCKFILE_RELEASE_NONE; + len = read(fd, buf, sizeof(buf)); + close(fd); + if (len <= 0) + return UTIL_LOCKFILE_RELEASE_FAIL; + buf[len] = 0; + lpid = atoi(buf); + + /* Only release the lock if its held by the right pid */ + if (pid != lpid) + return UTIL_LOCKFILE_RELEASE_FAIL; + + unlink(lockfile); + + return 0; +} + +/** + * Attempt to create a lockfile owned by this process at the specified path. + * + * @param[in] lockfile Path to the lock file + * @param[in] retries Number of times to retry if lock fails initially + * + * @retval 0 Lock created + * @retval !=0 Lock was not created + */ +int util_lockfile_lock(char *lockfile, int retries) +{ + return do_lockfile_lock(lockfile, retries, getpid()); +} + +/** + * Attempt to create a lockfile owned by the parent of this process at the + * specified path. + * + * @param[in] lockfile Path to the lock file + * @param[in] retries Number of times to retry if lock fails initially + * + * @retval 0 Lock created + * @retval !=0 Lock was not created + */ +int util_lockfile_parent_lock(char *lockfile, int retries) +{ + return do_lockfile_lock(lockfile, retries, getppid()); +} + +/** + * Attempt to release a lockfile owned by this process at the specified path. + * + * @param[in] lockfile Path to the lock file + * + * @retval 0 Lock released + * @retval !=0 Lock was not released or did not exist + */ +int util_lockfile_release(char *lockfile) +{ + return do_lockfile_release(lockfile, getpid()); +} + +/** + * Attempt to release a lockfile owned by the parent of this process at the + * specified path. + * + * @param[in] lockfile Path to the lock file + * + * @retval 0 Lock released + * @retval !=0 Lock was not released or did not exist + */ +int util_lockfile_parent_release(char *lockfile) +{ + return do_lockfile_release(lockfile, getppid()); +}
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor