xref: /freebsd/lib/libutil/tests/pidfile_test.c (revision 4db78cacdee1b6f3b7880eb8c5560e8edaf91698)
1 /*-
2  * Copyright (c) 2007-2009 Dag-Erling Coïdan Smørgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/wait.h>
33 #include <sys/event.h>
34 
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <signal.h>
38 #include <stdint.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 #include <libutil.h>
45 
46 /*
47  * We need a signal handler so kill(2) will interrupt the child
48  * instead of killing it.
49  */
50 static void
51 signal_handler(int sig)
52 {
53 	(void)sig;
54 }
55 
56 /*
57  * Test that pidfile_open() can create a pidfile and that pidfile_write()
58  * can write to it.
59  */
60 static const char *
61 test_pidfile_uncontested(void)
62 {
63 	const char *fn = "test_pidfile_uncontested";
64 	struct pidfh *pf;
65 	pid_t other = 0;
66 
67 	unlink(fn);
68 	pf = pidfile_open(fn, 0600, &other);
69 	if (pf == NULL && other != 0)
70 		return ("pidfile exists and is locked");
71 	if (pf == NULL)
72 		return (strerror(errno));
73 	if (pidfile_write(pf) != 0) {
74 		pidfile_close(pf);
75 		unlink(fn);
76 		return ("failed to write PID");
77 	}
78 	pidfile_close(pf);
79 	unlink(fn);
80 	return (NULL);
81 }
82 
83 /*
84  * Test that pidfile_open() locks against self.
85  */
86 static const char *
87 test_pidfile_self(void)
88 {
89 	const char *fn = "test_pidfile_self";
90 	struct pidfh *pf1, *pf2;
91 	pid_t other = 0;
92 	int serrno;
93 
94 	unlink(fn);
95 	pf1 = pidfile_open(fn, 0600, &other);
96 	if (pf1 == NULL && other != 0)
97 		return ("pidfile exists and is locked");
98 	if (pf1 == NULL)
99 		return (strerror(errno));
100 	if (pidfile_write(pf1) != 0) {
101 		serrno = errno;
102 		pidfile_close(pf1);
103 		unlink(fn);
104 		return (strerror(serrno));
105 	}
106 	// second open should fail
107 	pf2 = pidfile_open(fn, 0600, &other);
108 	if (pf2 != NULL) {
109 		pidfile_close(pf1);
110 		pidfile_close(pf2);
111 		unlink(fn);
112 		return ("managed to opened pidfile twice");
113 	}
114 	if (other != getpid()) {
115 		pidfile_close(pf1);
116 		unlink(fn);
117 		return ("pidfile contained wrong PID");
118 	}
119 	pidfile_close(pf1);
120 	unlink(fn);
121 	return (NULL);
122 }
123 
124 /*
125  * Common code for test_pidfile_{contested,inherited}.
126  */
127 static const char *
128 common_test_pidfile_child(const char *fn, int parent_open)
129 {
130 	struct pidfh *pf = NULL;
131 	pid_t other = 0, pid = 0;
132 	int fd[2], serrno, status;
133 	struct kevent event, ke;
134 	char ch;
135 	int kq;
136 
137 	unlink(fn);
138 	if (pipe(fd) != 0)
139 		return (strerror(errno));
140 
141 	if (parent_open) {
142 		pf = pidfile_open(fn, 0600, &other);
143 		if (pf == NULL && other != 0)
144 			return ("pidfile exists and is locked");
145 		if (pf == NULL)
146 			return (strerror(errno));
147 	}
148 
149 	pid = fork();
150 	if (pid == -1)
151 		return (strerror(errno));
152 	if (pid == 0) {
153 		// child
154 		close(fd[0]);
155 		signal(SIGINT, signal_handler);
156 		if (!parent_open) {
157 			pf = pidfile_open(fn, 0600, &other);
158 			if (pf == NULL && other != 0)
159 				return ("pidfile exists and is locked");
160 			if (pf == NULL)
161 				return (strerror(errno));
162 		}
163 		if (pidfile_write(pf) != 0) {
164 			serrno = errno;
165 			pidfile_close(pf);
166 			unlink(fn);
167 			return (strerror(serrno));
168 		}
169 		if (pf == NULL)
170 			_exit(1);
171 		if (pidfile_write(pf) != 0)
172 			_exit(2);
173 		kq = kqueue();
174 		if (kq == -1)
175 			_exit(3);
176 		EV_SET(&ke, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
177 		/* Attach event to the kqueue. */
178 		if (kevent(kq, &ke, 1, NULL, 0, NULL) != 0)
179 			_exit(4);
180 		/* Inform the parent we are ready to receive SIGINT */
181 		if (write(fd[1], "*", 1) != 1)
182 			_exit(5);
183 		/* Wait for SIGINT received */
184 		if (kevent(kq, NULL, 0, &event, 1, NULL) != 1)
185 			_exit(6);
186 		_exit(0);
187 	}
188 	// parent
189 	close(fd[1]);
190 	if (pf)
191 		pidfile_close(pf);
192 
193 	// wait for the child to signal us
194 	if (read(fd[0], &ch, 1) != 1) {
195 		serrno = errno;
196 		unlink(fn);
197 		kill(pid, SIGTERM);
198 		errno = serrno;
199 		return (strerror(errno));
200 	}
201 
202 	// We shouldn't be able to lock the same pidfile as our child
203 	pf = pidfile_open(fn, 0600, &other);
204 	if (pf != NULL) {
205 		pidfile_close(pf);
206 		unlink(fn);
207 		return ("managed to lock contested pidfile");
208 	}
209 
210 	// Failed to lock, but not because it was contested
211 	if (other == 0) {
212 		unlink(fn);
213 		return (strerror(errno));
214 	}
215 
216 	// Locked by the wrong process
217 	if (other != pid) {
218 		unlink(fn);
219 		return ("pidfile contained wrong PID");
220 	}
221 
222 	// check our child's fate
223 	if (pf)
224 		pidfile_close(pf);
225 	unlink(fn);
226 	if (kill(pid, SIGINT) != 0)
227 		return (strerror(errno));
228 	if (waitpid(pid, &status, 0) == -1)
229 		return (strerror(errno));
230 	if (WIFSIGNALED(status))
231 		return ("child caught signal");
232 	if (WEXITSTATUS(status) != 0)
233 		return ("child returned non-zero status");
234 
235 	// success
236 	return (NULL);
237 }
238 
239 /*
240  * Test that pidfile_open() fails when attempting to open a pidfile that
241  * is already locked, and that it returns the correct PID.
242  */
243 static const char *
244 test_pidfile_contested(void)
245 {
246 	const char *fn = "test_pidfile_contested";
247 	const char *result;
248 
249 	result = common_test_pidfile_child(fn, 0);
250 	return (result);
251 }
252 
253 /*
254  * Test that the pidfile lock is inherited.
255  */
256 static const char *
257 test_pidfile_inherited(void)
258 {
259 	const char *fn = "test_pidfile_inherited";
260 	const char *result;
261 
262 	result = common_test_pidfile_child(fn, 1);
263 	return (result);
264 }
265 
266 static struct test {
267 	const char *name;
268 	const char *(*func)(void);
269 } t[] = {
270 	{ "pidfile_uncontested", test_pidfile_uncontested },
271 	{ "pidfile_self", test_pidfile_self },
272 	{ "pidfile_contested", test_pidfile_contested },
273 	{ "pidfile_inherited", test_pidfile_inherited },
274 };
275 
276 int
277 main(void)
278 {
279 	const char *result;
280 	int i, nt;
281 
282 	nt = sizeof(t) / sizeof(*t);
283 	printf("1..%d\n", nt);
284 	for (i = 0; i < nt; ++i) {
285 		if ((result = t[i].func()) != NULL)
286 			printf("not ok %d - %s # %s\n", i + 1,
287 			    t[i].name, result);
288 		else
289 			printf("ok %d - %s\n", i + 1,
290 			    t[i].name);
291 	}
292 	exit(0);
293 }
294