xref: /dragonfly/contrib/dhcpcd/compat/pidfile.c (revision f0e61bb7)
1 /*	$NetBSD: pidfile.c,v 1.16 2021/08/01 15:29:29 andvar Exp $	*/
2 
3 /*-
4  * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/param.h>
33 
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <inttypes.h>
37 #include <limits.h>
38 #include <paths.h>
39 #include <stdbool.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #include <sys/file.h>	/* for flock(2) */
46 #include "config.h"
47 #include "defs.h"
48 
49 static pid_t pidfile_pid;
50 static char pidfile_path[PATH_MAX];
51 static int pidfile_fd = -1;
52 
53 /* Closes pidfile resources.
54  *
55  * Returns 0 on success, otherwise -1. */
56 static int
57 pidfile_close(void)
58 {
59 	int error;
60 
61 	pidfile_pid = 0;
62 	error = close(pidfile_fd);
63 	pidfile_fd = -1;
64 	pidfile_path[0] = '\0';
65 	return error;
66 }
67 
68 /* Truncate, close and unlink an existent pidfile,
69  * if and only if it was created by this process.
70  * The pidfile is truncated because we may have dropped permissions
71  * or entered a chroot and thus unable to unlink it.
72  *
73  * Returns 0 on truncation success, otherwise -1. */
74 int
75 pidfile_clean(void)
76 {
77 	int error;
78 
79 	if (pidfile_fd == -1) {
80 		errno = EBADF;
81 		return -1;
82 	}
83 
84 	if (pidfile_pid != getpid())
85 		error = EPERM;
86 	else if (ftruncate(pidfile_fd, 0) == -1)
87 		error = errno;
88 	else {
89 #ifndef HAVE_PLEDGE /* Avoid a pledge violating segfault. */
90 		(void)unlink(pidfile_path);
91 #endif
92 		error = 0;
93 	}
94 
95 	(void) pidfile_close();
96 
97 	if (error != 0) {
98 		errno = error;
99 		return -1;
100 	}
101 	return 0;
102 }
103 
104 /* atexit shim for pidfile_clean */
105 static void
106 pidfile_cleanup(void)
107 {
108 
109 	pidfile_clean();
110 }
111 
112 /* Constructs a name for a pidfile in the default location (/var/run).
113  * If 'bname' is NULL, uses the name of the current program for the name of
114  * the pidfile.
115  *
116  * Returns 0 on success, otherwise -1. */
117 static int
118 pidfile_varrun_path(char *path, size_t len, const char *bname)
119 {
120 
121 	if (bname == NULL)
122 		bname = PACKAGE;
123 
124 	/* _PATH_VARRUN includes trailing / */
125 	if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len)
126 	{
127 		errno = ENAMETOOLONG;
128 		return -1;
129 	}
130 	return 0;
131 }
132 
133 /* Returns the process ID inside path on success, otherwise -1.
134  * If no path is given, use the last pidfile path, otherwise the default one. */
135 pid_t
136 pidfile_read(const char *path)
137 {
138 	char dpath[PATH_MAX], buf[16], *eptr;
139 	int fd, error;
140 	ssize_t n;
141 	pid_t pid;
142 
143 	if (path == NULL && pidfile_path[0] != '\0')
144 		path = pidfile_path;
145 	if (path == NULL || strchr(path, '/') == NULL) {
146 		if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1)
147 			return -1;
148 		path = dpath;
149 	}
150 
151 	if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1)
152 		return  -1;
153 	n = read(fd, buf, sizeof(buf) - 1);
154 	error = errno;
155 	(void) close(fd);
156 	if (n == -1) {
157 		errno = error;
158 		return -1;
159 	}
160 	buf[n] = '\0';
161 	pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error);
162 	if (error && !(error == ENOTSUP && *eptr == '\n')) {
163 		errno = error;
164 		return -1;
165 	}
166 	return pid;
167 }
168 
169 /* Locks the pidfile specified by path and writes the process pid to it.
170  * The new pidfile is "registered" in the global variables pidfile_fd,
171  * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3)
172  * can check if we are recreating the same file or a new one.
173  *
174  * Returns 0 on success, otherwise the pid of the process who owns the
175  * lock if it can be read, otherwise -1. */
176 pid_t
177 pidfile_lock(const char *path)
178 {
179 	char dpath[PATH_MAX];
180 	static bool registered_atexit = false;
181 
182 	/* Register for cleanup with atexit. */
183 	if (!registered_atexit) {
184 		if (atexit(pidfile_cleanup) == -1)
185 			return -1;
186 		registered_atexit = true;
187 	}
188 
189 	if (path == NULL || strchr(path, '/') == NULL) {
190 		if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1)
191 			return -1;
192 		path = dpath;
193 	}
194 
195 	/* If path has changed (no good reason), clean up the old pidfile. */
196 	if (pidfile_fd != -1 && strcmp(pidfile_path, path) != 0)
197 		pidfile_clean();
198 
199 	if (pidfile_fd == -1) {
200 		int fd, opts;
201 
202 		opts = O_WRONLY | O_CREAT | O_NONBLOCK;
203 #ifdef O_CLOEXEC
204 		opts |= O_CLOEXEC;
205 #endif
206 #ifdef O_EXLOCK
207 		opts |=	O_EXLOCK;
208 #endif
209 		if ((fd = open(path, opts, 0644)) == -1)
210 			goto return_pid;
211 #ifndef O_CLOEXEC
212 		if ((opts = fcntl(fd, F_GETFD)) == -1 ||
213 		    fcntl(fd, F_SETFL, opts | FD_CLOEXEC) == -1)
214 		{
215 			int error = errno;
216 
217 			(void) close(fd);
218 			errno = error;
219 			return -1;
220 		}
221 #endif
222 #ifndef O_EXLOCK
223 		if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
224 			int error = errno;
225 
226 			(void) close(fd);
227 			if (error != EAGAIN) {
228 				errno = error;
229 				return -1;
230 			}
231 			fd = -1;
232 		}
233 #endif
234 
235 return_pid:
236 		if (fd == -1) {
237 			pid_t pid;
238 
239 			if (errno == EAGAIN) {
240 				/* The pidfile is locked, return the process ID
241 				 * it contains.
242 				 * If successful, set errno to EEXIST. */
243 				if ((pid = pidfile_read(path)) != -1)
244 					errno = EEXIST;
245 			} else
246 				pid = -1;
247 
248 			return pid;
249 		}
250 		pidfile_fd = fd;
251 		strlcpy(pidfile_path, path, sizeof(pidfile_path));
252 	}
253 
254 	pidfile_pid = getpid();
255 
256 	/* Truncate the file, as we could be re-writing it.
257 	 * Then write the process ID. */
258 	if (ftruncate(pidfile_fd, 0) == -1 ||
259 	    lseek(pidfile_fd, 0, SEEK_SET) == -1 ||
260 	    dprintf(pidfile_fd, "%d\n", pidfile_pid) == -1)
261 	{
262 		int error = errno;
263 
264 		pidfile_cleanup();
265 		errno = error;
266 		return -1;
267 	}
268 
269 	/* Hold the fd open to persist the lock. */
270 	return 0;
271 }
272