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