2
* pstree.c - display process tree
4
* Copyright (C) 1993-2002 Werner Almesberger
5
* Copyright (C) 2002-2009 Craig Small
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 2 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
41
#include <sys/types.h>
43
#include <sys/ioctl.h>
49
#include <selinux/selinux.h>
50
#endif /*WITH_SELINUX */
52
extern const char *__progname;
54
#define PROC_BASE "/proc"
56
/* UTF-8 defines by Johan Myreen, updated by Ben Winslow */
57
#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */
58
#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */
59
#define UTF_H "\342\224\200" /* U+2500, Horizontal */
60
#define UTF_UR "\342\224\224" /* U+2514, Up and right */
61
#define UTF_HD "\342\224\254" /* U+252C, Horizontal and down */
63
#define VT_BEG "\033(0\017" /* use graphic chars */
64
#define VT_END "\033(B" /* back to normal char set */
65
#define VT_V "x" /* see UTF definitions above */
71
typedef struct _proc {
72
char comm[COMM_LEN + 1];
73
char **argv; /* only used : argv[0] is 1st arg; undef if argc < 1 */
74
int argc; /* with -a : number of arguments, -1 if swapped */
78
security_context_t scontext;
79
#endif /*WITH_SELINUX */
81
struct _child *children;
87
#define PFLAG_HILIGHT 0x01
88
#define PFLAG_THREAD 0x02
90
typedef struct _child {
96
const char *empty_2; /* */
97
const char *branch_2; /* |- */
98
const char *vert_2; /* | */
99
const char *last_2; /* `- */
100
const char *single_3; /* --- */
101
const char *first_3; /* -+- */
103
" ", "|-", "| ", "`-", "---", "-+-"}
109
UTF_UR UTF_H, UTF_H UTF_H UTF_H, UTF_H UTF_HD UTF_H}, sym_vt100 = {
111
VT_BEG VT_VR VT_H VT_END,
112
VT_BEG VT_V VT_END " ",
113
VT_BEG VT_UR VT_H VT_END,
114
VT_BEG VT_H VT_H VT_H VT_END, VT_BEG VT_H VT_HD VT_H VT_END}
118
static PROC *list = NULL;
120
/* The buffers will be dynamically increased in size as needed. */
121
static int capacity = 0;
122
static int *width = NULL;
123
static int *more = NULL;
125
static int print_args = 0, compact = 1, user_change = 0, pids = 0,
126
show_parents = 0, by_pid = 0, trunc = 1, wait_end = 0;
128
static int show_scontext = 0;
129
#endif /*WITH_SELINUX */
130
static int output_width = 132;
131
static int cur_x = 1;
132
static char last_char = 0;
133
static int dumped = 0; /* used by dump_by_user */
134
static int charlen = 0; /* length of character */
137
* Allocates additional buffer space for width and more as needed.
138
* The first call will allocate the first buffer.
140
* index the index that will be used after the call
143
static void ensure_buffer_capacity(int index)
145
if (index >= capacity) {
150
if (!(width = realloc(width, capacity * sizeof(int)))) {
154
if (!(more = realloc(more, capacity * sizeof(int)))) {
162
* Frees any buffers allocated by ensure_buffer_capacity.
164
static void free_buffers()
177
static void out_char(char c)
179
if (charlen == 0) { /* "new" character */
180
if ((c & 0x80) == 0) {
181
charlen = 1; /* ASCII */
182
} else if ((c & 0xe0) == 0xc0) { /* 110.. 2 bytes */
184
} else if ((c & 0xf0) == 0xe0) { /* 1110.. 3 bytes */
186
} else if ((c & 0xf8) == 0xf0) { /* 11110.. 4 bytes */
191
cur_x++; /* count first byte of whatever it is only */
194
if (!trunc || cur_x <= output_width)
197
if (trunc && (cur_x == output_width + 1))
203
static void out_string(const char *str)
210
static int out_int(int x)
211
{ /* non-negative integers only */
215
for (div = 1; x / div; div *= 10)
219
for (div /= 10; div; div /= 10)
220
out_char('0' + (x / div) % 10);
225
static void out_scontext(security_context_t scontext)
228
out_string(scontext);
231
#endif /*WITH_SELINUX */
234
static void out_newline(void)
236
if (last_char && cur_x == output_width)
244
static PROC *find_proc(pid_t pid)
248
for (walk = list; walk; walk = walk->next)
249
if (walk->pid == pid)
255
static PROC *new_proc(const char *comm, pid_t pid, uid_t uid,
256
security_context_t scontext)
257
#else /*WITH_SELINUX */
258
static PROC *new_proc(const char *comm, pid_t pid, uid_t uid)
259
#endif /*WITH_SELINUX */
263
if (!(new = malloc(sizeof(PROC)))) {
267
strcpy(new->comm, comm);
274
new->scontext = scontext;
275
#endif /*WITH_SELINUX */
276
new->children = NULL;
283
static void add_child(PROC * parent, PROC * child)
288
if (!(new = malloc(sizeof(CHILD)))) {
293
for (walk = &parent->children; *walk; walk = &(*walk)->next)
295
if ((*walk)->child->pid > child->pid)
297
} else if ((cmp = strcmp((*walk)->child->comm, child->comm)) > 0)
299
else if (!cmp && (*walk)->child->uid > child->uid)
306
static void set_args(PROC * this, const char *args, int size)
316
for (i = 0; i < size - 1; i++)
321
if (!(this->argv = malloc(sizeof(char *) * this->argc))) {
325
start = strchr(args, 0) + 1;
326
size -= start - args;
327
if (!(this->argv[0] = malloc((size_t) size))) {
331
start = memcpy(this->argv[0], start, (size_t) size);
332
for (i = 1; i < this->argc; i++)
333
this->argv[i] = start = strchr(start, 0) + 1;
338
add_proc(const char *comm, pid_t pid, pid_t ppid, uid_t uid,
339
const char *args, int size, char isthread, security_context_t scontext)
340
#else /*WITH_SELINUX */
342
add_proc(const char *comm, pid_t pid, pid_t ppid, uid_t uid,
343
const char *args, int size, char isthread)
344
#endif /*WITH_SELINUX */
348
if (!(this = find_proc(pid)))
350
this = new_proc(comm, pid, uid, scontext);
351
#else /*WITH_SELINUX */
352
this = new_proc(comm, pid, uid);
353
#endif /*WITH_SELINUX */
355
strcpy(this->comm, comm);
359
set_args(this, args, size);
363
this->flags |= PFLAG_THREAD;
364
if (!(parent = find_proc(ppid)))
366
parent = new_proc("?", ppid, 0, scontext);
367
#else /*WITH_SELINUX */
368
parent = new_proc("?", ppid, 0);
369
#endif /*WITH_SELINUX */
370
add_child(parent, this);
371
this->parent = parent;
375
static int tree_equal(const PROC * a, const PROC * b)
377
const CHILD *walk_a, *walk_b;
379
if (strcmp(a->comm, b->comm))
381
if (user_change && a->uid != b->uid)
383
for (walk_a = a->children, walk_b = b->children; walk_a && walk_b;
384
walk_a = walk_a->next, walk_b = walk_b->next)
385
if (!tree_equal(walk_a->child, walk_b->child))
387
return !(walk_a || walk_b);
391
out_args(char *mystr)
397
for (here = mystr; *here; here++) {
401
} else if (*here >= ' ' && *here <= '~') {
405
sprintf(tmpstr, "\\%03o", (unsigned char) *here);
414
dump_tree(PROC * current, int level, int rep, int leaf, int last,
415
uid_t prev_uid, int closing)
417
CHILD *walk, *next, **scan;
418
const struct passwd *pw;
419
int lvl, i, add, offset, len, swapped, info, count, comm_len, first;
420
const char *tmp, *here;
422
assert(closing >= 0);
426
for (lvl = 0; lvl < level; lvl++) {
427
for (i = width[lvl] + 1; i; i--)
431
1 ? last ? sym->last_2 : sym->branch_2 : more[lvl +
433
sym->vert_2 : sym->empty_2);
438
add = out_int(rep) + 2;
441
if ((current->flags & PFLAG_HILIGHT) && (tmp = tgetstr("md", NULL)))
442
tputs(tmp, 1, putchar);
443
swapped = info = print_args;
444
if (swapped && current->argc < 0)
446
comm_len = out_args(current->comm);
449
out_char(info++ ? ',' : '(');
450
(void) out_int(current->pid);
452
if (user_change && prev_uid != current->uid) {
453
out_char(info++ ? ',' : '(');
454
if ((pw = getpwuid(current->uid)))
455
out_string(pw->pw_name);
457
(void) out_int(current->uid);
461
out_char(info++ ? ',' : '(');
462
out_scontext(current->scontext);
464
#endif /*WITH_SELINUX */
465
if ((swapped && print_args && current->argc < 0) || (!swapped && info))
467
if ((current->flags & PFLAG_HILIGHT) && (tmp = tgetstr("me", NULL)))
468
tputs(tmp, 1, putchar);
470
for (i = 0; i < current->argc; i++) {
471
if (i < current->argc - 1) /* Space between words but not at the end of last */
474
for (here = current->argv[i]; *here; here++)
475
len += *here >= ' ' && *here <= '~' ? 1 : 4;
477
output_width - (i == current->argc - 1 ? 0 : 4) || !trunc)
478
out_args(current->argv[i]);
486
if (show_scontext || print_args || !current->children)
487
#else /*WITH_SELINUX */
488
if (print_args || !current->children)
489
#endif /*WITH_SELINUX */
495
ensure_buffer_capacity(level);
499
if (show_scontext || print_args)
500
#else /*WITH_SELINUX */
502
#endif /*WITH_SELINUX */
504
width[level] = swapped + (comm_len > 1 ? 0 : -1);
507
for (walk = current->children; walk; walk = next) {
510
if (compact && (walk->child->flags & PFLAG_THREAD)) {
513
if (!tree_equal(walk->child, (*scan)->child)) {
514
scan = &(*scan)->next;
517
next = (*scan)->next;
519
*scan = (*scan)->next;
522
dump_tree(walk->child, level + 1, count + 1,
523
0, !next, current->uid, closing+ (count ? 2 : 1));
524
//closing + (count ? 1 : 0));
526
dump_tree(walk->child, level + 1, 1, 0, !walk->next,
532
width[level] = comm_len + cur_x - offset + add;
533
if (cur_x >= output_width && trunc) {
534
out_string(sym->first_3);
540
for (walk = current->children; walk; walk = next) {
546
if (!tree_equal(walk->child, (*scan)->child))
547
scan = &(*scan)->next;
550
next = (*scan)->next;
552
*scan = (*scan)->next;
556
out_string(next ? sym->first_3 : sym->single_3);
559
dump_tree(walk->child, level + 1, count + 1,
560
walk == current->children, !next, current->uid,
561
closing + (count ? 1 : 0));
566
static void dump_by_user(PROC * current, uid_t uid)
573
if (current->uid == uid) {
576
dump_tree(current, 0, 1, 1, 1, uid, 0);
580
for (walk = current->children; walk; walk = walk->next)
581
dump_by_user(walk->child, uid);
584
static void trim_tree_by_parent(PROC * current)
589
PROC * parent = current->parent;
594
parent->children = NULL;
595
add_child(parent, current);
596
trim_tree_by_parent(parent);
601
* read_proc now uses a similar method as procps for finding the process
602
* name in the /proc filesystem. My thanks to Albert and procps authors.
604
static void read_proc(void)
613
char readbuf[BUFSIZ + 1];
619
security_context_t scontext = NULL;
620
int selinux_enabled = is_selinux_enabled() > 0;
621
#endif /*WITH_SELINUX */
624
buffer_size = output_width + 1;
626
buffer_size = BUFSIZ + 1;
630
else if (!(buffer = malloc(buffer_size))) {
634
if (!(dir = opendir(PROC_BASE))) {
639
while ((de = readdir(dir)) != NULL)
640
if ((pid = (pid_t) atoi(de->d_name)) != 0) {
641
if (! (path = malloc(strlen(PROC_BASE) + strlen(de->d_name) + 10)))
643
sprintf(path, "%s/%d/stat", PROC_BASE, pid);
644
if ((file = fopen(path, "r")) != NULL) {
646
sprintf(path, "%s/%d", PROC_BASE, pid);
649
if (getpidcon(pid, &scontext) < 0) {
653
#endif /*WITH_SELINUX */
654
if (stat(path, &st) < 0) {
658
size = fread(readbuf, 1, BUFSIZ, file);
659
if (ferror(file) == 0) {
661
/* commands may have spaces or ) in them.
662
* so don't trust anything from the ( to the last ) */
663
if ((comm = strchr(readbuf, '('))
664
&& (tmpptr = strrchr(comm, ')'))) {
667
/* We now have readbuf with pid and cmd, and tmpptr+2
669
/*printf("tmpptr: %s\n", tmpptr+2); */
670
if (sscanf(tmpptr + 2, "%*c %d", &ppid) == 1) {
677
if (! (taskpath = malloc(strlen(path) + 10)))
679
sprintf(taskpath, "%s/task", path);
681
if ((taskdir = opendir(taskpath)) != 0) {
682
/* if we have this dir, we're on 2.6 */
683
if (! (threadname = malloc(COMM_LEN + 2 + 1))) {
686
sprintf(threadname, "{%.*s}", COMM_LEN, comm);
687
while ((dt = readdir(taskdir)) != NULL) {
688
if ((thread = atoi(dt->d_name)) != 0) {
692
add_proc(threadname, thread, pid, st.st_uid,
693
threadname, strlen (threadname) + 1, 1,scontext);
695
add_proc(threadname, thread, pid, st.st_uid,
696
NULL, 0, 1, scontext);
697
#else /*WITH_SELINUX */
699
add_proc(threadname, thread, pid, st.st_uid,
700
threadname, strlen (threadname) + 1, 1);
702
add_proc(threadname, thread, pid, st.st_uid,
704
#endif /*WITH_SELINUX */
709
(void) closedir(taskdir);
714
add_proc(comm, pid, ppid, st.st_uid, NULL, 0, 0, scontext);
715
#else /*WITH_SELINUX */
716
add_proc(comm, pid, ppid, st.st_uid, NULL, 0, 0);
717
#endif /*WITH_SELINUX */
719
sprintf(path, "%s/%d/cmdline", PROC_BASE, pid);
720
if ((fd = open(path, O_RDONLY)) < 0) {
724
if ((size = read(fd, buffer, buffer_size)) < 0) {
729
/* If we have read the maximum screen length of args, bring it back by one to stop overflow */
730
if (size >= buffer_size)
735
add_proc(comm, pid, ppid, st.st_uid,
736
buffer, size, 0, scontext);
737
#else /*WITH_SELINUX */
738
add_proc(comm, pid, ppid, st.st_uid,
740
#endif /*WITH_SELINUX */
749
(void) closedir(dir);
753
fprintf(stderr, _("%s is empty (not mounted ?)\n"), PROC_BASE);
761
/* Could use output of ps achlx | awk '{ print $3,$4,$2,$13 }' */
763
static void read_stdin(void)
765
char comm[PATH_MAX + 1];
769
while (scanf("%d %d %d %s\n", &pid, &ppid, &uid, comm) == 4) {
770
if (cmd = strrchr(comm, '/'))
777
add_proc(cmd, pid, ppid, uid, NULL, 0, NULL);
778
#else /*WITH_SELINUX */
779
add_proc(cmd, pid, ppid, uid, NULL, 0);
780
#endif /*WITH_SELINUX */
787
static void usage(void)
791
("Usage: pstree [ -a ] [ -c ] [ -h | -H PID ] [ -l ] [ -n ] [ -p ] [ -u ]\n"
792
" [ -A | -G | -U ] [ PID | USER ]\n"
793
" pstree -V\n" "Display a tree of processes.\n\n"
794
" -a, --arguments show command line arguments\n"
795
" -A, --ascii use ASCII line drawing characters\n"
796
" -c, --compact don't compact identical subtrees\n"
797
" -h, --highlight-all highlight current process and its ancestors\n"
799
" --highlight-pid=PID highlight this process and its ancestors\n"
800
" -G, --vt100 use VT100 line drawing characters\n"
801
" -l, --long don't truncate long lines\n"
802
" -n, --numeric-sort sort output by PID\n"
803
" -p, --show-pids show PIDs; implies -c\n"
804
" -s, --show-parents show parents of the selected process\n"
805
" -u, --uid-changes show uid transitions\n"
806
" -U, --unicode use UTF-8 (Unicode) line drawing characters\n"
807
" -V, --version display version information\n"));
810
_(" -Z show SELinux security contexts\n"));
811
#endif /*WITH_SELINUX */
812
fprintf(stderr, _(" PID start at this PID; default is 1 (init)\n"
813
" USER show only trees rooted at processes of this user\n\n"));
819
fprintf(stderr, _("pstree (PSmisc) %s\n"), VERSION);
822
("Copyright (C) 1993-2009 Werner Almesberger and Craig Small\n\n"));
824
_("PSmisc comes with ABSOLUTELY NO WARRANTY.\n"
825
"This is free software, and you are welcome to redistribute it under\n"
826
"the terms of the GNU General Public License.\n"
827
"For more information about these matters, see the files named COPYING.\n"));
831
int main(int argc, char **argv)
834
struct winsize winsz;
835
const struct passwd *pw;
836
pid_t pid, highlight;
837
char termcap_area[1024];
841
struct option options[] = {
842
{"arguments", 0, NULL, 'a'},
843
{"ascii", 0, NULL, 'A'},
844
{"compact", 0, NULL, 'c'},
845
{"vt100", 0, NULL, 'G'},
846
{"highlight-all", 0, NULL, 'h'},
847
{"highlight-pid", 1, NULL, 'H'},
848
{"long", 0, NULL, 'l'},
849
{"numeric-sort", 0, NULL, 'n'},
850
{"show-pids", 0, NULL, 'p'},
851
{"show-parents", 0, NULL, 's'},
852
{"uid-changes", 0, NULL, 'u'},
853
{"unicode", 0, NULL, 'U'},
854
{"version", 0, NULL, 'V'},
856
{"security-context", 0, NULL, 'Z'},
857
#endif /*WITH_SELINUX */
861
if (ioctl(1, TIOCGWINSZ, &winsz) >= 0)
863
output_width = winsz.ws_col;
869
setlocale(LC_ALL, "");
870
bindtextdomain(PACKAGE, LOCALEDIR);
874
if (!strcmp(__progname, "pstree.x11"))
878
* Attempt to figure out a good default symbol set. Will be overriden by
879
* command-line options, if given.
882
if (isatty(1) && !strcmp(nl_langinfo(CODESET), "UTF-8")) {
883
/* Use UTF-8 symbols if the locale's character set is UTF-8. */
885
} else if (isatty(1) && (termname = getenv("TERM")) &&
886
(strlen(termname) > 0) &&
887
(setupterm(NULL, 1 /* stdout */ , NULL) == OK) &&
888
(tigetstr("acsc") > 0)) {
890
* Failing that, if TERM is defined, a non-null value, and the terminal
891
* has the VT100 graphics charset, use it.
893
/* problems with VT100 on some terminals, making this ascci
898
/* Otherwise, fall back to ASCII. */
904
getopt_long(argc, argv, "aAcGhH:nplsuUVZ", options,
906
#else /*WITH_SELINUX */
908
getopt_long(argc, argv, "aAcGhH:nplsuUV", options, NULL)) != -1)
909
#endif /*WITH_SELINUX */
927
&& tgetent(termcap_area, getenv("TERM")) > 0)
928
highlight = getpid();
933
if (!getenv("TERM")) {
934
fprintf(stderr, _("TERM is not set\n"));
937
if (tgetent(termcap_area, getenv("TERM")) <= 0) {
938
fprintf(stderr, _("Can't get terminal capabilities\n"));
941
if (!(highlight = atoi(optarg)))
968
if (is_selinux_enabled() > 0)
972
"Warning: -Z ignored. Requires anx SELinux enabled kernel\n");
974
#endif /*WITH_SELINUX */
978
if (optind == argc - 1) {
979
if (isdigit(*argv[optind])) {
980
if (!(pid = (pid_t) atoi(argv[optind++])))
982
} else if (!(pw = getpwnam(argv[optind++]))) {
983
fprintf(stderr, _("No such user name: %s\n"),
991
for (current = find_proc(highlight); current;
992
current = current->parent)
993
current->flags |= PFLAG_HILIGHT;
995
if(show_parents && pid != 0) {
996
trim_tree_by_parent(find_proc(pid));
1002
dump_tree(find_proc(pid), 0, 1, 1, 1, 0, 0);
1004
dump_by_user(find_proc(1), pw->pw_uid);
1006
fprintf(stderr, _("No processes found.\n"));
1011
if (wait_end == 1) {
1012
fprintf(stderr, _("Press return to close\n"));