1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <signal.h>
5#include <string.h>
6#include <err.h>
7#include <sys/types.h>
8#include <sys/time.h>
9#include <sys/event.h>
10#include <sys/ptrace.h>
11#include <errno.h>
12#include <sys/proc.h>
13#include <libproc.h>
14#include <stdarg.h>
15
16/*
17 * We create a process hierarchy of:
18 *
19 * grandparent -> parent -> child
20 *                   \
21 *                    \--> debugger
22 *
23 * When the debugger calls ptrace(2) on child, it
24 * is temporarily reparented.
25 *
26 * We may also create a hierarchy of:
27 *
28 * grandparent -> parent/debugger -> child
29 *
30 */
31
32typedef enum {
33	eParentExitAfterWaitpid = 0,
34	eParentExitAfterWaitpidAndSIGCHLD,
35	eParentExitBeforeWaitpid,
36	eParentExitAfterDebuggerAttach,
37	eParentExitBeforeDebuggerAttach,
38	eParentIsDebugger
39} parent_exit_t;
40
41typedef enum {
42	eDebuggerExitAfterKillAndWaitpid = 0,
43	eDebuggerExitAfterKillWithoutWaitpid,
44	eDebuggerExitAfterDetach,
45	eDebuggerExitWithoutDetach
46} debugger_exit_t;
47
48void do_grandparent(pid_t parent, pid_t child, pid_t debugger, debugger_exit_t debugger_exit_time) __attribute__((noreturn));
49void do_parent(pid_t child, pid_t debugger, parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time) __attribute__((noreturn));
50void do_child(void) __attribute__((noreturn));
51void do_debugger(pid_t child, debugger_exit_t debugger_exit_time) __attribute__((noreturn));
52
53bool iszombie(pid_t p);
54
55char *str_kev_filter(int filter);
56char *str_kev_flags(int filter, uint16_t flags);
57char *str_kev_fflags(int filter, uint32_t fflags);
58char *str_kev_data(int filter, uint32_t fflags, int64_t data, uint64_t udata);
59char *print_exit(pid_t p, int stat_loc);
60
61void logline(const char *format, ...);
62
63void usage(void);
64int test_all_permutations(void);
65void test(parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time) __attribute__((noreturn));
66
67int main(int argc, char *argv[]) {
68	int ch;
69
70	int parent_exit_time = -1;
71	int debugger_exit_time = -1;
72
73	while ((ch = getopt(argc, argv, "p:w:")) != -1) {
74		switch (ch) {
75			case 'p':
76				parent_exit_time = atoi(optarg);
77				break;
78			case 'w':
79				debugger_exit_time = atoi(optarg);
80				break;
81			case '?':
82			default:
83				usage();
84		}
85	}
86
87	/* no explicit options, loop through them all */
88	if (parent_exit_time == -1 &&
89		debugger_exit_time == -1) {
90		return test_all_permutations();
91	}
92
93	if (parent_exit_time == -1 ||
94		debugger_exit_time == -1) {
95		usage();
96	}
97
98	test((parent_exit_t)parent_exit_time,
99		 (debugger_exit_t)debugger_exit_time);
100
101	return 0; /* never reached */
102}
103
104void test(parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time)
105{
106	pid_t parent, child, debugger;
107	int ret;
108	int fds[2];
109
110	/* pipe for parent to send child pid to grandparent */
111	ret = pipe(fds);
112	if (-1 == ret) {
113		err(1, "failed to create pipe");
114	}
115
116	parent = fork();
117	if (parent == 0) {
118		/* parent sub-branch */
119
120		ret = close(fds[0]);
121		if (ret == -1) {
122			err(1, "close read end of pipe");
123		}
124
125		child = fork();
126		if (child == 0) {
127			/* child */
128			ret = close(fds[1]);
129			if (ret == -1) {
130				err(1, "close write end of pipe");
131			}
132
133			do_child();
134		} else if (child == -1) {
135			err(1, "parent failed to fork child");
136		} else {
137			/* parent */
138			if (-1 == write(fds[1], &child, sizeof(child))) {
139				err(1, "writing child pid to grandparent");
140			}
141
142			if (parent_exit_time == eParentIsDebugger) {
143				debugger = -1;
144
145				if (-1 == write(fds[1], &debugger, sizeof(debugger))) {
146					err(1, "writing debugger pid to grandparent");
147				}
148				ret = close(fds[1]);
149				if (ret == -1) {
150					err(1, "close write end of pipe");
151				}
152
153				do_debugger(child, debugger_exit_time);
154			} else {
155				debugger = fork();
156				if (debugger == 0) {
157					/* debugger */
158					ret = close(fds[1]);
159					if (ret == -1) {
160						err(1, "close write end of pipe");
161					}
162
163					do_debugger(child, debugger_exit_time);
164				} else if (debugger == -1) {
165					err(1, "parent failed to fork debugger");
166				} else {
167					/* still parent */
168					if (-1 == write(fds[1], &debugger, sizeof(debugger))) {
169						err(1, "writing debugger pid to grandparent");
170					}
171					ret = close(fds[1]);
172					if (ret == -1) {
173						err(1, "close write end of pipe");
174					}
175
176					do_parent(child, debugger, parent_exit_time, debugger_exit_time);
177				}
178			}
179		}
180	} else if (parent == -1) {
181		err(1, "grandparent failed to fork parent");
182	} else {
183		ret = close(fds[1]);
184		if (ret == -1) {
185			err(1, "close write end of pipe");
186		}
187
188		if (-1 == read(fds[0], &child, sizeof(child))) {
189			err(1, "could not read child pid");
190		}
191
192		if (-1 == read(fds[0], &debugger, sizeof(debugger))) {
193			err(1, "could not read debugger pid");
194		}
195
196		ret = close(fds[0]);
197		if (ret == -1) {
198			err(1, "close read end of pipe");
199		}
200
201		do_grandparent(parent, child, debugger, debugger_exit_time);
202	}
203}
204
205void usage(void)
206{
207	errx(1, "Usage: %s [-p <parent_exit_time> -w <debugger_exit_time>]", getprogname());
208}
209
210int test_all_permutations(void)
211{
212	int p, w;
213	bool has_failure = false;
214
215	for (p = 0; p <= 5; p++) {
216		for (w = 0; w <= 3; w++) {
217			int testpid;
218			int ret;
219
220			testpid = fork();
221			if (testpid == 0) {
222				logline("-------------------------------------------------------");
223				logline("*** Executing self-test: %s -p %d -w %d",
224						getprogname(), p, w);
225				test((parent_exit_t)p,
226					 (debugger_exit_t)w);
227				_exit(1); /* never reached */
228			} else if (testpid == -1) {
229				err(1, "failed to fork test pid");
230			} else {
231				int stat_loc;
232
233				ret = waitpid(testpid, &stat_loc, 0);
234				if (ret == -1)
235					err(1, "waitpid(%d) by test harness failed", testpid);
236
237				logline("test process: %s", print_exit(testpid, stat_loc));
238				if (!WIFEXITED(stat_loc) || (0 != WEXITSTATUS(stat_loc))) {
239					logline("FAILED TEST");
240					has_failure = true;
241				}
242			}
243		}
244	}
245
246	if (has_failure) {
247		logline("test failures found");
248		return 1;
249	}
250
251	return 0;
252}
253
254void do_grandparent(pid_t parent, pid_t child, pid_t debugger, debugger_exit_t debugger_exit_time)
255{
256	pid_t result;
257	int stat_loc;
258	int exit_code = 0;
259	int kq;
260	int ret;
261	struct kevent64_s kev;
262	int neededdeathcount = (debugger != -1) ? 3 : 2;
263
264	setprogname("GRANDPARENT");
265
266	logline("grandparent pid %d has parent pid %d and child pid %d. waiting for parent process exit...", getpid(), parent, child);
267
268	/* make sure we can at least observe real child's exit */
269	kq = kqueue();
270	if (kq < 0)
271		err(1, "kqueue");
272
273	EV_SET64(&kev, child, EVFILT_PROC, EV_ADD|EV_ENABLE,
274			 NOTE_EXIT, 0, child, 0, 0);
275	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
276	if (ret == -1)
277		err(1, "kevent64 EVFILT_PROC");
278
279	EV_SET64(&kev, parent, EVFILT_PROC, EV_ADD|EV_ENABLE,
280			 NOTE_EXIT, 0, parent, 0, 0);
281	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
282	if (ret == -1)
283		err(1, "kevent64 EVFILT_PROC");
284
285	if (debugger != -1) {
286		EV_SET64(&kev, debugger, EVFILT_PROC, EV_ADD|EV_ENABLE,
287				 NOTE_EXIT, 0, debugger, 0, 0);
288		ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
289		if (ret == -1)
290			err(1, "kevent64 EVFILT_PROC");
291	}
292
293	EV_SET64(&kev, 5, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT,
294			 NOTE_SECONDS, 5, 0, 0, 0);
295	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
296	if (ret == -1)
297		err(1, "kevent64 EVFILT_TIMER");
298
299	while(1) {
300
301		ret = kevent64(kq, NULL, 0, &kev, 1, 0, NULL);
302		if (ret == -1) {
303			if (errno == EINTR)
304				continue;
305			err(1, "kevent64");
306		} else if (ret == 0) {
307			break;
308		}
309
310		logline("kevent64 returned ident %llu filter %s fflags %s data %s",
311				kev.ident, str_kev_filter(kev.filter),
312				str_kev_fflags(kev.filter, kev.fflags),
313				str_kev_data(kev.filter, kev.fflags, kev.data, kev.udata));
314		if (kev.filter == EVFILT_PROC) {
315			if (child == kev.udata) {
316				neededdeathcount--;
317			} else if (parent == kev.udata) {
318				neededdeathcount--;
319			} else if ((debugger != -1) && (debugger == kev.udata)) {
320				neededdeathcount--;
321			}
322		} else if (kev.filter == EVFILT_TIMER) {
323			logline("timed out waiting for NOTE_EXIT");
324			exit_code = 1;
325			break;
326		}
327
328		if (neededdeathcount == 0) {
329			break;
330		}
331	}
332
333	result = waitpid(parent, &stat_loc, 0);
334	if (result == -1)
335		err(1, "waitpid(%d) by grandparent failed", parent);
336
337
338	logline("parent process: %s", print_exit(parent, stat_loc));
339	if (!WIFEXITED(stat_loc) || (0 != WEXITSTATUS(stat_loc))) {
340		exit_code = 1;
341	}
342
343	if (iszombie(parent)) {
344		logline("parent %d is now a zombie", parent);
345		exit_code = 1;
346	}
347
348	if (iszombie(child)) {
349		logline("child %d is now a zombie", child);
350		exit_code = 1;
351	}
352
353	if ((debugger != -1) && iszombie(debugger)) {
354		logline("debugger %d is now a zombie", debugger);
355		exit_code = 1;
356	}
357
358	exit(exit_code);
359}
360
361/*
362 * debugger will register kevents, wait for quorum on events, then exit
363 */
364void do_parent(pid_t child, pid_t debugger, parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time)
365{
366	int kq;
367	int ret;
368	struct kevent64_s kev;
369	int deathcount = 0;
370	int childsignalcount = 0;
371	int stat_loc;
372
373	setprogname("PARENT");
374
375	logline("parent pid %d has child pid %d and debugger pid %d. waiting for processes to exit...", getpid(), child, debugger);
376
377	kq = kqueue();
378	if (kq < 0)
379		err(1, "kqueue");
380
381	EV_SET64(&kev, child, EVFILT_PROC, EV_ADD|EV_ENABLE,
382			 NOTE_EXIT|NOTE_EXITSTATUS|NOTE_EXIT_DETAIL|NOTE_FORK|NOTE_EXEC|NOTE_SIGNAL,
383			 0, child, 0, 0);
384	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
385	if (ret == -1)
386		err(1, "kevent64 EVFILT_PROC");
387
388	EV_SET64(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD|EV_ENABLE,
389			 0, 0, child, 0, 0);
390	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
391	if (ret == -1)
392		err(1, "kevent64 EVFILT_SIGNAL");
393
394	EV_SET64(&kev, 7, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT,
395			 NOTE_SECONDS, 7, 0, 0, 0);
396	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
397	if (ret == -1)
398		err(1, "kevent64 EVFILT_TIMER");
399
400	while(1) {
401		ret = kevent64(kq, NULL, 0, &kev, 1, 0, NULL);
402		if (ret == -1) {
403			if (errno == EINTR)
404				continue;
405			err(1, "kevent64");
406		} else if (ret == 0) {
407			break;
408		}
409
410		logline("kevent64 returned ident %llu filter %s fflags %s data %s",
411				kev.ident, str_kev_filter(kev.filter),
412				str_kev_fflags(kev.filter, kev.fflags),
413				str_kev_data(kev.filter, kev.fflags, kev.data, kev.udata));
414		if (kev.filter == EVFILT_SIGNAL) {
415			/* must be SIGCHLD */
416			deathcount++;
417		} else if (kev.filter == EVFILT_PROC) {
418			if (child == kev.udata) {
419				if ((kev.fflags & (NOTE_EXIT|NOTE_EXITSTATUS)) == (NOTE_EXIT|NOTE_EXITSTATUS)) {
420					deathcount++;
421				} else if (kev.fflags & NOTE_SIGNAL) {
422					childsignalcount++;
423					if ((parent_exit_time == eParentExitAfterDebuggerAttach) && (childsignalcount >= 2)) {
424						/* second signal is attach */
425						logline("exiting because of eParentExitAfterDebuggerAttach");
426						exit(0);
427					}
428				} else if (kev.fflags & NOTE_FORK) {
429					if (parent_exit_time == eParentExitBeforeDebuggerAttach) {
430						logline("exiting because of eParentExitBeforeDebuggerAttach");
431						exit(0);
432					}
433				}
434			}
435		} else if (kev.filter == EVFILT_TIMER) {
436			errx(1, "timed out waiting for NOTE_EXIT");
437		}
438
439		if (deathcount >= (parent_exit_time == eParentExitAfterWaitpidAndSIGCHLD ? 2 : 1)) {
440			break;
441		}
442	}
443
444	if (parent_exit_time == eParentExitBeforeWaitpid) {
445		logline("exiting because of eParentExitBeforeWaitpid");
446		exit(0);
447	}
448
449	ret = waitpid(child, &stat_loc, 0);
450	if (ret == -1)
451		err(1, "waitpid(%d) by parent failed", child);
452
453	logline("child process: %s", print_exit(child, stat_loc));
454	if (!WIFSIGNALED(stat_loc) || (SIGKILL != WTERMSIG(stat_loc)))
455		errx(1, "child did not exit as expected");
456
457	ret = waitpid(debugger, &stat_loc, 0);
458	if (ret == -1)
459		err(1, "waitpid(%d) by parent failed", debugger);
460
461	logline("debugger process: %s", print_exit(debugger, stat_loc));
462	if (!WIFEXITED(stat_loc) || (0 != WEXITSTATUS(stat_loc)))
463		errx(1, "debugger did not exit as expected");
464
465	/* Received both SIGCHLD and NOTE_EXIT, as needed */
466	logline("exiting beacuse of eParentExitAfterWaitpid/eParentExitAfterWaitpidAndSIGCHLD");
467	exit(0);
468}
469
470/* child will spin waiting to be killed by debugger or parent or someone */
471void do_child(void)
472{
473	pid_t doublechild;
474	int ret;
475	setprogname("CHILD");
476
477	logline("child pid %d. waiting for external termination...", getpid());
478
479	usleep(500000);
480
481	doublechild = fork();
482	if (doublechild == 0) {
483		exit(0);
484	} else if (doublechild == -1) {
485		err(1, "doublechild");
486	} else {
487		ret = waitpid(doublechild, NULL, 0);
488		if (ret == -1)
489			err(1, "waitpid(%d) by parent failed", doublechild);
490	}
491
492	while (1) {
493		sleep(60);
494	}
495}
496
497/*
498 * debugger will register kevents, attach+kill child, wait for quorum on events,
499 * then exit.
500 */
501void do_debugger(pid_t child, debugger_exit_t debugger_exit_time)
502{
503	int kq;
504	int ret;
505	struct kevent64_s kev;
506	int deathcount = 0;
507	int stat_loc;
508
509	setprogname("DEBUGGER");
510
511	logline("debugger pid %d has child pid %d. waiting for process exit...", getpid(), child);
512
513	sleep(1);
514	fprintf(stderr, "\n");
515	ret = ptrace(PT_ATTACH, child, 0, 0);
516	if (ret == -1)
517		err(1, "ptrace(PT_ATTACH)");
518
519	ret = waitpid(child, &stat_loc, WUNTRACED);
520	if (ret == -1)
521		err(1, "waitpid(child, WUNTRACED)");
522
523	logline("child process stopped: %s", print_exit(child, stat_loc));
524
525	if (debugger_exit_time == eDebuggerExitWithoutDetach) {
526		logline("exiting because of eDebuggerExitWithoutDetach");
527		exit(0);
528	} else if (debugger_exit_time == eDebuggerExitAfterDetach) {
529		ret = ptrace(PT_DETACH, child, 0, 0);
530		if (ret == -1)
531			err(1, "ptrace(PT_DETACH)");
532
533		ret = kill(child, SIGKILL);
534		if (ret == -1)
535			err(1, "kill(SIGKILL)");
536
537		logline("exiting because of eDebuggerExitAfterDetach");
538		exit(0);
539	}
540
541	kq = kqueue();
542	if (kq < 0)
543		err(1, "kqueue");
544
545	EV_SET64(&kev, child, EVFILT_PROC, EV_ADD|EV_ENABLE,
546			 NOTE_EXIT|NOTE_EXITSTATUS|NOTE_EXIT_DETAIL|NOTE_FORK|NOTE_EXEC|NOTE_SIGNAL,
547			 0, child, 0, 0);
548	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
549	if (ret == -1)
550		err(1, "kevent64 EVFILT_PROC");
551
552	EV_SET64(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD|EV_ENABLE,
553			 0, 0, child, 0, 0);
554	ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL);
555	if (ret == -1)
556		err(1, "kevent64 EVFILT_SIGNAL");
557
558	sleep(1);
559	fprintf(stderr, "\n");
560	ret = ptrace(PT_KILL, child, 0, 0);
561	if (ret == -1)
562		err(1, "ptrace(PT_KILL)");
563
564	while(1) {
565		ret = kevent64(kq, NULL, 0, &kev, 1, 0, NULL);
566		if (ret == -1) {
567			if (errno == EINTR)
568				continue;
569			err(1, "kevent64");
570		} else if (ret == 0) {
571			continue;
572		}
573
574		logline("kevent64 returned ident %llu filter %s fflags %s data %s",
575				kev.ident, str_kev_filter(kev.filter),
576				str_kev_fflags(kev.filter, kev.fflags),
577				str_kev_data(kev.filter, kev.fflags, kev.data, kev.udata));
578		if (kev.filter == EVFILT_SIGNAL) {
579			/* must be SIGCHLD */
580			deathcount++;
581		} else if (kev.filter == EVFILT_PROC) {
582			if ((kev.fflags & (NOTE_EXIT|NOTE_EXITSTATUS)) == (NOTE_EXIT|NOTE_EXITSTATUS)) {
583				deathcount++;
584			}
585		}
586
587		if (deathcount >= 2) {
588			break;
589		}
590	}
591
592	if (debugger_exit_time == eDebuggerExitAfterKillWithoutWaitpid) {
593		logline("exiting because of eDebuggerExitAfterKillWithoutWaitpid");
594		exit(0);
595	}
596
597	sleep(1);
598	fprintf(stderr, "\n");
599	ret = waitpid(child, &stat_loc, 0);
600	if (ret == -1)
601		err(1, "waitpid(%d) by debugger failed", child);
602
603	logline("child process: %s", print_exit(child, stat_loc));
604
605	/* Received both SIGCHLD and NOTE_EXIT */
606	exit(0);
607}
608
609void logline(const char *format, ...)
610{
611	char *line = NULL;
612	char newformat[1024];
613
614	snprintf(newformat, sizeof(newformat),  "%s: %s\n", getprogname(), format);
615
616	va_list va;
617
618	va_start(va, format);
619	vasprintf(&line, newformat, va);
620	va_end(va);
621
622	if (line) {
623		write(STDOUT_FILENO, line, strlen(line));
624		free(line);
625	} else {
626		write(STDOUT_FILENO, "error\n", 6);
627	}
628}
629
630
631char *str_kev_filter(int filter)
632{
633	static char filter_string[32];
634	if (filter == EVFILT_PROC)
635		strlcpy(filter_string, "EVFILT_PROC", sizeof(filter_string));
636	else if (filter == EVFILT_SIGNAL)
637		strlcpy(filter_string, "EVFILT_SIGNAL", sizeof(filter_string));
638	else if (filter == EVFILT_TIMER)
639		strlcpy(filter_string, "EVFILT_TIMER", sizeof(filter_string));
640	else
641		strlcpy(filter_string, "EVFILT_UNKNOWN", sizeof(filter_string));
642
643	return filter_string;
644}
645
646char *str_kev_flags(int filter, uint16_t flags)
647{
648	static char flags_string[128];
649
650	flags_string[0] = '\0';
651	if (filter & EV_ADD) strlcat(flags_string, "|EV_ADD", sizeof(flags_string));
652	if (filter & EV_DELETE) strlcat(flags_string, "|EV_DELETE", sizeof(flags_string));
653	if (filter & EV_ENABLE) strlcat(flags_string, "|EV_ENABLE", sizeof(flags_string));
654	if (filter & EV_DISABLE) strlcat(flags_string, "|EV_DISABLE", sizeof(flags_string));
655	if (filter & EV_RECEIPT) strlcat(flags_string, "|EV_RECEIPT", sizeof(flags_string));
656	if (filter & EV_ONESHOT) strlcat(flags_string, "|EV_ONESHOT", sizeof(flags_string));
657	if (filter & EV_CLEAR) strlcat(flags_string, "|EV_CLEAR", sizeof(flags_string));
658	if (filter & EV_DISPATCH) strlcat(flags_string, "|EV_DISPATCH", sizeof(flags_string));
659	if (filter & EV_EOF) strlcat(flags_string, "|EV_EOF", sizeof(flags_string));
660	if (filter & EV_ERROR) strlcat(flags_string, "|EV_ERROR", sizeof(flags_string));
661
662	if (flags_string[0] == '|')
663		return &flags_string[1];
664	else
665		return flags_string;
666}
667
668char *str_kev_fflags(int filter, uint32_t fflags)
669{
670	static char fflags_string[128];
671
672	fflags_string[0] = '\0';
673
674	if (filter == EVFILT_SIGNAL) {
675		if (fflags & NOTE_SIGNAL) strlcat(fflags_string, "|NOTE_SIGNAL", sizeof(fflags_string));
676	} else if (filter == EVFILT_PROC) {
677		if (fflags & NOTE_EXIT) strlcat(fflags_string, "|NOTE_EXIT", sizeof(fflags_string));
678		if (fflags & NOTE_FORK) strlcat(fflags_string, "|NOTE_FORK", sizeof(fflags_string));
679		if (fflags & NOTE_EXEC) strlcat(fflags_string, "|NOTE_EXEC", sizeof(fflags_string));
680		if (fflags & NOTE_SIGNAL) strlcat(fflags_string, "|NOTE_SIGNAL", sizeof(fflags_string));
681		if (fflags & NOTE_EXITSTATUS) strlcat(fflags_string, "|NOTE_EXITSTATUS", sizeof(fflags_string));
682		if (fflags & NOTE_EXIT_DETAIL) strlcat(fflags_string, "|NOTE_EXIT_DETAIL", sizeof(fflags_string));
683		if (fflags & NOTE_EXIT_DECRYPTFAIL) strlcat(fflags_string, "|NOTE_EXIT_DECRYPTFAIL", sizeof(fflags_string));
684		if (fflags & NOTE_EXIT_MEMORY) strlcat(fflags_string, "|NOTE_EXIT_MEMORY", sizeof(fflags_string));
685#ifdef NOTE_EXIT_CSERROR
686		if (fflags & NOTE_EXIT_CSERROR) strlcat(fflags_string, "|NOTE_EXIT_CSERROR", sizeof(fflags_string));
687#endif
688	} else if (filter == EVFILT_TIMER) {
689		if (fflags & NOTE_SECONDS) strlcat(fflags_string, "|NOTE_SECONDS", sizeof(fflags_string));
690	} else {
691		strlcat(fflags_string, "UNKNOWN", sizeof(fflags_string));
692	}
693
694	if (fflags_string[0] == '|')
695		return &fflags_string[1];
696	else
697		return fflags_string;
698}
699
700char *str_kev_data(int filter, uint32_t fflags, int64_t data, uint64_t udata)
701{
702	static char data_string[128];
703
704	if (filter == EVFILT_PROC) {
705		if ((fflags & (NOTE_EXIT|NOTE_EXITSTATUS)) == (NOTE_EXIT|NOTE_EXITSTATUS)) {
706			if (WIFEXITED(data)) {
707				snprintf(data_string, sizeof(data_string), "pid %llu exited with status %d", udata, WEXITSTATUS(data));
708			} else if (WIFSIGNALED(data)) {
709				snprintf(data_string, sizeof(data_string), "pid %llu received signal %d%s", udata, WTERMSIG(data), WCOREDUMP(data) ? " (core dumped)" : "");
710			} else if (WIFSTOPPED(data)) {
711				snprintf(data_string, sizeof(data_string), "pid %llu stopped with signal %d", udata, WSTOPSIG(data));
712			} else {
713				snprintf(data_string, sizeof(data_string), "pid %llu unknown exit status 0x%08llx", udata, data);
714			}
715		} else if (fflags & NOTE_EXIT) {
716			snprintf(data_string, sizeof(data_string), "pid %llu exited", udata);
717		} else {
718			data_string[0] = '\0';
719		}
720	} else if (filter == EVFILT_TIMER) {
721		snprintf(data_string, sizeof(data_string), "timer fired %lld time(s)", data);
722	} else {
723		data_string[0] = '\0';
724	}
725
726	return data_string;
727}
728
729char *print_exit(pid_t p, int stat_loc)
730{
731	return str_kev_data(EVFILT_PROC, NOTE_EXIT|NOTE_EXITSTATUS, stat_loc, p);
732}
733
734bool iszombie(pid_t p)
735{
736	int ret;
737	struct proc_bsdshortinfo bsdinfo;
738
739	ret = proc_pidinfo(p, PROC_PIDT_SHORTBSDINFO, 1, &bsdinfo, sizeof(bsdinfo));
740	if (ret != sizeof(bsdinfo)) {
741		return false;
742	}
743
744	if (bsdinfo.pbsi_status == SZOMB) {
745		return true;
746	} else {
747		return false;
748	}
749}
750