diff options
Diffstat (limited to 'src/pstree.c')
-rw-r--r-- | src/pstree.c | 1568 |
1 files changed, 1568 insertions, 0 deletions
diff --git a/src/pstree.c b/src/pstree.c new file mode 100644 index 0000000..39265d1 --- /dev/null +++ b/src/pstree.c @@ -0,0 +1,1568 @@ +/* + * pstree.c - display process tree + * + * Copyright (C) 1993-2002 Werner Almesberger + * Copyright (C) 2002-2024 Craig Small <csmall@dropbear.xyz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <getopt.h> +#include <pwd.h> +#include <dirent.h> +#include <errno.h> +#include <curses.h> +#include <term.h> +#include <termios.h> +#include <langinfo.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <limits.h> +#include <locale.h> + +#include "i18n.h" +#include "comm.h" + +#ifdef WITH_SELINUX +#include <dlfcn.h> +#include <selinux/selinux.h> +#endif /*WITH_SELINUX */ + +#ifdef WITH_APPARMOR +#include <dlfcn.h> +#include <sys/apparmor.h> +#endif /* WITH_APPARMOR */ + +#if !defined(WITH_SELINUX) && !defined(WITH_APPARMOR) +typedef void* security_context_t; /* DUMMY to remove most ifdefs */ +#endif /* !WITH_SELINUX && !WITH_APPARMOR */ + +extern const char *__progname; + +#define PROC_BASE "/proc" + +#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) +#define DEFAULT_ROOT_PID 0 +#else +#define DEFAULT_ROOT_PID 1 +#endif /* __FreeBSD__ */ + +/* UTF-8 defines by Johan Myreen, updated by Ben Winslow */ +#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ +#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ +#define UTF_H "\342\224\200" /* U+2500, Horizontal */ +#define UTF_UR "\342\224\224" /* U+2514, Up and right */ +#define UTF_HD "\342\224\254" /* U+252C, Horizontal and down */ + +#define VT_BEG "\033(0\017" /* use graphic chars */ +#define VT_END "\033(B" /* back to normal char set */ +#define VT_V "x" /* see UTF definitions above */ +#define VT_VR "t" +#define VT_H "q" +#define VT_UR "m" +#define VT_HD "w" + +#define THREAD_FORMAT "{%.*s}" /* Format for thread names */ + +enum ns_type { + CGROUPNS = 0, + IPCNS, + MNTNS, + NETNS, + PIDNS, + USERNS, + UTSNS, + TIMENS, + NUM_NS +}; + +enum color_type { + COLOR_NONE = 0, + COLOR_AGE, + NUM_COLOUR +}; + +static const char *ns_names[] = { + [CGROUPNS] = "cgroup", + [IPCNS] = "ipc", + [MNTNS] = "mnt", + [NETNS] = "net", + [PIDNS] = "pid", + [USERNS] = "user", + [UTSNS] = "uts", + [TIMENS] = "time", +}; + +typedef struct _proc { + char comm[COMM_LEN + 2 + 1]; /* add another 2 for thread brackets */ + char **argv; /* only used : argv[0] is 1st arg; undef if argc < 1 */ + int argc; /* with -a : number of arguments, -1 if swapped */ + pid_t pid; + pid_t pgid; + uid_t uid; + ino_t ns[NUM_NS]; + char flags; + double age; + struct _child *children; + struct _proc *parent; + struct _proc *next; +} PROC; + +/* For flags above */ +#define PFLAG_HILIGHT 0x01 +#define PFLAG_THREAD 0x02 + +typedef struct _child { + PROC *child; + struct _child *next; +} CHILD; + +struct ns_entry { + ino_t number; + CHILD *children; + struct ns_entry *next; +}; + +static struct { + const char *empty_2; /* */ + const char *branch_2; /* |- */ + const char *vert_2; /* | */ + const char *last_2; /* `- */ + const char *single_3; /* --- */ + const char *first_3; /* -+- */ +} sym_ascii = { +" ", "|-", "| ", "`-", "---", "-+-"} + +, sym_utf = { +" ", + UTF_VR UTF_H, + UTF_V " ", + UTF_UR UTF_H, UTF_H UTF_H UTF_H, UTF_H UTF_HD UTF_H}, sym_vt100 = { +" ", + VT_BEG VT_VR VT_H VT_END, + VT_BEG VT_V VT_END " ", + VT_BEG VT_UR VT_H VT_END, + VT_BEG VT_H VT_H VT_H VT_END, VT_BEG VT_H VT_HD VT_H VT_END} + +, *sym = &sym_ascii; + +static PROC *list = NULL; + +struct age_to_color { + unsigned int age_seconds; + char *color; +}; + +struct age_to_color age_to_color[] = { + { 60, "\033[32m"}, + {3600, "\033[33m"}, + {0, "\033[31m"} + }; + +/* The buffers will be dynamically increased in size as needed. */ +static int capacity = 0; +static int *width = NULL; +static int *more = NULL; + +static int print_args = 0, compact = 1, user_change = 0, pids = 0, pgids = 0, + show_parents = 0, by_pid = 0, trunc = 1, wait_end = 0, ns_change = 0, + thread_names = 0, hide_threads = 0; +static int show_scontext = 0; +static int output_width = 132; +static int cur_x = 1; +static char last_char = 0; +static int dumped = 0; /* used by dump_by_user */ +static int charlen = 0; /* length of character */ +static enum color_type color_highlight = COLOR_NONE; + +/* + * Find the root PID. + * Check to see if PID 0 exists, such as in LXC + * Otherwise return 0 for BSD, 1 for others + */ +static pid_t find_root_pid(void) +{ + struct stat s; + + if (stat(PROC_BASE "/0", &s) == 0) + return 0; + return DEFAULT_ROOT_PID; +} + +const char *get_ns_name(enum ns_type id) { + if (id >= NUM_NS) + return NULL; + return ns_names[id]; +} + +static enum ns_type get_ns_id(const char *name) { + int i; + + for (i = 0; i < NUM_NS; i++) + if (!strcmp(ns_names[i], name)) + return i; + return NUM_NS; +} + +static int verify_ns(enum ns_type id) +{ + char filename[50]; + struct stat s; + + snprintf(filename, 50, "/proc/%i/ns/%s", getpid(), get_ns_name(id)); + + return stat(filename, &s); +} + +static inline void new_proc_ns(PROC *ns_task) +{ + struct stat st; + char buff[50]; + pid_t pid = ns_task->pid; + int i; + + for (i = 0; i < NUM_NS; i++) { + snprintf(buff, sizeof(buff), "/proc/%i/ns/%s", pid, + get_ns_name(i)); + if (stat(buff, &st)) { + ns_task->ns[i] = 0; + continue; + } + ns_task->ns[i] = st.st_ino; + } +} + +static void find_ns_and_add(struct ns_entry **root, PROC *r, enum ns_type id) +{ + struct ns_entry *ptr, *last = NULL; + CHILD *tmp_child, **c; + + for (ptr = *root; ptr; ptr = ptr->next) { + if (ptr->number == r->ns[id]) + break; + last = ptr; + } + + if (!ptr) { + + if (!(ptr = malloc(sizeof(*ptr)))) { + perror("malloc"); + exit(1); + } + + memset(ptr, 0, sizeof(*ptr)); + ptr->number = r->ns[id]; + if (*root == NULL) + *root = ptr; + else + last->next = ptr; + } + + /* move the child to under the namespace's umbrella */ + for (c = &ptr->children; *c; c = &(*c)->next) + ; + + if (!(*c = malloc(sizeof(CHILD)))) { + perror("malloc"); + exit(1); + } + + (*c)->child = r; + (*c)->next = NULL; + + /* detaching from parent */ + if (r->parent) { + for (c = &r->parent->children; *c; c = &(*c)->next) { + if ((*c)->child == r) { + tmp_child = (*c)->next; + free(*c); + *c = tmp_child; + break; + } + } + r->parent = NULL; + } + +} + +static PROC *find_proc(pid_t pid); +static void sort_by_namespace(PROC *r, enum ns_type id, struct ns_entry **root) +{ + CHILD *walk, *next; + + /* first run, find the first process */ + if (!r) { + r = find_proc(1); + if (!r) + return; + } + + if (r->parent == NULL || r->parent->ns[id] != r->ns[id]) + find_ns_and_add(root, r, id); + + walk = r->children; + while (walk) { + next = walk->next; + sort_by_namespace(walk->child, id, root); + walk = next; + } +} + +static void fix_orphans(const pid_t root_pid); + +/* + * Determine the correct output width, what we use is: + */ +static int get_output_width(void) +{ + char *ep, *env_columns; + struct winsize winsz; + + env_columns = getenv("COLUMNS"); + if (env_columns && *env_columns) { + long t; + t = strtol(env_columns, &ep, 0); + if (!*ep && (t > 0) && (t < 0x7fffffffL)) + return (int)t; + } + if (ioctl(1, TIOCGWINSZ, &winsz) >= 0) + if (winsz.ws_col) + return winsz.ws_col; + return 132; + +} + +/* + * Allocates additional buffer space for width and more as needed. + * The first call will allocate the first buffer. + * + * index the index that will be used after the call + * to this function. + */ +static void ensure_buffer_capacity(int index) +{ + if (index >= capacity) { + if (capacity == 0) + capacity = 100; + else + capacity *= 2; + if (!(width = realloc(width, capacity * sizeof(int)))) { + perror("realloc"); + exit(1); + } + if (!(more = realloc(more, capacity * sizeof(int)))) { + perror("realloc"); + exit(1); + } + } +} + +/* + * Frees any buffers allocated by ensure_buffer_capacity. + */ +static void free_buffers() +{ + if (width != NULL) { + free(width); + width = NULL; + } + if (more != NULL) { + free(more); + more = NULL; + } + capacity = 0; +} + +static void free_children(CHILD *children) +{ + CHILD *walk, *next; + + walk = children; + while (walk != NULL) { + next = walk->next; + free(walk); + walk = next; + } +} + +static void free_proc() +{ + PROC *walk, *next; + walk = list; + while (walk != NULL) { + next = walk->next; + free_children(walk->children); + if (walk->argv) { + free(walk->argv[0]); + free(walk->argv); + } + free(walk); + walk = next; + } + list = NULL; +} + +static void free_namespace(struct ns_entry **nsroot) +{ + struct ns_entry *walk, *next; + walk = *nsroot; + while (walk != NULL) { + next = walk->next; + free_children(walk->children); + free(walk); + walk = next; + } + *nsroot = NULL; +} + +static void out_char(char c) +{ + if (charlen == 0) { /* "new" character */ + if ((c & 0x80) == 0) { + charlen = 1; /* ASCII */ + } else if ((c & 0xe0) == 0xc0) { /* 110.. 2 bytes */ + charlen = 2; + } else if ((c & 0xf0) == 0xe0) { /* 1110.. 3 bytes */ + charlen = 3; + } else if ((c & 0xf8) == 0xf0) { /* 11110.. 4 bytes */ + charlen = 4; + } else { + charlen = 1; + } + cur_x++; /* count first byte of whatever it is only */ + } + charlen--; + if (!trunc || cur_x <= output_width) + putchar(c); + else { + if (trunc && (cur_x == output_width + 1)) + putchar('+'); + } +} + + +static void out_string(const char *str) +{ + while (*str) + out_char(*str++); +} + + +static int out_int(int x) +{ /* non-negative integers only */ + int digits, div; + + digits = 0; + for (div = 1; x / div; div *= 10) + digits++; + if (!digits) + digits = 1; + for (div /= 10; div; div /= 10) + out_char('0' + (x / div) % 10); + return digits; +} + +#ifdef WITH_SELINUX +static bool out_selinux_context(const PROC *current) +{ + static void (*my_freecon)(char*) = 0; + static int (*my_getpidcon)(pid_t pid, char **context) = 0; + static int (*my_is_selinux_enabled)(void) = 0; + static int selinux_enabled = 0; + static int tried_load = 0; + bool ret = false; + char *context; + + if (!my_getpidcon && !tried_load) { + void *handle = dlopen("libselinux.so.1", RTLD_NOW); + if (handle) { + my_freecon = dlsym(handle, "freecon"); + if (dlerror()) + my_freecon = 0; + dlerror(); + my_getpidcon = dlsym(handle, "getpidcon"); + if (dlerror()) + my_getpidcon = 0; + my_is_selinux_enabled = dlsym(handle, "is_selinux_enabled"); + if (dlerror()) + my_is_selinux_enabled = 0; + else + selinux_enabled = my_is_selinux_enabled(); + } + tried_load++; + } + if (my_getpidcon && selinux_enabled && !my_getpidcon(current->pid, &context)) { + out_string(context); + my_freecon(context); + ret = true; + } + return ret; +} +#endif /* WITH_SELINUX */ + +#ifdef WITH_APPARMOR +static bool out_apparmor_context(const PROC *current) +{ + static int (*my_aa_gettaskcon)(pid_t pid, char **context, char **mode) = 0; + static int (*my_aa_is_enabled)(void) = 0; + static int apparmor_enabled = 0; + static int tried_load = 0; + bool ret = false; + char *context; + + if (!my_aa_gettaskcon && !tried_load) { + void *handle = dlopen("libapparmor.so.1", RTLD_NOW); + if (handle) { + my_aa_gettaskcon = dlsym(handle, "aa_gettaskcon"); + if (dlerror()) + my_aa_gettaskcon = 0; + my_aa_is_enabled = dlsym(handle, "aa_is_enabled"); + if (dlerror()) + my_aa_is_enabled = 0; + else + apparmor_enabled = my_aa_is_enabled(); + } + tried_load++; + } + if (my_aa_gettaskcon && apparmor_enabled && my_aa_gettaskcon(current->pid, &context, NULL) >= 0) { + out_string(context); + free(context); + ret = true; + } + return ret; +} +#endif /* WITH_APPARMOR */ + +/* + * Print the security context of the current process. This is largely lifted + * from pr_context from procps ps/output.c + */ +static void out_scontext(const PROC *current) +{ + bool success = false; + out_string("`"); + +#ifdef WITH_SELINUX + success = out_selinux_context(current); +#endif /* WITH_SELINUX */ + +#ifdef WITH_APPARMOR + success |= out_apparmor_context(current); +#endif /* WITH_APPARMOR */ + + if (!success) { + FILE *file; + char path[50]; + char readbuf[BUFSIZ+1]; + int num_read; + snprintf(path, sizeof path, "/proc/%d/attr/current", current->pid); + if ( (file = fopen(path, "r")) != NULL) { + if (fgets(readbuf, BUFSIZ, file) != NULL) { + num_read = strlen(readbuf); + readbuf[num_read-1] = '\0'; + out_string(readbuf); + } + fclose(file); + } + } + out_string("'"); +} + +static void out_newline(void) +{ + if (last_char && cur_x == output_width) + putchar(last_char); + last_char = 0; + putchar('\n'); + cur_x = 1; +} + +static void reset_color(void) +{ + if (color_highlight != COLOR_NONE) { + char *str = "\033[0m"; + while (*str) putchar(*str++); + } +} + +static void print_proc_color(const int process_age) +{ + struct age_to_color *p; + switch(color_highlight) { + case COLOR_AGE: + for(p=age_to_color; p->age_seconds != 0; p++) + if (process_age < p->age_seconds) break; + + char *str = p->color; + while (*str) putchar(*str++); + break; + default: + break; + } +} + +static PROC *find_proc(pid_t pid) +{ + PROC *walk; + + for (walk = list; walk; walk = walk->next) { + if (walk->pid == pid) + return walk; + } + return NULL; +} + +static PROC *new_proc(const char *comm, pid_t pid, uid_t uid) +{ + PROC *new; + + if (!(new = malloc(sizeof(PROC)))) { + perror("malloc"); + exit(1); + } + + strncpy(new->comm, comm, COMM_LEN+2); + new->comm[COMM_LEN+1] = '\0'; /* make sure nul terminated*/ + new->pid = pid; + new->uid = uid; + new->flags = 0; + new->argc = 0; + new->argv = NULL; + new->children = NULL; + new->parent = NULL; + new->next = list; + new_proc_ns(new); + return list = new; +} + + +static void add_child(PROC * parent, PROC * child) +{ + CHILD *new, **walk; + int cmp; + + if (!(new = malloc(sizeof(CHILD)))) { + perror("malloc"); + exit(1); + } + new->child = child; + for (walk = &parent->children; *walk; walk = &(*walk)->next) + if (by_pid) { + if ((*walk)->child->pid > child->pid) + break; + } else if ((cmp = strcmp((*walk)->child->comm, child->comm)) > 0) { + break; } + else if (!cmp && (*walk)->child->uid > child->uid) + break; + new->next = *walk; + *walk = new; +} + + +static void set_args(PROC * this, const char *args, int size) +{ + char *start; + int i; + + if (!size) { + this->argc = -1; + return; + } + this->argc = 0; + for (i = 0; i < size - 1; i++) + if (!args[i]) { + this->argc++; + /* now skip consecutive NUL */ + while(!args[i] && (i < size -1 )) + i++; + } + if (!this->argc) + return; + if (!(this->argv = malloc(sizeof(char *) * this->argc))) { + perror("malloc"); + exit(1); + } + start = strchr(args, 0) + 1; + size -= start - args; + if (!(this->argv[0] = malloc((size_t) size))) { + perror("malloc"); + exit(1); + } + start = memcpy(this->argv[0], start, (size_t) size); + for (i = 1; i < this->argc; i++) + this->argv[i] = start = strchr(start, 0) + 1; +} + +static void +rename_proc(PROC *this, const char *comm, uid_t uid) +{ + PROC *tmp_child, *parent; + CHILD **walk; + + strncpy(this->comm, comm, COMM_LEN+2); + this->comm[COMM_LEN+1] = '\0'; + this->uid = uid; + + /* Re-sort children in parent, now we have a name */ + if (!by_pid && this->parent) { + parent = this->parent; + for (walk = &parent->children; *walk; walk = &(*walk)->next) { + if ( + ((*walk)->next != NULL) && + strcmp((*walk)->child->comm, (*walk)->next->child->comm) > 0 ) { + tmp_child = (*walk)->child; + (*walk)->child = (*walk)->next->child; + (*walk)->next->child = tmp_child; + } + } + } +} + +static void +add_proc(const char *comm, pid_t pid, pid_t ppid, pid_t pgid, uid_t uid, + const char *args, int size, char isthread, double process_age_sec) +{ + PROC *this, *parent; + + if (!(this = find_proc(pid))) + this = new_proc(comm, pid, uid); + else { + rename_proc(this, comm, uid); + } + if (args) + set_args(this, args, size); + if (pid == ppid) + ppid = 0; + this->pgid = pgid; + this->age = process_age_sec; + if (isthread) + this->flags |= PFLAG_THREAD; + if (!(parent = find_proc(ppid))) { + parent = new_proc("?", ppid, 0); + } + if (pid != 0) { + add_child(parent, this); + this->parent = parent; + } +} + + +static int tree_equal(const PROC * a, const PROC * b) +{ + const CHILD *walk_a, *walk_b; + int i; + + if (strcmp(a->comm, b->comm)) + return 0; + if (user_change && a->uid != b->uid) + return 0; + if (ns_change) { + for (i = 0; i < NUM_NS; i++) + if (a->ns[i] != b->ns[i]) + 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, + uid_t prev_uid, int closing) +{ + CHILD *walk, *next, *tmp_child, **scan; + const struct passwd *pw; + int lvl, i, add, offset, len, swapped, info, count, comm_len, first; + const char *tmp, *here; + + assert(closing >= 0); + if (!current) + return; + if (!leaf) + for (lvl = 0; lvl < level; lvl++) { + for (i = width[lvl] + 1; i; i--) + out_char(' '); + out_string(lvl == + level - + 1 ? last ? sym->last_2 : sym->branch_2 : more[lvl + + 1] ? + sym->vert_2 : sym->empty_2); + } + + if (rep < 2) + add = 0; + else { + add = out_int(rep) + 2; + out_string("*["); + } + print_proc_color(current->age); + if ((current->flags & PFLAG_HILIGHT) && (tmp = tgetstr("md", NULL))) + tputs(tmp, 1, putchar); + swapped = info = print_args; + if (swapped && current->argc < 0) + out_char('('); + comm_len = out_args(current->comm); + offset = cur_x; + if (pids) { + out_char(info++ ? ',' : '('); + (void) out_int(current->pid); + } + if (pgids) { + out_char(info++ ? ',' : '('); + (void) out_int(current->pgid); + } + if (user_change && prev_uid != current->uid) { + out_char(info++ ? ',' : '('); + if ((pw = getpwuid(current->uid))) + out_string(pw->pw_name); + else + (void) out_int(current->uid); + } + if (ns_change && current->parent) { + for (i = 0; i < NUM_NS; i++) { + if (current->ns[i] == 0 || current->parent->ns[i] == 0) + continue; + if (current->ns[i] != current->parent->ns[i]) { + out_char(info++ ? ',' : '('); + out_string(get_ns_name(i)); + } + } + } + if (show_scontext) { + out_char(info++ ? ',' : '('); + out_scontext(current); + } + if ((swapped && print_args && current->argc < 0) || (!swapped && info)) + out_char(')'); + if ((current->flags & PFLAG_HILIGHT) && (tmp = tgetstr("me", NULL))) + tputs(tmp, 1, putchar); + if (print_args) { + for (i = 0; i < current->argc; i++) { + if (i < current->argc - 1) /* Space between words but not at the end of last */ + out_char(' '); + len = 0; + for (here = current->argv[i]; *here; here++) + len += *here >= ' ' && *here <= '~' ? 1 : 4; + if (cur_x + len <= + output_width - (i == current->argc - 1 ? 0 : 4) || !trunc) + out_args(current->argv[i]); + else { + out_string("..."); + break; + } + } + } + reset_color(); + if (show_scontext || print_args || !current->children) + { + while (closing--) + out_char(']'); + out_newline(); + } + ensure_buffer_capacity(level); + more[level] = !last; + + if (show_scontext || print_args) + { + width[level] = swapped + (comm_len > 1 ? 0 : -1); + count=0; + first=1; + for (walk = current->children; walk; walk = next) { + next = walk->next; + count=0; + if (compact && (walk->child->flags & PFLAG_THREAD)) { + scan = &walk->next; + while (*scan) { + if (!tree_equal(walk->child, (*scan)->child)) { + scan = &(*scan)->next; + } else { + if (next == *scan) + next = (*scan)->next; + count++; + tmp_child = (*scan)->next; + free(*scan); + *scan = tmp_child; + } + } + dump_tree(walk->child, level + 1, count + 1, + 0, !next, current->uid, closing+ (count ? 2 : 1)); + //closing + (count ? 1 : 0)); + } else { + dump_tree(walk->child, level + 1, 1, 0, !walk->next, + current->uid, 0); + } + } + return; + } + width[level] = comm_len + cur_x - offset + add; + if (cur_x >= output_width && trunc) { + out_string(sym->first_3); + out_string("+"); + out_newline(); + return; + } + first = 1; + for (walk = current->children; walk; walk = next) { + count = 0; + next = walk->next; + if (compact) { + scan = &walk->next; + while (*scan) + if (!tree_equal(walk->child, (*scan)->child)) + scan = &(*scan)->next; + else { + if (next == *scan) + next = (*scan)->next; + count++; + tmp_child = (*scan)->next; + free(*scan); + *scan = tmp_child; + } + } + if (first) { + out_string(next ? sym->first_3 : sym->single_3); + first = 0; + } + dump_tree(walk->child, level + 1, count + 1, + walk == current->children, !next, current->uid, + 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 (dumped) + putchar('\n'); + dump_tree(current, 0, 1, 1, 1, uid, 0); + dumped = 1; + return; + } + for (walk = current->children; walk; walk = walk->next) + dump_by_user(walk->child, uid); +} + +static void dump_by_namespace(struct ns_entry *root) +{ + struct ns_entry *ptr = root; + CHILD *c; + char buff[14]; + + for ( ; ptr; ptr = ptr->next) { + snprintf(buff, sizeof(buff), "[%li]\n", (long int)ptr->number); + out_string(buff); + for (c = ptr->children; c; c = c->next) + dump_tree(c->child, 0, 1, 1, 1, 0, 0); + } +} + +static void trim_tree_by_parent(PROC * current) +{ + if (!current) + return; + + PROC * parent = current->parent; + + if (!parent) + return; + + free_children(parent->children); + parent->children = NULL; + add_child(parent, current); + trim_tree_by_parent(parent); +} + +static double +uptime() +{ + char * savelocale; + char buf[2048]; + FILE* file; + if (!(file=fopen( PROC_BASE "/uptime", "r"))) { + fprintf(stderr, "pstree: error opening uptime file\n"); + exit(1); + } + savelocale = setlocale(LC_NUMERIC,"C"); + if (fscanf(file, "%2047s", buf) == EOF) perror("uptime"); + fclose(file); + setlocale(LC_NUMERIC,savelocale); + return atof(buf); +} + +/* process age from jiffies to seconds via uptime */ +static double process_age(const unsigned long long jf) +{ + double age; + double sc_clk_tck = sysconf(_SC_CLK_TCK); + assert(sc_clk_tck > 0); + age = uptime() - jf / sc_clk_tck; + if (age < 0L) + return 0L; + return age; +} + +static char* get_threadname(const pid_t pid, const int tid, const char *comm) +{ + FILE *file; + char *thread_comm, *endcomm, *threadname; + char *path = NULL; + int len, nbytes; + char readbuf[BUFSIZ + 1]; + + if (! (threadname = malloc(COMM_LEN + 2 + 1))) { + exit(2); + } + if (!thread_names) { + sprintf(threadname, THREAD_FORMAT, COMM_LEN, comm); + return threadname; + } + len = snprintf(NULL, 0, "%s/%d/task/%d/stat", PROC_BASE, pid, tid); + if (len < 0) + exit(2); + len++; + path = malloc(len); + if (path == NULL) + exit(2); + nbytes = snprintf(path, len, "%s/%d/task/%d/stat", PROC_BASE, pid, tid); + if (nbytes < 0 || nbytes >= len) + perror("get_threadname: snprintf"); + if ( (file = fopen(path, "r")) != NULL) { + if (fgets(readbuf, BUFSIZ, file) != NULL) { + if ((thread_comm = strchr(readbuf, '(')) + && (endcomm = strrchr(thread_comm, ')'))) { + ++thread_comm; + *endcomm = '\0'; + sprintf(threadname, THREAD_FORMAT, COMM_LEN, thread_comm); + (void) fclose(file); + free(path); + return threadname; + } + } + fclose(file); + } + free(path); + + /* Fall back to old method */ + sprintf(threadname, THREAD_FORMAT, COMM_LEN, comm); + return threadname; +} + +/* + * 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(const pid_t root_pid) +{ + DIR *dir; + struct dirent *de; + FILE *file; + struct stat st; + char *path, *comm; + char *buffer; + size_t buffer_size; + char readbuf[BUFSIZ + 1]; + char *tmpptr, *endptr; + pid_t pid, ppid, pgid; + int fd, size; + int empty; + unsigned long long proc_stt_jf = 0; + double process_age_sec = 0; + + if (trunc) + buffer_size = output_width + 1; + else + buffer_size = BUFSIZ + 1; + + if (!print_args) + buffer = NULL; + else if (!(buffer = malloc(buffer_size))) { + perror("malloc"); + exit(1); + } + if (!(dir = opendir(PROC_BASE))) { + perror(PROC_BASE); + exit(1); + } + empty = 1; + while ((de = readdir(dir)) != NULL) { + pid = (pid_t) strtol(de->d_name, &endptr, 10); + if (endptr != de->d_name && endptr[0] == '\0') { + if (! (path = malloc(strlen(PROC_BASE) + strlen(de->d_name) + 10))) + exit(2); + sprintf(path, "%s/%d/stat", PROC_BASE, pid); + if ((file = fopen(path, "r")) != NULL) { + empty = 0; + sprintf(path, "%s/%d", PROC_BASE, pid); + if (stat(path, &st) < 0) { + (void) fclose(file); + free(path); + continue; + } + 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 ) */ + if ((comm = strchr(readbuf, '(')) + && (tmpptr = strrchr(comm, ')'))) { + ++comm; + *tmpptr = 0; + /* We now have readbuf with pid and cmd, and tmpptr+2 + * with the rest */ + /*printf("tmpptr: %s\n", tmpptr+2); */ + if (sscanf(tmpptr + 2, "%*c %d %d %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %Lu", + &ppid, &pgid, &proc_stt_jf) == 3) { + DIR *taskdir; + struct dirent *dt; + char *taskpath; + int thread; + + process_age_sec = process_age(proc_stt_jf); + /* handle process threads */ + if (! hide_threads) { + if (! (taskpath = malloc(strlen(path) + 10))) + exit(2); + sprintf(taskpath, "%s/task", path); + + if ((taskdir = opendir(taskpath)) != 0) { + /* if we have this dir, we're on 2.6 */ + while ((dt = readdir(taskdir)) != NULL) { + if ((thread = atoi(dt->d_name)) != 0) { + if (thread != pid) { + char *threadname; + threadname = get_threadname(pid, thread, comm); + if (print_args) + add_proc(threadname, thread, pid, pgid, st.st_uid, + threadname, strlen (threadname) + 1, 1, + process_age_sec); + else + add_proc(threadname, thread, pid, pgid, st.st_uid, + NULL, 0, 1, + process_age_sec); + free(threadname); + } + } + } + (void) closedir(taskdir); + } + free(taskpath); + } + + /* handle process */ + if (!print_args) + add_proc(comm, pid, ppid, pgid, st.st_uid, NULL, 0, 0, + process_age_sec); + else { + sprintf(path, "%s/%d/cmdline", PROC_BASE, pid); + if ((fd = open(path, O_RDONLY)) < 0) { + /* If this fails then the process is gone. If a PID + * was specified on the command-line then we might + * not even be interested in the current process. + * There's no sensible way of dealing with this race + * so we might as well behave as if the current + * process did not exist. */ + (void) fclose(file); + free(path); + continue; + } + if ((size = read(fd, buffer, buffer_size)) < 0) { + /* As above. */ + close(fd); + (void) fclose(file); + free(path); + continue; + } + (void) close(fd); + /* If we have read the maximum screen length of args, + * bring it back by one to stop overflow */ + if (size >= (int)buffer_size) + size--; + if (size) + buffer[size++] = 0; + add_proc(comm, pid, ppid, pgid, st.st_uid, + buffer, size, 0, process_age_sec); + } + } + } + } + (void) fclose(file); + } + free(path); + } + } + (void) closedir(dir); + fix_orphans(root_pid); + if (print_args) + free(buffer); + if (empty) { + fprintf(stderr, _("%s is empty (not mounted ?)\n"), PROC_BASE); + exit(1); + } +} + + +/* When using kernel 3.3 with hidepid feature enabled on /proc + * then we need fake root pid and gather all the orphan processes + * that is, processes with no known parent + * As we cannot be sure if it is just the root pid or others missing + * we gather the lot + */ +static void fix_orphans(const pid_t root_pid) +{ + PROC *root, *walk; + + if (!(root = find_proc(root_pid))) { + root = new_proc("?", root_pid, 0); + } + for (walk = list; walk; walk = walk->next) { + if (walk->pid == 1 || walk->pid == 0) + continue; + if (walk->parent == NULL) { + add_child(root, walk); + walk->parent = root; + } + } +} + + +static void usage(void) +{ + fprintf(stderr, _( + "Usage: pstree [-acglpsStTuZ] [ -h | -H PID ] [ -n | -N type ]\n" + " [ -A | -G | -U ] [ PID | USER ]\n" + " or: pstree -V\n")); + fprintf(stderr, _( + "\n" + "Display a tree of processes.\n\n")); + fprintf(stderr, _( + " -a, --arguments show command line arguments\n" + " -A, --ascii use ASCII line drawing characters\n" + " -c, --compact-not don't compact identical subtrees\n")); + fprintf(stderr, _( + " -C, --color=TYPE color process by attribute\n" + " (age)\n")); + fprintf(stderr, _( + " -g, --show-pgids show process group ids; implies -c\n" + " -G, --vt100 use VT100 line drawing characters\n")); + fprintf(stderr, _( + " -h, --highlight-all highlight current process and its ancestors\n" + " -H PID, --highlight-pid=PID\n" + " highlight this process and its ancestors\n" + " -l, --long don't truncate long lines\n")); + fprintf(stderr, _( + " -n, --numeric-sort sort output by PID\n" + " -N TYPE, --ns-sort=TYPE\n" + " sort output by this namespace type\n" + " (cgroup, ipc, mnt, net, pid, time, user, uts)\n" + " -p, --show-pids show PIDs; implies -c\n")); + fprintf(stderr, _( + " -s, --show-parents show parents of the selected process\n" + " -S, --ns-changes show namespace transitions\n" + " -t, --thread-names show full thread names\n" + " -T, --hide-threads hide threads, show only processes\n")); + fprintf(stderr, _( + " -u, --uid-changes show uid transitions\n" + " -U, --unicode use UTF-8 (Unicode) line drawing characters\n" + " -V, --version display version information\n")); + fprintf(stderr, _( + " -Z, --security-context\n" + " show security attributes\n")); + fprintf(stderr, _("\n" + " PID start at this PID; default is 1 (init)\n" + " USER show only trees rooted at processes of this user\n\n")); + exit(1); +} + +void print_version() +{ + fprintf(stderr, _("pstree (PSmisc) %s\n"), VERSION); + fprintf(stderr, + _ + ("Copyright (C) 1993-2024 Werner Almesberger and Craig Small\n\n")); + fprintf(stderr, + _("PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + + +int main(int argc, char **argv) +{ + PROC *current; + const struct passwd *pw; + struct ns_entry *nsroot = NULL; + pid_t pid, highlight, root_pid; + char termcap_area[1024]; + char *termname, *endptr; + int c, pid_set = 0; + enum ns_type nsid = NUM_NS; + + struct option options[] = { + {"arguments", 0, NULL, 'a'}, + {"ascii", 0, NULL, 'A'}, + {"compact-not", 0, NULL, 'c'}, + {"color", 1, NULL, 'C'}, + {"vt100", 0, NULL, 'G'}, + {"highlight-all", 0, NULL, 'h'}, + {"highlight-pid", 1, NULL, 'H'}, + {"long", 0, NULL, 'l'}, + {"numeric-sort", 0, NULL, 'n'}, + {"ns-sort", 1, NULL, 'N' }, + {"show-pids", 0, NULL, 'p'}, + {"show-pgids", 0, NULL, 'g'}, + {"show-parents", 0, NULL, 's'}, + {"ns-changes", 0, NULL, 'S' }, + {"thread-names", 0, NULL, 't'}, + {"hide-threads", 0, NULL, 'T'}, + {"uid-changes", 0, NULL, 'u'}, + {"unicode", 0, NULL, 'U'}, + {"version", 0, NULL, 'V'}, + {"security-context", 0, NULL, 'Z'}, + { 0, 0, 0, 0 } + }; + + output_width = get_output_width(); + root_pid = find_root_pid(); + pid = root_pid; + highlight = 0; + pw = NULL; + +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + if (!strcmp(__progname, "pstree.x11")) + wait_end = 1; + + /* + * Attempt to figure out a good default symbol set. Will be overriden by + * command-line options, if given. + */ + + if (isatty(1) && !strcmp(nl_langinfo(CODESET), "UTF-8")) { + /* Use UTF-8 symbols if the locale's character set is UTF-8. */ + sym = &sym_utf; + } else if (isatty(1) && (termname = getenv("TERM")) && + (strlen(termname) > 0) && + (setupterm(NULL, 1 /* stdout */ , NULL) == OK) && + (tigetstr("acsc") != NULL) && (tigetstr("acsc") != (char *)-1)) { + /* + * Failing that, if TERM is defined, a non-null value, and the terminal + * has the VT100 graphics charset, use it. + */ + /* problems with VT100 on some terminals, making this ascci + * for now + */ + sym = &sym_ascii; + } else { + /* Otherwise, fall back to ASCII. */ + sym = &sym_ascii; + } + + while ((c = + getopt_long(argc, argv, "aAcC:GhH:nN:pglsStTuUVZ", options, + NULL)) != -1) + switch (c) { + case 'a': + print_args = 1; + break; + case 'A': + sym = &sym_ascii; + break; + case 'c': + compact = 0; + break; + case 'C': + if (strcasecmp("age", optarg) == 0) { + color_highlight = COLOR_AGE; + } else { + usage(); + } + break; + case 'G': + sym = &sym_vt100; + break; + case 'h': + if (highlight) + usage(); + if (getenv("TERM") + && tgetent(termcap_area, getenv("TERM")) > 0) + highlight = getpid(); + break; + case 'H': + if (highlight) + usage(); + if (!getenv("TERM")) { + fprintf(stderr, _("TERM is not set\n")); + return 1; + } + if (tgetent(termcap_area, getenv("TERM")) <= 0) { + fprintf(stderr, _("Can't get terminal capabilities\n")); + return 1; + } + if (!(highlight = atoi(optarg))) + usage(); + break; + case 'l': + trunc = 0; + break; + case 'n': + by_pid = 1; + break; + case 'N': + nsid = get_ns_id(optarg); + if (nsid == NUM_NS) + usage(); + if (verify_ns(nsid)) { + fprintf(stderr, + _("procfs file for %s namespace not available\n"), + optarg); + return 1; + } + break; + case 'p': + pids = 1; + compact = 0; + break; + case 'g': + pgids = 1; + break; + case 's': + show_parents = 1; + break; + case 'S': + ns_change = 1; + break; + case 't': + thread_names = 1; + break; + case 'T': + hide_threads = 1; + break; + case 'u': + user_change = 1; + break; + case 'U': + sym = &sym_utf; + break; + case 'V': + print_version(); + return 0; + case 'Z': + show_scontext = 1; + break; + default: + usage(); + } + if (optind == argc - 1) { + if (isdigit(*argv[optind])) { + pid = (pid_t) strtol(argv[optind++], &endptr, 10); + pid_set = 1; + if (endptr[0] != '\0') + usage(); + } else if (!(pw = getpwnam(argv[optind++]))) { + fprintf(stderr, _("No such user name: %s\n"), + argv[optind - 1]); + return 1; + } + } + if (optind != argc) + usage(); + read_proc(root_pid); + for (current = find_proc(highlight); current; + current = current->parent) + current->flags |= PFLAG_HILIGHT; + + if(show_parents && pid_set == 1) { + PROC *child_proc; + + if ( (child_proc = find_proc(pid)) == NULL) { + fprintf(stderr, _("Process %d not found.\n"), pid); + return 1; + } + trim_tree_by_parent(child_proc); + + pid = root_pid; + } + + if (nsid != NUM_NS) { + sort_by_namespace(NULL, nsid, &nsroot); + dump_by_namespace(nsroot); + } else if (!pw) + dump_tree(find_proc(pid), 0, 1, 1, 1, 0, 0); + else { + dump_by_user(find_proc(root_pid), pw->pw_uid); + if (!dumped) { + fprintf(stderr, _("No processes found.\n")); + return 1; + } + } + free_buffers(); + free_proc(); + free_namespace(&nsroot); + if (wait_end == 1) { + fprintf(stderr, _("Press return to close\n")); + (void) getchar(); + } + + return 0; +} |