#include <sys/param.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <paths.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef PIDFILE_LOCAL
#include "pidfile.h"
#else
#include <util.h>
#endif
#ifdef __RCSID
__RCSID("$NetBSD: pidfile.c,v 1.20 2026/06/11 21:13:47 roy Exp $");
#endif
static char *pf_path;
static int pf_fd = -1;
static bool pf_removeable = true;
static pid_t
pidfile_readfd(int fd)
{
char buf[16], *eptr;
int error;
ssize_t n;
pid_t pid = -1;
if (lseek(fd, 0, SEEK_SET) == -1)
return -1;
n = read(fd, buf, sizeof(buf) - 1);
if (n == -1)
return -1;
buf[n] = '\0';
pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error);
if (error != 0 && !(error == ENOTSUP && *eptr == '\n')) {
errno = error;
return -1;
}
return pid;
}
int
pidfile_fd(void)
{
return pf_fd;
}
const char *
pidfile_path(void)
{
return pf_path;
}
void
pidfile_unremoveable(void)
{
pf_removeable = false;
}
int
pidfile_unlock(void)
{
int error;
if (pf_fd == -1) {
error = -1;
errno = EBADF;
} else {
error = close(pf_fd);
pf_fd = -1;
}
free(pf_path);
pf_path = NULL;
return error;
}
int
pidfile_clean(void)
{
int error;
pid_t pid;
if (pf_fd == -1) {
errno = EBADF;
return -1;
}
pid = pidfile_readfd(pf_fd);
if (pid == -1)
error = errno;
else if (pid != getpid())
error = EPERM;
else if (ftruncate(pf_fd, 0) == -1)
error = errno;
else if (pf_removeable && unlink(pf_path) == -1)
error = errno;
else
error = 0;
(void)pidfile_unlock();
if (error != 0) {
errno = error;
return -1;
}
return 0;
}
static void
pidfile_cleanup(void)
{
(void)pidfile_clean();
}
static int
pidfile_varrun_path(char **path, const char *bname)
{
if (bname == NULL)
bname = getprogname();
return asprintf(path, "%s%s.pid", _PATH_VARRUN, bname);
}
pid_t
pidfile_read(const char *path)
{
char *dpath = NULL;
int fd;
pid_t pid = -1;
if (path == NULL && pf_path != NULL)
path = pf_path;
if (path == NULL || strchr(path, '/') == NULL) {
if (pidfile_varrun_path(&dpath, path) == -1)
goto out;
path = dpath;
}
if (pf_fd != -1 && strcmp(path, pf_path) == 0)
fd = pf_fd;
else if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1)
goto out;
pid = pidfile_readfd(fd);
if (fd != pf_fd)
(void)close(fd);
out:
free(dpath);
return pid;
}
pid_t
pidfile_lock(const char *path)
{
char *dpath = NULL;
static bool registered_atexit = false;
pid_t pid = -1;
if (!registered_atexit) {
if (atexit(pidfile_cleanup) == -1)
goto out;
registered_atexit = true;
}
if (path == NULL || strchr(path, '/') == NULL) {
if (pidfile_varrun_path(&dpath, path) == -1)
goto out;
path = dpath;
}
if (pf_fd != -1 && strcmp(pf_path, path) != 0)
(void)pidfile_clean();
if (pf_fd == -1) {
int fd, opts;
opts = O_RDWR | O_CREAT | O_NONBLOCK;
#ifdef O_CLOEXEC
opts |= O_CLOEXEC;
#endif
#ifdef O_EXLOCK
opts |= O_EXLOCK;
#endif
if ((fd = open(path, opts, 0644)) == -1)
goto return_pid;
#ifndef O_CLOEXEC
if ((opts = fcntl(fd, F_GETFD)) == -1 ||
fcntl(fd, F_SETFD, opts | FD_CLOEXEC) == -1) {
int error = errno;
(void)close(fd);
errno = error;
goto out;
}
#endif
#ifndef O_EXLOCK
if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
int error = errno;
(void)close(fd);
if (error != EAGAIN) {
errno = error;
goto out;
}
fd = -1;
}
#endif
return_pid:
if (fd == -1) {
if (errno == EAGAIN) {
if ((pid = pidfile_read(path)) != -1)
errno = EEXIST;
else
errno = EAGAIN;
} else
pid = -1;
goto out;
}
pf_fd = fd;
if (path == dpath) {
pf_path = dpath;
dpath = NULL;
} else {
pf_path = strdup(path);
if (pf_path == NULL) {
int error = errno;
(void)close(pf_fd);
pf_fd = -1;
errno = error;
goto out;
}
}
}
if (ftruncate(pf_fd, 0) == -1 || lseek(pf_fd, 0, SEEK_SET) == -1 ||
dprintf(pf_fd, "%ld\n", (long)getpid()) == -1) {
int error = errno;
(void)pidfile_clean();
errno = error;
goto out;
}
pid = 0;
out:
free(dpath);
return pid;
}
int
pidfile(const char *path)
{
pid_t pid;
pid = pidfile_lock(path);
return pid == 0 ? 0 : -1;
}