[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