xref: /freebsd/lib/libutil/tests/pidfile_test.c (revision 7431dfd4580e850375fe5478d92ec770344db098)
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 
34 #include <fcntl.h>
35 #include <errno.h>
36 #include <signal.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include <libutil.h>
44 
45 /*
46  * We need a signal handler so kill(2) will interrupt our child's
47  * select(2) instead of killing it.
48  */
49 static void
50 signal_handler(int sig)
51 {
52 	(void)sig;
53 }
54 
55 /*
56  * Test that pidfile_open() can create a pidfile and that pidfile_write()
57  * can write to it.
58  */
59 static const char *
60 test_pidfile_uncontested(void)
61 {
62 	const char *fn = "test_pidfile_uncontested";
63 	struct pidfh *pf;
64 	pid_t other = 0;
65 
66 	unlink(fn);
67 	pf = pidfile_open(fn, 0600, &other);
68 	if (pf == NULL && other != 0)
69 		return ("pidfile exists and is locked");
70 	if (pf == NULL)
71 		return (strerror(errno));
72 	if (pidfile_write(pf) != 0) {
73 		pidfile_close(pf);
74 		unlink(fn);
75 		return ("failed to write PID");
76 	}
77 	pidfile_close(pf);
78 	unlink(fn);
79 	return (NULL);
80 }
81 
82 /*
83  * Test that pidfile_open() locks against self.
84  */
85 static const char *
86 test_pidfile_self(void)
87 {
88 	const char *fn = "test_pidfile_self";
89 	struct pidfh *pf1, *pf2;
90 	pid_t other = 0;
91 	int serrno;
92 
93 	unlink(fn);
94 	pf1 = pidfile_open(fn, 0600, &other);
95 	if (pf1 == NULL && other != 0)
96 		return ("pidfile exists and is locked");
97 	if (pf1 == NULL)
98 		return (strerror(errno));
99 	if (pidfile_write(pf1) != 0) {
100 		serrno = errno;
101 		pidfile_close(pf1);
102 		unlink(fn);
103 		return (strerror(serrno));
104 	}
105 	// second open should fail
106 	pf2 = pidfile_open(fn, 0600, &other);
107 	if (pf2 != NULL) {
108 		pidfile_close(pf1);
109 		pidfile_close(pf2);
110 		unlink(fn);
111 		return ("managed to opened pidfile twice");
112 	}
113 	if (other != getpid()) {
114 		pidfile_close(pf1);
115 		unlink(fn);
116 		return ("pidfile contained wrong PID");
117 	}
118 	pidfile_close(pf1);
119 	unlink(fn);
120 	return (NULL);
121 }
122 
123 /*
124  * Common code for test_pidfile_{contested,inherited}.
125  */
126 static const char *
127 common_test_pidfile_child(const char *fn, int parent_open)
128 {
129 	struct pidfh *pf = NULL;
130 	pid_t other = 0, pid = 0;
131 	int fd[2], serrno, status;
132 	char ch;
133 
134 	unlink(fn);
135 	if (pipe(fd) != 0)
136 		return (strerror(errno));
137 
138 	if (parent_open) {
139 		pf = pidfile_open(fn, 0600, &other);
140 		if (pf == NULL && other != 0)
141 			return ("pidfile exists and is locked");
142 		if (pf == NULL)
143 			return (strerror(errno));
144 	}
145 
146 	pid = fork();
147 	if (pid == -1)
148 		return (strerror(errno));
149 	if (pid == 0) {
150 		// child
151 		close(fd[0]);
152 		signal(SIGINT, signal_handler);
153 		if (!parent_open) {
154 			pf = pidfile_open(fn, 0600, &other);
155 			if (pf == NULL && other != 0)
156 				return ("pidfile exists and is locked");
157 			if (pf == NULL)
158 				return (strerror(errno));
159 		}
160 		if (pidfile_write(pf) != 0) {
161 			serrno = errno;
162 			pidfile_close(pf);
163 			unlink(fn);
164 			return (strerror(serrno));
165 		}
166 		if (pf == NULL)
167 			_exit(1);
168 		if (pidfile_write(pf) != 0)
169 			_exit(1);
170 		if (write(fd[1], "*", 1) != 1)
171 			_exit(1);
172 		select(0, 0, 0, 0, 0);
173 		_exit(0);
174 	}
175 	// parent
176 	close(fd[1]);
177 	if (pf)
178 		pidfile_close(pf);
179 
180 	// wait for the child to signal us
181 	if (read(fd[0], &ch, 1) != 1) {
182 		serrno = errno;
183 		unlink(fn);
184 		kill(pid, SIGTERM);
185 		errno = serrno;
186 		return (strerror(errno));
187 	}
188 
189 	// We shouldn't be able to lock the same pidfile as our child
190 	pf = pidfile_open(fn, 0600, &other);
191 	if (pf != NULL) {
192 		pidfile_close(pf);
193 		unlink(fn);
194 		return ("managed to lock contested pidfile");
195 	}
196 
197 	// Failed to lock, but not because it was contested
198 	if (other == 0) {
199 		unlink(fn);
200 		return (strerror(errno));
201 	}
202 
203 	// Locked by the wrong process
204 	if (other != pid) {
205 		unlink(fn);
206 		return ("pidfile contained wrong PID");
207 	}
208 
209 	// check our child's fate
210 	if (pf)
211 		pidfile_close(pf);
212 	unlink(fn);
213 	if (kill(pid, SIGINT) != 0)
214 		return (strerror(errno));
215 	if (waitpid(pid, &status, 0) == -1)
216 		return (strerror(errno));
217 	if (WIFSIGNALED(status))
218 		return ("child caught signal");
219 	if (WEXITSTATUS(status) != 0)
220 		return ("child returned non-zero status");
221 
222 	// success
223 	return (NULL);
224 }
225 
226 /*
227  * Test that pidfile_open() fails when attempting to open a pidfile that
228  * is already locked, and that it returns the correct PID.
229  */
230 static const char *
231 test_pidfile_contested(void)
232 {
233 	const char *fn = "test_pidfile_contested";
234 	const char *result;
235 
236 	result = common_test_pidfile_child(fn, 0);
237 	return (result);
238 }
239 
240 /*
241  * Test that the pidfile lock is inherited.
242  */
243 static const char *
244 test_pidfile_inherited(void)
245 {
246 	const char *fn = "test_pidfile_inherited";
247 	const char *result;
248 
249 	result = common_test_pidfile_child(fn, 1);
250 	return (result);
251 }
252 
253 static struct test {
254 	const char *name;
255 	const char *(*func)(void);
256 } t[] = {
257 	{ "pidfile_uncontested", test_pidfile_uncontested },
258 	{ "pidfile_self", test_pidfile_self },
259 	{ "pidfile_contested", test_pidfile_contested },
260 	{ "pidfile_inherited", test_pidfile_inherited },
261 };
262 
263 int
264 main(void)
265 {
266 	const char *result;
267 	int i, nt;
268 
269 	nt = sizeof(t) / sizeof(*t);
270 	printf("1..%d\n", nt);
271 	for (i = 0; i < nt; ++i) {
272 		if ((result = t[i].func()) != NULL)
273 			printf("not ok %d - %s # %s\n", i + 1,
274 			    t[i].name, result);
275 		else
276 			printf("ok %d - %s\n", i + 1,
277 			    t[i].name);
278 	}
279 	exit(0);
280 }
281