#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2025 Chainguard, Inc. All Rights Reserved.
#
# FS QA Test No. 841
#
# Test that XFS filesystems created with reproducibility options produce
# identical images across multiple runs. This verifies that the combination
# of SOURCE_DATE_EPOCH, DETERMINISTIC_SEED, and -m uuid= options result in
# bit-for-bit reproducible filesystem images.

. ./common/preamble
_begin_fstest auto quick mkfs

# Image file settings
IMG_SIZE="512M"
IMG_FILE="$TEST_DIR/xfs_reproducible_test.img"
PROTO_DIR="$TEST_DIR/proto"

# Fixed values for reproducibility
FIXED_UUID="12345678-1234-1234-1234-123456789abc"
FIXED_EPOCH="1234567890"

_cleanup() {
	cd /
	command -v _kill_fsstress &>/dev/null && _kill_fsstress
	rm -r -f $tmp.* "$PROTO_DIR" "$IMG_FILE"
}

# Check if mkfs.xfs supports required options
_check_mkfs_xfs_options()
{
	local check_img="$TEST_DIR/mkfs_check.img"
	truncate -s 64M "$check_img" || return 1

	# Check -m uuid support
	$MKFS_XFS_PROG -m uuid=00000000-0000-0000-0000-000000000000 \
		-N "$check_img" &> /dev/null
	local uuid_support=$?

	# Check -p support (protofile/directory population)
	$MKFS_XFS_PROG 2>&1 | grep populate &> /dev/null
	local proto_support=$?

	grep -q SOURCE_DATE_EPOCH "$MKFS_XFS_PROG"
	local reproducible_support=$?

	rm -f "$check_img"

	if [ $uuid_support -ne 0 ]; then
		_notrun "mkfs.xfs does not support -m uuid= option"
	fi
	if [ $proto_support -ne 0 ]; then
		_notrun "mkfs.xfs does not support -p option for directory population"
	fi
	if [ $reproducible_support -ne 0 ]; then
		_notrun "mkfs.xfs does not support env options for reproducibility"
	fi
}

# Create a prototype directory with all file types supported by mkfs.xfs -p
_create_proto_dir()
{
	rm -rf "$PROTO_DIR"
	mkdir -p "$PROTO_DIR"

	FSSTRESS_ARGS=`_scale_fsstress_args -d $PROTO_DIR -s 1 -n 2000 -p 2 -z \
		-f creat=15 \
		-f mkdir=8 \
		-f write=15 \
		-f truncate=5 \
		-f symlink=8 \
		-f link=8 \
		-f setfattr=12 \
		-f chown=3 \
		-f rename=5 \
		-f unlink=2 \
		-f rmdir=1`
        _run_fsstress $FSSTRESS_ARGS

	# FIFO (named pipe)
	mkfifo "$PROTO_DIR/fifo"

	# Unix socket
	$here/src/af_unix "$PROTO_DIR/socket" 2> /dev/null || true

	# Block device (requires root)
	# Uses the device for $TEST_DIR to ensure it always exists.
	mknod "$PROTO_DIR/blockdev" b $(stat -c '%Hd %Ld' $TEST_DIR) \
		2> /dev/null || true

	# Character device (requires root)
	# Uses /dev/null, which should always exist
	mknod "$PROTO_DIR/chardev" c 1 3 2> /dev/null || true
}

_require_test
_check_mkfs_xfs_options

# Create XFS filesystem with full reproducibility options
# Uses -p to populate from directory during mkfs (no mount needed)
_mkfs_xfs_reproducible()
{
	local img=$1

	# Create fresh image file
	rm -f "$img"
	truncate -s $IMG_SIZE "$img" || return 1

	# Set environment variables for reproducibility:
	# - SOURCE_DATE_EPOCH: fixes all inode timestamps to this value
	# - DETERMINISTIC_SEED: uses fixed seed (0x53454544) instead of
	#   getrandom()
	#
	# mkfs.xfs options:
	# - -m uuid=: fixed filesystem UUID
	# - -p dir: populate filesystem from directory during creation
	SOURCE_DATE_EPOCH=$FIXED_EPOCH \
	DETERMINISTIC_SEED=1 \
	$MKFS_XFS_PROG \
		-f \
		-m uuid=$FIXED_UUID \
		-p "$PROTO_DIR" \
		"$img" >> $seqres.full 2>&1

	return $?
}

# Compute hash of the image file
_hash_image()
{
	sha256sum "$1" | awk '{print $1}'
}

# Run a single reproducibility test iteration
_run_iteration()
{
	local iteration=$1

	echo "Iteration $iteration: Creating filesystem with -p $PROTO_DIR" >> $seqres.full
	if ! _mkfs_xfs_reproducible "$IMG_FILE"; then
		echo "mkfs.xfs failed" >> $seqres.full
		return 1
	fi

	local hash=$(_hash_image "$IMG_FILE")
	echo "Iteration $iteration: Hash = $hash" >> $seqres.full

	echo $hash
}

# Create the prototype directory with various file types
_create_proto_dir

echo "Test: XFS reproducible filesystem image creation"

# Run three iterations
hash1=$(_run_iteration 1)
[ -z "$hash1" ] && _fail "Iteration 1 failed"

hash2=$(_run_iteration 2)
[ -z "$hash2" ] && _fail "Iteration 2 failed"

hash3=$(_run_iteration 3)
[ -z "$hash3" ] && _fail "Iteration 3 failed"

# Verify all hashes match
if [ "$hash1" = "$hash2" ] && [ "$hash2" = "$hash3" ]; then
	echo "All filesystem images are identical."
else
	echo "ERROR: Filesystem images differ!"
fi

# success, all done
status=0
exit
