#!/bin/bash -efu
# SPDX-License-Identifier: GPL-3.0-or-later

if [ -z "${__included_make_initrd_sh_functions-}" ]; then
__included_make_initrd_sh_functions=1

. shell-error
. wrapper-functions

message()  { msg-timestamp "$PROG: $*" >&2; }
verbose1() { [ -z "${verbose1-}" ] || message "$@"; }
verbose2() { [ -z "${verbose2-}" ] || message "$@"; }
verbose3() { [ -z "${verbose3-}" ] || message "$@"; }

if [ -n "${KERNEL-}" ]; then
	kernel_major="${KERNEL%%.*}"

	kernel_minor="${KERNEL#*.}"
	kernel_minor="${kernel_minor%%.*}"

	kernel_patch="${KERNEL#*.*.}"
	kernel_patch="${kernel_patch%%[!0-9]*}"

	export kernel_major kernel_minor kernel_patch

	if [ -z "${kernel_major##*[!0-9]*}" ] ||
	   [ -z "${kernel_minor##*[!0-9]*}" ] ||
	   [ -z "${kernel_patch##*[!0-9]*}" ]
	then
		fatal "Invalid kernel version \"$KERNEL\""
	fi
fi

if [ -n "${KERNEL_MODULES_DIR-}" ]; then
	[ -d "$KERNEL_MODULES_DIR" ] ||
		fatal "Directory \"$KERNEL_MODULES_DIR\" doesn't exist or not accessible."
fi

get_majmin() {
	local v devnode

	if [ -n "${MAKE_INITRD_BUG_REPORT-}" ]; then
		while read -r perm maj min dev; do
			case "$perm" in
				[bc]*)
					[ "$1" = "$dev" ] ||
						continue
					printf '%d:%d\n' "0x$maj" "0x$min"
					return 0
					;;
			esac
		done < "${DEVLIST_FILE:-/dev/devlist}"
		return 1
	fi

	devnode="$(readlink -ev "$1" 2>/dev/null)" ||
		return 1

	v="$(stat -c '%02t:%02T' "$devnode")" &&
		printf '%d:%d\n' "0x${v%:*}" "0x${v#*:}" ||
		return 1
}

readline() {
	local __v=
	# shellcheck disable=SC2162
	read __v < "$2" ||:
	eval "$1=\"\$__v\""
}

in_list() {
	local value elememt

	value="$1"
	shift

	for element in "$@"; do
		[ "$element" != "$value" ] || return 0
	done
	return 1
}

TRACE_ENTER=:
TRACE_ENTER_RETCODE=:
TRACE_SOURCE=:
TRACE_RESOLVE_MODALIAS=:
TRACE_RUN=

if [ -n "${MAKE_INITRD_TRACE:-${MAKE_INITRD_TRACE_TIMESTAMP-}}" ]; then
	MAKE_INITRD_TRACE_PIDS=${MAKE_INITRD_TRACE_PIDS-1}

	TRACE_KIND_ENTER=0
	TRACE_KIND_RUN=1
	TRACE_KIND_SOURCE=2
	TRACE_KIND_RESOLVE_MODALIAS=3
	TRACE_KIND_FILTERED=4
	TRACE_KIND_RESULT=5

	TRACE_KINDS=()
	TRACE_KINDS_LEN=0

	TRACE_KINDS[$TRACE_KIND_ENTER]="function"
	TRACE_KINDS[$TRACE_KIND_RUN]="run"
	TRACE_KINDS[$TRACE_KIND_SOURCE]="execute"
	TRACE_KINDS[$TRACE_KIND_RESOLVE_MODALIAS]="modules"
	TRACE_KINDS[$TRACE_KIND_FILTERED]="filtered"
	TRACE_KINDS[$TRACE_KIND_RESULT]="result"

	for i in "${TRACE_KINDS[@]}"; do
		[ $TRACE_KINDS_LEN -ge ${#i} ] ||
			TRACE_KINDS_LEN=${#i}
	done

	TRACE_ENTER=trace_enter
	TRACE_ENTER_RETCODE=trace_enter_retcode
	TRACE_SOURCE=trace_source
	TRACE_RESOLVE_MODALIAS=trace_resolve_modalias
	TRACE_RUN=trace_run

	TRACE_FUNCNAMES=("main")

	__trace_get_func() {
		local i

		func="${FUNCNAME[2]}"
		i=$(( ${#TRACE_FUNCNAMES[@]} - 1 ))

		[ "${TRACE_FUNCNAMES[$i]}" != main ] || [ "$func" != main ] ||
			func=subshell
	}

	__is_traceable() {
		local arg pattern
		arg="$1"; shift
		[ -n "$arg" ] || return 0
		for pattern; do [ -n "${arg##$pattern}" ] || return 0; done
		[ $# -eq 0 ] || return 1
	}

	__trace_source_enabled() {
		local src="$1"

		[ -z "${MAKE_INITRD_TRACE_ACTIVE-}" ] ||
			return 0

		__is_traceable "$src" ${MAKE_INITRD_TRACE_EXECUTE_PATTERNS-}
	}

	__trace_normalize_source() {
		local src="$1"

		src="${src#${PROJECTDIR}/}"
		src="${src#${TOPDIR}/}"

		printf '%s\n' "$src"
	}

	__trace_get_source() {
		local i src policy trace_lib

		policy="${MAKE_INITRD_TRACE_SOURCE_POLICY:-top}"
		trace_lib="${BASH_SOURCE[0]}"

		case "$policy" in
			top)
				for (( i=${#BASH_SOURCE[@]} - 1; i >= 0; i-- )); do
					src="${BASH_SOURCE[$i]}"
					[ -n "$src" ] || continue
					[ "$src" != "$trace_lib" ] || continue
					__trace_normalize_source "$src"
					return 0
				done
				;;
			current)
				for (( i=1; i < ${#BASH_SOURCE[@]}; i++ )); do
					src="${BASH_SOURCE[$i]}"
					[ -n "$src" ] || continue
					[ "$src" != "$trace_lib" ] || continue
					__trace_normalize_source "$src"
					return 0
				done
				;;
			*)
				fatal "Unsupported trace source policy: $policy"
				;;
		esac

		__trace_normalize_source "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
	}

	__trace_debug_filtered() {
		local reason depth src funcs entity format payload text_payload

		[ -z "${MAKE_INITRD_TRACE_DEBUG_FILTERS-}" ] &&
			return 0

		reason="$1"; shift
		depth="$1"; shift
		funcs="$1"; shift
		entity="$1"; shift
		format="${MAKE_INITRD_TRACE_FORMAT:-text}"
		payload="$*"
		src="$(__trace_get_source)"

		[ -z "${MAKE_INITRD_TRACE_TIMESTAMP-}" ] ||
			msg-timestamp -n >&2

		case "$format" in
			tsv)
				printf >&2 \
					'TRACE\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
					"${TRACE_KINDS[$TRACE_KIND_FILTERED]}" \
					"${MAKE_INITRD_TRACE_PIDS:+$BASHPID}" \
					"$depth" \
					"$src" \
					"$funcs" \
					"$entity" \
					"$reason${payload:+: $payload}"
				;;
			text|'')
				text_payload="$reason"
				[ -z "$entity" ] || text_payload="$text_payload: $entity"
				[ -z "$payload" ] || text_payload="$text_payload $payload"
				printf >&2 \
					'TRACE %-'${TRACE_KINDS_LEN}'s:%s source=%s: %s: %s\n' \
					"${TRACE_KINDS[$TRACE_KIND_FILTERED]}" \
					"${MAKE_INITRD_TRACE_PIDS:+ pid=$BASHPID:}" \
					"$src" "$funcs" "$text_payload"
				;;
			*)
				fatal "Unsupported trace format: $format"
				;;
		esac
	}

	__trace_print_result() {
		local rc depth funcs entity details

		[ -n "${MAKE_INITRD_TRACE_RESULTS-}" ] ||
			return 0

		rc="$1"; shift
		depth="$1"; shift
		funcs="$1"; shift
		entity="$1"; shift
		details="$*"
		details="rc=$rc${details:+: $details}"

		__trace_print $TRACE_KIND_RESULT \
			"$depth" "$funcs" "$entity" "$details"
	}

	__trace_print() {
		local kind depth src funcs entity format payload text_payload

		kind="$1"; shift
		depth="$1"; shift
		funcs="$1"; shift
		entity="$1"; shift
		format="${MAKE_INITRD_TRACE_FORMAT:-text}"
		payload="$*"
		src="$(__trace_get_source)"

		local GLOBIGNORE='.*:*'

		if ! __trace_source_enabled "$src"; then
			__trace_debug_filtered "execute-patterns" \
				"$depth" "$funcs" "$entity" "$payload"
			return 0
		fi

		if ! __is_traceable "$src" ${MAKE_INITRD_TRACE_SRC_PATTERNS-}; then
			__trace_debug_filtered "source-patterns" \
				"$depth" "$funcs" "$entity" "$payload"
			return 0
		fi

		[ -z "${MAKE_INITRD_TRACE_TIMESTAMP-}" ] ||
			msg-timestamp -n >&2

		case "$format" in
			tsv)
				printf >&2 \
					'TRACE\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
					"${TRACE_KINDS[$kind]}" \
					"${MAKE_INITRD_TRACE_PIDS:+$BASHPID}" \
					"$depth" \
					"$src" \
					"$funcs" \
					"$entity" \
					"$payload"
				;;
			text|'')
				text_payload="$payload"
				case "$kind" in
					"$TRACE_KIND_RUN")
						text_payload="$entity${payload:+ $payload}"
						;;
					"$TRACE_KIND_RESOLVE_MODALIAS")
						text_payload="$entity -> $payload"
						;;
					"$TRACE_KIND_RESULT")
						text_payload="$entity${payload:+: $payload}"
						;;
				esac
				printf >&2 \
					'TRACE %-'${TRACE_KINDS_LEN}'s:%s source=%s: %s: %s\n' \
					"${TRACE_KINDS[$kind]}" "${MAKE_INITRD_TRACE_PIDS:+ pid=$BASHPID:}" "$src" "$funcs" "$text_payload"
				;;
			*)
				fatal "Unsupported trace format: $format"
				;;
		esac
	}

	trace_source() {
		local src

		src="$(__trace_get_source)"

		if ! __trace_source_enabled "$src"; then
			__trace_debug_filtered "execute-patterns" \
				0 "main" "" "$*"
			return 0
		fi

		__trace_print $TRACE_KIND_SOURCE \
			0 "main" "" "$*"
		export MAKE_INITRD_TRACE_ACTIVE=1
	}

	trace_resolve_modalias() {
		local modalias_list kmodules_list kmodules m

		modalias_list="$1"; shift
		kmodules_list="$1"; shift

		kmodules=()
		while read -r m; do
			kmodules+=("${m##/lib/modules/$KERNEL/}")
		done < "$kmodules_list"

		__trace_print $TRACE_KIND_RESOLVE_MODALIAS \
			0 "main" "${modalias_list##*/}" "(${kmodules[*]})"
	}

	trace_enter() {
		local func
		__trace_get_func

		local GLOBIGNORE='.*:*'

		if ! __is_traceable "$func" ${MAKE_INITRD_TRACE_FUNC_PATTERNS-}; then
			__trace_debug_filtered "function-patterns" \
				"${#TRACE_FUNCNAMES[@]}" "${TRACE_FUNCNAMES[*]} $func" "$func" "$*"
			return 0
		fi

		__trace_print $TRACE_KIND_ENTER \
			"${#TRACE_FUNCNAMES[@]}" "${TRACE_FUNCNAMES[*]} $func" "$func" "$*"
	}

	trace_enter_retcode() {
		local func rc
		rc="$1"
		__trace_get_func
		__trace_print_result \
			"$rc" "${#TRACE_FUNCNAMES[@]}" "${TRACE_FUNCNAMES[*]} $func" "$func"
		return "$rc"
	}

	trace_run() {
		local rc i func cmd cmdname
		__trace_get_func

		i=${#TRACE_FUNCNAMES[@]}
		TRACE_FUNCNAMES+=("$func")

		cmd="$1"
		shift

		[ -z "${cmd##/*}" ] &&
			cmdname="${cmd#$TOPDIR/}" ||
			cmdname="$cmd"

		local prev_GLOBIGNORE="${GLOBIGNORE-}"
		local GLOBIGNORE='.*:*'

		if __is_traceable "$cmdname" ${MAKE_INITRD_TRACE_RUN_PATTERNS-}; then
			GLOBIGNORE="$prev_GLOBIGNORE"
			__trace_print $TRACE_KIND_RUN \
				"$(( ${#TRACE_FUNCNAMES[@]} - 1 ))" \
				"${TRACE_FUNCNAMES[*]}" "$cmdname" "$*"
		elif [ -n "${MAKE_INITRD_TRACE_DEBUG_FILTERS-}" ]; then
			GLOBIGNORE="$prev_GLOBIGNORE"
			__trace_debug_filtered "run-patterns" \
				"$(( ${#TRACE_FUNCNAMES[@]} - 1 ))" \
				"${TRACE_FUNCNAMES[*]}" "$cmdname" "$*"
		fi

		GLOBIGNORE="$prev_GLOBIGNORE"

		rc=0
		"$cmd" "$@" || rc=$?
		__trace_print_result \
			"$rc" "$(( ${#TRACE_FUNCNAMES[@]} - 1 ))" \
			"${TRACE_FUNCNAMES[*]}" "$cmdname"

		unset "TRACE_FUNCNAMES[$i]"
		return $rc
	}
fi

fi #__included_make_initrd_sh_functions
