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

# shellcheck disable=SC2153
export verbose="${VERBOSE:+-v}"

export workdir="${WORKDIR:?Work directory required}"
export rootdir="${ROOTDIR:?Root directory required}"
export guessdir="${GUESSDIR:?Autodetect directory required}"
export reportdir="${REPORTDIR:?Bug report directory required}"
export imagefile="${IMAGEFILE-}"
export modules_ignore=" ${MODULES_IGNORE-} "

export compress_method="${COMPRESS-}"
export outfile="$workdir/initrd.img"
export kernel="${KERNEL:?}"
export kernel_modules_dir="${KERNEL_MODULES_DIR:?}"

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

[ -d "$kernel_modules_dir" ] ||
	fatal "Directory \"$kernel_modules_dir\" doesn't exist or not accessible."

get_majmin() {
	local v devnode

	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\""
}

list_libs() {
	local ldconfig
	ldconfig="$(type -P ldconfig)" || return 0
	$ldconfig -p | sed -n -e 's,.* => ,,p'
}

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-}" ]; 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_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"

	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_print() {
		local n kind src funcs

		kind="$1"; shift
		funcs="$1"; shift

		n="$(( ${#BASH_SOURCE[@]} - 1 ))"
		src="${BASH_SOURCE[$n]#${PROJECTDIR}/}"
		src="${BASH_SOURCE[$n]#${TOPDIR}/}"

		local GLOBIGNORE='.*:*'

		if ! __is_traceable "$src" ${MAKE_INITRD_TRACE_EXECUTE_PATTERNS-}; then
			export MAKE_INITRD_TRACE_SRC_PATTERNS='-'
			export MAKE_INITRD_TRACE_FUNC_PATTERNS='-'
			export MAKE_INITRD_TRACE_RUN_PATTERNS='-'
			return 0
		fi

		__is_traceable "$src" ${MAKE_INITRD_TRACE_SRC_PATTERNS-} ||
			return 0

		printf >&2 \
			'TRACE %-'${TRACE_KINDS_LEN}'s:%s source=%s: %s: %s\n' \
			"${TRACE_KINDS[$kind]}" "${MAKE_INITRD_TRACE_PIDS:+ pid=$BASHPID:}" "$src" "$funcs" "$*"
	}

	trace_source() {
		__trace_print $TRACE_KIND_SOURCE \
			"main" "$*"
		export MAKE_INITRD_TRACE_EXECUTE_PATTERNS=''
	}

	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 \
			"main" "${modalias_list##*/} -> (${kmodules[*]})"
	}

	trace_enter() {
		local func
		__trace_get_func

		local GLOBIGNORE='.*:*'

		__is_traceable "$func" ${MAKE_INITRD_TRACE_FUNC_PATTERNS-} ||
			return 0

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

	trace_enter_retcode() {
		return $1
	}

	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[*]}" "$cmdname" "$*"
		fi

		GLOBIGNORE="$prev_GLOBIGNORE"

		rc=0
		"$cmd" "$@" || rc=$?

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

fi #__included_make_initrd_sh_functions
