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