#!/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)

		local ppid
		ppid=$(echo "${details}" | awk '{print $1}')

		local cmd
		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
}
