[PATCH] pstree: New applet, v5

Eric Lammerts busybox at lists.lammerts.org
Thu Dec 2 00:45:27 UTC 2010


On Wed, 1 Dec 2010, Lauri Kasanen wrote:
> Posting an updated version of pstree:
>
> v5:
> - more styling
> - divide read_proc into two functions
>
> - Lauri
>
> PS: If there's radio silence on this one too, I'll go away, I promise.

Maybe they hate it because it doesn't have a -p option? I do :-)

I added the -p option to your code and made a git patch.

cheers,

Eric
-------------- next part --------------
From e0971d97f628562a5ec5018627ab92d1ca4e577c Mon Sep 17 00:00:00 2001
From: Eric Lammerts <eric at lammerts.org>
Date: Wed, 1 Dec 2010 19:43:27 -0500
Subject: [PATCH] add pstree applet

---
 include/applets.src.h |    1 +
 include/usage.src.h   |    6 +
 procps/Config.src     |    6 +
 procps/Kbuild.src     |    1 +
 procps/pstree.c       |  509 +++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 523 insertions(+), 0 deletions(-)
 create mode 100644 procps/pstree.c

diff --git a/include/applets.src.h b/include/applets.src.h
index 9dd5b6d..1644150 100644
--- a/include/applets.src.h
+++ b/include/applets.src.h
@@ -271,6 +271,7 @@ IF_PRINTENV(APPLET_NOFORK(printenv, printenv, _BB_DIR_BIN, _BB_SUID_DROP, printe
 IF_PRINTF(APPLET_NOFORK(printf, printf, _BB_DIR_USR_BIN, _BB_SUID_DROP, printf))
 IF_PS(APPLET(ps, _BB_DIR_BIN, _BB_SUID_DROP))
 IF_PSCAN(APPLET(pscan, _BB_DIR_USR_BIN, _BB_SUID_DROP))
+IF_PSTREE(APPLET(pstree, _BB_DIR_USR_BIN, _BB_SUID_DROP))
 IF_PWD(APPLET_NOFORK(pwd, pwd, _BB_DIR_BIN, _BB_SUID_DROP, pwd))
 IF_RAIDAUTORUN(APPLET(raidautorun, _BB_DIR_SBIN, _BB_SUID_DROP))
 IF_RDATE(APPLET(rdate, _BB_DIR_USR_SBIN, _BB_SUID_DROP))
diff --git a/include/usage.src.h b/include/usage.src.h
index c413fbb..f77d064 100644
--- a/include/usage.src.h
+++ b/include/usage.src.h
@@ -2989,6 +2989,12 @@ INSERT
      "\n	-t	Timeout (default 5000 ms)" \
      "\n	-T	Minimum rtt (default 5 ms, increase for congested hosts)" \
 
+#define pstree_trivial_usage \
+       "[-p] [PID|USER]"
+#define pstree_full_usage "\n\n" \
+       "Display a tree of processes\n" \
+       "\nOptionally start from USER or PID."
+
 #define pwd_trivial_usage \
        ""
 #define pwd_full_usage "\n\n" \
diff --git a/procps/Config.src b/procps/Config.src
index 1ff6dfd..702e861 100644
--- a/procps/Config.src
+++ b/procps/Config.src
@@ -124,6 +124,12 @@ config FEATURE_PS_UNUSUAL_SYSTEMS
 	  Include support for measuring HZ on old kernels and non-ELF systems
 	  (if you are on Linux 2.4.0+ and use ELF, you don't need this)
 
+config PSTREE
+	bool "pstree"
+	default y
+	help
+	  Display a tree of processes.
+
 config RENICE
 	bool "renice"
 	default y
diff --git a/procps/Kbuild.src b/procps/Kbuild.src
index 791d656..e76ba30 100644
--- a/procps/Kbuild.src
+++ b/procps/Kbuild.src
@@ -16,6 +16,7 @@ lib-$(CONFIG_PGREP)	+= pgrep.o
 lib-$(CONFIG_PKILL)	+= pgrep.o
 lib-$(CONFIG_PIDOF)	+= pidof.o
 lib-$(CONFIG_PS)	+= ps.o
+lib-$(CONFIG_PSTREE)	+= pstree.o
 lib-$(CONFIG_RENICE)	+= renice.o
 lib-$(CONFIG_BB_SYSCTL)	+= sysctl.o
 lib-$(CONFIG_TOP)	+= top.o
diff --git a/procps/pstree.c b/procps/pstree.c
new file mode 100644
index 0000000..fc3b61a
--- /dev/null
+++ b/procps/pstree.c
@@ -0,0 +1,509 @@
+/*
+ * pstree.c - display process tree
+ *
+ * Copyright (C) 1993-2002 Werner Almesberger
+ * Copyright (C) 2002-2009 Craig Small
+ * Copyright (C) 2010 Lauri Kasanen
+ *
+ * Based on pstree (PSmisc) 22.13.
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+#include "libbb.h"
+
+#define PROC_BASE "/proc"
+#define G (*ptr_to_globals)
+
+#define OPT_PID  (1 << 0)
+
+typedef struct _proc {
+	char comm[COMM_LEN + 1];
+	pid_t pid;
+	uid_t uid;
+	char flags;
+	struct _child *children;
+	struct _proc *parent;
+	struct _proc *next;
+} PROC;
+
+/* For flags above */
+#define PFLAG_THREAD	0x01
+
+typedef struct _child {
+	PROC *child;
+	struct _child *next;
+} CHILD;
+
+struct sym_t {
+	const char *empty_2;
+	const char *branch_2;
+	const char *vert_2;
+	const char *last_2;
+	const char *single_3;
+	const char *first_3;
+};
+
+struct globals {
+
+	struct sym_t sym;
+
+	PROC *list;
+
+	/* The buffers will be dynamically increased in size as needed. */
+	int capacity;
+	int *width;
+	int *more;
+
+	uint32_t opts;
+	int output_width;
+	int cur_x;
+	char last_char;
+	int dumped;			/* used by dump_by_user */
+	int charlen;			/* length of character */
+};
+
+/*
+ * Allocates additional buffer space for width and more as needed.
+ * The first call will allocate the first buffer.
+ *
+ * bufindex	the index that will be used after the call
+ *		to this function.
+ */
+static void ensure_buffer_capacity(int bufindex)
+{
+	if (bufindex >= G.capacity) {
+		if (G.capacity == 0)
+			G.capacity = 100;
+		else
+			G.capacity *= 2;
+
+		G.width = xrealloc(G.width, G.capacity * sizeof(int));
+		G.more = xrealloc(G.more, G.capacity * sizeof(int));
+	}
+}
+
+
+#if ENABLE_FEATURE_CLEAN_UP
+/*
+ * Frees any buffers allocated by ensure_buffer_capacity.
+ */
+static void maybe_free_buffers(void)
+{
+	free(G.width);
+	G.width = NULL;
+	free(G.more);
+	G.more = NULL;
+
+	G.capacity = 0;
+}
+#else
+static void maybe_free_buffers(void){}
+#endif
+
+static void out_char(char c)
+{
+	if (G.charlen == 0) {				/* "new" character */
+		if ((c & 0x80) == 0) {
+			G.charlen = 1;			/* ASCII */
+		} else if ((c & 0xe0) == 0xc0) {	/* 110.. 2 bytes */
+			G.charlen = 2;
+		} else if ((c & 0xf0) == 0xe0) {	/* 1110.. 3 bytes */
+			G.charlen = 3;
+		} else if ((c & 0xf8) == 0xf0) {	/* 11110.. 4 bytes */
+			G.charlen = 4;
+		} else {
+			G.charlen = 1;
+		}
+		G.cur_x++;	/* count first byte of whatever it is only */
+	}
+	G.charlen--;
+	if (G.cur_x <= G.output_width)
+		putchar(c);
+	else {
+		if (G.cur_x == G.output_width + 1)
+			putchar('+');
+	}
+}
+
+
+static void out_string(const char *str)
+{
+	while (*str)
+		out_char(*str++);
+}
+
+/* non-negative integers only */
+static int out_int(int x)
+{
+	int digits, mydiv;
+
+	digits = 0;
+
+	for (mydiv = 1; x / mydiv; mydiv *= 10)
+		digits++;
+	if (!digits)
+		digits = 1;
+	for (mydiv /= 10; mydiv; mydiv /= 10)
+		out_char('0' + (x / mydiv) % 10);
+
+	return digits;
+}
+
+
+static void out_newline(void)
+{
+	if (G.last_char && G.cur_x == G.output_width)
+		putchar(G.last_char);
+
+	G.last_char = 0;
+	putchar('\n');
+	G.cur_x = 1;
+}
+
+
+static PROC *find_proc(pid_t pid)
+{
+	PROC *walk;
+
+	for (walk = G.list; walk; walk = walk->next)
+		if (walk->pid == pid)
+			break;
+
+	return walk;
+}
+
+static PROC *new_proc(const char *comm, pid_t pid, uid_t uid)
+{
+	PROC *new;
+
+	new = xzalloc(sizeof(PROC));
+
+	strcpy(new->comm, comm);
+	new->pid = pid;
+	new->uid = uid;
+	new->next = G.list;
+
+	return G.list = new;
+}
+
+
+static void add_child(PROC * parent, PROC * child)
+{
+	CHILD *new, **walk;
+	int cmp;
+
+	new = xmalloc(sizeof(CHILD));
+
+	new->child = child;
+	for (walk = &parent->children; *walk; walk = &(*walk)->next) {
+		cmp = strcmp((*walk)->child->comm, child->comm);
+		if (cmp > 0)
+			break;
+		else if (!cmp && (*walk)->child->uid > child->uid)
+			break;
+	}
+	new->next = *walk;
+	*walk = new;
+}
+
+
+static void add_proc(const char *comm, pid_t pid, pid_t ppid,
+			uid_t uid, char isthread)
+{
+	PROC *this, *parent;
+
+	this = find_proc(pid);
+	if (!this)
+		this = new_proc(comm, pid, uid);
+	else {
+		strcpy(this->comm, comm);
+		this->uid = uid;
+	}
+
+	if (pid == ppid)
+		ppid = 0;
+	if (isthread)
+		this->flags |= PFLAG_THREAD;
+
+	parent = find_proc(ppid);
+	if (!parent)
+		parent = new_proc("?", ppid, 0);
+
+	add_child(parent, this);
+	this->parent = parent;
+}
+
+
+static int tree_equal(const PROC * a, const PROC * b)
+{
+	const CHILD *walk_a, *walk_b;
+
+	if (strcmp(a->comm, b->comm))
+		return 0;
+	if ((G.opts & OPT_PID) && a->pid != b->pid)
+		return 0;
+
+	for (walk_a = a->children, walk_b = b->children; walk_a && walk_b;
+		walk_a = walk_a->next, walk_b = walk_b->next)
+			if (!tree_equal(walk_a->child, walk_b->child))
+				return 0;
+
+	return !(walk_a || walk_b);
+}
+
+static int out_args(char *mystr)
+{
+	char *here;
+	int strcount=0;
+	char tmpstr[5];
+
+	for (here = mystr; *here; here++) {
+
+		if (*here == '\\') {
+			out_string("\\\\");
+			strcount += 2;
+		} else if (*here >= ' ' && *here <= '~') {
+			out_char(*here);
+			strcount++;
+		} else {
+			sprintf(tmpstr, "\\%03o", (unsigned char) *here);
+			out_string(tmpstr);
+			strcount += 4;
+		}
+	} /* for */
+
+	return strcount;
+}
+
+static void
+dump_tree(PROC * current, int level, int rep, int leaf, int last, int closing)
+{
+	CHILD *walk, *next, **scan;
+	int lvl, i, add, offset, count, comm_len, first;
+
+	if (!current)
+		return;
+
+	if (!leaf) {
+		for (lvl = 0; lvl < level; lvl++) {
+			for (i = G.width[lvl] + 1; i; i--)
+				out_char(' ');
+
+			if (lvl == level - 1) {
+				if (last) {
+					out_string(G.sym.last_2);
+				} else {
+					out_string(G.sym.branch_2);
+				}
+			} else {
+				if (G.more[lvl + 1]) {
+					out_string(G.sym.vert_2);
+				} else {
+					out_string(G.sym.empty_2);
+				}
+			}
+		}
+	}
+
+	if (rep < 2)
+		add = 0;
+	else {
+		add = out_int(rep) + 2;
+		out_string("*[");
+	}
+	comm_len = out_args(current->comm);
+	if (G.opts & OPT_PID)
+	{
+		out_char('(');
+		comm_len += out_int(current->pid) + 2;
+		out_char(')');
+	}
+	offset = G.cur_x;
+
+	if (!current->children)
+	{
+		while (closing--)
+			out_char(']');
+		out_newline();
+	}
+	ensure_buffer_capacity(level);
+	G.more[level] = !last;
+
+	G.width[level] = comm_len + G.cur_x - offset + add;
+	if (G.cur_x >= G.output_width) {
+		out_string(G.sym.first_3);
+		out_string("+");
+		out_newline();
+		return;
+	}
+
+	first = 1;
+	for (walk = current->children; walk; walk = next) {
+		count = 0;
+		next = walk->next;
+			scan = &walk->next;
+			while (*scan)
+				if (!tree_equal(walk->child, (*scan)->child))
+					scan = &(*scan)->next;
+				else {
+					if (next == *scan)
+						next = (*scan)->next;
+					count++;
+					*scan = (*scan)->next;
+				}
+		if (first) {
+			out_string(next ? G.sym.first_3 : G.sym.single_3);
+			first = 0;
+		}
+
+		dump_tree(walk->child, level + 1, count + 1,
+				walk == current->children, !next,
+				closing + (count ? 1 : 0));
+	}
+}
+
+
+static void dump_by_user(PROC * current, uid_t uid)
+{
+	const CHILD *walk;
+
+	if (!current)
+		return;
+
+	if (current->uid == uid) {
+		if (G.dumped)
+			putchar('\n');
+		dump_tree(current, 0, 1, 1, 1, 0);
+		G.dumped = 1;
+		return;
+	}
+	for (walk = current->children; walk; walk = walk->next)
+		dump_by_user(walk->child, uid);
+}
+
+static void check_thread(const char *path, const char *comm, pid_t pid, pid_t ppid, uid_t uid)
+{
+	DIR *taskdir;
+	struct dirent *dt;
+	char *taskpath = NULL;
+	char *threadname = NULL;
+	int thread;
+
+	taskpath = xasprintf("%s/task", path);
+	taskdir = opendir(taskpath);
+
+	if (taskdir != 0) {
+		/* if we have this dir, we're on 2.6 */
+		threadname = xasprintf("{%.*s}", COMM_LEN-2, comm);
+		while ((dt = readdir(taskdir)) != NULL) {
+			thread = atoi(dt->d_name);
+			if (thread != 0 && thread != pid) {
+				add_proc(threadname, thread, pid, uid, 1);
+			}
+		}
+		free(threadname);
+		(void) closedir(taskdir);
+	}
+	free(taskpath);
+	add_proc(comm, pid, ppid, uid, 0);
+}
+
+/*
+ * read_proc now uses a similar method as procps for finding the process
+ * name in the /proc filesystem. My thanks to Albert and procps authors.
+ */
+static void read_proc(void)
+{
+	DIR *dir;
+	struct dirent *de;
+	FILE *file;
+	struct stat st;
+	char *path, *comm, *tmpptr;
+	char readbuf[BUFSIZ + 1];
+	pid_t pid, ppid;
+	int size;
+	int empty = 1;
+
+	dir = xopendir(PROC_BASE);
+
+	while ((de = readdir(dir)) != NULL) {
+		pid = (pid_t) atoi(de->d_name);
+		if (pid != 0) {
+			path = xasprintf("%s/%d/stat", PROC_BASE, pid);
+			file = fopen_for_read(path);
+			if (file != NULL) {
+				empty = 0;
+				sprintf(path, "%s/%d", PROC_BASE, pid);
+				xstat(path, &st);
+				size = fread(readbuf, 1, BUFSIZ, file);
+				if (ferror(file) == 0) {
+					readbuf[size] = 0;
+					/* commands may have spaces or ) in them.
+					 * so don't trust anything from the ( to the last ) */
+					comm = strchr(readbuf, '(');
+					tmpptr = strrchr(comm, ')');
+					if (comm && tmpptr) {
+						++comm;
+						*tmpptr = 0;
+
+						/* We now have readbuf with pid and cmd, and tmpptr+2
+						 * with the rest */
+						if (sscanf(tmpptr + 2, "%*c %d", &ppid) == 1)
+							check_thread(path, comm, pid, ppid, st.st_uid);
+					}
+				}
+				(void) fclose(file);
+			}
+			free(path);
+		}
+	}
+	(void) closedir(dir);
+
+	if (empty)
+		bb_error_msg_and_die("%s is empty (not mounted ?)", PROC_BASE);
+}
+
+
+int pstree_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pstree_main(int argc, char **argv)
+{
+	unsigned w;
+	pid_t pid = 1;
+	long uid = 0;
+
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G)));
+
+	G.sym = (struct sym_t) { "  ", "|-", "| ", "`-", "---", "-+-"};
+	G.output_width = 132;
+	G.cur_x = 1;
+
+	if (get_terminal_width_height(1,&w,NULL) == 0)
+		G.output_width = w;
+
+	G.opts = getopt32(argv, "p");
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 1) bb_show_usage();
+	else if (argc == 1) {
+		if (*argv[0] >= '0' && *argv[0] <= '9') {
+			pid = (pid_t) xatoi(argv[0]);
+		} else {
+			uid = xuname2uid(argv[0]);
+		}
+	}
+
+	read_proc();
+	if (!uid)
+		dump_tree(find_proc(pid), 0, 1, 1, 1, 0);
+	else {
+		dump_by_user(find_proc(1), uid);
+		if (!G.dumped) {
+			bb_error_msg_and_die("No processes found.");
+		}
+	}
+	maybe_free_buffers();
+
+	return 0;
+}
-- 
1.6.3.3



More information about the busybox mailing list