diff --git a/.bashrc.d/20_emulate_pstree.sh b/.bashrc.d/20_emulate_pstree.sh index a8a1337..e2f231c 100644 --- a/.bashrc.d/20_emulate_pstree.sh +++ b/.bashrc.d/20_emulate_pstree.sh @@ -7,8 +7,64 @@ case "$SRC_ENABLED" in esac # --------------------------------- +# +# pstree_compat.sh +# +# Meant to be sourced (e.g. from .bashrc). Defines functions only — +# sourcing this file must never print anything or execute pstree. +# +# Compatibility target: bash 3.2+ (RHEL 5) and bash 4.1 (Debian Squeeze). +# +# CAVEAT: this guard only protects against an old/wrong *bash*. If this +# file is sourced by a non-bash POSIX shell (e.g. dash as /bin/sh on +# Debian), the bashisms below (arrays, [[ ]], local -a) will cause a +# parse error at function-definition time, before any runtime check +# can run. Only source this from bash (.bashrc, not .profile/.sh). +# +# After sourcing, call: +# pstree_compat # root pid 1 +# pstree_compat 1234 # specific pid +# + +# +# Returns 0 if running under bash >= 3.0, else prints an error to +# stderr and returns 1. Called at the top of every public entry point, +# never at source time. +# +bash_pstree_check_bash() +{ + if [[ -z "$BASH_VERSION" ]]; then + printf 'pstree_compat: requires bash, current shell is not bash\n' >&2 + return 1 + fi + if [[ -z "${BASH_VERSINFO[0]}" ]] || (( BASH_VERSINFO[0] < 3 )); then + printf 'pstree_compat: requires bash >= 3.0 (found %s)\n' "$BASH_VERSION" >&2 + return 1 + fi + return 0 +} + +pstree_compat() +{ + bash_pstree_check_bash || return 1 + + local root_pid="${1:-1}" + if command -v pstree >/dev/null 2>&1; then + pstree -p "$root_pid" + else + bash_pstree "$root_pid" + fi +} + +# +# Iterative (non-recursive) process tree walker. +# Avoids deep bash function recursion, which can blow the stack +# and crash the shell on large/deep process trees. +# bash_pstree() { + bash_pstree_check_bash || return 1 + local root_pid="${1:-1}" local procdir pid key value name ppid idx @@ -17,9 +73,6 @@ bash_pstree() NAMES=() idx=0 - # - # Build process table from /proc/*/status. - # for procdir in /proc/[0-9]*; do [[ -d "$procdir" ]] || continue [[ -r "$procdir/status" ]] || continue @@ -41,33 +94,28 @@ bash_pstree() done bash_pstree_set_chars - bash_pstree_root "$root_pid" + + if ! bash_pstree_find_name "$root_pid" >/dev/null; then + return 1 + fi + + bash_pstree_walk "$root_pid" } -# -# Pick UTF-8 box-drawing chars if the locale supports them, else -# fall back to plain ASCII (works on any RHEL5 console/terminal). -# bash_pstree_set_chars() { local charmap charmap=$(locale charmap 2>/dev/null) - if [[ "$charmap" == "UTF-8" ]]; then - BPT_VERT="│ " - BPT_TEE="├── " - BPT_LAST="└── " + BPT_VERT="│ "; BPT_TEE="├── "; BPT_LAST="└── " else - BPT_VERT="| " - BPT_TEE="|-- " - BPT_LAST="\`-- " + BPT_VERT="| "; BPT_TEE="|-- "; BPT_LAST="\`-- " fi } bash_pstree_find_name() { - local target="$1" - local i + local target="$1" i for ((i = 0; i < ${#PIDS[@]}; i++)); do if [[ "${PIDS[$i]}" == "$target" ]]; then printf '%s' "${NAMES[$i]}" @@ -78,13 +126,12 @@ bash_pstree_find_name() } # -# Print numerically-sorted child PIDs of $1, one per line. +# Prints numerically-sorted children of $1, one per line. +# Called once per node during the iterative walk (not recursively). # bash_pstree_children() { - local parent="$1" - local i list - list="" + local parent="$1" i list="" for ((i = 0; i < ${#PIDS[@]}; i++)); do if [[ "${PPIDS[$i]}" == "$parent" ]]; then list="$list${PIDS[$i]} @@ -94,56 +141,78 @@ bash_pstree_children() [[ -n "$list" ]] && printf '%s' "$list" | sort -n } -bash_pstree_root() -{ - local pid="$1" name - name=$(bash_pstree_find_name "$pid") || return - printf '%s(%s)\n' "$name" "$pid" - bash_pstree_show_children "$pid" "" -} - # -# $1 = parent pid, $2 = prefix accumulated so far +# Iterative preorder walk using an explicit stack (three parallel +# arrays) instead of function recursion. # -bash_pstree_show_children() +bash_pstree_walk() { - local parent="$1" prefix="$2" - local child name last_line - - local children - children=$(bash_pstree_children "$parent") - [[ -n "$children" ]] || return - - last_line=$(printf '%s\n' "$children" | tail -n 1) - - while IFS= read -r child; do - [[ -n "$child" ]] || continue - name=$(bash_pstree_find_name "$child") || continue - if [[ "$child" == "$last_line" ]]; then - printf '%s%s%s(%s)\n' "$prefix" "$BPT_LAST" "$name" "$child" - bash_pstree_show_children "$child" "${prefix} " - else - printf '%s%s%s(%s)\n' "$prefix" "$BPT_TEE" "$name" "$child" - bash_pstree_show_children "$child" "${prefix}${BPT_VERT}" + local root_pid="$1" + local -a S_PID S_PRINT_PREFIX S_CHILD_PREFIX + local top pid print_prefix child_prefix name + local children child last_child new_prefix branch cont + local -a rev_children + local i n visited_guard=0 + local max_iterations=200000 # safety valve against any /proc PPid cycle + + S_PID=("$root_pid") + S_PRINT_PREFIX=("") + S_CHILD_PREFIX=("") + + while [[ ${#S_PID[@]} -gt 0 ]]; do + visited_guard=$((visited_guard + 1)) + if [[ $visited_guard -gt $max_iterations ]]; then + printf 'bash_pstree: aborting, too many nodes (possible /proc cycle)\n' >&2 + return 1 fi - done <<< "$children" -} -# -# Wrapper: -# -# pstree_compat -# pstree_compat 1234 -# -pstree_compat() -{ - local root_pid="${1:-1}" - if command -v pstree >/dev/null 2>&1; then - pstree -p "$root_pid" - else - bash_pstree "$root_pid" - fi + top=$((${#S_PID[@]} - 1)) + pid="${S_PID[$top]}" + print_prefix="${S_PRINT_PREFIX[$top]}" + child_prefix="${S_CHILD_PREFIX[$top]}" + + # pop + unset 'S_PID[top]' 'S_PRINT_PREFIX[top]' 'S_CHILD_PREFIX[top]' + S_PID=("${S_PID[@]}") + S_PRINT_PREFIX=("${S_PRINT_PREFIX[@]}") + S_CHILD_PREFIX=("${S_CHILD_PREFIX[@]}") + + name=$(bash_pstree_find_name "$pid") || continue + printf '%s%s(%s)\n' "$print_prefix" "$name" "$pid" + + children=$(bash_pstree_children "$pid") + [[ -n "$children" ]] || continue + + last_child=$(printf '%s\n' "$children" | tail -n 1) + + rev_children=() + n=0 + while IFS= read -r child; do + [[ -n "$child" ]] || continue + rev_children[$n]="$child" + n=$((n + 1)) + done <<< "$children" + + for ((i = n - 1; i >= 0; i--)); do + child="${rev_children[$i]}" + if [[ "$child" == "$last_child" ]]; then + branch="$BPT_LAST" + cont="${child_prefix} " + else + branch="$BPT_TEE" + cont="${child_prefix}${BPT_VERT}" + fi + new_prefix="${child_prefix}${branch}" + + S_PID+=("$child") + S_PRINT_PREFIX+=("$new_prefix") + S_CHILD_PREFIX+=("$cont") + done + done } -alias pstree='pstree_compat' +# NOTE: intentionally no code runs here. Sourcing this file must be a no-op +# other than defining the functions above. + +alias pstree="pstree_compat"