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