#!/bin/sh
set -u

test_dir=/root/repo22-namei
mount_dir=/mnt/repo22-namei
module_id=
md_device=

fail()
{
	echo "FAIL: $*" >&2
	exit 1
}

cleanup()
{
	set +e
	if mount | grep -q " on $mount_dir "; then
		umount "$mount_dir"
	fi
	if [ -n "$md_device" ]; then
		mdconfig -d -u "${md_device#md}"
	fi
	if [ -n "$module_id" ] && kldstat -q -i "$module_id"; then
		kldunload -i "$module_id"
	fi
}
trap cleanup EXIT INT TERM

mount_image()
{
	image=$1
	md_device=$(mdconfig -a -t vnode -f "$test_dir/$image") ||
		fail "mdconfig $image"
	mount -t erofs "/dev/$md_device" "$mount_dir" ||
		fail "mount $image"
	echo "mounted image=$image device=$md_device"
}

unmount_image()
{
	umount "$mount_dir" || fail "umount $md_device"
	mdconfig -d -u "${md_device#md}" || fail "detach $md_device"
	md_device=
}

expect_integrity_failure()
{
	image=$1
	lookup_path=$2
	readdir_path=$3
	label=${image%.erofs}

	mount_image "$image"
	attempt=1
	while [ "$attempt" -le 2 ]; do
		output="$test_dir/$label-lookup-$attempt.txt"
		if stat "$mount_dir/$lookup_path" >"$output" 2>&1; then
			fail "$image lookup attempt $attempt unexpectedly succeeded"
		fi
		if grep -qi "No such file" "$output"; then
			fail "$image lookup attempt $attempt became ENOENT"
		fi
		attempt=$((attempt + 1))
	done
	output="$test_dir/$label-readdir.txt"
	if ./readdir_probe "$mount_dir/$readdir_path" 0 512 2 \
	    >"$output" 2>&1; then
		fail "$image readdir unexpectedly succeeded"
	fi
	unmount_image
	echo "integrity image=$image repeated-lookup=error readdir=error"
}

cd "$test_dir" || exit 1
mkdir -p "$mount_dir"

if mount | grep -qi erofs; then
	fail "pre-existing EROFS mount"
fi
if kldstat | grep -qi erofs; then
	fail "pre-existing EROFS module"
fi
if [ -n "$(mdconfig -l)" ]; then
	fail "pre-existing md device"
fi

echo "== guest =="
uname -a
date -u

echo "== module load =="
kldload "$test_dir/erofs.ko" || fail "kldload"
module_id=$(kldstat | awk '$NF == "erofs.ko" { print $1 }')
[ -n "$module_id" ] || fail "loaded module not found"
kldstat -v -i "$module_id"

echo "== cold nested lookup =="
mount_image nested.erofs
payload=$(cat "$mount_dir/alpha/bravo/charlie/payload.txt") ||
	fail "cold nested lookup"
[ "$payload" = "cold nested lookup payload" ] || fail "cold payload mismatch"
payload=$(cat "$mount_dir/alpha/bravo/charlie/payload.txt") ||
	fail "repeated nested lookup"
[ "$payload" = "cold nested lookup payload" ] || fail "repeat payload mismatch"
payload=$(cat "$mount_dir/alpha/bravo/repeat.txt") ||
	fail "sibling nested lookup"
[ "$payload" = "repeat lookup payload" ] || fail "sibling payload mismatch"

attempt=1
while [ "$attempt" -le 2 ]; do
	output="$test_dir/missing-$attempt.txt"
	if stat "$mount_dir/alpha/bravo/missing" >"$output" 2>&1; then
		fail "missing lookup attempt $attempt unexpectedly succeeded"
	fi
	grep -qi "No such file" "$output" || fail "missing lookup was not ENOENT"
	attempt=$((attempt + 1))
done
payload=$(cat "$mount_dir/alpha/bravo/charlie/payload.txt") ||
	fail "existing lookup after negative cache"
[ "$payload" = "cold nested lookup payload" ] || fail "post-negative payload mismatch"
echo "cold lookup=pass repeat=pass negative-cache=pass"

echo "== large readdir and cookies =="
wide_count=$(ls -A1 "$mount_dir/wide" | wc -l | tr -d ' ')
[ "$wide_count" = 320 ] || fail "wide entry count $wide_count"
./readdir_probe "$mount_dir/wide" 0 128 400 > wide-probe.txt ||
	fail "wide readdir probe"
awk '/^  off=/ {
	off = $1; sub(/^off=/, "", off);
	name = $5; sub(/^name=/, "", name);
	print off, name;
}' wide-probe.txt > wide-cookies.txt
wide_dirents=$(wc -l < wide-cookies.txt | tr -d ' ')
[ "$wide_dirents" = 322 ] || fail "wide dirent count $wide_dirents"
awk '{ cookie[NR] = $1; name[NR] = $2 }
	END {
		for (i = 1; i <= NR; i++)
			print cookie[i], (i < NR ? name[i + 1] : "<EOF>");
	}' wide-cookies.txt > wide-resume-cases.txt
while read -r cookie expected; do
	./readdir_probe "$mount_dir/wide" "$cookie" 128 1 > wide-resume.txt ||
		fail "resume cookie $cookie"
	if [ "$expected" = "<EOF>" ]; then
		grep -q 'bytes=0$' wide-resume.txt || fail "cookie $cookie not EOF"
	else
		actual=$(awk '/^  off=/ {
			name = $5; sub(/^name=/, "", name); print name; exit;
		}' wide-resume.txt)
		[ "$actual" = "$expected" ] ||
			fail "cookie $cookie expected $expected got $actual"
	fi
done < wide-resume-cases.txt
echo "wide entries=$wide_dirents all-resume-cookies=pass"
unmount_image

echo "== dot omitted cookies =="
mount_image dot-omitted.erofs
./readdir_probe "$mount_dir" 0 512 8 > dot-probe.txt || fail "dot probe"
awk '/^  off=/ {
	off = $1; sub(/^off=/, "", off);
	name = $5; sub(/^name=/, "", name);
	print off, name;
}' dot-probe.txt > dot-cookies.txt
cat > dot-expected.txt <<'EOF'
12 ..
24 alpha
47 wide
48 .
EOF
cmp dot-cookies.txt dot-expected.txt || fail "dot cookie sequence"
./readdir_probe "$mount_dir" 47 128 1 > dot-resume-47.txt || fail "dot resume 47"
grep -q '^  off=48 .* name=\.$' dot-resume-47.txt || fail "dot resume 47 result"
./readdir_probe "$mount_dir" 48 128 1 > dot-resume-48.txt || fail "dot resume 48"
grep -q 'bytes=0$' dot-resume-48.txt || fail "dot resume 48 not EOF"
echo "dot cookies=12,24,47,48 resume=pass"
unmount_image

echo "== corrupted directories =="
expect_integrity_failure corrupt-short-block.erofs alpha .
expect_integrity_failure corrupt-nameoff.erofs alpha .
expect_integrity_failure \
	corrupt-padding.erofs \
	wide/entry-000-abcdefghijklmnopqrstuvwxyz.txt \
	wide

echo "== module unload =="
kldunload -i "$module_id" || fail "kldunload"
module_id=
if kldstat | grep -qi erofs; then
	fail "module remains loaded"
fi
if mount | grep -qi erofs; then
	fail "mount remains"
fi
if [ -n "$(mdconfig -l)" ]; then
	fail "md remains"
fi

dmesg | tail -120 > dmesg-tail.txt
echo "PASS: all VM regressions"
