# --- enable/disable this file --- SRC_ENABLED=true case "$SRC_ENABLED" in 1|true|yes|on) unset SRC_ENABLED ;; # enabled — clean up and continue *) unset SRC_ENABLED; return ;; # disabled — clean up and skip this file 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 PIDS=() PPIDS=() NAMES=() idx=0 for procdir in /proc/[0-9]*; do [[ -d "$procdir" ]] || continue [[ -r "$procdir/status" ]] || continue pid="${procdir#/proc/}" name="" ppid="" while IFS=: read -r key value; do value="${value#"${value%%[![:space:]]*}"}" case "$key" in Name) name="$value" ;; PPid) ppid="$value" ;; esac done < "$procdir/status" [[ -n "$ppid" ]] || continue PIDS[$idx]="$pid" PPIDS[$idx]="$ppid" NAMES[$idx]="${name:-?}" idx=$((idx + 1)) done bash_pstree_set_chars if ! bash_pstree_find_name "$root_pid" >/dev/null; then return 1 fi bash_pstree_walk "$root_pid" } bash_pstree_set_chars() { local charmap charmap=$(locale charmap 2>/dev/null) if [[ "$charmap" == "UTF-8" ]]; then BPT_VERT="│ "; BPT_TEE="├── "; BPT_LAST="└── " else BPT_VERT="| "; BPT_TEE="|-- "; BPT_LAST="\`-- " fi } bash_pstree_find_name() { local target="$1" i for ((i = 0; i < ${#PIDS[@]}; i++)); do if [[ "${PIDS[$i]}" == "$target" ]]; then printf '%s' "${NAMES[$i]}" return 0 fi done return 1 } # # 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" i list="" for ((i = 0; i < ${#PIDS[@]}; i++)); do if [[ "${PPIDS[$i]}" == "$parent" ]]; then list="$list${PIDS[$i]} " fi done [[ -n "$list" ]] && printf '%s' "$list" | sort -n } # # Iterative preorder walk using an explicit stack (three parallel # arrays) instead of function recursion. # bash_pstree_walk() { 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 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 } # NOTE: intentionally no code runs here. Sourcing this file must be a no-op # other than defining the functions above. alias pstree="pstree_compat"