Home | History | Annotate | Line # | Download | only in libutil
      1 /*	$NetBSD: pidfile.c,v 1.20 2026/06/11 21:13:47 roy Exp $	*/
      2 
      3 /*-
      4  * SPDX-License-Identifier: BSD-2-Clause
      5  * Copyright (c) 1999, 2016, 2026 The NetBSD Foundation, Inc.
      6  * All rights reserved.
      7  *
      8  * This code is derived from software contributed to The NetBSD Foundation
      9  * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples.
     10  *
     11  * Redistribution and use in source and binary forms, with or without
     12  * modification, are permitted provided that the following conditions
     13  * are met:
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  * 2. Redistributions in binary form must reproduce the above copyright
     17  *    notice, this list of conditions and the following disclaimer in the
     18  *    documentation and/or other materials provided with the distribution.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 #include <sys/param.h>
     34 #include <sys/file.h>
     35 #include <sys/stat.h>
     36 
     37 #include <errno.h>
     38 #include <fcntl.h>
     39 #include <inttypes.h>
     40 #include <limits.h>
     41 #include <paths.h>
     42 #include <stdbool.h>
     43 #include <stdio.h>
     44 #include <stdlib.h>
     45 #include <string.h>
     46 #include <unistd.h>
     47 
     48 #ifdef PIDFILE_LOCAL
     49 #include "pidfile.h"
     50 #else
     51 #include <util.h>
     52 #endif
     53 
     54 #ifdef __RCSID
     55 __RCSID("$NetBSD: pidfile.c,v 1.20 2026/06/11 21:13:47 roy Exp $");
     56 #endif
     57 
     58 static char *pf_path;
     59 static int pf_fd = -1;
     60 static bool pf_removeable = true;
     61 
     62 /* Reads a pid from a file descriptor */
     63 static pid_t
     64 pidfile_readfd(int fd)
     65 {
     66 	char buf[16], *eptr;
     67 	int error;
     68 	ssize_t n;
     69 	pid_t pid = -1;
     70 
     71 	if (lseek(fd, 0, SEEK_SET) == -1)
     72 		return -1;
     73 
     74 	n = read(fd, buf, sizeof(buf) - 1);
     75 	if (n == -1)
     76 		return -1;
     77 
     78 	buf[n] = '\0';
     79 	pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error);
     80 	if (error != 0 && !(error == ENOTSUP && *eptr == '\n')) {
     81 		errno = error;
     82 		return -1;
     83 	}
     84 
     85 	return pid;
     86 }
     87 
     88 int
     89 pidfile_fd(void)
     90 {
     91 
     92 	return pf_fd;
     93 }
     94 
     95 const char *
     96 pidfile_path(void)
     97 {
     98 
     99 	return pf_path;
    100 }
    101 
    102 void
    103 pidfile_unremoveable(void)
    104 {
    105 
    106 	pf_removeable = false;
    107 }
    108 
    109 /* Releases pidfile resources.
    110  *
    111  * Returns 0 on success, otherwise -1. */
    112 int
    113 pidfile_unlock(void)
    114 {
    115 	int error;
    116 
    117 	if (pf_fd == -1) {
    118 		error = -1;
    119 		errno = EBADF;
    120 	} else {
    121 		error = close(pf_fd);
    122 		pf_fd = -1;
    123 	}
    124 	free(pf_path);
    125 	pf_path = NULL;
    126 	return error;
    127 }
    128 
    129 /* Truncate, close and unlink an existent pidfile,
    130  * if and only if it was created by this process.
    131  * The pidfile is truncated because we may have dropped permissions
    132  * or entered a chroot and thus unable to unlink it.
    133  *
    134  * Returns 0 on success, otherwise -1. */
    135 int
    136 pidfile_clean(void)
    137 {
    138 	int error;
    139 	pid_t pid;
    140 
    141 	if (pf_fd == -1) {
    142 		errno = EBADF;
    143 		return -1;
    144 	}
    145 
    146 	pid = pidfile_readfd(pf_fd);
    147 	if (pid == -1)
    148 		error = errno;
    149 	else if (pid != getpid())
    150 		error = EPERM;
    151 	else if (ftruncate(pf_fd, 0) == -1)
    152 		error = errno;
    153 	else if (pf_removeable && unlink(pf_path) == -1)
    154 		error = errno;
    155 	else
    156 		error = 0;
    157 
    158 	(void)pidfile_unlock();
    159 
    160 	if (error != 0) {
    161 		errno = error;
    162 		return -1;
    163 	}
    164 	return 0;
    165 }
    166 
    167 /* atexit shim for pidfile_clean */
    168 static void
    169 pidfile_cleanup(void)
    170 {
    171 
    172 	(void)pidfile_clean();
    173 }
    174 
    175 /* Constructs a name for a pidfile in the default location (/var/run).
    176  * If 'bname' is NULL, uses the name of the current program for the name of
    177  * the pidfile.
    178  *
    179  * Returns 0 on success, otherwise -1. */
    180 static int
    181 pidfile_varrun_path(char **path, const char *bname)
    182 {
    183 
    184 	if (bname == NULL)
    185 		bname = getprogname();
    186 
    187 	/* _PATH_VARRUN includes trailing / */
    188 	return asprintf(path, "%s%s.pid", _PATH_VARRUN, bname);
    189 }
    190 
    191 /* Returns the process ID inside path on success, otherwise -1.
    192  * If no path is given, use the last pidfile path, otherwise the default one. */
    193 pid_t
    194 pidfile_read(const char *path)
    195 {
    196 	char *dpath = NULL;
    197 	int fd;
    198 	pid_t pid = -1;
    199 
    200 	if (path == NULL && pf_path != NULL)
    201 		path = pf_path;
    202 	if (path == NULL || strchr(path, '/') == NULL) {
    203 		if (pidfile_varrun_path(&dpath, path) == -1)
    204 			goto out;
    205 		path = dpath;
    206 	}
    207 
    208 	if (pf_fd != -1 && strcmp(path, pf_path) == 0)
    209 		fd = pf_fd;
    210 	else if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1)
    211 		goto out;
    212 
    213 	pid = pidfile_readfd(fd);
    214 	if (fd != pf_fd)
    215 		(void)close(fd);
    216 
    217 out:
    218 	free(dpath);
    219 	return pid;
    220 }
    221 
    222 /* Locks the pidfile specified by path and writes the process pid to it.
    223  * The new pidfile is "registered" in the global variables pf_fd,
    224  * pf_path and so that any further call to pidfile_lock(3)
    225  * can check if we are recreating the same file or a new one.
    226  *
    227  * Returns 0 on success, otherwise the pid of the process who owns the
    228  * lock if it can be read, otherwise -1. */
    229 pid_t
    230 pidfile_lock(const char *path)
    231 {
    232 	char *dpath = NULL;
    233 	static bool registered_atexit = false;
    234 	pid_t pid = -1;
    235 
    236 	/* Register for cleanup with atexit. */
    237 	if (!registered_atexit) {
    238 		if (atexit(pidfile_cleanup) == -1)
    239 			goto out;
    240 		registered_atexit = true;
    241 	}
    242 
    243 	if (path == NULL || strchr(path, '/') == NULL) {
    244 		if (pidfile_varrun_path(&dpath, path) == -1)
    245 			goto out;
    246 		path = dpath;
    247 	}
    248 
    249 	/* If path has changed (no good reason), clean up the old pidfile. */
    250 	if (pf_fd != -1 && strcmp(pf_path, path) != 0)
    251 		(void)pidfile_clean();
    252 
    253 	if (pf_fd == -1) {
    254 		int fd, opts;
    255 
    256 		opts = O_RDWR | O_CREAT | O_NONBLOCK;
    257 #ifdef O_CLOEXEC
    258 		opts |= O_CLOEXEC;
    259 #endif
    260 #ifdef O_EXLOCK
    261 		opts |= O_EXLOCK;
    262 #endif
    263 		if ((fd = open(path, opts, 0644)) == -1)
    264 			goto return_pid;
    265 
    266 #ifndef O_CLOEXEC
    267 		if ((opts = fcntl(fd, F_GETFD)) == -1 ||
    268 		    fcntl(fd, F_SETFD, opts | FD_CLOEXEC) == -1) {
    269 			int error = errno;
    270 
    271 			(void)close(fd);
    272 			errno = error;
    273 			goto out;
    274 		}
    275 #endif
    276 #ifndef O_EXLOCK
    277 		if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
    278 			int error = errno;
    279 
    280 			(void)close(fd);
    281 			if (error != EAGAIN) {
    282 				errno = error;
    283 				goto out;
    284 			}
    285 			fd = -1;
    286 		}
    287 #endif
    288 
    289 return_pid:
    290 		if (fd == -1) {
    291 			if (errno == EAGAIN) {
    292 				/* The pidfile is locked, return the process ID
    293 				 * it contains.
    294 				 * If successful, set errno to EEXIST.
    295 				 * Otherwise set EAGAIN to show it's locked,
    296 				 * as that is more important than why a pid
    297 				 * cannot be read from the locked file. */
    298 				if ((pid = pidfile_read(path)) != -1)
    299 					errno = EEXIST;
    300 				else
    301 					errno = EAGAIN;
    302 			} else
    303 				pid = -1;
    304 
    305 			goto out;
    306 		}
    307 
    308 		pf_fd = fd;
    309 		if (path == dpath) {
    310 			pf_path = dpath;
    311 			dpath = NULL;
    312 		} else {
    313 			pf_path = strdup(path);
    314 			if (pf_path == NULL) {
    315 				int error = errno;
    316 
    317 				(void)close(pf_fd);
    318 				pf_fd = -1;
    319 				errno = error;
    320 				goto out;
    321 			}
    322 		}
    323 	}
    324 
    325 	/* Truncate the file, as we could be re-writing it.
    326 	 * Then write the process ID. */
    327 	if (ftruncate(pf_fd, 0) == -1 || lseek(pf_fd, 0, SEEK_SET) == -1 ||
    328 	    dprintf(pf_fd, "%ld\n", (long)getpid()) == -1) {
    329 		int error = errno;
    330 
    331 		(void)pidfile_clean();
    332 		errno = error;
    333 		goto out;
    334 	}
    335 
    336 	/* Hold the fd open to persist the lock. */
    337 	pid = 0;
    338 
    339 out:
    340 	free(dpath);
    341 	return pid;
    342 }
    343 
    344 /* The old function.
    345  * Historical behaviour is that pidfile is not re-written
    346  * if path has not changed.
    347  *
    348  * Returns 0 on success, otherwise -1.
    349  * As such we have no way of knowing the process ID who owns the lock. */
    350 int
    351 pidfile(const char *path)
    352 {
    353 	pid_t pid;
    354 
    355 	pid = pidfile_lock(path);
    356 	return pid == 0 ? 0 : -1;
    357 }
    358