#!/usr/bin/env bash # # Copyright (c) 2022-2024 Intel Corporation # # SPDX-License-Identifier: Apache-2.0 # # Description: Error handling functions. # Write specified message to stderr. stderr() { local msg="$*" echo >&2 "$msg" } # Simplified version of die that should be called by functions that # could fail as part of the normal "die()" code path. Required to # avoid infinite recursion. _fatal() { local msg="$*" echo >&2 "FATAL: $msg" exit 1 } # Canonicalize the specified path (which must be valid). resolve_path() { local file="${1:-}" [ -z "$file" ] && _fatal "need file to resolve" local path path=$(readlink --canonicalize-existing "$file" || \ _fatal "failed to resolve file '$file'") echo "$path" } show_proc_hierarchy() { local pid="${$}" local details stderr "process-hierarchy:" local -i i for ((i=0; ; i++)) do local details local msg local current='' [ "${pid}" = "${$}" ] && current=", current='yes'" details=$(ps --no-headers -p "$pid" -o ppid,cmd) # The parent PID is always the first column due to the # format specifier used above. local ppid=$(echo "$details" | awk '{print $1}') # But the command part has a variable number of fields # (since it could contain any number of spaces). # Hence, delete the first field (PPID) and what # remains is the entire command line. local cmd=$(echo "$details" |\ awk '{$1=""; print $0}' |\ sed \ -e 's/^ *//g' \ -e 's/ *$//g') msg=$(printf " %d: {pid: %d, command: '%s'%s}" \ "${i}" \ "${pid}" \ "${cmd}" \ "${current}") stderr "$msg" [ "$pid" = 1 ] && break pid="$ppid" done } show_stacktrace() { local err_line="${1:-}" local err_func="${2:-}" local err_path="${3:-}" [ -z "$err_line" ] && _fatal "need error location line number" [ -z "$err_func" ] && _fatal "need error location func" [ -z "$err_path" ] && _fatal "need error location file path" local line local func local file local -i i stderr "stacktrace:" for ((i = 0; ; i++)) do local result result=$(caller "$i" || true) [ -z "$result" ] && break line=$(echo "$result"|awk '{print $1}') func=$(echo "$result"|awk '{print $2}') file=$(echo "$result"|awk '{print $3}') local path path=$(resolve_path "$file") local msg local current='' # Add a visual marker showing where the original error was # detected. [ "${line}" = "${err_line}" ] && \ [ "${func}" = "${err_func}" ] && \ [ "${path}" = "${err_path}" ] && \ current=", current='yes'" msg=$(printf " %d: {function: '%s', file: '%s', line: %d%s}\n" \ "${i}" \ "${func}" \ "${path}" \ "${line}" \ "${current}" ) stderr "$msg" done } # Function to be called by die() or a trap/signal handler to dump all # details of the environment (in YAML format), to help with debugging. dump_details() { set +x local err_line="${1:-}" local err_func="${2:-}" local err_path="${3:-}" [ -z "$err_line" ] && _fatal "need error location line number" [ -z "$err_func" ] && _fatal "need error location func" [ -z "$err_path" ] && _fatal "need error location file path" # Spacer stderr stderr "script:" stderr " name: '$0'" stderr " pid: $$" stderr " directory: '$PWD'" stderr " details: '$(ls -dlZ "$PWD")'" stderr "failure:" stderr " function: '$func'" stderr " file: '$path'" stderr " line: $line" stderr " name: '$0'" show_stacktrace \ "${err_line}" \ "${err_func}" \ "${err_path}" show_proc_hierarchy stderr "time: '$(date -Isec)'" stderr "runtime-seconds: ${SECONDS}" stderr "host:" stderr " name: '$(hostname)'" stderr " uname: '$(uname -a)'" stderr "locale:" locale 2>/dev/null | sed \ -e 's/^/ /g' \ -e 's/=/: '\''/g' \ -e 's/$/'\''/g' \ >&2 stderr "user:" stderr " uid: {value: $UID, name: '$(getent passwd "$UID"|cut -d: -f1)'}" stderr " euid: {value: $EUID , name: '$(getent passwd "$EUID"|cut -d: -f1)'}" stderr " groups: '$(id)'" stderr "bash:" stderr " version: '${BASH_VERSION}'" stderr " version-info: '${BASH_VERSINFO[*]}'" stderr "environment:" # Remove bash functions (that can span multiple lines) env |\ grep -v "^BASH_FUNC" |\ grep -Ei "^[a-z_][a-z0-9_]+=" |\ sort -t '=' -k1 |\ sed \ -e 's/^/ /g' \ -e 's/=/: '\''/' \ -e 's/$/'\''/g' \ >&2 stderr "mounts: |" mount | sed 's/^/ /g' >&2 stderr "processes: |" ps -eF | sed 's/^/ /g' >&2 # Spacer stderr }