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