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 (c) 2014, Joyent, Inc. All rights reserved.
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 "ptools_common.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
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 char ppath[PATH_MAX];
107
108 DIR *dirp;
109 struct dirent *dentp;
110 char pname[PATH_MAX];
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 (void) proc_snprintf(ppath, sizeof (ppath), "/proc");
174
175 /*
176 * Search the /proc directory for all processes.
177 */
178 if ((dirp = opendir(ppath)) == NULL) {
179 (void) fprintf(stderr, "%s: cannot open %s directory\n",
180 command, ppath);
181 return (1);
182 }
183
184 (void) strcpy(pname, ppath);
185 pdlen = strlen(pname);
186 pname[pdlen++] = '/';
187
188 /* for each active process --- */
189 while (dentp = readdir(dirp)) {
190 int procfd; /* filedescriptor for /proc/nnnnn/psinfo */
191
192 if (dentp->d_name[0] == '.') /* skip . and .. */
193 continue;
194 (void) strcpy(pname + pdlen, dentp->d_name);
195 (void) strcpy(pname + strlen(pname), "/psinfo");
196 retry:
197 if ((procfd = open(pname, O_RDONLY)) == -1)
198 continue;
199
200 /*
201 * Get the info structure for the process and close quickly.
202 */
203 if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
204 int saverr = errno;
205
206 (void) close(procfd);
207 if (saverr == EAGAIN)
208 goto retry;
209 if (saverr != ENOENT)
210 perror(pname);
211 continue;
212 }
213 (void) close(procfd);
214
215 /*
216 * We make sure there's always a free slot in the table
217 * in case we need to add a fake p0.
218 */
219 if (nps + 1 >= psize) {
220 if ((psize *= 2) == 0)
221 psize = 20;
222 if ((ps = realloc(ps, psize*sizeof (ps_t *))) == NULL) {
223 perror("realloc()");
224 return (1);
225 }
226 }
227 if ((p = malloc(sizeof (ps_t))) == NULL) {
228 perror("malloc()");
229 return (1);
230 }
231 ps[nps++] = p;
232 p->done = 0;
233 p->uid = info.pr_uid;
234 p->gid = info.pr_gid;
235 p->pid = info.pr_pid;
236 p->ppid = info.pr_ppid;
237 p->pgrp = info.pr_pgid;
238 p->sid = info.pr_sid;
239 p->zoneid = info.pr_zoneid;
240 p->ctid = info.pr_contract;
241 p->start = info.pr_start;
242 proc_unctrl_psinfo(&info);
243 if (info.pr_nlwp == 0)
244 (void) strcpy(p->psargs, "<defunct>");
245 else if (info.pr_psargs[0] == '\0')
246 (void) strncpy(p->psargs, info.pr_fname,
247 sizeof (p->psargs));
248 else
249 (void) strncpy(p->psargs, info.pr_psargs,
250 sizeof (p->psargs));
251 p->psargs[sizeof (p->psargs)-1] = '\0';
252 p->pp = NULL;
253 p->sp = NULL;
254 p->cp = NULL;
255 if (p->pid == p->ppid)
256 proc0 = p;
257 if (p->pid == 1)
258 proc1 = p;
259 }
260
261 (void) closedir(dirp);
262 if (proc0 == NULL)
263 proc0 = fakepid0();
264 if (proc1 == NULL)
265 proc1 = proc0;
266
267 for (n = 0; n < nps; n++) {
268 p = ps[n];
269 if (p->pp == NULL)
270 prsort(p);
271 }
272
273 if (cflag)
274 /* Parent all orphan contracts to process 0. */
275 for (n = 0; n < nctps; n++) {
276 p = ctps[n];
277 if (p->pp == NULL)
278 insertchild(proc0, p);
279 }
280
281 if (argc == 0) {
282 for (p = aflag ? proc0->cp : proc1->cp; p != NULL; p = p->sp) {
283 markprocs(p);
284 printsubtree(p, 0);
285 }
286 return (0);
287 }
288
289 /*
290 * Initially, assume we're not going to find any processes. If we do
291 * mark any, then set this to 0 to indicate no error.
292 */
293 errflg = 1;
294
295 while (argc-- > 0) {
296 char *arg;
297 char *next;
298 pid_t pid;
299 uid_t uid;
300 int n;
301
302 /* in case some silly person said 'ptree /proc/[0-9]*' */
303 arg = strrchr(*argv, '/');
304 if (arg++ == NULL)
305 arg = *argv;
306 argv++;
307 uid = (uid_t)-1;
308 errno = 0;
309 pid = strtoul(arg, &next, 10);
310 if (errno != 0 || *next != '\0') {
311 struct passwd *pw = getpwnam(arg);
312 if (pw == NULL) {
313 (void) fprintf(stderr,
314 "%s: invalid username: %s\n",
315 command, arg);
316 retc = 1;
317 continue;
318 }
319 uid = pw->pw_uid;
320 pid = -1;
321 }
322
323 for (n = 0; n < nps; n++) {
324 ps_t *p = ps[n];
325
326 /*
327 * A match on pid causes the subtree starting at pid
328 * to be printed, regardless of the -a flag.
329 * For uid matches, we never include pid 0 and only
330 * include the children of pid 0 if -a was specified.
331 */
332 if (p->pid == pid || (p->uid == uid && p->pid != 0 &&
333 (p->ppid != 0 || aflag))) {
334 errflg = 0;
335 markprocs(p);
336 if (p->pid != 0)
337 for (p = p->pp; p != NULL &&
338 p->done != 1 && p->pid != 0;
339 p = p->pp)
340 if ((p->ppid != 0 || aflag) &&
341 (!zflag ||
342 p->zoneid == zoneid))
343 p->done = 1;
344 if (uid == (uid_t)-1)
345 break;
346 }
347 }
348 }
349
350 printsubtree(proc0, 0);
351 /*
352 * retc = 1 if an invalid username was supplied.
353 * errflg = 1 if no matching processes were found.
354 */
355 return (retc || errflg);
356 }
357
358 #define PIDWIDTH 5
359
360 static int
361 printone(ps_t *p, int level)
362 {
363 int n, indent;
364
365 if (p->done && !FAKEDPID0(p)) {
366 indent = level * 2;
367 if ((n = columns - PIDWIDTH - indent - 2) < 0)
368 n = 0;
369 if (p->pid >= 0) {
370 (void) printf("%*.*s%-*d %.*s\n", indent, indent, " ",
371 PIDWIDTH, (int)p->pid, n, p->psargs);
372 } else {
373 assert(cflag != 0);
374 (void) printf("%*.*s[process contract %d]\n",
375 indent, indent, " ", (int)p->ctid);
376 }
377 return (1);
378 }
379 return (0);
380 }
381
382 static void
383 insertchild(ps_t *pp, ps_t *cp)
384 {
385 /* insert as child process of p */
386 ps_t **here;
387 ps_t *sp;
388
389 /* sort by start time */
390 for (here = &pp->cp, sp = pp->cp;
391 sp != NULL;
392 here = &sp->sp, sp = sp->sp) {
393 if (cp->start.tv_sec < sp->start.tv_sec)
394 break;
395 if (cp->start.tv_sec == sp->start.tv_sec &&
396 cp->start.tv_nsec < sp->start.tv_nsec)
397 break;
398 }
399 cp->pp = pp;
400 cp->sp = sp;
401 *here = cp;
402 }
403
404 static void
405 ctsort(ctid_t ctid, ps_t *p)
406 {
407 ps_t *pp;
408 int fd, n;
409 ct_stathdl_t hdl;
410 struct stat64 st;
411
412 for (n = 0; n < nctps; n++)
413 if (ctps[n]->ctid == ctid) {
414 insertchild(ctps[n], p);
415 return;
416 }
417
418 if ((fd = contract_open(ctid, "process", "status", O_RDONLY)) == -1)
419 return;
420 if (fstat64(fd, &st) == -1 || ct_status_read(fd, CTD_COMMON, &hdl)) {
421 (void) close(fd);
422 return;
423 }
424 (void) close(fd);
425
426 if (nctps >= ctsize) {
427 if ((ctsize *= 2) == 0)
428 ctsize = 20;
429 if ((ctps = realloc(ctps, ctsize * sizeof (ps_t *))) == NULL) {
430 perror("realloc()");
431 exit(1);
432 }
433 }
434 pp = calloc(sizeof (ps_t), 1);
435 if (pp == NULL) {
436 perror("calloc()");
437 exit(1);
438 }
439 ctps[nctps++] = pp;
440
441 pp->pid = -1;
442 pp->ctid = ctid;
443 pp->start.tv_sec = st.st_ctime;
444 insertchild(pp, p);
445
446 pp->zoneid = ct_status_get_zoneid(hdl);
447 /*
448 * In a zlogin <zonename>, the contract belongs to the
449 * global zone and the shell opened belongs to <zonename>.
450 * If the -c and -z zonename flags are used together, then
451 * we need to adjust the zoneid in the contract's ps_t as
452 * follows:
453 *
454 * ptree -c -z <zonename> --> zoneid == p->zoneid
455 * ptree -c -z global --> zoneid == pp->zoneid
456 *
457 * The approach assumes that no tool can create processes in
458 * different zones under the same contract. If this is
459 * possible, ptree will need to refactor how it builds
460 * its internal tree of ps_t's
461 */
462 if (zflag && p->zoneid != pp->zoneid &&
463 (zoneid == p->zoneid || zoneid == pp->zoneid))
464 pp->zoneid = p->zoneid;
465 if (ct_status_get_state(hdl) == CTS_OWNED) {
466 pp->ppid = ct_status_get_holder(hdl);
467 prsort(pp);
468 } else if (ct_status_get_state(hdl) == CTS_INHERITED) {
469 ctsort(ct_status_get_holder(hdl), pp);
470 }
471 ct_status_free(hdl);
472 }
473
474 static void
475 prsort(ps_t *p)
476 {
477 int n;
478 ps_t *pp;
479
480 /* If this node already has a parent, it's sorted */
481 if (p->pp != NULL)
482 return;
483
484 for (n = 0; n < nps; n++) {
485 pp = ps[n];
486
487 if (pp != NULL && p != pp && p->ppid == pp->pid) {
488 if (cflag && p->pid >= 0 &&
489 p->ctid != -1 && p->ctid != pp->ctid) {
490 ctsort(p->ctid, p);
491 } else {
492 insertchild(pp, p);
493 prsort(pp);
494 }
495 return;
496 }
497 }
498
499 /* File parentless processes under their contracts */
500 if (cflag && p->pid >= 0)
501 ctsort(p->ctid, p);
502 }
503
504 static void
505 printsubtree(ps_t *p, int level)
506 {
507 int printed;
508
509 printed = printone(p, level);
510 if (level != 0 || printed == 1)
511 level++;
512 for (p = p->cp; p != NULL; p = p->sp)
513 printsubtree(p, level);
514 }
515
516 static void
517 markprocs(ps_t *p)
518 {
519 if (!zflag || p->zoneid == zoneid)
520 p->done = 1;
521 for (p = p->cp; p != NULL; p = p->sp)
522 markprocs(p);
523 }
524
525 /*
526 * If there's no "top" process, we fake one; it will be the parent of
527 * all orphans.
528 */
529 static ps_t *
530 fakepid0(void)
531 {
532 ps_t *p0, *p;
533 int n;
534
535 if ((p0 = malloc(sizeof (ps_t))) == NULL) {
536 perror("malloc()");
537 exit(1);
538 }
539 (void) memset(p0, '\0', sizeof (ps_t));
540
541 /* First build all partial process trees. */
542 for (n = 0; n < nps; n++) {
543 p = ps[n];
544 if (p->pp == NULL)
545 prsort(p);
546 }
547
548 /* Then adopt all orphans. */
549 for (n = 0; n < nps; n++) {
550 p = ps[n];
551 if (p->pp == NULL)
552 insertchild(p0, p);
553 }
554
555 /* We've made sure earlier there's room for this. */
556 ps[nps++] = p0;
557 return (p0);
558 }
559
560 /* convert string containing zone name or id to a numeric id */
561 static zoneid_t
562 getzone(char *arg)
563 {
564 zoneid_t zoneid;
565
566 if (zone_get_id(arg, &zoneid) != 0) {
567 (void) fprintf(stderr, "%s: unknown zone: %s\n", command, arg);
568 exit(1);
569 }
570 return (zoneid);
571 }