1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * Copyright 2019 Joyent, Inc. 25 */ 26 27 /* 28 * ptree -- print family tree of processes 29 */ 30 31 #include <assert.h> 32 #include <stdio.h> 33 #include <string.h> 34 #include <errno.h> 35 #include <err.h> 36 #include <fcntl.h> 37 #include <sys/debug.h> 38 #include <sys/types.h> 39 #include <sys/termios.h> 40 #include <unistd.h> 41 #include <stdlib.h> 42 #include <dirent.h> 43 #include <pwd.h> 44 #include <libproc.h> 45 #include <libzonecfg.h> 46 #include <limits.h> 47 #include <libcontract.h> 48 #include <locale.h> 49 #include <sys/contract.h> 50 #include <sys/ctfs.h> 51 #include <libcontract_priv.h> 52 #include <sys/stat.h> 53 #include <stdbool.h> 54 55 #define COLUMN_DEFAULT 80 56 #define CHUNK_SIZE 256 /* Arbitrary amount */ 57 #define FAKEDPID0(p) (p->pid == 0 && p->psargs[0] == '\0') 58 #define HAS_SIBLING(p) ((p)->sp != NULL && (p)->sp->done != 0) 59 60 typedef struct ps { 61 int done; 62 uid_t uid; 63 uid_t gid; 64 pid_t pid; /* pid == -1 indicates this is a contract */ 65 pid_t ppid; 66 pid_t pgrp; 67 pid_t sid; 68 zoneid_t zoneid; 69 ctid_t ctid; 70 char *svc_fmri; 71 timestruc_t start; 72 char psargs[PRARGSZ]; 73 struct ps *pp; /* parent */ 74 struct ps *sp; /* sibling */ 75 struct ps *cp; /* child */ 76 } ps_t; 77 78 enum { DASH = 0, BAR, CORNER, VRIGHT }; 79 80 static ps_t **ps; /* array of ps_t's */ 81 static unsigned psize; /* size of array */ 82 static int nps; /* number of ps_t's */ 83 static ps_t **ctps; /* array of contract ps_t's */ 84 static unsigned ctsize; /* size of contract array */ 85 static int nctps; /* number of contract ps_t's */ 86 static ps_t *proc0; /* process 0 */ 87 static ps_t *proc1; /* process 1 */ 88 89 static int aflag = 0; 90 static int cflag = 0; 91 static int gflag = 0; 92 static int sflag = 0; 93 static int wflag = 0; 94 static int zflag = 0; 95 static zoneid_t zoneid; 96 static char *match_svc; 97 static char *match_inst; 98 static int columns; 99 100 static const char *box_ascii[] = { 101 [DASH] = "-", 102 [BAR] = "|", 103 [CORNER] = "`", 104 [VRIGHT] = "+" 105 }; 106 107 static const char *box_utf8[] = { 108 [DASH] = "\xe2\x94\x80", /* \u2500 */ 109 [BAR] = "\xe2\x94\x82", /* \u2502 */ 110 [CORNER] = "\xe2\x94\x94", /* \u2514 */ 111 [VRIGHT] = "\xe2\x94\x9c", /* \u251c */ 112 }; 113 114 static const char **box; 115 116 static size_t get_termwidth(void); 117 static const char **get_boxchars(void); 118 static int add_proc(psinfo_t *, lwpsinfo_t *, void *); 119 static bool match_proc(ps_t *); 120 static void markprocs(ps_t *); 121 static int printone(ps_t *, int); 122 static void insertchild(ps_t *, ps_t *); 123 static void prsort(ps_t *); 124 static void printsubtree(ps_t *, int); 125 static void p_get_svc_fmri(ps_t *, ct_stathdl_t); 126 static char *parse_svc(const char *, char **); 127 static zoneid_t getzone(const char *); 128 static ps_t *fakepid0(void); 129 130 static void *zalloc(size_t); 131 static void *xreallocarray(void *, size_t, size_t); 132 static char *xstrdup(const char *); 133 134 static void __NORETURN 135 usage(void) 136 { 137 (void) fprintf(stderr, 138 "usage:\t%s [-ac] [-s svc] [-z zone] [ {pid|user} ... ]\n", 139 getprogname()); 140 (void) fprintf(stderr, 141 " (show process trees)\n"); 142 (void) fprintf(stderr, 143 " list can include process-ids and user names\n"); 144 (void) fprintf(stderr, 145 " -a : include children of process 0\n"); 146 (void) fprintf(stderr, 147 " -c : show contracts\n"); 148 (void) fprintf(stderr, 149 " -g : use line drawing characters in output\n"); 150 (void) fprintf(stderr, 151 " -s : print only processes with given service FMRI\n"); 152 (void) fprintf(stderr, 153 " -w : allow lines to wrap instead of truncating\n"); 154 (void) fprintf(stderr, 155 " -z : print only processes in given zone\n"); 156 exit(2); 157 } 158 159 int 160 main(int argc, char **argv) 161 { 162 int opt; 163 int errflg = 0; 164 int n; 165 int retc = 0; 166 167 ps_t *p; 168 169 /* options */ 170 while ((opt = getopt(argc, argv, "acgs:wz:")) != EOF) { 171 switch (opt) { 172 case 'a': /* include children of process 0 */ 173 aflag = 1; 174 break; 175 case 'c': /* display contract ownership */ 176 aflag = cflag = 1; 177 break; 178 case 'g': 179 gflag = 1; 180 box = get_boxchars(); 181 break; 182 case 's': 183 sflag = 1; 184 match_svc = parse_svc(optarg, &match_inst); 185 break; 186 case 'w': 187 wflag = 1; 188 break; 189 case 'z': /* only processes in given zone */ 190 zflag = 1; 191 zoneid = getzone(optarg); 192 break; 193 default: 194 errflg = 1; 195 break; 196 } 197 } 198 199 argc -= optind; 200 argv += optind; 201 202 if (errflg) 203 usage(); 204 205 if (!wflag) { 206 columns = get_termwidth(); 207 VERIFY3S(columns, >, 0); 208 } 209 210 nps = 0; 211 psize = 0; 212 ps = NULL; 213 214 /* Currently, this can only fail if the 3rd argument is invalid */ 215 VERIFY0(proc_walk(add_proc, NULL, PR_WALK_PROC|PR_WALK_INCLUDE_SYS)); 216 217 if (proc0 == NULL) 218 proc0 = fakepid0(); 219 if (proc1 == NULL) 220 proc1 = proc0; 221 222 for (n = 0; n < nps; n++) { 223 p = ps[n]; 224 if (p->pp == NULL) 225 prsort(p); 226 } 227 228 if (cflag) 229 /* Parent all orphan contracts to process 0. */ 230 for (n = 0; n < nctps; n++) { 231 p = ctps[n]; 232 if (p->pp == NULL) 233 insertchild(proc0, p); 234 } 235 236 if (argc == 0) { 237 for (p = aflag ? proc0->cp : proc1->cp; p != NULL; p = p->sp) { 238 markprocs(p); 239 printsubtree(p, 0); 240 } 241 return (0); 242 } 243 244 /* 245 * Initially, assume we're not going to find any processes. If we do 246 * mark any, then set this to 0 to indicate no error. 247 */ 248 errflg = 1; 249 250 while (argc-- > 0) { 251 char *arg; 252 char *next; 253 pid_t pid; 254 uid_t uid; 255 int n; 256 257 /* in case some silly person said 'ptree /proc/[0-9]*' */ 258 arg = strrchr(*argv, '/'); 259 if (arg++ == NULL) 260 arg = *argv; 261 argv++; 262 uid = (uid_t)-1; 263 errno = 0; 264 pid = strtoul(arg, &next, 10); 265 if (errno != 0 || *next != '\0') { 266 struct passwd *pw = getpwnam(arg); 267 if (pw == NULL) { 268 warnx("invalid username: %s", arg); 269 retc = 1; 270 continue; 271 } 272 uid = pw->pw_uid; 273 pid = -1; 274 } 275 276 for (n = 0; n < nps; n++) { 277 ps_t *p = ps[n]; 278 279 /* 280 * A match on pid causes the subtree starting at pid 281 * to be printed, regardless of the -a flag. 282 * For uid matches, we never include pid 0 and only 283 * include the children of pid 0 if -a was specified. 284 */ 285 if (p->pid == pid || (p->uid == uid && p->pid != 0 && 286 (p->ppid != 0 || aflag))) { 287 errflg = 0; 288 markprocs(p); 289 if (p->pid != 0) 290 for (p = p->pp; p != NULL && 291 p->done != 1 && p->pid != 0; 292 p = p->pp) 293 if ((p->ppid != 0 || aflag) && 294 match_proc(p)) 295 p->done = 1; 296 if (uid == (uid_t)-1) 297 break; 298 } 299 } 300 } 301 302 printsubtree(proc0, 0); 303 /* 304 * retc = 1 if an invalid username was supplied. 305 * errflg = 1 if no matching processes were found. 306 */ 307 return (retc || errflg); 308 } 309 310 311 #define PIDWIDTH 6 312 313 static void 314 printlines(ps_t *p, int level) 315 { 316 if (level == 0) 317 return; 318 319 if (!gflag) { 320 (void) printf("%*s", level * 2, ""); 321 return; 322 } 323 324 for (int i = 1; i < level; i++) { 325 ps_t *ancestor = p; 326 327 /* Find our ancestor at depth 'i' */ 328 for (int j = i; j < level; j++) 329 ancestor = ancestor->pp; 330 331 (void) printf("%s ", HAS_SIBLING(ancestor) ? box[BAR] : " "); 332 } 333 334 (void) printf("%s%s", HAS_SIBLING(p) ? box[VRIGHT] : box[CORNER], 335 box[DASH]); 336 } 337 338 static int 339 printone(ps_t *p, int level) 340 { 341 int n, indent; 342 343 if (p->done && !FAKEDPID0(p)) { 344 indent = level * 2; 345 346 if (wflag) { 347 n = strlen(p->psargs); 348 } else { 349 if ((n = columns - PIDWIDTH - indent - 2) < 0) 350 n = 0; 351 } 352 353 printlines(p, level); 354 if (p->pid >= 0) { 355 (void) printf("%-*d %.*s\n", PIDWIDTH, (int)p->pid, n, 356 p->psargs); 357 } else { 358 assert(cflag != 0); 359 (void) printf("[process contract %d: %s]\n", 360 (int)p->ctid, 361 p->svc_fmri == NULL ? "?" : p->svc_fmri); 362 } 363 return (1); 364 } 365 return (0); 366 } 367 368 static void 369 insertchild(ps_t *pp, ps_t *cp) 370 { 371 /* insert as child process of p */ 372 ps_t **here; 373 ps_t *sp; 374 375 /* sort by start time */ 376 for (here = &pp->cp, sp = pp->cp; 377 sp != NULL; 378 here = &sp->sp, sp = sp->sp) { 379 if (cp->start.tv_sec < sp->start.tv_sec) 380 break; 381 if (cp->start.tv_sec == sp->start.tv_sec && 382 cp->start.tv_nsec < sp->start.tv_nsec) 383 break; 384 } 385 cp->pp = pp; 386 cp->sp = sp; 387 *here = cp; 388 } 389 390 static ct_stathdl_t 391 ct_status_open(ctid_t ctid, struct stat64 *stp) 392 { 393 ct_stathdl_t hdl; 394 int fd; 395 396 if ((fd = contract_open(ctid, "process", "status", O_RDONLY)) == -1) 397 return (NULL); 398 399 if (fstat64(fd, stp) == -1 || ct_status_read(fd, CTD_FIXED, &hdl)) { 400 (void) close(fd); 401 return (NULL); 402 } 403 404 (void) close(fd); 405 406 return (hdl); 407 } 408 409 /* 410 * strdup() failure is OK - better to report something than fail totally. 411 */ 412 static void 413 p_get_svc_fmri(ps_t *p, ct_stathdl_t inhdl) 414 { 415 ct_stathdl_t hdl = inhdl; 416 struct stat64 st; 417 char *fmri; 418 419 if (hdl == NULL && (hdl = ct_status_open(p->ctid, &st)) == NULL) 420 return; 421 422 if (ct_pr_status_get_svc_fmri(hdl, &fmri) == 0) 423 p->svc_fmri = strdup(fmri); 424 425 if (inhdl == NULL) 426 ct_status_free(hdl); 427 } 428 429 static void 430 ctsort(ctid_t ctid, ps_t *p) 431 { 432 ps_t *pp; 433 int n; 434 ct_stathdl_t hdl; 435 struct stat64 st; 436 437 for (n = 0; n < nctps; n++) 438 if (ctps[n]->ctid == ctid) { 439 insertchild(ctps[n], p); 440 return; 441 } 442 443 if ((hdl = ct_status_open(ctid, &st)) == NULL) 444 return; 445 446 if (nctps >= ctsize) { 447 ctsize += CHUNK_SIZE; 448 ctps = xreallocarray(ctps, ctsize, sizeof (ps_t *)); 449 } 450 pp = zalloc(sizeof (*pp)); 451 ctps[nctps++] = pp; 452 453 pp->pid = -1; 454 pp->ctid = ctid; 455 456 p_get_svc_fmri(pp, hdl); 457 458 pp->start.tv_sec = st.st_ctime; 459 insertchild(pp, p); 460 461 pp->zoneid = ct_status_get_zoneid(hdl); 462 463 /* 464 * In a zlogin <zonename>, the contract belongs to the 465 * global zone and the shell opened belongs to <zonename>. 466 * If the -c and -z zonename flags are used together, then 467 * we need to adjust the zoneid in the contract's ps_t as 468 * follows: 469 * 470 * ptree -c -z <zonename> --> zoneid == p->zoneid 471 * ptree -c -z global --> zoneid == pp->zoneid 472 * 473 * The approach assumes that no tool can create processes in 474 * different zones under the same contract. If this is 475 * possible, ptree will need to refactor how it builds 476 * its internal tree of ps_t's 477 */ 478 if (zflag && p->zoneid != pp->zoneid && 479 (zoneid == p->zoneid || zoneid == pp->zoneid)) 480 pp->zoneid = p->zoneid; 481 if (ct_status_get_state(hdl) == CTS_OWNED) { 482 pp->ppid = ct_status_get_holder(hdl); 483 prsort(pp); 484 } else if (ct_status_get_state(hdl) == CTS_INHERITED) { 485 ctsort(ct_status_get_holder(hdl), pp); 486 } 487 ct_status_free(hdl); 488 } 489 490 static void 491 prsort(ps_t *p) 492 { 493 int n; 494 ps_t *pp; 495 496 /* If this node already has a parent, it's sorted */ 497 if (p->pp != NULL) 498 return; 499 500 for (n = 0; n < nps; n++) { 501 pp = ps[n]; 502 503 if (pp != NULL && p != pp && p->ppid == pp->pid) { 504 if (cflag && p->pid >= 0 && 505 p->ctid != -1 && p->ctid != pp->ctid) { 506 ctsort(p->ctid, p); 507 } else { 508 insertchild(pp, p); 509 prsort(pp); 510 } 511 return; 512 } 513 } 514 515 /* File parentless processes under their contracts */ 516 if (cflag && p->pid >= 0) 517 ctsort(p->ctid, p); 518 } 519 520 static void 521 printsubtree(ps_t *p, int level) 522 { 523 int printed; 524 525 printed = printone(p, level); 526 if (level != 0 || printed == 1) 527 level++; 528 for (p = p->cp; p != NULL; p = p->sp) 529 printsubtree(p, level); 530 } 531 532 /* 533 * Match against the service name (and just the final component), and any 534 * specified instance name. 535 */ 536 static bool 537 match_proc(ps_t *p) 538 { 539 bool matched = false; 540 const char *cp; 541 char *p_inst; 542 char *p_svc; 543 544 if (zflag && p->zoneid != zoneid) 545 return (false); 546 547 if (!sflag) 548 return (true); 549 550 if (p->svc_fmri == NULL) 551 return (false); 552 553 p_svc = parse_svc(p->svc_fmri, &p_inst); 554 555 if (strcmp(p_svc, match_svc) != 0 && 556 ((cp = strrchr(p_svc, '/')) == NULL || 557 strcmp(cp + 1, match_svc) != 0)) { 558 goto out; 559 } 560 561 if (strlen(match_inst) == 0 || 562 strcmp(p_inst, match_inst) == 0) 563 matched = true; 564 565 out: 566 free(p_svc); 567 free(p_inst); 568 return (matched); 569 } 570 571 static void 572 markprocs(ps_t *p) 573 { 574 if (match_proc(p)) 575 p->done = 1; 576 577 for (p = p->cp; p != NULL; p = p->sp) 578 markprocs(p); 579 } 580 581 /* 582 * If there's no "top" process, we fake one; it will be the parent of 583 * all orphans. 584 */ 585 static ps_t * 586 fakepid0(void) 587 { 588 ps_t *p0, *p; 589 int n; 590 591 p0 = zalloc(sizeof (*p0)); 592 593 /* First build all partial process trees. */ 594 for (n = 0; n < nps; n++) { 595 p = ps[n]; 596 if (p->pp == NULL) 597 prsort(p); 598 } 599 600 /* Then adopt all orphans. */ 601 for (n = 0; n < nps; n++) { 602 p = ps[n]; 603 if (p->pp == NULL) 604 insertchild(p0, p); 605 } 606 607 /* We've made sure earlier there's room for this. */ 608 ps[nps++] = p0; 609 return (p0); 610 } 611 612 /* convert string containing zone name or id to a numeric id */ 613 static zoneid_t 614 getzone(const char *arg) 615 { 616 zoneid_t zoneid; 617 618 if (zone_get_id(arg, &zoneid) != 0) 619 err(EXIT_FAILURE, "unknown zone: %s", arg); 620 621 return (zoneid); 622 } 623 624 /* svc:/mysvc:default -> mysvc, default */ 625 static char * 626 parse_svc(const char *arg, char **instp) 627 { 628 const char *p = arg; 629 char *ret; 630 char *cp; 631 632 if (strncmp(p, "svc:/", strlen("svc:/")) == 0) 633 p += strlen("svc:/"); 634 635 ret = xstrdup(p); 636 637 if ((cp = strrchr(ret, ':')) != NULL) { 638 *cp = '\0'; 639 cp++; 640 } else { 641 cp = ""; 642 } 643 644 *instp = xstrdup(cp); 645 return (ret); 646 } 647 648 static int 649 add_proc(psinfo_t *info, lwpsinfo_t *lwp __unused, void *arg __unused) 650 { 651 ps_t *p; 652 653 /* 654 * We make sure there is always a free slot in the table 655 * in case we need to add a fake p0; 656 */ 657 if (nps + 1 >= psize) { 658 psize += CHUNK_SIZE; 659 ps = xreallocarray(ps, psize, sizeof (ps_t)); 660 } 661 662 p = zalloc(sizeof (*p)); 663 ps[nps++] = p; 664 p->done = 0; 665 p->uid = info->pr_uid; 666 p->gid = info->pr_gid; 667 p->pid = info->pr_pid; 668 p->ppid = info->pr_ppid; 669 p->pgrp = info->pr_pgid; 670 p->sid = info->pr_sid; 671 p->zoneid = info->pr_zoneid; 672 p->ctid = info->pr_contract; 673 p->start = info->pr_start; 674 proc_unctrl_psinfo(info); 675 if (info->pr_nlwp == 0) 676 (void) strcpy(p->psargs, "<defunct>"); 677 else if (info->pr_psargs[0] == '\0') 678 (void) strncpy(p->psargs, info->pr_fname, 679 sizeof (p->psargs)); 680 else 681 (void) strncpy(p->psargs, info->pr_psargs, 682 sizeof (p->psargs)); 683 p->psargs[sizeof (p->psargs)-1] = '\0'; 684 p->pp = NULL; 685 p->sp = NULL; 686 687 if (sflag) 688 p_get_svc_fmri(p, NULL); 689 690 if (p->pid == p->ppid) 691 proc0 = p; 692 if (p->pid == 1) 693 proc1 = p; 694 695 return (0); 696 } 697 698 699 static size_t 700 get_termwidth(void) 701 { 702 char *s; 703 704 if ((s = getenv("COLUMNS")) != NULL) { 705 unsigned long n; 706 707 errno = 0; 708 n = strtoul(s, NULL, 10); 709 if (n != 0 && errno == 0) { 710 /* Sanity check on the range */ 711 if (n > INT_MAX) 712 n = COLUMN_DEFAULT; 713 return (n); 714 } 715 } 716 717 struct winsize winsize; 718 719 if (isatty(STDOUT_FILENO) && 720 ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) == 0 && 721 winsize.ws_col != 0) { 722 return (winsize.ws_col); 723 } 724 725 return (COLUMN_DEFAULT); 726 } 727 728 static const char ** 729 get_boxchars(void) 730 { 731 char *loc = setlocale(LC_ALL, ""); 732 733 if (loc == NULL) 734 return (box_ascii); 735 736 const char *p = strstr(loc, "UTF-8"); 737 738 /* 739 * Only use the UTF-8 box drawing characters if the locale ends 740 * with "UTF-8". 741 */ 742 if (p != NULL && p[5] == '\0') 743 return (box_utf8); 744 745 return (box_ascii); 746 } 747 748 static void * 749 zalloc(size_t len) 750 { 751 void *p = calloc(1, len); 752 753 if (p == NULL) 754 err(EXIT_FAILURE, "calloc"); 755 return (p); 756 } 757 758 static void * 759 xreallocarray(void *ptr, size_t nelem, size_t elsize) 760 { 761 void *p = reallocarray(ptr, nelem, elsize); 762 763 if (p == NULL) 764 err(EXIT_FAILURE, "reallocarray"); 765 return (p); 766 } 767 768 static char * 769 xstrdup(const char *s) 770 { 771 char *news = strdup(s); 772 773 if (news == NULL) 774 err(EXIT_FAILURE, "strdup"); 775 return (news); 776 } 777