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

. shell-error
. shell-signal
. sh-functions

tempdir=
exit_handler()
{
	[ -z "$tempdir" ] || rm -rf -- "$tempdir"
}

set_cleanup_handler exit_handler

tempdir="${TEMPDIR:-/tmp}/modules"
mkdir -p -- "$tempdir"

normalize_modname()
{
	local x="$*"
	printf '%s\n' "${x//-/_}"
}

blacklist="${BLACKLIST_MODULES-}"

[ -z "$blacklist" ] ||
	blacklist="$(normalize_modname "$blacklist")"

in_blacklist()
{
	local m f="$1"
	for m in ${blacklist-}; do
		[ "$f" != "$m" ] || return 0
	done
	return 1
}

find_modules()
{
	local errfunc a

	a="$1"; shift

	case "$a" in
		required) errfunc=fatal ;;
		optional) errfunc=:     ;;
	esac

	for n; do
		if [ -n "${n##*[/\[\].*&^\$\\\\/]*}" ]; then
			printf '%s\n' "$n"
			continue
		fi

		if [ -z "${n##/*}" ] && [ -f "$n" ]; then
			printf '%s\n' "$n"
			continue
		fi

		if [ -n "$KERNEL_MODULES_DIR" ]; then
			[ -f "$tempdir/kmodules" ] ||
				find "$KERNEL_MODULES_DIR" \
					-type f \
					-a \( -name '*.ko' -o -name '*.ko.*' \) \
					> "$tempdir/kmodules"

			grep -E -e "$n" "$tempdir/kmodules" ||
				$errfunc "Unable to handle pattern: $n"

			continue
		fi

		$errfunc "Unable to find module: $n"
	done > "$tempdir/mods.$a"
}

load_modules()
{
	[ "$#" -gt 1 ] ||
		return 0

	local n m modules_file

	modules_file="/etc/initrd/modules-$1"
	shift

	mkdir -p -- "$ROOTDIR/etc/initrd"

	for n; do
		m="${n##*/}"
		m="$(normalize_modname "${m%.ko*}")"
		! in_blacklist "$m" ||
			continue
		printf '%s\n' "$n"
	done >> "$ROOTDIR/$modules_file"

	sort -uo "$ROOTDIR/$modules_file" "$ROOTDIR/$modules_file"
}

get_depinfo()
{
	[ -s "$1" ] ||
		return 0
	local rc=0
	sort -u "$1" |
		depinfo --input=- \
			${USE_MODPROBE_BLACKLIST:+--use-blacklist} \
			--missing-firmware \
			--set-version="$KERNEL" || rc=$?
	:> "$1"
	return $rc
}

find_modules required ${MODULES_ADD-} ${RESCUE_MODULES-} ${MODULES_PRELOAD-} ${MODULES_LOAD-}
find_modules optional ${MODULES_TRY_ADD-}

if [ -n "${MODULES_PATTERN_SETS-}" ]; then
	i=0
	for var in $MODULES_PATTERN_SETS; do
		eval "MODULES_PATTERN=\"\${$var-}\""
		for pattern in $MODULES_PATTERN; do
			[ -z "${pattern##*:*}" ] ||
				fatal "unexpected pattern format: $pattern"

			keyword="${pattern%%:*}"
			regexp="${pattern#*:}"

			prefix="${keyword%%-*}"
			if [ "$prefix" != "$keyword" ]; then
				keyword="${keyword#*-}"
			else
				prefix=
			fi

			case "$keyword" in
				alias|author|depends|description|filename|firmware|license|name|symbol)
					;;
				*)
					fatal "unknown keyword: $keyword"
			esac

			printf '%s %s\n' "${prefix:+$prefix-}$keyword" "$regexp"
		done > "$tempdir/filter-$i"

		MODULES_PATTERN_SETS_FILES="${MODULES_PATTERN_SETS_FILES-} $tempdir/filter-$i"

		i=$(($i + 1))
	done
fi

if [ -n "${MODULES_PATTERN_SETS_FILES-}" ]; then
	printf '%s\n' ${MODULES_PATTERN_SETS_FILES-} |
		xargs -r initrd-scanmod --set-version="$KERNEL" \
		>> "$tempdir/mods.required"
fi

:> "$tempdir/firmwares"
:> "$tempdir/missing-firmwares"

while [ -s "$tempdir/mods.required" ] || [ -s "$tempdir/mods.optional" ]; do
	{
		get_depinfo "$tempdir/mods.optional" 2>/dev/null ||:
		get_depinfo "$tempdir/mods.required"
	} > "$tempdir/depinfo"

	module=

	while read -r keyword path; do
		modname="${path##*/}"
		modname="${modname%.ko*}"

		! in_blacklist "$modname" ||
			continue

		case "$keyword" in
			builtin)
				verbose1 "Builtin module \"$modname\""
				;;
			module)
				module="$modname"
				printf '%s\t%s\n' "$modname" "$path"
				;;
			firmware)
				printf 'none\t%s\n' "$path" >> "$tempdir/firmwares"
				;;
			missing-firmware)
				printf '%s\t%s\n' "$module" "$path" >> "$tempdir/missing-firmwares"
				;;
		esac
	done < "$tempdir/depinfo" > "$tempdir/more-modules"

	[ -s "$tempdir/more-modules" ] ||
		break

	sort -uo "$tempdir/more-modules" "$tempdir/more-modules"
	cat "$tempdir/more-modules"

	find -L "$KMODDEPSDIR" -type f -executable \
		-exec '{}' "$tempdir/more-modules" ';' > "$tempdir/mods.optional"

done > "$tempdir/modules"

cut -f2- -- "$tempdir/firmwares" "$tempdir/modules" |
	sort -u |
	tr '\n' '\0' |
	xargs -r0 put-file "$ROOTDIR"

if [ -s "$tempdir/missing-firmwares" ]; then
	if [ -n "${verbose2-}" ]; then
		sort "$tempdir/missing-firmwares" |
		while read -r modname fwpath; do
			message "WARNING: Possible missing firmware \`$fwpath' for kernel module '$modname'"
		done
	else
		n_mods="$(cut -f1 "$tempdir/missing-firmwares" | sort -u | wc -l)"
		n_fw="$(cut -f2-  "$tempdir/missing-firmwares" | sort -u | wc -l)"

		message "WARNING: Possible missing firmware counters: $n_fw file(s), $n_mods module(s)."
	fi
fi

load_modules preudev  ${MODULES_PRELOAD-}
load_modules postudev ${MODULES_LOAD-}
