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