xref: /illumos-gate/usr/src/cmd/ptools/ptree/ptree.c (revision ed093b41a93e8563e6e1e5dae0768dda2a7bcc27)
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