mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
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 Höppner <hoeppner@linux.ibm.com>
301 lines
7.4 KiB
C
301 lines
7.4 KiB
C
/*
|
|
* 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());
|
|
}
|