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