1*f3744ac9SRoy Marples /* $NetBSD: pidfile.c,v 1.16 2021/08/01 15:29:29 andvar Exp $ */
27827cba2SAaron LI
37827cba2SAaron LI /*-
47827cba2SAaron LI * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc.
57827cba2SAaron LI * All rights reserved.
67827cba2SAaron LI *
77827cba2SAaron LI * This code is derived from software contributed to The NetBSD Foundation
87827cba2SAaron LI * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples.
97827cba2SAaron LI *
107827cba2SAaron LI * Redistribution and use in source and binary forms, with or without
117827cba2SAaron LI * modification, are permitted provided that the following conditions
127827cba2SAaron LI * are met:
137827cba2SAaron LI * 1. Redistributions of source code must retain the above copyright
147827cba2SAaron LI * notice, this list of conditions and the following disclaimer.
157827cba2SAaron LI * 2. Redistributions in binary form must reproduce the above copyright
167827cba2SAaron LI * notice, this list of conditions and the following disclaimer in the
177827cba2SAaron LI * documentation and/or other materials provided with the distribution.
187827cba2SAaron LI *
197827cba2SAaron LI * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
207827cba2SAaron LI * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
217827cba2SAaron LI * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
227827cba2SAaron LI * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
237827cba2SAaron LI * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
247827cba2SAaron LI * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
257827cba2SAaron LI * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
267827cba2SAaron LI * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
277827cba2SAaron LI * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
287827cba2SAaron LI * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
297827cba2SAaron LI * POSSIBILITY OF SUCH DAMAGE.
307827cba2SAaron LI */
317827cba2SAaron LI
327827cba2SAaron LI #include <sys/param.h>
337827cba2SAaron LI
347827cba2SAaron LI #include <errno.h>
357827cba2SAaron LI #include <fcntl.h>
367827cba2SAaron LI #include <inttypes.h>
377827cba2SAaron LI #include <limits.h>
387827cba2SAaron LI #include <paths.h>
397827cba2SAaron LI #include <stdbool.h>
407827cba2SAaron LI #include <stdlib.h>
417827cba2SAaron LI #include <stdio.h>
427827cba2SAaron LI #include <string.h>
437827cba2SAaron LI #include <unistd.h>
447827cba2SAaron LI
457827cba2SAaron LI #include <sys/file.h> /* for flock(2) */
467827cba2SAaron LI #include "config.h"
477827cba2SAaron LI #include "defs.h"
487827cba2SAaron LI
497827cba2SAaron LI static pid_t pidfile_pid;
507827cba2SAaron LI static char pidfile_path[PATH_MAX];
517827cba2SAaron LI static int pidfile_fd = -1;
527827cba2SAaron LI
537827cba2SAaron LI /* Closes pidfile resources.
547827cba2SAaron LI *
557827cba2SAaron LI * Returns 0 on success, otherwise -1. */
567827cba2SAaron LI static int
pidfile_close(void)577827cba2SAaron LI pidfile_close(void)
587827cba2SAaron LI {
597827cba2SAaron LI int error;
607827cba2SAaron LI
617827cba2SAaron LI pidfile_pid = 0;
627827cba2SAaron LI error = close(pidfile_fd);
637827cba2SAaron LI pidfile_fd = -1;
647827cba2SAaron LI pidfile_path[0] = '\0';
657827cba2SAaron LI return error;
667827cba2SAaron LI }
677827cba2SAaron LI
687827cba2SAaron LI /* Truncate, close and unlink an existent pidfile,
697827cba2SAaron LI * if and only if it was created by this process.
707827cba2SAaron LI * The pidfile is truncated because we may have dropped permissions
717827cba2SAaron LI * or entered a chroot and thus unable to unlink it.
727827cba2SAaron LI *
737827cba2SAaron LI * Returns 0 on truncation success, otherwise -1. */
747827cba2SAaron LI int
pidfile_clean(void)757827cba2SAaron LI pidfile_clean(void)
767827cba2SAaron LI {
777827cba2SAaron LI int error;
787827cba2SAaron LI
797827cba2SAaron LI if (pidfile_fd == -1) {
807827cba2SAaron LI errno = EBADF;
817827cba2SAaron LI return -1;
827827cba2SAaron LI }
837827cba2SAaron LI
847827cba2SAaron LI if (pidfile_pid != getpid())
857827cba2SAaron LI error = EPERM;
867827cba2SAaron LI else if (ftruncate(pidfile_fd, 0) == -1)
877827cba2SAaron LI error = errno;
887827cba2SAaron LI else {
89d4fb1e02SRoy Marples #ifndef HAVE_PLEDGE /* Avoid a pledge violating segfault. */
907827cba2SAaron LI (void)unlink(pidfile_path);
91d4fb1e02SRoy Marples #endif
927827cba2SAaron LI error = 0;
937827cba2SAaron LI }
947827cba2SAaron LI
957827cba2SAaron LI (void) pidfile_close();
967827cba2SAaron LI
977827cba2SAaron LI if (error != 0) {
987827cba2SAaron LI errno = error;
997827cba2SAaron LI return -1;
1007827cba2SAaron LI }
1017827cba2SAaron LI return 0;
1027827cba2SAaron LI }
1037827cba2SAaron LI
1047827cba2SAaron LI /* atexit shim for pidfile_clean */
1057827cba2SAaron LI static void
pidfile_cleanup(void)1067827cba2SAaron LI pidfile_cleanup(void)
1077827cba2SAaron LI {
1087827cba2SAaron LI
1097827cba2SAaron LI pidfile_clean();
1107827cba2SAaron LI }
1117827cba2SAaron LI
1127827cba2SAaron LI /* Constructs a name for a pidfile in the default location (/var/run).
1137827cba2SAaron LI * If 'bname' is NULL, uses the name of the current program for the name of
1147827cba2SAaron LI * the pidfile.
1157827cba2SAaron LI *
1167827cba2SAaron LI * Returns 0 on success, otherwise -1. */
1177827cba2SAaron LI static int
pidfile_varrun_path(char * path,size_t len,const char * bname)1187827cba2SAaron LI pidfile_varrun_path(char *path, size_t len, const char *bname)
1197827cba2SAaron LI {
1207827cba2SAaron LI
1217827cba2SAaron LI if (bname == NULL)
1227827cba2SAaron LI bname = PACKAGE;
1237827cba2SAaron LI
1247827cba2SAaron LI /* _PATH_VARRUN includes trailing / */
1257827cba2SAaron LI if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len)
1267827cba2SAaron LI {
1277827cba2SAaron LI errno = ENAMETOOLONG;
1287827cba2SAaron LI return -1;
1297827cba2SAaron LI }
1307827cba2SAaron LI return 0;
1317827cba2SAaron LI }
1327827cba2SAaron LI
1337827cba2SAaron LI /* Returns the process ID inside path on success, otherwise -1.
134*f3744ac9SRoy Marples * If no path is given, use the last pidfile path, otherwise the default one. */
1357827cba2SAaron LI pid_t
pidfile_read(const char * path)1367827cba2SAaron LI pidfile_read(const char *path)
1377827cba2SAaron LI {
1387827cba2SAaron LI char dpath[PATH_MAX], buf[16], *eptr;
1397827cba2SAaron LI int fd, error;
1407827cba2SAaron LI ssize_t n;
1417827cba2SAaron LI pid_t pid;
1427827cba2SAaron LI
1437827cba2SAaron LI if (path == NULL && pidfile_path[0] != '\0')
1447827cba2SAaron LI path = pidfile_path;
1457827cba2SAaron LI if (path == NULL || strchr(path, '/') == NULL) {
1467827cba2SAaron LI if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1)
1477827cba2SAaron LI return -1;
1487827cba2SAaron LI path = dpath;
1497827cba2SAaron LI }
1507827cba2SAaron LI
1517827cba2SAaron LI if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1)
1527827cba2SAaron LI return -1;
1537827cba2SAaron LI n = read(fd, buf, sizeof(buf) - 1);
1547827cba2SAaron LI error = errno;
1557827cba2SAaron LI (void) close(fd);
1567827cba2SAaron LI if (n == -1) {
1577827cba2SAaron LI errno = error;
1587827cba2SAaron LI return -1;
1597827cba2SAaron LI }
1607827cba2SAaron LI buf[n] = '\0';
1617827cba2SAaron LI pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error);
1627827cba2SAaron LI if (error && !(error == ENOTSUP && *eptr == '\n')) {
1637827cba2SAaron LI errno = error;
1647827cba2SAaron LI return -1;
1657827cba2SAaron LI }
1667827cba2SAaron LI return pid;
1677827cba2SAaron LI }
1687827cba2SAaron LI
1697827cba2SAaron LI /* Locks the pidfile specified by path and writes the process pid to it.
1707827cba2SAaron LI * The new pidfile is "registered" in the global variables pidfile_fd,
1717827cba2SAaron LI * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3)
1727827cba2SAaron LI * can check if we are recreating the same file or a new one.
1737827cba2SAaron LI *
1747827cba2SAaron LI * Returns 0 on success, otherwise the pid of the process who owns the
1757827cba2SAaron LI * lock if it can be read, otherwise -1. */
1767827cba2SAaron LI pid_t
pidfile_lock(const char * path)1777827cba2SAaron LI pidfile_lock(const char *path)
1787827cba2SAaron LI {
1797827cba2SAaron LI char dpath[PATH_MAX];
1807827cba2SAaron LI static bool registered_atexit = false;
1817827cba2SAaron LI
1827827cba2SAaron LI /* Register for cleanup with atexit. */
1837827cba2SAaron LI if (!registered_atexit) {
1847827cba2SAaron LI if (atexit(pidfile_cleanup) == -1)
1857827cba2SAaron LI return -1;
1867827cba2SAaron LI registered_atexit = true;
1877827cba2SAaron LI }
1887827cba2SAaron LI
1897827cba2SAaron LI if (path == NULL || strchr(path, '/') == NULL) {
1907827cba2SAaron LI if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1)
1917827cba2SAaron LI return -1;
1927827cba2SAaron LI path = dpath;
1937827cba2SAaron LI }
1947827cba2SAaron LI
1957827cba2SAaron LI /* If path has changed (no good reason), clean up the old pidfile. */
1967827cba2SAaron LI if (pidfile_fd != -1 && strcmp(pidfile_path, path) != 0)
1977827cba2SAaron LI pidfile_clean();
1987827cba2SAaron LI
1997827cba2SAaron LI if (pidfile_fd == -1) {
2007827cba2SAaron LI int fd, opts;
2017827cba2SAaron LI
2027827cba2SAaron LI opts = O_WRONLY | O_CREAT | O_NONBLOCK;
2037827cba2SAaron LI #ifdef O_CLOEXEC
2047827cba2SAaron LI opts |= O_CLOEXEC;
2057827cba2SAaron LI #endif
2067827cba2SAaron LI #ifdef O_EXLOCK
2077827cba2SAaron LI opts |= O_EXLOCK;
2087827cba2SAaron LI #endif
2097827cba2SAaron LI if ((fd = open(path, opts, 0644)) == -1)
2107827cba2SAaron LI goto return_pid;
2117827cba2SAaron LI #ifndef O_CLOEXEC
2127827cba2SAaron LI if ((opts = fcntl(fd, F_GETFD)) == -1 ||
2136e63cc1fSRoy Marples fcntl(fd, F_SETFL, opts | FD_CLOEXEC) == -1)
2147827cba2SAaron LI {
2157827cba2SAaron LI int error = errno;
2167827cba2SAaron LI
2177827cba2SAaron LI (void) close(fd);
2187827cba2SAaron LI errno = error;
2197827cba2SAaron LI return -1;
2207827cba2SAaron LI }
2217827cba2SAaron LI #endif
2227827cba2SAaron LI #ifndef O_EXLOCK
2237827cba2SAaron LI if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
2247827cba2SAaron LI int error = errno;
2257827cba2SAaron LI
2267827cba2SAaron LI (void) close(fd);
2277827cba2SAaron LI if (error != EAGAIN) {
2287827cba2SAaron LI errno = error;
2297827cba2SAaron LI return -1;
2307827cba2SAaron LI }
2317827cba2SAaron LI fd = -1;
2327827cba2SAaron LI }
2337827cba2SAaron LI #endif
2347827cba2SAaron LI
2357827cba2SAaron LI return_pid:
2367827cba2SAaron LI if (fd == -1) {
2377827cba2SAaron LI pid_t pid;
2387827cba2SAaron LI
2397827cba2SAaron LI if (errno == EAGAIN) {
2407827cba2SAaron LI /* The pidfile is locked, return the process ID
2417827cba2SAaron LI * it contains.
242*f3744ac9SRoy Marples * If successful, set errno to EEXIST. */
2437827cba2SAaron LI if ((pid = pidfile_read(path)) != -1)
2447827cba2SAaron LI errno = EEXIST;
2457827cba2SAaron LI } else
2467827cba2SAaron LI pid = -1;
2477827cba2SAaron LI
2487827cba2SAaron LI return pid;
2497827cba2SAaron LI }
2507827cba2SAaron LI pidfile_fd = fd;
2517827cba2SAaron LI strlcpy(pidfile_path, path, sizeof(pidfile_path));
2527827cba2SAaron LI }
2537827cba2SAaron LI
2547827cba2SAaron LI pidfile_pid = getpid();
2557827cba2SAaron LI
2567827cba2SAaron LI /* Truncate the file, as we could be re-writing it.
2577827cba2SAaron LI * Then write the process ID. */
2587827cba2SAaron LI if (ftruncate(pidfile_fd, 0) == -1 ||
2597827cba2SAaron LI lseek(pidfile_fd, 0, SEEK_SET) == -1 ||
2607827cba2SAaron LI dprintf(pidfile_fd, "%d\n", pidfile_pid) == -1)
2617827cba2SAaron LI {
2627827cba2SAaron LI int error = errno;
2637827cba2SAaron LI
2647827cba2SAaron LI pidfile_cleanup();
2657827cba2SAaron LI errno = error;
2667827cba2SAaron LI return -1;
2677827cba2SAaron LI }
2687827cba2SAaron LI
2697827cba2SAaron LI /* Hold the fd open to persist the lock. */
2707827cba2SAaron LI return 0;
2717827cba2SAaron LI }
272