#!/bin/dash
#
# Regression check: seat tags and ID_SEAT in the udev-compat database after
# vdev phase-1 coldplug (seatd, libudev, libinput seat0).
#
# Usage:
#   check-seat-udev.sh
#   VDEV_PHASE1_STAMP=/run/vdev/vdev.ready check-seat-udev.sh
#   VDEV_SEAT_CHECK_TIMEOUT=120 check-seat-udev.sh
#
# Exit 0 if all applicable checks pass; 1 on failure; 2 if stamp never appears.

VDEV_PHASE1_STAMP="${VDEV_PHASE1_STAMP:-/run/vdev/vdev.ready}"
VDEV_SEAT_CHECK_TIMEOUT="${VDEV_SEAT_CHECK_TIMEOUT:-60}"
VDEV_MOUNTPOINT="${VDEV_MOUNTPOINT:-/dev}"
DRI_DIR="${VDEV_DRI_DIR:-$VDEV_MOUNTPOINT/dri}"
FB_GLOB="${VDEV_FB_GLOB:-$VDEV_MOUNTPOINT/fb[0-9]*}"

FAIL=0
WARN=0

log_ok() {
	printf 'check-seat-udev: OK  %s\n' "$1"
}

log_fail() {
	printf 'check-seat-udev: FAIL %s\n' "$1" >&2
	FAIL=1
}

log_warn() {
	printf 'check-seat-udev: WARN %s\n' "$1" >&2
	WARN=1
}

resolve_udev_root() {
	if [ -n "$VDEV_UDEV_ROOT" ]; then
		printf '%s' "$VDEV_UDEV_ROOT"
		return 0
	fi
	if [ -L /run/udev ] || [ -d /run/udev ]; then
		readlink -f /run/udev 2>/dev/null && return 0
	fi
	if [ -d "$VDEV_MOUNTPOINT/metadata/udev" ]; then
		printf '%s/metadata/udev' "$VDEV_MOUNTPOINT"
		return 0
	fi
	return 1
}

wait_phase1_stamp() {
	_secs="$VDEV_SEAT_CHECK_TIMEOUT"
	while [ "$_secs" -gt 0 ]; do
		if [ -f "$VDEV_PHASE1_STAMP" ]; then
			return 0
		fi
		sleep 1
		_secs=$((_secs - 1))
	done
	return 1
}

char_device_id() {
	_node="$1"
	_t="" _maj="" _min=""
	_t="$(stat -c '%t' "$_node" 2>/dev/null)" || return 1
	_min="$(stat -c '%T' "$_node" 2>/dev/null)" || return 1
	_maj=$((0x$_t))
	_min=$((0x$_min))
	printf 'c%s:%s\n' "$_maj" "$_min"
}

check_node_mode() {
	_node="$1"
	_want_group="$2"
	_want_mode="$3"
	_label="$4"

	if [ ! -e "$_node" ]; then
		log_fail "$_label: missing $_node"
		return 1
	fi

	_got_group="$(stat -c '%G' "$_node")"
	_got_mode="$(stat -c '%a' "$_node")"
	if [ "$_got_group" != "$_want_group" ]; then
		log_fail "$_label: group is $_got_group, want $_want_group ($_node)"
	fi
	if [ "$_got_mode" != "$_want_mode" ]; then
		log_fail "$_label: mode is $_got_mode, want $_want_mode ($_node)"
	fi
	if [ "$_got_group" = "$_want_group" ] && [ "$_got_mode" = "$_want_mode" ]; then
		log_ok "$_label permissions $_node ($_want_group:$_want_mode)"
	fi
}

check_udev_seat_record() {
	_id="$1"
	_label="$2"
	_want_master="${3:-0}"

	_data="$_UDEV_ROOT/data/$_id"
	_tag_idx="$_UDEV_ROOT/tags/seat/$_id"

	if [ ! -f "$_data" ]; then
		log_fail "$_label: no udev data $_data"
		return 1
	fi
	if ! /bin/grep -q '^G:seat$' "$_data" 2>/dev/null; then
		log_fail "$_label: $_data lacks G:seat"
	fi
	if ! /bin/grep -q '^E:ID_SEAT=seat0$' "$_data" 2>/dev/null; then
		log_fail "$_label: $_data lacks E:ID_SEAT=seat0"
	fi
	if [ ! -e "$_tag_idx" ]; then
		log_fail "$_label: missing seat tag index $_tag_idx"
	fi
	if [ "$_want_master" -eq 1 ]; then
		if ! /bin/grep -q '^G:master-of-seat$' "$_data" 2>/dev/null; then
			log_fail "$_label: $_data lacks G:master-of-seat"
		fi
	else
		if /bin/grep -q '^G:master-of-seat$' "$_data" 2>/dev/null; then
			log_warn "$_label: unexpected G:master-of-seat in $_data"
		fi
	fi
	if [ -f "$_data" ] && /bin/grep -q '^G:seat$' "$_data" && \
	    /bin/grep -q '^E:ID_SEAT=seat0$' "$_data" && [ -e "$_tag_idx" ]; then
		log_ok "$_label udev DB $_id"
	fi
}

check_drm_nodes() {
	if [ ! -d "$DRI_DIR" ]; then
		log_warn "no $DRI_DIR (skip DRM checks)"
		return 0
	fi

	_found=0
	for _card in "$DRI_DIR"/card[0-9]*; do
		[ -e "$_card" ] || continue
		_found=1
		_id="$(char_device_id "$_card")" || continue
		check_node_mode "$_card" video 660 "DRM card"
		check_udev_seat_record "$_id" "$(basename "$_card")" 1
	done

	for _render in "$DRI_DIR"/renderD[0-9]*; do
		[ -e "$_render" ] || continue
		_found=1
		_id="$(char_device_id "$_render")" || continue
		_want_group=render
		if ! getent group render >/dev/null 2>&1; then
			_want_group=video
		fi
		check_node_mode "$_render" "$_want_group" 660 "DRM render"
		check_udev_seat_record "$_id" "$(basename "$_render")" 0
	done

	if [ "$_found" -eq 0 ]; then
		log_warn "$DRI_DIR exists but has no card*/renderD* nodes"
	fi
}

check_fb_nodes() {
	_found=0
	for _fb in $FB_GLOB; do
		[ -e "$_fb" ] || continue
		_found=1
		_id="$(char_device_id "$_fb")" || continue
		_base="$(basename "$_fb")"
		_want_master=0
		case "$_base" in
		fb0) _want_master=1 ;;
		esac
		check_node_mode "$_fb" video 660 "framebuffer $_base"
		check_udev_seat_record "$_id" "$_base" "$_want_master"
	done
	if [ "$_found" -eq 0 ]; then
		log_ok "no framebuffer nodes (skipped)"
	fi
}

check_input_seat_index() {
	_tag_dir="$_UDEV_ROOT/tags/seat"
	if [ ! -d "$_tag_dir" ]; then
		log_fail "missing $_tag_dir"
		return 1
	fi
	_n="$(find "$_tag_dir" -maxdepth 1 -type f -name 'c*:*' 2>/dev/null | wc -l)"
	if [ "$_n" -eq 0 ]; then
		log_fail "seat tag index is empty under $_tag_dir"
		return 1
	fi
	log_ok "seat tag index has $_n device(s)"
}

check_sound_udev_records() {
	local _data _id _found _bad

	_found=0
	_bad=0
	for _data in "$_UDEV_ROOT/data/c"[0-9]*:*; do
		[ -f "$_data" ] || continue
		if ! grep -q '^G:uaccess$' "$_data" 2>/dev/null; then
			continue
		fi
		if ! grep -q '^E:SUBSYSTEM=sound$' "$_data" 2>/dev/null; then
			continue
		fi
		_found=$((_found + 1))
		if ! grep -qE '^E:DEVNAME=/dev/snd/' "$_data" 2>/dev/null; then
			log_fail "$(basename "$_data"): missing E:DEVNAME=/dev/snd/… (elogind seat ACL sync)"
			_bad=$((_bad + 1))
		fi
	done
	if [ "$_found" -eq 0 ]; then
		log_warn "no sound+uaccess udev data records (skipped)"
		return 0
	fi
	if [ "$_bad" -eq 0 ]; then
		log_ok "sound udev records have E:DEVNAME ($_found device(s))"
	fi
}

# --- main ---

if ! wait_phase1_stamp; then
	printf 'check-seat-udev: stamp %s not ready within %ss\n' \
	    "$VDEV_PHASE1_STAMP" "$VDEV_SEAT_CHECK_TIMEOUT" >&2
	exit 2
fi
log_ok "phase-1 stamp $VDEV_PHASE1_STAMP"

if ! _UDEV_ROOT="$(resolve_udev_root)"; then
	log_fail "cannot resolve udev-compat root (/run/udev or $VDEV_MOUNTPOINT/metadata/udev)"
	exit 1
fi
log_ok "udev-compat root $_UDEV_ROOT"

if [ -L /run/udev ] || [ -d /run/udev ]; then
	log_ok "/run/udev -> $(readlink -f /run/udev 2>/dev/null || echo /run/udev)"
else
	log_fail "/run/udev is missing (libudev consumers expect it)"
fi

for _dir in "$_UDEV_ROOT/data" "$_UDEV_ROOT/tags/seat"; do
	if [ ! -d "$_dir" ]; then
		log_fail "missing directory $_dir"
	fi
done

check_drm_nodes
check_fb_nodes
check_input_seat_index
check_sound_udev_records

if [ "$FAIL" -ne 0 ]; then
	printf 'check-seat-udev: failed (see above)\n' >&2
	exit 1
fi
if [ "$WARN" -ne 0 ]; then
	printf 'check-seat-udev: passed with warnings\n'
fi
printf 'check-seat-udev: all checks passed\n'
exit 0
