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