#!/usr/sbin/openrc-run

set -e

description="Wait for vdev two-phase startup to settle"

depend()
{
	need vdev networking
	before lightdm
	provide vdev-settle dev-settle
	keyword -containers
}

_json="${VDEV_GLOBALS_JSON:-/etc/vdev/vdevd.globals.json}"

: "${VDEV_HELPERS_DIR:=/lib/vdev}"
: "${VDEV_STATE_DIR:=/run/vdev}"
: "${VDEV_STAMP_DIR:=/run/vdev}"
: "${VDEV_MODPROBE_STAMP:=/run/vdev/vdevd-0-coldplug.ready}"
: "${VDEV_EARLY_STAMP:=/run/vdev/vdevd-1-coldplug.ready}"

if [ -r "${_json}" ] && command -v jq >/dev/null 2>&1; then
	VDEV_JSON_DATA="$(sed -E 's|//.*||g' "${_json}" |
		sed 's|/\*.*\*/||g' |
		grep -v '^$' |
		jq -r .)"
	export VDEV_JSON_DATA
fi

get_json_get_key() {
	printf '%s' "${VDEV_JSON_DATA}" | jq -r --arg k "$1" '
		.globals[$k] // empty'
}

get_json_get_stamp() {
	printf '%s' "${VDEV_JSON_DATA}" | jq -r --arg s "$1" '
		.states[$s].stamp // empty'
}

if [ -n "${VDEV_JSON_DATA}" ]; then
	_helpers_dir="$(get_json_get_key "helpers-dir")"
	VDEV_HELPERS_DIR="${_helpers_dir:-/lib/vdev}"

	_state_dir="$(get_json_get_key "states-stamp-dir")"
	if [ -z "${_state_dir}" ]; then
		_state_dir="$(get_json_get_key "stamp_dir")"
	fi
	VDEV_STATE_DIR="${_state_dir:-/run/vdev}"
	VDEV_STAMP_DIR="${_state_dir:-/run/vdev}"

	_stamp_modprobe="$(get_json_get_stamp "modprobe")"
	VDEV_MODPROBE_STAMP="${VDEV_STAMP_DIR}/${_stamp_modprobe:-vdevd-0-coldplug.ready}"

	_stamp_early="$(get_json_get_stamp "early")"
	VDEV_EARLY_STAMP="${VDEV_STAMP_DIR}/${_stamp_early:-vdevd-1-coldplug.ready}"
fi

VDEV_SETTLE_CTL="${VDEV_STATE_DIR}/vdev-settle.ctl"
VDEV_SETTLE_RSP="${VDEV_STATE_DIR}/vdev-settle.rsp"
VDEV_SETTLE_BIN="${VDEV_HELPERS_DIR}/vdev_settle"
VDEV_SETTLE_RETRIGGERED="${VDEV_STATE_DIR}/vdev-settle.retriggered"
VDEV_SETTLE_TRIGGER_FAILED="${VDEV_STATE_DIR}/vdev-settle.trigger-failed"

vdev_wait_for_stamp() {
	local _vs_path="$1" _vs_max="${2:-25}" _vs_sleep="${3:-0.2}" _vs_i=0

	while [ "${_vs_i}" -lt "${_vs_max}" ]; do
		[ -e "${_vs_path}" ] && return 0
		sleep "${_vs_sleep}"
		_vs_i=$((_vs_i + 1))
	done
	return 1
}

vdev_settle_cmd() {
	local _scmd="$1"

	if [ ! -p "${VDEV_SETTLE_CTL}" ] || [ ! -p "${VDEV_SETTLE_RSP}" ]; then
		return 1
	fi
	(
		exec 3<"${VDEV_SETTLE_RSP}"
		printf '%s\n' "${_scmd}" >"${VDEV_SETTLE_CTL}"
		cat <&3
	)
}

vdev_settle_stop_daemon() {
	local _vs_pid

	if [ ! -f "${VDEV_STATE_DIR}/vdev-settle.pid" ]; then
		return 0
	fi
	read -r _vs_pid < "${VDEV_STATE_DIR}/vdev-settle.pid" 2>/dev/null || _vs_pid=""
	if [ -n "${_vs_pid}" ]; then
		kill -TERM "${_vs_pid}" 2>/dev/null || true
	fi
	rm -f "${VDEV_STATE_DIR}/vdev-settle.pid"
}

vdev_settle_event_seen() {
	local _slist="$1" _sev="$2"

	printf '%s\n' "${_slist}" | cut -f1 | grep -Fx "${_sev}" >/dev/null 2>&1
}

vdev_settle_event_triggered() {
	local _tev="$1"

	[ -f "${VDEV_SETTLE_RETRIGGERED}" ] || return 1
	grep -Fx "${_tev}" "${VDEV_SETTLE_RETRIGGERED}" >/dev/null 2>&1
}

vdev_settle_trigger_add() {
	local _tev="$1" _tuevent="/sys/class/input/${_tev}/uevent"

	if [ ! -e "${_tuevent}" ]; then
		return 1
	fi
	if ! printf 'add\n' >"${_tuevent}" 2>/dev/null; then
		if command -v udevadm >/dev/null 2>&1 &&
			[ -e "/sys/class/input/${_tev}" ] &&
			udevadm trigger --action=add "/sys/class/input/${_tev}" >/dev/null 2>&1; then
			:
		else
			printf '%s %s\n' "$(date -Iseconds 2>/dev/null || date)" "${_tev}" \
				>>"${VDEV_SETTLE_TRIGGER_FAILED}"
			return 1
		fi
	fi
	if ! vdev_settle_event_triggered "${_tev}"; then
		printf '%s\n' "${_tev}" >>"${VDEV_SETTLE_RETRIGGERED}"
	fi
	return 0
}

vdev_settle_foreach_event() {
	local _fed

	if [ ! -d /sys/class/input ]; then
		return 0
	fi
	for _fed in /sys/class/input/event*; do
		[ -e "${_fed}" ] || continue
		basename "${_fed}"
	done
}

vdev_settle_trigger_all_events() {
	local _fta_ev

	for _fta_ev in $(vdev_settle_foreach_event); do
		vdev_settle_trigger_add "${_fta_ev}" || true
	done
}

vdev_input_settle_worker() {
	local _vw_i=0 _vw_max=30 _vw_stable=0 _vw_stable_rounds=2
	local _vw_seen _vw_pending _vw_ev

	modprobe -q evdev 2>/dev/null || true

	mkdir -p "${VDEV_STATE_DIR}"
	: >"${VDEV_SETTLE_RETRIGGERED}"
	: >"${VDEV_SETTLE_TRIGGER_FAILED}"

	vdev_wait_for_stamp "${VDEV_EARLY_STAMP}" 250 0.1 || true
	sleep "${VDEV_SETTLE_VDEVD_DELAY:-0.5}"

	vdev_settle_trigger_all_events

	if [ -x "${VDEV_SETTLE_BIN}" ]; then
		"${VDEV_SETTLE_BIN}" --state-dir "${VDEV_STATE_DIR}" || true
		_vw_i=0
		while [ "${_vw_i}" -lt 50 ]; do
			[ -p "${VDEV_SETTLE_CTL}" ] && [ -p "${VDEV_SETTLE_RSP}" ] && break
			sleep 0.1
			_vw_i=$((_vw_i + 1))
		done
	fi

	_vw_i=0
	while [ "${_vw_i}" -lt "${_vw_max}" ]; do
		_vw_seen="$(vdev_settle_cmd list 2>/dev/null || true)"
		_vw_pending=0

		for _vw_ev in $(vdev_settle_foreach_event); do
			if ! vdev_settle_event_triggered "${_vw_ev}"; then
				vdev_settle_trigger_add "${_vw_ev}" || true
			fi
			if ! vdev_settle_event_seen "${_vw_seen}" "${_vw_ev}"; then
				_vw_pending=1
			fi
		done

		if [ "${_vw_pending}" -eq 0 ]; then
			_vw_stable=$((_vw_stable + 1))
			if [ "${_vw_stable}" -ge "${_vw_stable_rounds}" ]; then
				break
			fi
		else
			_vw_stable=0
		fi

		sleep 0.5
		_vw_i=$((_vw_i + 1))
	done

	vdev_settle_stop_daemon
}

start()
{
	ebegin "Waiting for vdev settle"
	vdev_wait_for_stamp "${VDEV_MODPROBE_STAMP}" || true
	vdev_wait_for_stamp "${VDEV_EARLY_STAMP}" || true
	(
		vdev_input_settle_worker
	) >/dev/null 2>&1 &
	eend 0
}

stop()
{
	vdev_settle_stop_daemon
}
