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

. shell-error
. shell-quote
. sh-functions

declare -A DIRS FILES LOCALFILES FEATUREFILES

FEATUREFILES=()
LOCALFILES=()
FILES=()
DIRS=()

LIB_PATHS=()
BIN_PATHS=()

quote_bash_args()
{
### This is an internal function to avoid the use of ugly namespace.
__quote_bash_args_toggle() {
	### toggle $1 value
	eval [ -n \"\$$1\" ] && eval "$1=" || eval "$1=\$$2"
}
__quote_bash_args() {
	local i=0 c=''
	### backslash/double/single quote mode
	local bq='' dq='' sq=''

	__quote_bash_args_out=''

	for (( i=0; i<${#1}; i++ )); do
		c="${1:i:1}"

		case "$c" in
			\")
				### toggle double quote mode unless
				### in backslash or single quote mode
				[ -n "$bq$sq" ] || __quote_bash_args_toggle dq c
				;;
			\')
				### toggle single quote mode unless
				### in backslash or double quote mode
				[ -n "$bq$dq" ] || __quote_bash_args_toggle sq c
				;;
			\$|\`)
				### quote special character unless
				### in backslash or single quote mode
				[ -n "$bq$sq" ] || bq=\\
				;;
			\\)
				### toggle backslash quote mode unless
				### in single quote mode
				if [ -z "$sq" ]; then
					if [ -z "$bq" ]; then
						### enter backslash quote mode
						bq=\\
						continue
					else
						### leave backslash quote mode
						__quote_bash_args_out+="\\"
						bq=
					fi
				fi
				;;
			[!A-Za-z0-9_\ \	])
				### quote non-regular character unless
				### in any quote mode
				[ -n "$bq$dq$sq" ] || bq=\\
				;;
		esac
		__quote_bash_args_out+="$bq$c"
		### leave backslash quote mode if any
		bq=
	done

	[ -z "$bq$dq$sq" ] ||
		{ message "unmatched character ($bq$dq$sq) found"; return 1; }
}
	local __quote_bash_args_rc=0

	if [[ "$2" =~ [\"\'\$\`\\\\] ]]; then
		local __quote_bash_args_out=''
		__quote_bash_args "$2" || __quote_bash_args_rc=1
		eval "$1=\"\$__quote_bash_args_out\""
	else
		eval "$1=\"\$2\""
	fi

	### Remove internal functions from user namespace.
	unset -f __quote_bash_args __quote_bash_args_toggle

	return $__quote_bash_args_rc
}

in_runtimedir()
{
	local f="${1#$LOCALBUILDDIR}"
	[ -z "${f##$RUNTIMEDIR/*}" ]
}

in_features_bindir()
{
	local f="${1#$LOCALBUILDDIR}"
	[ -z "${f##$BIN_FEATURESDIR/*}" ]
}

append_progs()
{
	local n f path found args

	[ -n "$1" ] ||
		return 0

	quote_bash_args args "$1"
	eval "set -- $args"

	for n; do
		[ -n "$n" ] ||
			continue
		if [ -z "${n##/*}" ]; then
			message "WARNING: it doesn't make sense to search in PATH for a utility with absolute path: $n"
			FILES["$n"]=1
			continue
		elif [ -z "${n##*/*}" ]; then
			message "WARNING: it doesn't make sense to search in PATH for a utility with subpath: $n"
			n="${n##*/}"
		fi
		found=
		for path in "${BIN_PATHS[@]}"; do
			f="$path/$n"
			if [ -x "$f" ]; then
				found=1
				break
			fi
		done

		[ -n "$found" ] ||
			fatal "Not found utility: $n"

		# We ignore the files from RUNTIMEDIR because this entire
		# directory will be copied later.
		! in_runtimedir "$f" ||
			continue

		# We put these utilities on a separate list because we need to
		# remove a prefix from them.
		if in_features_bindir "$f"; then
			FEATUREFILES["$f"]=1
			continue
		fi

		FILES["$f"]=1
	done
}

get_lib_paths()
{
	LIB_PATHS=()
	readarray -t LIB_PATHS < <(get-ldconfig --libdirs)
}

get_bin_paths()
{
	BIN_PATHS=()

	[ -z "$LOCALBUILDDIR" ] ||
		BIN_PATHS+=("$LOCALBUILDDIR$RUNTIMEDIR/sbin" "$LOCALBUILDDIR$RUNTIMEDIR/bin")

	local n d
	for n in $ALL_ACTIVE_FEATURES; do
		for d in "$LOCALBUILDDIR$BIN_FEATURESDIR/$n"/{bin,sbin} "$BIN_FEATURESDIR/$n"/{bin,sbin}; do
			[ ! -d "$d" ] ||
				BIN_PATHS+=("$d")
		done
	done

	while read -r -d: d; do
		[ ! -d "$d" ] ||
			BIN_PATHS+=("$d")
	done <<<"$BUSYBOX_PATH:$SYSTEM_PATH"
}

append_libs()
{
	local n fn optional args

	optional="$1"
	shift

	[ -n "$1" ] ||
		return 0

	quote_bash_args args "$1"
	eval "set -- $args"

	for n; do
		[ -n "$n" ] ||
			continue

		if [ -z "${n##/*}" ]; then
			FILES["$n"]=1
			continue
		elif [ -z "${n##*/*}" ]; then
			message "WARNING: it doesn't make sense to search in PATH for a library with subpath: $n"
			n="${n##*/}"
		fi

		local fn="$n"
		names=("$fn")
		[[ "$fn" == *".so"* ]] || { fn="$fn.so"; names+=("$fn"); }
		[[ "$fn" == "lib"*  ]] || { fn="lib$fn"; names+=("$fn"); }

		found=
		for path in "${LIB_PATHS[@]}"; do
			for fn in "${names[@]}"; do
				f="$path/$fn"
				if [ -f "$f" ] || [ -h "$f" ]; then
					found=1
					break 2
				fi
			done
		done

		if [ -z "$found" ]; then
			[ "$optional" = optional ] ||
				fatal "Not found library: $n"
			continue
		fi

		FILES["$f"]=1

	done
}

get_bin_paths
get_lib_paths

quote_bash_args args "${PUT_FEATURE_DIRS-} $PUT_DIRS"
eval "set -- $args"

for n in \
	"$RUNTIMEDIR" "$BASEDATADIR" \
	${LOCALBUILDDIR:+"${LOCALBUILDDIR-}$RUNTIMEDIR"} \
	"$@";
do
	DIRS["$n"]=1
done

for n in \
	$(find-terminfo linux) \
	$(find "$KERNEL_MODULES_DIR" -type f \( -name 'modules.*' -a \! -name 'modules.*.bin' \)) \
	/var/resolv \
	/etc/modprobe.conf;
do
	[ ! -e "$n" ] ||
		FILES["$n"]=1
done

append_progs "${PUT_FEATURE_PROGS-}${PUT_PROGS:+ $PUT_PROGS}"
append_libs required "${PUT_FEATURE_LIBS-}"
append_libs optional "${PUT_FEATURE_OPTIONAL_LIBS-}"

if [ -n "${PUT_FEATURE_PROGS_WILDCARD-}" ]; then
	quote_bash_args args "$PUT_FEATURE_PROGS_WILDCARD"
	eval "set -- $args"

	while read -d: -r n; do
		[ -n "$n" ] && [ -d "$n" ] ||
			continue
		while read -r bin; do
			[ -n "$bin" ] ||
				continue
			for pattern in "$@"; do
				[ -z "${bin##$pattern}" ] ||
					continue
				if [ -n "$LOCALBUILDDIR" ] && [ -z "${bin##$LOCALBUILDDIR/*}" ]; then
					LOCALFILES["$bin"]=1
					continue
				fi
				[ -n "${bin##$RUNTIMEDIR/*}" ] ||
					continue
				FILES["$bin"]=1
			done
		done < <(set +f; shopt -s nullglob dotglob; printf '%s\n' "$n"/*)
	done <<<"$BUILDDIR_PATH:$BUSYBOX_PATH:$SYSTEM_PATH"
fi

if [ -n "${PUT_FEATURE_FILES-}" ] || [ -n "${PUT_FILES-}" ]; then
	quote_bash_args args "${PUT_FEATURE_FILES-} ${PUT_FILES-}"
	eval "set -- $args"

	for n in "$@"; do
		FILES["$n"]=1
	done
fi

cd "$ROOTDIR"

mkdir_save_symlinks()
{
	local dir sub_dir follow_dir canon_path

	for dir in "$@"; do
		[ -n "$dir" ] ||
			continue

		sub_dir="$(dirname "$dir")"

		if [ ! -e "$sub_dir" ] && [ ! -h "$sub_dir" ]; then
			mkdir_save_symlinks "$sub_dir"
		fi

		if [ ! -h "/$dir" ]; then
			mkdir -p -- "./$dir"
		else
			follow_dir=".$(readlink -f "/$dir")"
			mkdir_save_symlinks "$follow_dir"

			canon_path="$(realpath --relative-to="$sub_dir" "$follow_dir")"
			ln -s "$canon_path" "./$dir"
		fi
	done
}

mkdir -p -- {,home/}root

mkdir_save_symlinks \
	{dev,mnt,run,proc,sys,tmp,{,usr/}{,s}bin,{,usr/}{share,lib{,32,x32,64}},var/{cache,lock/subsys,log,run}} \
	lib/{modules,udev,initrd/{all,kmodules,post,pre}} \
	etc/{{initrd,modprobe.d,sysconfig},udev/rules.d} \
	.initrd/{killall,uevent/events,uevent/queues/udev/.tmp} \
	"${KERNEL_MODULES_DIR#/}" \
	#

initrd-release

mkfifo ./.initrd/telinit
mkfifo ./.initrd/rdshell

ln_if_missing()
{
	[ -h "$2" ] || [ -e "$2" ] || ln $verbose3 -s -- "$1" "$2"
}

[ -n "$BASH" ] ||
	BASH="$(find /bin/ /sbin/ /usr/bin/ /usr/sbin/ /usr/local/bin/ /usr/local/sbin/ -name bash -print -quit)"

[ -n "$BASH" ] ||
	fatal "bash not found"
put-file . "$BASH"

ln_if_missing "$BASH" ./bin/sh
ln_if_missing "$BASH" ./bin/bash

if [ -h /bin/sh ]; then
	f="$(readlink -f /bin/sh)"
	mkdir -p -- ".${f%/*}"
	ln_if_missing "$BASH" ".$f"
fi

for f in "${!FEATUREFILES[@]}"; do
	n="${f#$LOCALBUILDDIR}"
	n="${n#$BIN_FEATURESDIR/*/}"
	p="${f%/$n}"
	put-file -r "$p" . "$f"
done

[ "${#DIRS[@]}"       -eq 0 ] || printf '%s\0' "${!DIRS[@]}"       |xargs -0 put-tree .
[ "${#FILES[@]}"      -eq 0 ] || printf '%s\0' "${!FILES[@]}"      |xargs -0 put-file .
[ "${#LOCALFILES[@]}" -eq 0 ] || printf '%s\0' "${!LOCALFILES[@]}" |xargs -0 put-file -r "$LOCALBUILDDIR" .

gen-ld-so-conf . > etc/ld.so.conf

[ ! -f ".$UDEVD"   ] || ln_if_missing "$UDEVD"   ./sbin/udevd
[ ! -f ".$UDEVADM" ] || ln_if_missing "$UDEVADM" ./sbin/udevadm

for n in group gshadow passwd shadow fstab; do
	touch "./etc/$n"
done

ln -s /proc/self/mounts ./etc/mtab

n_file=0
for filename in ${SYSCONFIG_FILES-}; do
	n_file=$(($n_file + 1))
	eval "vars=\"\${SYSCONFIG_NAMES_${n_file}-}\""

	n_var=0
	for varname in $vars; do
		n_var=$(($n_var + 1))
		eval "value=\"\${SYSCONFIG_${n_file}_${n_var}-}\""

		quote_shell_variable value "$value"
		printf '%s="%s"\n' "$varname" "$value"
	done >> "./etc/sysconfig/$filename"
done

for d in lib etc; do
	if [ -d "/$d/modprobe.d" ]; then
		verbose1 "Copying files from $d/modprobe.d ..."
		find -L "/$d/modprobe.d" \
				-name '*.conf' \
			-exec cp $verbose3 -aLt ./etc/modprobe.d -- '{}' '+'
	fi
done

for n in $ALL_ACTIVE_FEATURES; do
	mkdir -p "./.initrd/features/$n"
done
