213 lines
8.6 KiB
Bash
213 lines
8.6 KiB
Bash
|
#!/usr/bin/env bash
|
||
|
#
|
||
|
# SPDX-License-Identifier: GPL-2.0
|
||
|
#
|
||
|
# Copyright (c) 2013-2023 Igor Pecovnik, igor@armbian.com
|
||
|
#
|
||
|
# This file is a part of the Armbian Build Framework
|
||
|
# https://github.com/armbian/build/
|
||
|
|
||
|
# Initialize and prepare the trap managers, one for each of ERR, INT, TERM and EXIT traps.
|
||
|
# Bash goes insane regarding line numbers and other stuff if we try to overwrite the traps.
|
||
|
# This also implements the custom "cleanup" handlers, which always run at the end of build, or when exiting prematurely for any reason.
|
||
|
function traps_init() {
|
||
|
# shellcheck disable=SC2034 # Array of cleanup handlers.
|
||
|
declare -g -a trap_manager_cleanup_handlers=()
|
||
|
# shellcheck disable=SC2034 # Global to avoid doubly reporting ERR/EXIT pairs.
|
||
|
declare -g -i trap_manager_error_handled=0
|
||
|
trap 'main_trap_handler "ERR" "$?"' ERR
|
||
|
trap 'main_trap_handler "EXIT" "$?"' EXIT
|
||
|
trap 'main_trap_handler "INT" "$?"' INT
|
||
|
trap 'main_trap_handler "TERM" "$?"' TERM
|
||
|
}
|
||
|
|
||
|
# This is setup early in compile.sh as a trap handler for ERR, EXIT and INT signals.
|
||
|
# There are arrays trap_manager_error_handlers=() trap_manager_exit_handlers=() trap_manager_int_handlers=()
|
||
|
# that will receive the actual handlers.
|
||
|
# First param is the type of trap, the second is the value of "$?"
|
||
|
# In order of occurrence.
|
||
|
# 1) Ctrl-C causes INT [stack unreliable], then ERR, then EXIT with trap_exit_code > 0
|
||
|
# 2) Stuff failing causes ERR [stack OK], then EXIT with trap_exit_code > 0
|
||
|
# 3) exit_with_error causes EXIT [stack OK, with extra frame] directly with trap_exit_code == 43
|
||
|
# 4) EXIT can also be called directly [stack unreliable], with trap_exit_code == 0 if build successful.
|
||
|
# So the EXIT trap will do:
|
||
|
# - show stack, if not previously shown (trap_manager_error_handled==0), and if trap_exit_code > 0
|
||
|
# - allow for debug shell, if trap_exit_code > 0
|
||
|
# - call all the cleanup functions (always)
|
||
|
function main_trap_handler() {
|
||
|
local trap_type="${1}"
|
||
|
local trap_exit_code="${2}"
|
||
|
local stack_caller short_stack
|
||
|
stack_caller="$(show_caller_full)"
|
||
|
short_stack="${BASH_SOURCE[1]}:${BASH_LINENO[0]}"
|
||
|
|
||
|
display_alert "main_trap_handler" "${trap_type} and ${trap_exit_code} trap_manager_error_handled:${trap_manager_error_handled} short_stack:${short_stack}" "trap"
|
||
|
|
||
|
case "${trap_type}" in
|
||
|
TERM | INT)
|
||
|
display_alert "Build interrupted" "Build interrupted by SIG${trap_type}" "warn"
|
||
|
trap_manager_error_handled=1
|
||
|
return # Nothing else to do here. Let the ERR trap show the stack, and the EXIT trap do cleanups.
|
||
|
;;
|
||
|
|
||
|
ERR)
|
||
|
# If error occurs in subshell (eg: inside $()), we would show the error twice.
|
||
|
# Determine if we're in a subshell, and if so, output a single message.
|
||
|
# BASHPID is the current subshell; $$ is parent shell pid
|
||
|
if [[ "${BASHPID}" == "${$}" ]]; then
|
||
|
# Not in subshell, dump the error, complete with log, and show the stack.
|
||
|
if [[ ! ${trap_manager_error_handled} -gt 0 ]]; then
|
||
|
logging_error_show_log
|
||
|
display_alert "Error ${trap_exit_code} occurred in main shell" "at ${short_stack}\n${stack_caller}\n" "err"
|
||
|
fi
|
||
|
else
|
||
|
# In a subshell. This trap will run again in the parent shell, so just output a message about it;
|
||
|
# When the parent shell trap runs, it will show the stack and log.
|
||
|
display_alert "Error ${trap_exit_code} occurred in SUBSHELL" "SUBSHELL at ${short_stack}" "err"
|
||
|
fi
|
||
|
trap_manager_error_handled=1
|
||
|
return # Nothing else to do here, let the EXIT trap do the cleanups.
|
||
|
;;
|
||
|
|
||
|
EXIT)
|
||
|
if [[ ${trap_manager_error_handled} -lt 1 ]] && [[ ${trap_exit_code} -gt 0 ]]; then
|
||
|
logging_error_show_log
|
||
|
display_alert "Exiting with error ${trap_exit_code}" "at ${short_stack}\n${stack_caller}\n" "err"
|
||
|
trap_manager_error_handled=1
|
||
|
fi
|
||
|
|
||
|
if [[ ${trap_exit_code} -gt 0 ]] && [[ "${ERROR_DEBUG_SHELL}" == "yes" ]]; then
|
||
|
declare -g ERROR_DEBUG_SHELL=no # dont do it twice
|
||
|
display_alert "MOUNT" "${MOUNT}" "debug"
|
||
|
display_alert "SDCARD" "${SDCARD}" "debug"
|
||
|
display_alert "ERROR_DEBUG_SHELL=yes, starting a shell." "ERROR_DEBUG_SHELL; exit to cleanup." "debug"
|
||
|
bash < /dev/tty >&2 || true
|
||
|
fi
|
||
|
|
||
|
# Run the cleanup handlers, always. pass it the exit code so it keep the red theme of errors in its messages.
|
||
|
cleanup_exit_code="${trap_exit_code}" run_cleanup_handlers || true
|
||
|
|
||
|
# If global_final_exit_code is set, use it as the exit code. (used by docker CLI handler)
|
||
|
if [[ -n "${global_final_exit_code}" ]]; then
|
||
|
display_alert "Final exit code" "Final exit code ${global_final_exit_code}" "debug"
|
||
|
# disable the trap, so we don't get called again.
|
||
|
trap - EXIT
|
||
|
exit "${global_final_exit_code}"
|
||
|
fi
|
||
|
;;
|
||
|
*)
|
||
|
display_alert "main_trap_handler" "Unknown trap type '${trap_type}'" "err"
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
# Run the cleanup handlers, if any, and clean the cleanup list.
|
||
|
function run_cleanup_handlers() {
|
||
|
display_alert "run_cleanup_handlers! list:" "${trap_manager_cleanup_handlers[*]}" "cleanup"
|
||
|
if [[ ${#trap_manager_cleanup_handlers[@]} -lt 1 ]]; then
|
||
|
return 0 # No handlers set, just return.
|
||
|
else
|
||
|
if [[ ${cleanup_exit_code:-0} -gt 0 ]]; then
|
||
|
# No use polluting GHA/CI with notices about cleanup, if we're already failing. skip_ci_special="yes"
|
||
|
skip_ci_special="yes" display_alert "Cleaning up" "please wait for cleanups to finish" "error"
|
||
|
else
|
||
|
display_alert "Cleaning up" "please wait for cleanups to finish" "info"
|
||
|
fi
|
||
|
fi
|
||
|
# Loop over the handlers, execute one by one. Ignore errors.
|
||
|
# IMPORTANT: cleanups are added first to the list, so cleanups run in the reverse order they were added.
|
||
|
local one_cleanup_handler
|
||
|
for one_cleanup_handler in "${trap_manager_cleanup_handlers[@]}"; do
|
||
|
run_one_cleanup_handler "${one_cleanup_handler}"
|
||
|
done
|
||
|
# Clear the cleanup handler list, so they don't accidentally run again.
|
||
|
trap_manager_cleanup_handlers=()
|
||
|
}
|
||
|
|
||
|
# Adds a callback for trap types; first and only argument is eval code to call during cleanup. If such, that needs proper quoting (@Q)
|
||
|
function add_cleanup_handler() {
|
||
|
if [[ $# -gt 1 ]]; then
|
||
|
exit_with_error "add_cleanup_handler: too many params"
|
||
|
fi
|
||
|
local callback="$1" # simple function name or @Q quoted eval code
|
||
|
# validate
|
||
|
if [[ -z "${callback}" ]]; then
|
||
|
exit_with_error "add_cleanup_handler: no callback specified"
|
||
|
fi
|
||
|
|
||
|
display_alert "Add callback as cleanup handler" "${callback}" "cleanup"
|
||
|
# IMPORTANT: cleanups are added first to the list, so they're executed in reverse order.
|
||
|
trap_manager_cleanup_handlers=("${callback}" "${trap_manager_cleanup_handlers[@]}")
|
||
|
}
|
||
|
|
||
|
function execute_and_remove_cleanup_handler() {
|
||
|
local callback="$1"
|
||
|
display_alert "Execute and remove cleanup handler" "${callback}" "cleanup"
|
||
|
local remaining_cleanups=()
|
||
|
for one_cleanup_handler in "${trap_manager_cleanup_handlers[@]}"; do
|
||
|
if [[ "${one_cleanup_handler}" != "${callback}" ]]; then
|
||
|
remaining_cleanups+=("${one_cleanup_handler}")
|
||
|
else
|
||
|
run_one_cleanup_handler "${one_cleanup_handler}"
|
||
|
fi
|
||
|
done
|
||
|
trap_manager_cleanup_handlers=("${remaining_cleanups[@]}")
|
||
|
}
|
||
|
|
||
|
function run_one_cleanup_handler() {
|
||
|
declare one_cleanup_handler="$1"
|
||
|
display_alert "Running cleanup handler" "${one_cleanup_handler}" "cleanup"
|
||
|
|
||
|
eval "${one_cleanup_handler}" || {
|
||
|
display_alert "Cleanup handler failed, this is a severe bug in the build system or extensions" "${one_cleanup_handler}" "err"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function remove_all_trap_handlers() {
|
||
|
# @TODO find usages and kill
|
||
|
display_alert "calling obsolete method remove_all_trap_handlers()" "not doing anything" "warning"
|
||
|
}
|
||
|
|
||
|
# exit_with_error <message> <highlight>
|
||
|
# a way to terminate build process with verbose error message
|
||
|
function exit_with_error() {
|
||
|
# Log the error and exit.
|
||
|
# Everything else will be done by shared trap handling, above.
|
||
|
local _file="${BASH_SOURCE[1]}"
|
||
|
local _function=${FUNCNAME[1]}
|
||
|
local _line="${BASH_LINENO[0]}"
|
||
|
|
||
|
display_alert "error!" "${1} ${2}" "err"
|
||
|
|
||
|
#display_alert "Build terminating..." "please wait for cleanups to finish" "err"
|
||
|
|
||
|
# @TODO: move this into trap handler
|
||
|
# @TODO: integrate both overlayfs and the FD locking with cleanup logic
|
||
|
overlayfs_wrapper "cleanup"
|
||
|
|
||
|
## This does not really make sense. wtf?
|
||
|
## unlock loop device access in case of starvation # @TODO: hmm, say that again?
|
||
|
#exec {FD}> /var/lock/armbian-debootstrap-losetup
|
||
|
#flock -u "${FD}"
|
||
|
|
||
|
# do NOT close the fd 13 here, otherwise the error will not be logged to logfile...
|
||
|
|
||
|
exit 43
|
||
|
}
|
||
|
|
||
|
# terminate build with a specific error code (44), meaning "target not supported".
|
||
|
# this is exactly like exit_with_error, but will be handled differently by the build pipeline.
|
||
|
function exit_with_target_not_supported_error() {
|
||
|
# Log the error and exit.
|
||
|
# Everything else will be done by shared trap handling, above.
|
||
|
local _file="${BASH_SOURCE[1]}"
|
||
|
local _function=${FUNCNAME[1]}
|
||
|
local _line="${BASH_LINENO[0]}"
|
||
|
|
||
|
display_alert "target not supported! " "${1} ${2}" "err"
|
||
|
|
||
|
overlayfs_wrapper "cleanup"
|
||
|
|
||
|
exit 44
|
||
|
}
|