1 /*	$OpenBSD: manager.c,v 1.4 2015/10/23 18:50:54 mmcc Exp $ */
2 /*
3  * Copyright (c) 2015 Sebastien Marie <semarie@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/syslimits.h>
19 #include <sys/wait.h>
20 
21 #include <ctype.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 extern char *__progname;
33 
34 static const char *
35 coredump_name()
36 {
37 	static char coredump[PATH_MAX] = "";
38 
39 	if (*coredump)
40 		return (coredump);
41 
42 	if (strlcpy(coredump, __progname, sizeof(coredump)) >= sizeof(coredump))
43 		errx(1, "coredump: strlcpy");
44 
45 	if (strlcat(coredump, ".core", sizeof(coredump)) >= sizeof(coredump))
46 		errx(1, "coredump: strlcat");
47 
48 	return (coredump);
49 }
50 
51 
52 static int
53 check_coredump()
54 {
55 	const char *coredump = coredump_name();
56 	int fd;
57 
58 	if ((fd = open(coredump, O_RDONLY)) == -1) {
59 		if (errno == ENOENT)
60 			return (1); /* coredump not found */
61 		else
62 			return (-1); /* error */
63 	}
64 
65 	(void)close(fd);
66 	return (0); /* coredump found */
67 }
68 
69 
70 static int
71 clear_coredump(int *ret, const char *test_name)
72 {
73 	int saved_errno = errno;
74 	int u;
75 
76 	if (((u = unlink(coredump_name())) != 0) && (errno != ENOENT)) {
77 		warn("test(%s): clear_coredump", test_name);
78 		*ret = EXIT_FAILURE;
79 		return (-1);
80 	}
81 	errno = saved_errno;
82 
83 	return (0);
84 }
85 
86 
87 static int
88 grab_syscall(pid_t pid)
89 {
90 	int	 ret = -1;
91 	char	*search = NULL;
92 	int	 searchlen;
93 	FILE	*fd;
94 	char	 line[1024];
95 	char	*end;
96 
97 	/* build searched string */
98 	if ((searchlen = asprintf(&search, "%s(%d): syscall ", __progname, pid))
99 	    <= 0)
100 		goto out;
101 
102 	/* call dmesg */
103 	if ((fd = popen("/sbin/dmesg", "r")) == NULL)
104 		goto out;
105 
106 	/* search the string */
107 	while (1) {
108 		/* read a line */
109 		fgets(line, sizeof(line), fd);
110 
111 		/* error checking */
112 		if (ferror(fd)) {
113 			ret = -1;
114 			goto out;
115 		}
116 
117 		/* quit */
118 		if (feof(fd))
119 			break;
120 
121 		/* strip trailing '\n' */
122 		end = strchr(line, '\n');
123 		if (*end == '\n')
124 			*end = '\0';
125 
126 		/* check if found */
127 		if (strncmp(search, line, searchlen) == 0) {
128 			const char *errstr = NULL;
129 			char *c;
130 			/* truncate at first no-number */
131 			for (c = line + searchlen; (*c != '\0') && isdigit((unsigned char)*c);
132 			     c++)
133 				;
134 			*c = '\0';
135 
136 			/* convert it */
137 			ret = strtonum(line + searchlen, 0, 255, &errstr);
138 			if (errstr) {
139 				warn("strtonum: line=%s err=%s", line, errstr);
140 				return (-1);
141 			}
142 		}
143 	}
144 
145 	/* cleanup */
146 	if (pclose(fd) == -1)
147 		goto out;
148 
149 	/* not found */
150 	if (ret == -1)
151 		ret = 0;
152 
153 out:
154 	free(search);
155 	return (ret);
156 }
157 
158 /* mainly stolen from src/bin/cat/cat.c */
159 static int
160 drainfd(int rfd, int wfd)
161 {
162 	char buf[1024];
163 	ssize_t nr, nw, off;
164 
165 	while ((nr = read(rfd, buf, sizeof(buf))) != -1 && nr != 0)
166 		for (off = 0; nr; nr -= nw, off += nw)
167 			if ((nw = write(wfd, buf + off, (size_t)nr)) == 0 ||
168 			    nw == -1)
169 				return (-1);
170 	if (nr < 0)
171 		return (-1);
172 
173 	return (0);
174 }
175 
176 void
177 _start_test(int *ret, const char *test_name, const char *request,
178     const char *paths[], void (*test_func)(void))
179 {
180 	int fildes[2];
181 	pid_t pid;
182 	int status;
183 	int i;
184 
185 	/* early print testname */
186 	printf("test(%s): pledge=", test_name);
187 	if (request) {
188 		printf("(\"%s\",", request);
189 		if (paths) {
190 			printf("{");
191 			for (i = 0; paths[i] != NULL; i++)
192 				printf("\"%s\",", paths[i]);
193 			printf("NULL})");
194 		} else
195 			printf("NULL)");
196 	} else
197 		printf("skip");
198 
199 	/* unlink previous coredump (if exists) */
200 	if (clear_coredump(ret, test_name) == -1)
201 		return;
202 
203 	/* flush outputs (for STDOUT_FILENO manipulation) */
204 	if (fflush(NULL) != 0) {
205 		warn("test(%s) fflush", test_name);
206 		*ret = EXIT_FAILURE;
207 		return;
208 	}
209 
210 	/* make pipe to grab output */
211 	if (pipe(fildes) != 0) {
212 		warn("test(%s) pipe", test_name);
213 		*ret = EXIT_FAILURE;
214 		return;
215 	}
216 
217 	/* fork and launch the test */
218 	switch (pid = fork()) {
219 	case -1:
220 		(void)close(fildes[0]);
221 		(void)close(fildes[1]);
222 
223 		warn("test(%s) fork", test_name);
224 		*ret = EXIT_FAILURE;
225 		return;
226 
227 	case 0:
228 		/* output to pipe */
229 		(void)close(fildes[0]);
230 		while (dup2(fildes[1], STDOUT_FILENO) == -1)
231 			if (errno != EINTR)
232 				err(errno, "dup2");
233 
234 		/* create a new session (for kill) */
235 		setsid();
236 
237 		/* set pledge policy */
238 		if (request && pledge(request, paths) != 0)
239 			err(errno, "pledge");
240 
241 		/* reset errno and launch test */
242 		errno = 0;
243 		test_func();
244 
245 		if (errno != 0)
246 			_exit(errno);
247 
248 		_exit(EXIT_SUCCESS);
249 		/* NOTREACHED */
250 	}
251 
252 	/* copy pipe to output */
253 	(void)close(fildes[1]);
254 	if (drainfd(fildes[0], STDOUT_FILENO) != 0) {
255 		warn("test(%s): drainfd", test_name);
256 		*ret = EXIT_FAILURE;
257 		return;
258 	}
259 	if (close(fildes[0]) != 0) {
260 		warn("test(%s): close", test_name);
261 		*ret = EXIT_FAILURE;
262 		return;
263 	}
264 
265 	/* wait for test to terminate */
266 	while (waitpid(pid, &status, 0) < 0) {
267 		if (errno == EAGAIN)
268 			continue;
269 		warn("test(%s): waitpid", test_name);
270 		*ret = EXIT_FAILURE;
271 		return;
272 	}
273 
274 	/* show status and details */
275 	printf(" status=%d", status);
276 
277 	if (WIFCONTINUED(status))
278 		printf(" continued");
279 
280 	if (WIFEXITED(status)) {
281 		int e = WEXITSTATUS(status);
282 		printf(" exit=%d", e);
283 		if (e > 0 && e <= ELAST)
284 			printf(" (errno: \"%s\")", strerror(e));
285 	}
286 
287 	if (WIFSIGNALED(status)) {
288 		int signal = WTERMSIG(status);
289 		printf(" signal=%d", signal);
290 
291 		/* check if core file is really here ? */
292 		if (WCOREDUMP(status)) {
293 			int coredump = check_coredump();
294 
295 			switch(coredump) {
296 			case -1: /* error */
297 				warn("test(%s): check_coredump", test_name);
298 				*ret = EXIT_FAILURE;
299 				return;
300 
301 			case 0: /* found */
302 				printf(" coredump=present");
303 				break;
304 
305 			case 1:	/* not found */
306 				printf(" coredump=absent");
307 				break;
308 
309 			default:
310 				warnx("test(%s): unknown coredump code %d",
311 				    test_name, coredump);
312 				*ret = EXIT_FAILURE;
313 				return;
314 			}
315 
316 		}
317 
318 		/* grab pledged syscall from dmesg */
319 		if ((signal == SIGKILL) || (signal = SIGABRT)) {
320 			int syscall = grab_syscall(pid);
321 			switch (syscall) {
322 			case -1:	/* error */
323 				warn("test(%s): grab_syscall pid=%d", test_name,
324 				    pid);
325 				*ret = EXIT_FAILURE;
326 				return;
327 
328 			case 0:		/* not found */
329 				printf(" pledged_syscall=not_found");
330 				break;
331 
332 			default:
333 				printf(" pledged_syscall=%d", syscall);
334 			}
335 		}
336 	}
337 
338 	if (WIFSTOPPED(status))
339 		printf(" stop=%d", WSTOPSIG(status));
340 
341 	printf("\n");
342 }
343