X-Git-Url: https://iankelling.org/git/?a=blobdiff_plain;f=fai%2Fconfig%2Ffiles%2Fboot%2Fbash-trace%2FDEFAULT;h=dc1a218786f9b2109c6b9acc2de849b8e3a7ff8a;hb=1b08e82a978c8db76e1ebc67f3b4ae875ba27537;hp=61f8ae52edb9958c799001e43a1f91b8f8994f74;hpb=11a2db1a576e78f58af7f1e7e4c83422635b630d;p=automated-distro-installer diff --git a/fai/config/files/boot/bash-trace/DEFAULT b/fai/config/files/boot/bash-trace/DEFAULT index 61f8ae5..dc1a218 100644 --- a/fai/config/files/boot/bash-trace/DEFAULT +++ b/fai/config/files/boot/bash-trace/DEFAULT @@ -1,48 +1,220 @@ -# meant to be sourced. copy/pasted from https://iankelling.org/git/?p=errhandle;a=summary - -bash-trace() { - local -i argc_index=0 arg frame i start=${1:-1} max_indent=8 indent - local source - local extdebug=false - if [[ $(shopt -p extdebug) == *-s* ]]; then - extdebug=true +#!/bin/bash +# Copyright (C) 2019 Ian Kelling +# SPDX-License-Identifier: GPL-3.0-or-later + +# Commentary: Print stack trace and exit/return on errors, or use +# functions below for for more details and manual error handling. See +# end of file for credits etc. + +####################################### +# err-catch: Setup trap on ERR to print stack trace and exit (or return +# if the shell is interactive). This is the most common use case so we +# run it after defining it, you can call err-allow to undo that. +# +# This also sets pipefail because it's a good practice to catch more +# errors. +# +# Note: In interactive shell, stack calling line number is not +# available, so we print function definition lines. +# +# Globals +# +# err_catch_ignore Array containing glob patterns to test against +# filenames to ignore errors from in interactive +# shell. Initialized to ignore bash-completion +# scripts on debian based systems. +# +# err-cleanup If set, this command will run just before exiting. +# +# _err_func_last Used internally in err-bash-trace-interactive +# +####################################### +err-catch() { + set -E; + if [[ $- == *i* ]]; then + if ! test ${err_catch_ignore+defined}; then + err_catch_ignore=( + '/etc/bash_completion.d/*' + '*/bash-completion/*' + ) fi + declare -i _err_func_last=0 + shopt -s extdebug + # shellcheck disable=SC2154 + trap '_err-bash-trace-interactive $? "$BASH_COMMAND" ${BASH_ARGC[0]} "${BASH_ARGV[@]}" || return $?' ERR + else + # Man bash on exdebug: "If set at shell invocation, arrange to + # execute the debugger". We want to avoid that, but I want this file + # to be sourceable from bash startup files. noninteractive ssh and + # sources .bashrc on invocation. login_shell sources things on + # invocation. + # + # extdebug allows us to print function arguments in our stack trace. + if ! shopt login_shell >/dev/null && [[ ! $SSH_CONNECTION ]]; then + shopt -s extdebug + fi + trap err-exit ERR + fi + set -o pipefail +} +# This is the most common use case so run it now. +err-catch - for ((frame=0; frame < ${#FUNCNAME[@]}-1; frame++)); do - argc=${BASH_ARGC[frame]} - argc_index+=$argc - ((frame < start)) && continue - if (( ${#BASH_SOURCE[@]} > 1 )); then - source="${BASH_SOURCE[frame+1]}:${BASH_LINENO[frame]}:" - fi - indent=$((frame-start+1)) - indent=$((indent < max_indent ? indent : max_indent)) - printf "%${indent}s↳%sin \`%s" '' "$source" "${FUNCNAME[frame]}" - if $extdebug; then - for ((i=argc_index-1; i >= argc_index-argc; i--)); do - printf " %s" "${BASH_ARGV[i]}" - done - fi - echo \' - done +####################################### +# Undo err-catch/err-catch-interactive +####################################### +err-allow() { + shopt -u extdebug + set +E +o pipefail + trap ERR +} + +####################################### +# err-exit: Print stack trace and exit +# +# Use this instead of the exit command to be more informative. +# +# usage: err-exit [-EXIT_CODE] [MESSAGE] +# +# EXIT_CODE Default: $? if it is nonzero, otherwise 1. +# MESSAGE Print MESSAGE to stderr. Default: +# ${BASH_SOURCE[1]}:${BASH_LINENO[0]}: `$BASH_COMMAND' returned $? +# +# Globals +# +# err-cleanup If set, this command will run just before exiting. +# +####################################### +err-exit() { + local err=$? + # This has to come before most things or vars get changed + local msg="${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $err" + set +x + if [[ $1 == -* ]]; then + err=${1#-} + shift + elif (( ! err )); then + err=1 + fi + if [[ $1 ]]; then + msg="$1" + fi + printf "%s\n" "$msg" >&2 + err-bash-trace 2 + set -e # err trap does not work within an error trap + if type -t err-cleanup >/dev/null; then + err-cleanup + fi + printf "%s: exiting with status %s\n" "$0" "$err" >&2 + exit $err } +####################################### +# Print stack trace +# +# usage: err-bash-trace [FRAME_START] +# +# This function is called by the other functions which print stack +# traces. +# +# It does not show function args unless you first run: +# shopt -s extdebug +# which err-catch does for you. +# +# FRAME_START Optional variable to set before calling. The frame to +# start printing on. default=1. If ${#FUNCNAME[@]} <= +# FRAME_START + 1, don't print anything because we are at +# the top level of the script and better off printing a +# general message, for example see what our callers print. +# +####################################### +err-bash-trace() { + local -i argc_index=0 frame i frame_start=${1:-1} + local source_loc + if (( ${#FUNCNAME[@]} <= frame_start + 1 )); then + return 0 + fi + for ((frame=0; frame < ${#FUNCNAME[@]}; frame++)); do + argc=${BASH_ARGC[frame]} + argc_index+=$argc + if ((frame < frame_start)); then continue; fi + if (( ${#BASH_SOURCE[@]} > 1 )); then + source_loc="${BASH_SOURCE[frame]}:${BASH_LINENO[frame-1]}:" + fi + printf " from %sin \`%s" "$source_loc" "${FUNCNAME[frame]}" >&2 + if shopt extdebug >/dev/null; then + for ((i=argc_index-1; i >= argc_index-argc; i--)); do + printf " %s" "${BASH_ARGV[i]}" >&2 + done + fi + echo \' >&2 + done + return 0 +} + +####################################### +# Internal function for err-catch. Prints stack trace from interactive +# shell trap. +# +# Usage: see err-catch-interactive +####################################### +_err-bash-trace-interactive() { + if (( ${#FUNCNAME[@]} <= 1 )); then + return 0 + fi -errcatch() { - set -E; shopt -s extdebug - _err-trap() { - err=$? - exec >&2 - set +x - echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}:in \`$BASH_COMMAND' returned $err" - bash-trace 2 - set -e - "${_errcatch_cleanup[@]}" - echo "$0: exiting with code $err" - exit $err - } - trap _err-trap ERR - set -o pipefail + for pattern in "${err_catch_ignore[@]}"; do + # shellcheck disable=SC2053 + if [[ ${BASH_SOURCE[1]} == $pattern ]]; then + return 0 + fi + done + + local ret bash_command argc pattern i last + last=$_err_func_last + _err_func_last=${#FUNCNAME[@]} + # We have these passed to us because they are lost inside the + # function. + ret=$1 + bash_command="$2" + argc=$(( $3 - 1 )) + shift 3 + argv=("$@") + # The trap returns a nonzero, then gets called again. This condition + # tells us if we are the first. + if (( _err_func_last > last )); then + printf "ERR: \`%s\' returned %s\n" "$bash_command" $ret >&2 + fi + printf " from \`%s" "${FUNCNAME[1]}" >&2 + if shopt extdebug >/dev/null; then + for ((i=argc; i >= 0; i--)); do + printf " %s" "${argv[i]}" >&2 + done + fi + printf "\' defined at %s:%s\n" "${BASH_SOURCE[1]}" "$(declare -F "${FUNCNAME[1]}"|awk "{print \$2}")" >&2 + if [[ -t 1 ]]; then + return $ret + else + # Part of an outgoing pipe, avoid getting get us stuck in a weird + # subshell if we returned nonzero, which would happen in a situation + # like this: + # + # tf() { while read -r line; do :; done < <(asdf); }; + # tf + # + # Note: exit $ret also avoids the stuck subshell problem, and I + # can't notice any difference, but this seems more proper. + return 0 + fi } -errcatch +# Credits etc: +# +# Related: see my bash script template repo at https://iankelling.org/git. +# +# +# Please email me if you have a patches, bugs, feedback, or if you use +# it or republish it since I'm not aware of any users yet +# Ian Kelling . +# +# Tested on bash 4.4.20(1)-release (x86_64-pc-linux-gnu). If you test