/* * 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 #include #include #include #include #include #include #include #include #include #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 DEF_WAITINC_US 5000000 /* Additional time to wait each retry */ #define DEF_MAXWAIT_US 60000000 /* 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] waitinc How many micro-seconds to extend wait time before * additional retry * @param[in] maxwait Maximum wait time before retry * @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, unsigned int waitinc, unsigned int maxwait) { unsigned int tries = retries + 1; int fd, plen, len, rc = 0; unsigned int snooze = 0; 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; usleep(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(), DEF_WAITINC_US, DEF_MAXWAIT_US); } /** * Attempt to create a lockfile owned by this process at the specified path * using a custom wait/retry time. * * @param[in] lockfile Path to the lock file * @param[in] retries Number of times to retry if lock fails initially * @param[in] waitinc How many micro-seconds to extend wait time before * additional retry * @param[in] maxwait Maximum wait time before retry * * @retval 0 Lock created * @retval !=0 Lock was not created */ int util_lockfile_lock_cw(char *lockfile, int retries, unsigned int waitinc, unsigned int maxwait) { return do_lockfile_lock(lockfile, retries, getpid(), waitinc, maxwait); } /** * 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(), DEF_WAITINC_US, DEF_MAXWAIT_US); } /** * Attempt to create a lockfile owned by the parent of this process at the * specified path using a custom wait/retry time. * * @param[in] lockfile Path to the lock file * @param[in] retries Number of times to retry if lock fails initially * @param[in] waitinc How many micro-seconds to extend wait time before * additional retry * @param[in] maxwait Maximum wait time before retry * * @retval 0 Lock created * @retval !=0 Lock was not created */ int util_lockfile_parent_lock_cw(char *lockfile, int retries, unsigned int waitinc, unsigned int maxwait) { return do_lockfile_lock(lockfile, retries, getppid(), waitinc, maxwait); } /** * 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()); } /** * Return the pid that owns the specified lockfile. * * @param[in] lockfile Path to the lock file * @param[in,out] pid Buffer to place owning pid * * @retval 0 pid provided in buffer * @retval !=0 Error, no pid provided */ int util_lockfile_peek_owner(char *lockfile, int *pid) { char buf[BUFSIZE]; int fd, len; if (!lockfile || !pid) return UTIL_LOCKFILE_ERR; /* Open lockfile, read the owning pid if it exists */ fd = open(lockfile, O_RDONLY); if (fd < 0) return UTIL_LOCKFILE_ERR; len = read(fd, buf, sizeof(buf)); close(fd); if (len <= 0) return UTIL_LOCKFILE_ERR; buf[len] = 0; *pid = atoi(buf); return 0; }