#!/bin/bash
# Delay 66-scandir -d readiness notify until scandir infrastructure sockets
# exist. With CATCHLOG=0, upstream notifies stage2 before the first scan:
# oneshot services race oneshotd/s, and logged classics race fdholderd/s
# (66-execute dies/restarts forever getting the log pipe → hang after
# "Initialized successfully: haveged" / ubus).
set -euo pipefail

REAL=/usr/bin/66-scandir.real
if [[ ! -x $REAL ]]; then
	REAL=/usr/lib/genuen-init-select/66-scandir.real
fi
if [[ ! -x $REAL ]]; then
	printf '%s\n' "genuen 66-scandir-gate: missing 66-scandir.real" >/dev/console 2>/dev/null || true
	exit 127
fi

notif=
args=()
while (( $# > 0 )); do
	case $1 in
		-d)
			if (( $# < 2 )); then
				printf '%s\n' "genuen 66-scandir-gate: -d needs an fd" >&2
				exit 100
			fi
			notif=$2
			shift 2
			;;
		*)
			args+=("$1")
			shift
			;;
	esac
done

if [[ -z $notif ]]; then
	exec "$REAL" "${args[@]}"
fi

msg() {
	printf '%s\n' "$*" >/dev/console 2>/dev/null || printf '%s\n' "$*" >&2 || true
}

release_stage2() {
	local why=$1
	if printf '\n' >&"$stage2_w" 2>/dev/null; then
		msg "genuen: $why — releasing stage2"
	else
		msg "genuen: $why — stage2 notify failed (fd $stage2_w)"
	fi
}

# oneshotd execve()s /run/66/state/<uid>/<svc>/run. EACCES if the file lacks
# +x, or if /run is noexec. Livestate is copied from svc/ only when stage2
# starts services — chmod before that finds nothing. Fix svc first (copy
# source), remount /run exec, then keep chmod'ing as live runs appear.
fix_oneshot_runs() {
	local run
	for run in /var/lib/66/system/service/svc/*/run \
		/var/lib/66/system/service/svc/*/finish \
		/var/lib/66/system/service/svc/*/run.user \
		/run/66/state/*/*/run \
		/run/66/state/*/*/finish \
		/run/66/state/*/*/run.user
	do
		[[ -f $run ]] || continue
		chmod a+rx "$run" 2>/dev/null || true
	done
}

ensure_run_exec() {
	# Best-effort: tmpfs /run is sometimes mounted noexec.
	mount -o remount,exec /run 2>/dev/null || true
	fix_oneshot_runs
}

# name|daemon_bin  — directory under scandir + /usr/libexec binary
need_ready() {
	local name=$1
	[[ -S $scandir/$name/s ]]
}

ensure_svc() {
	local name=$1
	local bin=$2
	local dir=$scandir/$name
	local flag_var=$3
	# oneshotd/fdholderd listen on relative "s"; eventd takes absolute scandir
	local operand=s
	[[ $name == eventd ]] && operand=$scandir

	# Start supervisor if scandir has not yet
	if [[ ! -e $dir/supervise/control && -d $dir && -x /usr/bin/66-supervise ]] \
		&& (( ${!flag_var} == 0 && i >= 10 )); then
		msg "genuen: no supervise for $name — starting 66-supervise"
		( cd "$scandir" && exec /usr/bin/66-supervise "$name" ) &
		printf -v "$flag_var" '%s' 1
	fi

	if [[ -e $dir/supervise/control ]]; then
		rm -f "$dir/down"
		printf U >"$dir/supervise/control" 2>/dev/null || true
	fi

	# Last resort: run daemon ourselves
	if [[ ! -S $dir/s && -x /usr/libexec/$bin && -d $dir ]] \
		&& (( ${!flag_var} < 2 && i >= 40 )); then
		msg "genuen: starting $bin manually"
		(
			cd "$dir" || exit 1
			exec 3>"$dir/.notify-dummy"
			exec "/usr/libexec/$bin" -v2 -d 3 -- "$operand"
		) &
		printf -v "$flag_var" '%s' 2
	fi
}

exec {stage2_w}>&"$notif"

scandir=/run/66/scandir/0
if (( ${#args[@]} > 0 )); then
	scandir=${args[-1]}
fi

mkdir -p /run/66
fifo=/run/66/scandir-gate.fifo
rm -f "$fifo"
mkfifo -m 600 "$fifo"

(
	exec {fifo_r}<"$fifo"
	dd bs=1 count=1 <&"$fifo_r" >/dev/null 2>&1 || true
	msg "genuen: scandir up, waiting for oneshotd+fdholderd (+eventd)"

	i=0
	# 0=none 1=supervise spawned 2=daemon spawned
	st_oneshotd=0
	st_fdholderd=0
	st_eventd=0

	ensure_run_exec

	while (( i < 200 )); do
		ok=1
		need_ready oneshotd || ok=0
		need_ready fdholderd || ok=0
		# eventd is nice-to-have for boot events; require it too (same race)
		need_ready eventd || ok=0

		if (( ok )); then
			ensure_run_exec
			# Poll after release: livestate .../run is created when stage2
			# starts services — that is after this notify.
			(
				j=0
				while (( j < 400 )); do
					fix_oneshot_runs
					sleep 0.01
					j=$((j + 1))
				done
			) &
			release_stage2 "infra ready (oneshotd+fdholderd+eventd)"
			exit 0
		fi

		ensure_svc oneshotd 66-oneshotd st_oneshotd
		ensure_svc fdholderd 66-fdholderd st_fdholderd
		ensure_svc eventd 66-eventd st_eventd

		if (( i % 20 == 0 )); then
			msg "genuen: waiting sockets (try $i): oneshotd=$([[ -S $scandir/oneshotd/s ]] && echo y || echo n) fdholderd=$([[ -S $scandir/fdholderd/s ]] && echo y || echo n) eventd=$([[ -S $scandir/eventd/s ]] && echo y || echo n)"
			fix_oneshot_runs
		fi
		sleep 0.05
		i=$((i + 1))
	done
	ensure_run_exec
	(
		j=0
		while (( j < 400 )); do
			fix_oneshot_runs
			sleep 0.01
			j=$((j + 1))
		done
	) &
	release_stage2 "infra timeout"
) &

exec {fifo_w}>"$fifo"
exec "$REAL" -d "$fifo_w" "${args[@]}"
