blob: 1d124d2d2a9a5b1ef325d36501dfe8b0284c502d [file] [log] [blame]
Lauri Kasanen6578f2c2010-12-05 05:22:29 +01001/*
2 * pstree.c - display process tree
3 *
4 * Copyright (C) 1993-2002 Werner Almesberger
5 * Copyright (C) 2002-2009 Craig Small
6 * Copyright (C) 2010 Lauri Kasanen
7 *
8 * Based on pstree (PSmisc) 22.13.
9 *
10 * Licensed under GPLv2, see file LICENSE in this source tree.
11 */
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010012//config:config PSTREE
Denys Vlasenkob097a842018-12-28 03:20:17 +010013//config: bool "pstree (9.3 kb)"
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010014//config: default y
15//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020016//config: Display a tree of processes.
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010017
Denys Vlasenko00c18112017-08-05 22:25:00 +020018//applet:IF_PSTREE(APPLET_NOEXEC(pstree, pstree, BB_DIR_USR_BIN, BB_SUID_DROP, pstree))
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010019
20//kbuild:lib-$(CONFIG_PSTREE) += pstree.o
21
22//usage:#define pstree_trivial_usage
23//usage: "[-p] [PID|USER]"
24//usage:#define pstree_full_usage "\n\n"
25//usage: "Display process tree, optionally start from USER or PID\n"
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010026//usage: "\n -p Show pids"
27
28#include "libbb.h"
29
30#define PROC_BASE "/proc"
31
32#define OPT_PID (1 << 0)
33
34struct child;
35
Denys Vlasenko5d72ae52017-06-15 17:11:59 +020036#if ENABLE_FEATURE_SHOW_THREADS
Mike Frysingerfea25882013-06-19 11:29:57 -040037/* For threads, we add {...} around the comm, so we need two extra bytes */
38# define COMM_DISP_LEN (COMM_LEN + 2)
39#else
40# define COMM_DISP_LEN COMM_LEN
41#endif
42
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010043typedef struct proc {
Mike Frysingerfea25882013-06-19 11:29:57 -040044 char comm[COMM_DISP_LEN + 1];
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010045// char flags; - unused, delete?
46 pid_t pid;
47 uid_t uid;
48 struct child *children;
49 struct proc *parent;
50 struct proc *next;
51} PROC;
52
53/* For flags above */
54//#define PFLAG_THREAD 0x01
55
56typedef struct child {
57 PROC *child;
58 struct child *next;
59} CHILD;
60
61#define empty_2 " "
62#define branch_2 "|-"
63#define vert_2 "| "
64#define last_2 "`-"
65#define single_3 "---"
66#define first_3 "-+-"
67
68struct globals {
Denys Vlasenko2161bd72010-12-05 19:36:58 +010069 /* 0-based. IOW: the number of chars we printed on current line */
Lauri Kasanene48e6f82010-12-05 15:53:55 +010070 unsigned cur_x;
71 unsigned output_width;
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010072
73 /* The buffers will be dynamically increased in size as needed */
74 unsigned capacity;
Lauri Kasanene48e6f82010-12-05 15:53:55 +010075 unsigned *width;
76 uint8_t *more;
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010077
Lauri Kasanene48e6f82010-12-05 15:53:55 +010078 PROC *list;
79
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010080 smallint dumped; /* used by dump_by_user */
81};
82#define G (*ptr_to_globals)
83#define INIT_G() do { \
Denys Vlasenkob7c9fb22011-02-03 00:05:48 +010084 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010085} while (0)
86
87
88/*
89 * Allocates additional buffer space for width and more as needed.
90 * The first call will allocate the first buffer.
91 *
Lauri Kasanene48e6f82010-12-05 15:53:55 +010092 * bufindex the index that will be used after the call to this function.
Lauri Kasanen6578f2c2010-12-05 05:22:29 +010093 */
94static void ensure_buffer_capacity(int bufindex)
95{
96 if (bufindex >= G.capacity) {
97 G.capacity += 0x100;
98 G.width = xrealloc(G.width, G.capacity * sizeof(G.width[0]));
99 G.more = xrealloc(G.more, G.capacity * sizeof(G.more[0]));
100 }
101}
102
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100103/* NB: this function is never called with "bad" chars
104 * (control chars or chars >= 0x7f)
105 */
106static void out_char(char c)
107{
108 G.cur_x++;
Denys Vlasenkoc32e6262010-12-05 16:05:03 +0100109 if (G.cur_x > G.output_width)
110 return;
Lauri Kasanene48e6f82010-12-05 15:53:55 +0100111 if (G.cur_x == G.output_width)
112 c = '+';
Denys Vlasenkoc32e6262010-12-05 16:05:03 +0100113 putchar(c);
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100114}
115
116/* NB: this function is never called with "bad" chars
117 * (control chars or chars >= 0x7f)
118 */
119static void out_string(const char *str)
120{
121 while (*str)
122 out_char(*str++);
123}
124
125static void out_newline(void)
126{
127 putchar('\n');
Lauri Kasanene48e6f82010-12-05 15:53:55 +0100128 G.cur_x = 0;
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100129}
130
131static PROC *find_proc(pid_t pid)
132{
133 PROC *walk;
134
135 for (walk = G.list; walk; walk = walk->next)
136 if (walk->pid == pid)
137 break;
138
139 return walk;
140}
141
142static PROC *new_proc(const char *comm, pid_t pid, uid_t uid)
143{
144 PROC *new = xzalloc(sizeof(*new));
145
146 strcpy(new->comm, comm);
147 new->pid = pid;
148 new->uid = uid;
149 new->next = G.list;
150
151 G.list = new;
152 return G.list;
153}
154
155static void add_child(PROC *parent, PROC *child)
156{
157 CHILD *new, **walk;
158 int cmp;
159
160 new = xmalloc(sizeof(*new));
161
162 new->child = child;
163 for (walk = &parent->children; *walk; walk = &(*walk)->next) {
164 cmp = strcmp((*walk)->child->comm, child->comm);
165 if (cmp > 0)
166 break;
167 if (cmp == 0 && (*walk)->child->uid > child->uid)
168 break;
169 }
170 new->next = *walk;
171 *walk = new;
172}
173
174static void add_proc(const char *comm, pid_t pid, pid_t ppid,
175 uid_t uid /*, char isthread*/)
176{
177 PROC *this, *parent;
178
179 this = find_proc(pid);
180 if (!this)
181 this = new_proc(comm, pid, uid);
182 else {
183 strcpy(this->comm, comm);
184 this->uid = uid;
185 }
186
187 if (pid == ppid)
188 ppid = 0;
189// if (isthread)
190// this->flags |= PFLAG_THREAD;
191
192 parent = find_proc(ppid);
193 if (!parent)
194 parent = new_proc("?", ppid, 0);
195
196 add_child(parent, this);
197 this->parent = parent;
198}
199
200static int tree_equal(const PROC *a, const PROC *b)
201{
202 const CHILD *walk_a, *walk_b;
203
204 if (strcmp(a->comm, b->comm) != 0)
205 return 0;
206 if ((option_mask32 /*& OPT_PID*/) && a->pid != b->pid)
207 return 0;
208
209 for (walk_a = a->children, walk_b = b->children;
210 walk_a && walk_b;
211 walk_a = walk_a->next, walk_b = walk_b->next
212 ) {
213 if (!tree_equal(walk_a->child, walk_b->child))
214 return 0;
215 }
216
217 return !(walk_a || walk_b);
218}
219
220static int out_args(const char *mystr)
221{
222 const char *here;
223 int strcount = 0;
224 char tmpstr[5];
225
226 for (here = mystr; *here; here++) {
227 if (*here == '\\') {
228 out_string("\\\\");
229 strcount += 2;
230 } else if (*here >= ' ' && *here < 0x7f) {
231 out_char(*here);
232 strcount++;
233 } else {
234 sprintf(tmpstr, "\\%03o", (unsigned char) *here);
235 out_string(tmpstr);
236 strcount += 4;
237 }
238 }
239
240 return strcount;
241}
242
243static void
244dump_tree(PROC *current, int level, int rep, int leaf, int last, int closing)
245{
246 CHILD *walk, *next, **scan;
247 int lvl, i, add, offset, count, comm_len, first;
Lauri Kasanene48e6f82010-12-05 15:53:55 +0100248 char tmp[sizeof(int)*3 + 4];
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100249
250 if (!current)
251 return;
252
253 if (!leaf) {
254 for (lvl = 0; lvl < level; lvl++) {
255 i = G.width[lvl] + 1;
256 while (--i >= 0)
257 out_char(' ');
258
259 if (lvl == level - 1) {
260 if (last) {
261 out_string(last_2);
262 } else {
263 out_string(branch_2);
264 }
265 } else {
266 if (G.more[lvl + 1]) {
267 out_string(vert_2);
268 } else {
269 out_string(empty_2);
270 }
271 }
272 }
273 }
274
Lauri Kasanene48e6f82010-12-05 15:53:55 +0100275 add = 0;
276 if (rep > 1) {
277 add += sprintf(tmp, "%d*[", rep);
278 out_string(tmp);
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100279 }
280 comm_len = out_args(current->comm);
281 if (option_mask32 /*& OPT_PID*/) {
Lauri Kasanene48e6f82010-12-05 15:53:55 +0100282 comm_len += sprintf(tmp, "(%d)", (int)current->pid);
283 out_string(tmp);
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100284 }
285 offset = G.cur_x;
286
287 if (!current->children) {
288 while (closing--)
289 out_char(']');
290 out_newline();
291 }
292 ensure_buffer_capacity(level);
293 G.more[level] = !last;
294
295 G.width[level] = comm_len + G.cur_x - offset + add;
Lauri Kasanene48e6f82010-12-05 15:53:55 +0100296 if (G.cur_x >= G.output_width) {
297 //out_string(first_3); - why? it won't print anything
298 //out_char('+');
299 out_newline();
300 return;
301 }
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100302
303 first = 1;
304 for (walk = current->children; walk; walk = next) {
305 count = 0;
306 next = walk->next;
307 scan = &walk->next;
308 while (*scan) {
309 if (!tree_equal(walk->child, (*scan)->child))
310 scan = &(*scan)->next;
311 else {
312 if (next == *scan)
313 next = (*scan)->next;
314 count++;
315 *scan = (*scan)->next;
316 }
317 }
318 if (first) {
319 out_string(next ? first_3 : single_3);
320 first = 0;
321 }
322
323 dump_tree(walk->child, level + 1, count + 1,
324 walk == current->children, !next,
325 closing + (count ? 1 : 0));
326 }
327}
328
329static void dump_by_user(PROC *current, uid_t uid)
330{
331 const CHILD *walk;
332
333 if (!current)
334 return;
335
336 if (current->uid == uid) {
337 if (G.dumped)
338 putchar('\n');
339 dump_tree(current, 0, 1, 1, 1, 0);
340 G.dumped = 1;
341 return;
342 }
343 for (walk = current->children; walk; walk = walk->next)
344 dump_by_user(walk->child, uid);
345}
346
Denys Vlasenkoa9e5c432011-03-27 16:15:02 +0200347#if ENABLE_FEATURE_SHOW_THREADS
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100348static void handle_thread(const char *comm, pid_t pid, pid_t ppid, uid_t uid)
349{
Mike Frysingerfea25882013-06-19 11:29:57 -0400350 char threadname[COMM_DISP_LEN + 1];
Denys Vlasenko173aa782013-06-28 01:59:25 +0200351 sprintf(threadname, "{%.*s}", (int)sizeof(threadname) - 3, comm);
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100352 add_proc(threadname, pid, ppid, uid/*, 1*/);
353}
Denys Vlasenkoa9e5c432011-03-27 16:15:02 +0200354#endif
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100355
356static void mread_proc(void)
357{
358 procps_status_t *p = NULL;
Denys Vlasenko4e08a122017-01-16 17:31:05 +0100359#if ENABLE_FEATURE_SHOW_THREADS
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100360 pid_t parent = 0;
Denys Vlasenko4e08a122017-01-16 17:31:05 +0100361#endif
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100362 int flags = PSSCAN_COMM | PSSCAN_PID | PSSCAN_PPID | PSSCAN_UIDGID | PSSCAN_TASKS;
363
364 while ((p = procps_scan(p, flags)) != NULL) {
365#if ENABLE_FEATURE_SHOW_THREADS
366 if (p->pid != p->main_thread_pid)
367 handle_thread(p->comm, p->pid, parent, p->uid);
368 else
369#endif
370 {
371 add_proc(p->comm, p->pid, p->ppid, p->uid/*, 0*/);
Denys Vlasenko4e08a122017-01-16 17:31:05 +0100372#if ENABLE_FEATURE_SHOW_THREADS
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100373 parent = p->pid;
Denys Vlasenko4e08a122017-01-16 17:31:05 +0100374#endif
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100375 }
376 }
377}
378
379int pstree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
380int pstree_main(int argc UNUSED_PARAM, char **argv)
381{
382 pid_t pid = 1;
383 long uid = 0;
384
385 INIT_G();
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100386
Denys Vlasenko641caae2015-10-23 01:44:22 +0200387 G.output_width = get_terminal_width(0);
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100388
Denys Vlasenko22542ec2017-08-08 21:55:02 +0200389 getopt32(argv, "^" "p" "\0" "?1");
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100390 argv += optind;
391
392 if (argv[0]) {
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100393 if (argv[0][0] >= '0' && argv[0][0] <= '9') {
394 pid = xatoi(argv[0]);
395 } else {
396 uid = xuname2uid(argv[0]);
397 }
398 }
399
400 mread_proc();
401
402 if (!uid)
403 dump_tree(find_proc(pid), 0, 1, 1, 1, 0);
404 else {
405 dump_by_user(find_proc(1), uid);
406 if (!G.dumped) {
407 bb_error_msg_and_die("no processes found");
408 }
409 }
410
Denys Vlasenko2161bd72010-12-05 19:36:58 +0100411 if (ENABLE_FEATURE_CLEAN_UP) {
412 free(G.width);
413 free(G.more);
414 }
Lauri Kasanen6578f2c2010-12-05 05:22:29 +0100415 return 0;
416}