xref: /illumos-gate/usr/src/cmd/ptools/ptree/ptree.c (revision 7c478bd9)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * ptree -- print family tree of processes
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include <assert.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <fcntl.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 <sys/contract.h>
49 #include <sys/ctfs.h>
50 #include <libcontract_priv.h>
51 #include <sys/stat.h>
52 
53 #define	FAKEDPID0(p)	(p->pid == 0 && p->psargs[0] == '\0')
54 
55 typedef struct ps {
56 	int	done;
57 	uid_t	uid;
58 	uid_t	gid;
59 	pid_t	pid;		/* pid == -1 indicates this is a contract */
60 	pid_t	ppid;
61 	pid_t	pgrp;
62 	pid_t	sid;
63 	zoneid_t zoneid;
64 	ctid_t	ctid;
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	zflag = 0;
86 static	zoneid_t zoneid;
87 static	int	columns = 80;
88 
89 static void markprocs(ps_t *p);
90 static int printone(ps_t *p, int level);
91 static void insertchild(ps_t *, ps_t *);
92 static void prsort(ps_t *p);
93 static void printsubtree(ps_t *p, int level);
94 static zoneid_t getzone(char *arg);
95 static ps_t *fakepid0(void);
96 
97 int
98 main(int argc, char **argv)
99 {
100 	psinfo_t info;	/* process information structure from /proc */
101 	int opt;
102 	int errflg = 0;
103 	struct winsize winsize;
104 	char *s;
105 	int n;
106 	int retc = 0;
107 
108 	DIR *dirp;
109 	struct dirent *dentp;
110 	char	pname[100];
111 	int	pdlen;
112 
113 	ps_t *p;
114 
115 	if ((command = strrchr(argv[0], '/')) == NULL)
116 		command = argv[0];
117 	else
118 		command++;
119 
120 	/* options */
121 	while ((opt = getopt(argc, argv, "acz:")) != EOF) {
122 		switch (opt) {
123 		case 'a':		/* include children of process 0 */
124 			aflag = 1;
125 			break;
126 		case 'c':		/* display contract ownership */
127 			aflag = cflag = 1;
128 			break;
129 		case 'z':		/* only processes in given zone */
130 			zflag = 1;
131 			zoneid = getzone(optarg);
132 			break;
133 		default:
134 			errflg = 1;
135 			break;
136 		}
137 	}
138 
139 	argc -= optind;
140 	argv += optind;
141 
142 	if (errflg) {
143 		(void) fprintf(stderr,
144 		    "usage:\t%s [-ac] [-z zone] [ {pid|user} ... ]\n",
145 		    command);
146 		(void) fprintf(stderr,
147 		    "  (show process trees)\n");
148 		(void) fprintf(stderr,
149 		    "  list can include process-ids and user names\n");
150 		(void) fprintf(stderr,
151 		    "  -a : include children of process 0\n");
152 		(void) fprintf(stderr,
153 		    "  -c : show contract ownership\n");
154 		(void) fprintf(stderr,
155 		    "  -z : print only processes in given zone\n");
156 		return (2);
157 	}
158 
159 	/*
160 	 * Kind of a hack to determine the width of the output...
161 	 */
162 	if ((s = getenv("COLUMNS")) != NULL && (n = atoi(s)) > 0)
163 		columns = n;
164 	else if (isatty(fileno(stdout)) &&
165 	    ioctl(fileno(stdout), TIOCGWINSZ, &winsize) == 0 &&
166 	    winsize.ws_col != 0)
167 		columns = winsize.ws_col;
168 
169 	nps = 0;
170 	psize = 0;
171 	ps = NULL;
172 
173 	/*
174 	 * Search the /proc directory for all processes.
175 	 */
176 	if ((dirp = opendir("/proc")) == NULL) {
177 		(void) fprintf(stderr, "%s: cannot open /proc directory\n",
178 		    command);
179 		return (1);
180 	}
181 
182 	(void) strcpy(pname, "/proc");
183 	pdlen = strlen(pname);
184 	pname[pdlen++] = '/';
185 
186 	/* for each active process --- */
187 	while (dentp = readdir(dirp)) {
188 		int	procfd;	/* filedescriptor for /proc/nnnnn/psinfo */
189 
190 		if (dentp->d_name[0] == '.')		/* skip . and .. */
191 			continue;
192 		(void) strcpy(pname + pdlen, dentp->d_name);
193 		(void) strcpy(pname + strlen(pname), "/psinfo");
194 retry:
195 		if ((procfd = open(pname, O_RDONLY)) == -1)
196 			continue;
197 
198 		/*
199 		 * Get the info structure for the process and close quickly.
200 		 */
201 		if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
202 			int	saverr = errno;
203 
204 			(void) close(procfd);
205 			if (saverr == EAGAIN)
206 				goto retry;
207 			if (saverr != ENOENT)
208 				perror(pname);
209 			continue;
210 		}
211 		(void) close(procfd);
212 
213 		/*
214 		 * We make sure there's always a free slot in the table
215 		 * in case we need to add a fake p0.
216 		 */
217 		if (nps + 1 >= psize) {
218 			if ((psize *= 2) == 0)
219 				psize = 20;
220 			if ((ps = realloc(ps, psize*sizeof (ps_t *))) == NULL) {
221 				perror("realloc()");
222 				return (1);
223 			}
224 		}
225 		if ((p = malloc(sizeof (ps_t))) == NULL) {
226 			perror("malloc()");
227 			return (1);
228 		}
229 		ps[nps++] = p;
230 		p->done = 0;
231 		p->uid = info.pr_uid;
232 		p->gid = info.pr_gid;
233 		p->pid = info.pr_pid;
234 		p->ppid = info.pr_ppid;
235 		p->pgrp = info.pr_pgid;
236 		p->sid = info.pr_sid;
237 		p->zoneid = info.pr_zoneid;
238 		p->ctid = info.pr_contract;
239 		p->start = info.pr_start;
240 		proc_unctrl_psinfo(&info);
241 		if (info.pr_nlwp == 0)
242 			(void) strcpy(p->psargs, "<defunct>");
243 		else if (info.pr_psargs[0] == '\0')
244 			(void) strncpy(p->psargs, info.pr_fname,
245 			    sizeof (p->psargs));
246 		else
247 			(void) strncpy(p->psargs, info.pr_psargs,
248 			    sizeof (p->psargs));
249 		p->psargs[sizeof (p->psargs)-1] = '\0';
250 		p->pp = NULL;
251 		p->sp = NULL;
252 		p->cp = NULL;
253 		if (p->pid == p->ppid)
254 			proc0 = p;
255 		if (p->pid == 1)
256 			proc1 = p;
257 	}
258 
259 	(void) closedir(dirp);
260 	if (proc0 == NULL)
261 		proc0 = fakepid0();
262 	if (proc1 == NULL)
263 		proc1 = proc0;
264 
265 	for (n = 0; n < nps; n++) {
266 		p = ps[n];
267 		if (p->pp == NULL)
268 			prsort(p);
269 	}
270 
271 	if (cflag)
272 		/* Parent all orphan contracts to process 0. */
273 		for (n = 0; n < nctps; n++) {
274 			p = ctps[n];
275 			if (p->pp == NULL)
276 				insertchild(proc0, p);
277 		}
278 
279 	if (argc == 0) {
280 		for (p = aflag ? proc0->cp : proc1->cp; p != NULL; p = p->sp) {
281 			markprocs(p);
282 			printsubtree(p, 0);
283 		}
284 		return (0);
285 	}
286 
287 	/*
288 	 * Initially, assume we're not going to find any processes.  If we do
289 	 * mark any, then set this to 0 to indicate no error.
290 	 */
291 	errflg = 1;
292 
293 	while (argc-- > 0) {
294 		char *arg;
295 		char *next;
296 		pid_t pid;
297 		uid_t uid;
298 		int n;
299 
300 		/* in case some silly person said 'ptree /proc/[0-9]*' */
301 		arg = strrchr(*argv, '/');
302 		if (arg++ == NULL)
303 			arg = *argv;
304 		argv++;
305 		uid = -1;
306 		errno = 0;
307 		pid = strtoul(arg, &next, 10);
308 		if (errno != 0 || *next != '\0') {
309 			struct passwd *pw = getpwnam(arg);
310 			if (pw == NULL) {
311 				(void) fprintf(stderr,
312 					"%s: invalid username: %s\n",
313 					command, arg);
314 				retc = 1;
315 				continue;
316 			}
317 			uid = pw->pw_uid;
318 			pid = -1;
319 		}
320 
321 		for (n = 0; n < nps; n++) {
322 			ps_t *p = ps[n];
323 
324 			/*
325 			 * A match on pid causes the subtree starting at pid
326 			 * to be printed, regardless of the -a flag.
327 			 * For uid matches, we never include pid 0 and only
328 			 * include the children of pid 0 if -a was specified.
329 			 */
330 			if (p->pid == pid || (p->uid == uid && p->pid != 0 &&
331 			    (p->ppid != 0 || aflag))) {
332 				errflg = 0;
333 				markprocs(p);
334 				if (p->pid != 0)
335 					for (p = p->pp; p != NULL &&
336 					    p->done != 1 && p->pid != 0;
337 					    p = p->pp)
338 						if ((p->ppid != 0 || aflag) &&
339 						    (!zflag ||
340 						    p->zoneid == zoneid))
341 							p->done = 1;
342 				if (uid == -1)
343 					break;
344 			}
345 		}
346 	}
347 
348 	printsubtree(proc0, 0);
349 	/*
350 	 * retc = 1 if an invalid username was supplied.
351 	 * errflg = 1 if no matching processes were found.
352 	 */
353 	return (retc || errflg);
354 }
355 
356 #define	PIDWIDTH	5
357 
358 static int
359 printone(ps_t *p, int level)
360 {
361 	int n, indent;
362 
363 	if (p->done && !FAKEDPID0(p)) {
364 		indent = level * 2;
365 		if ((n = columns - PIDWIDTH - indent - 2) < 0)
366 			n = 0;
367 		if (p->pid >= 0) {
368 			(void) printf("%*.*s%-*d %.*s\n", indent, indent, " ",
369 			    PIDWIDTH, (int)p->pid, n, p->psargs);
370 		} else {
371 			assert(cflag != 0);
372 			(void) printf("%*.*s[process contract %d]\n",
373 			    indent, indent, " ", (int)p->ctid);
374 		}
375 		return (1);
376 	}
377 	return (0);
378 }
379 
380 static void
381 insertchild(ps_t *pp, ps_t *cp)
382 {
383 	/* insert as child process of p */
384 	ps_t **here;
385 	ps_t *sp;
386 
387 	/* sort by start time */
388 	for (here = &pp->cp, sp = pp->cp;
389 	    sp != NULL;
390 	    here = &sp->sp, sp = sp->sp) {
391 		if (cp->start.tv_sec < sp->start.tv_sec)
392 			break;
393 		if (cp->start.tv_sec == sp->start.tv_sec &&
394 		    cp->start.tv_nsec < sp->start.tv_nsec)
395 			break;
396 	}
397 	cp->pp = pp;
398 	cp->sp = sp;
399 	*here = cp;
400 }
401 
402 static void
403 ctsort(ctid_t ctid, ps_t *p)
404 {
405 	ps_t *pp;
406 	int fd, n;
407 	ct_stathdl_t hdl;
408 	struct stat64 st;
409 
410 	for (n = 0; n < nctps; n++)
411 		if (ctps[n]->ctid == ctid) {
412 			insertchild(ctps[n], p);
413 			return;
414 		}
415 
416 	if ((fd = contract_open(ctid, "process", "status", O_RDONLY)) == -1)
417 		return;
418 	if (fstat64(fd, &st) == -1 || ct_status_read(fd, CTD_COMMON, &hdl)) {
419 		(void) close(fd);
420 		return;
421 	}
422 	(void) close(fd);
423 
424 	if (nctps >= ctsize) {
425 		if ((ctsize *= 2) == 0)
426 			ctsize = 20;
427 		if ((ctps = realloc(ctps, ctsize * sizeof (ps_t *))) == NULL) {
428 			perror("realloc()");
429 			exit(1);
430 		}
431 	}
432 	pp = calloc(sizeof (ps_t), 1);
433 	if (pp == NULL) {
434 		perror("calloc()");
435 		exit(1);
436 	}
437 	ctps[nctps++] = pp;
438 
439 	pp->pid = -1;
440 	pp->ctid = ctid;
441 	pp->start.tv_sec = st.st_ctime;
442 	insertchild(pp, p);
443 
444 	if (ct_status_get_state(hdl) == CTS_OWNED) {
445 		pp->ppid = ct_status_get_holder(hdl);
446 		prsort(pp);
447 	} else if (ct_status_get_state(hdl) == CTS_INHERITED) {
448 		ctsort(ct_status_get_holder(hdl), pp);
449 	}
450 	ct_status_free(hdl);
451 }
452 
453 static void
454 prsort(ps_t *p)
455 {
456 	int n;
457 	ps_t *pp;
458 
459 	/* If this node already has a parent, it's sorted */
460 	if (p->pp != NULL)
461 		return;
462 
463 	for (n = 0; n < nps; n++) {
464 		pp = ps[n];
465 
466 		if (pp != NULL && p != pp && p->ppid == pp->pid) {
467 			if (cflag && p->pid >= 0 && p->ctid != pp->ctid) {
468 				ctsort(p->ctid, p);
469 			} else {
470 				insertchild(pp, p);
471 				prsort(pp);
472 			}
473 			return;
474 		}
475 	}
476 
477 	/* File parentless processes under their contracts */
478 	if (cflag && p->pid >= 0)
479 		ctsort(p->ctid, p);
480 }
481 
482 static void
483 printsubtree(ps_t *p, int level)
484 {
485 	int printed;
486 
487 	printed = printone(p, level);
488 	if (level != 0 || printed == 1)
489 		level++;
490 	for (p = p->cp; p != NULL; p = p->sp)
491 		printsubtree(p, level);
492 }
493 
494 static void
495 markprocs(ps_t *p)
496 {
497 	if (!zflag || p->zoneid == zoneid)
498 		p->done = 1;
499 	for (p = p->cp; p != NULL; p = p->sp)
500 		markprocs(p);
501 }
502 
503 /*
504  * If there's no "top" process, we fake one; it will be the parent of
505  * all orphans.
506  */
507 static ps_t *
508 fakepid0(void)
509 {
510 	ps_t *p0, *p;
511 	int n;
512 
513 	if ((p0 = malloc(sizeof (ps_t))) == NULL) {
514 		perror("malloc()");
515 		exit(1);
516 	}
517 	(void) memset(p0, '\0', sizeof (ps_t));
518 
519 	/* First build all partial process trees. */
520 	for (n = 0; n < nps; n++) {
521 		p = ps[n];
522 		if (p->pp == NULL)
523 			prsort(p);
524 	}
525 
526 	/* Then adopt all orphans. */
527 	for (n = 0; n < nps; n++) {
528 		p = ps[n];
529 		if (p->pp == NULL)
530 			insertchild(p0, p);
531 	}
532 
533 	/* We've made sure earlier there's room for this. */
534 	ps[nps++] = p0;
535 	return (p0);
536 }
537 
538 /* convert string containing zone name or id to a numeric id */
539 static zoneid_t
540 getzone(char *arg)
541 {
542 	zoneid_t zoneid;
543 
544 	if (zone_get_id(arg, &zoneid) != 0) {
545 		(void) fprintf(stderr, "%s: unknown zone: %s\n", command, arg);
546 		exit(1);
547 	}
548 	return (zoneid);
549 }
550