#!/bin/sh
set -eu

script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
output=${1:?usage: prepare_g1_fixtures.sh OUTPUT}
source_dir="$output/source"
base_dir="$output/base"
image_dir="$output/images"
expected_dir="$output/expected"

for tool in mkfs.erofs fsck.erofs dump.erofs python3 sha256sum stat; do
	command -v "$tool" >/dev/null 2>&1 || {
		echo "missing tool: $tool" >&2
		exit 1
	}
done
test "$(id -u)" -eq 0 || {
	echo "root is required to create device-node fixtures" >&2
	exit 1
}
test ! -e "$output" || {
	echo "output already exists: $output" >&2
	exit 1
}

mkdir -p \
	"$source_dir/compact/dir" \
	"$source_dir/compact/dotdir" \
	"$source_dir/extended" \
	"$source_dir/flat/very/long/path/to/a/deeply/nested/deterministic/target/directory" \
	"$source_dir/inline" \
	"$base_dir" "$expected_dir"

SOURCE_DIR="$source_dir" EXPECTED_DIR="$expected_dir" python3 <<'PY'
from pathlib import Path
import os

source = Path(os.environ["SOURCE_DIR"])
expected = Path(os.environ["EXPECTED_DIR"])


def write_pattern(path: Path, size: int, seed: int) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("wb") as out:
        offset = 0
        while offset < size:
            count = min(65536, size - offset)
            out.write(bytes(
                (((offset + index) * 131 + seed) ^ ((offset + index) >> 5)) & 0xff
                for index in range(count)
            ))
            offset += count


compact = source / "compact"
(compact / "root.txt").write_text("repo22 G1 root payload\n", encoding="ascii")
write_pattern(compact / "testfile.txt", 65537, 1)
(compact / "small.txt").write_text("compact inode payload\n", encoding="ascii")
(compact / "single.txt").write_text("single-link payload\n", encoding="ascii")
(compact / "hard-a.txt").write_text("hard-link payload\n", encoding="ascii")
(compact / "dotdir" / "child.txt").write_text("dot directory child\n", encoding="ascii")
(compact / "invalid-target.txt").write_text("invalid nid target\n", encoding="ascii")
(compact / "special-target.txt").write_text("special target\n", encoding="ascii")

extended = source / "extended"
write_pattern(extended / "large-file.bin", 2 * 1024 * 1024 + 731, 2)
(extended / "huge-sparse.dat").write_bytes(b"x")
(extended / "oversize.dat").write_bytes(b"x")

flat = source / "flat"
(flat / "small.txt").write_text("flat plain small payload\n", encoding="ascii")
write_pattern(flat / "medium.dat", 100 * 1024 + 17, 3)
write_pattern(flat / "large.bin", 10 * 1024 * 1024 + 31, 4)
write_pattern(flat / "data.txt", 256 * 1024 + 19, 5)
write_pattern(flat / "random-test.bin", 1024 * 1024, 6)
write_pattern(flat / "huge-data.bin", 100 * 1024 * 1024 + 257, 7)
(flat / "very" / "long" / "path" / "to" / "a" / "deeply" / "nested" /
 "deterministic" / "target" / "directory" / "file.txt").write_text(
    "long symlink target payload\n", encoding="ascii"
)

inline = source / "inline"
(inline / "tiny.txt").write_text("inline data\n", encoding="ascii")
(inline / "empty.txt").write_bytes(b"")
(inline / "inline.txt").write_bytes(b"inline-tail-payload-0123456789\n")
write_pattern(inline / "tailpacked.dat", 5000, 8)
write_pattern(inline / "file-4095.dat", 4095, 9)
write_pattern(inline / "file-4096.dat", 4096, 10)
write_pattern(inline / "file-4097.dat", 4097, 11)
(inline / "target.txt").write_text("short symlink target payload\n", encoding="ascii")

for source_path, offset, length, output_name in (
    (flat / "medium.dat", 10 * 4096, 5 * 4096, "medium-10-5.bin"),
    (flat / "random-test.bin", 0, 10 * 1024, "random-start.bin"),
    (flat / "random-test.bin", 500 * 1024, 10 * 1024, "random-mid.bin"),
    (flat / "random-test.bin", 1000 * 1024, 24 * 1024, "random-end.bin"),
    (flat / "huge-data.bin", 50 * 1024 * 1024, 5 * 1024 * 1024,
     "huge-sample.bin"),
):
    with source_path.open("rb") as handle:
        handle.seek(offset)
        data = handle.read(length)
    if len(data) != length:
        raise RuntimeError(f"short expected range from {source_path}")
    (expected / output_name).write_bytes(data)
(expected / "large-hole-zero-64k.bin").write_bytes(bytes(65536))
(expected / "large-hole-description.txt").write_text(
    "path=/huge-sparse.dat size=4294971393 content=all-zero\n", encoding="ascii"
)
PY

ln "$source_dir/compact/testfile.txt" "$source_dir/compact/dir/testfile.txt"
ln "$source_dir/compact/hard-a.txt" "$source_dir/compact/hard-b.txt"
ln -s special-target.txt "$source_dir/compact/special-link"
mknod "$source_dir/compact/char-large" c 2748 344865
mknod "$source_dir/compact/block-large" b 2748 344865
mkfifo "$source_dir/compact/fifo"
ln -s target.txt "$source_dir/inline/link1"
ln -s /nonexistent/repo22-g1-target "$source_dir/inline/brokenlink"
ln -s very/long/path/to/a/deeply/nested/deterministic/target/directory/file.txt \
	"$source_dir/flat/longlink"
find "$source_dir" -exec touch -h -t 197001010000.00 {} +

build_image()
{
	uuid=$1
	image=$2
	source=$3
	shift 3
	mkfs.erofs -d0 -x-1 -T0 --all-time --all-root --workers=1 \
		-U "$uuid" "$@" "$base_dir/$image" "$source_dir/$source"
}

build_image 31000000-0000-0000-0000-000000000001 \
	compact.erofs compact -E force-inode-compact
build_image 31000000-0000-0000-0000-000000000002 \
	extended.erofs extended -E force-inode-extended
build_image 31000000-0000-0000-0000-000000000003 \
	flat.erofs flat -E noinline_data,force-inode-compact
build_image 31000000-0000-0000-0000-000000000004 \
	inline.erofs inline -E force-inode-compact

python3 "$script_dir/g1_fixtures.py" \
	--compact "$base_dir/compact.erofs" \
	--extended "$base_dir/extended.erofs" \
	--flat "$base_dir/flat.erofs" \
	--inline "$base_dir/inline.erofs" \
	--output "$image_dir"

for image in compact.erofs extended.erofs flat.erofs inline.erofs \
	compact-nlink1.erofs inline-zero.erofs extended-large-hole.erofs; do
	fsck.erofs "$image_dir/$image" >/dev/null
done

printf '%s\n' \
	'48-bit fixtures are field/CRC checked by g1_fixtures.py;' \
	'production fsck.erofs 1.8.6 does not recognize incompat feature 0x80.'

dump.erofs --cat --path=/small.txt "$image_dir/compact.erofs" |
	cmp - "$source_dir/compact/small.txt"
dump.erofs --cat --path=/large-file.bin "$image_dir/extended.erofs" |
	cmp - "$source_dir/extended/large-file.bin"
dump.erofs --cat --path=/medium.dat "$image_dir/flat.erofs" |
	cmp - "$source_dir/flat/medium.dat"
dump.erofs --cat --path=/tailpacked.dat "$image_dir/inline.erofs" |
	cmp - "$source_dir/inline/tailpacked.dat"

(
	cd "$output"
	find source expected -type f -print0 | sort -z | xargs -0 sha256sum
) > "$output/SOURCE-SHA256SUMS"
(
	cd "$output"
	find source -type l -print | sort | while IFS= read -r path; do
		printf '%s -> %s\n' "$path" "$(readlink "$path")"
	done
	find source -type p -printf '%p type=fifo\n'
	for kind in block:b char:c; do
		label=${kind%:*}
		type=${kind#*:}
		find source -type "$type" -print | sort | while IFS= read -r path; do
			set -- $(stat -c '%t %T' "$path")
			printf '%s type=%s major=%d minor=%d\n' \
				"$path" "$label" "$((0x$1))" "$((0x$2))"
		done
	done
) > "$output/SOURCE-METADATA"
(
	cd "$output"
	sha256sum SOURCE-METADATA
) > "$output/SOURCE-METADATA.sha256"

cat "$image_dir/fixture-evidence.txt"
cat "$output/SOURCE-SHA256SUMS"
cat "$output/SOURCE-METADATA"
cat "$image_dir/IMAGE-SHA256SUMS"
