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
usage(void)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
main(int argc,char ** argv)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
printlines(ps_t * p,int level)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
printone(ps_t * p,int level)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
insertchild(ps_t * pp,ps_t * cp)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
ct_status_open(ctid_t ctid,struct stat64 * stp)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
p_get_svc_fmri(ps_t * p,ct_stathdl_t inhdl)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
ctsort(ctid_t ctid,ps_t * p)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
prsort(ps_t * p)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
printsubtree(ps_t * p,int level)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
match_proc(ps_t * p)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
markprocs(ps_t * p)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 *
fakepid0(void)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
getzone(const char * arg)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 *
parse_svc(const char * arg,char ** instp)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
add_proc(psinfo_t * info,lwpsinfo_t * lwp __unused,void * arg __unused)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
get_termwidth(void)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 **
get_boxchars(void)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 *
zalloc(size_t len)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 *
xreallocarray(void * ptr,size_t nelem,size_t elsize)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 *
xstrdup(const char * s)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