#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.
# Copyright (C) 2023 CTERA Networks. All Rights Reserved.
#
# FS QA Test No. 080
#
# Test fs-verity functionallity
#
. ./common/preamble
_begin_fstest auto quick metacopy redirect verity

# Import common functions.
. ./common/filter
. ./common/attr
. ./common/verity

# real QA test starts here
_supported_fs overlay
# We use non-default scratch underlying overlay dirs, we need to check
# them explicity after test.
_require_scratch_nocheck
_require_scratch_overlay_features redirect_dir metacopy
_require_scratch_overlay_lowerdata_layers
_require_scratch_overlay_verity

# remove all files from previous tests
_scratch_mkfs

verityname="verityfile"
noverityname="noverityfile"
wrongverityname="wrongverityfile"
missingverityname="missingverityfile"
lowerdata="data1"
lowerdata2="data2"
lowerdata3="data3"
lowerdata4="data4"
lowersize="5"

# Create test directories
lowerdir=$OVL_BASE_SCRATCH_MNT/lower
lowerdir2=$OVL_BASE_SCRATCH_MNT/lower2
upperdir=$OVL_BASE_SCRATCH_MNT/upper
workdir=$OVL_BASE_SCRATCH_MNT/workdir
workdir2=$OVL_BASE_SCRATCH_MNT/workdir2

# Check metacopy xattr
check_metacopy()
{
	local target=$1 exist=$2 dataonlybase=$3
	local out_f target_f
	local msg

	out_f=$( { _getfattr --absolute-names --only-values -n \
		$OVL_XATTR_METACOPY $target 2>&3 | od -A n -t x1 -w256 ; } 3>&1 | _filter_scratch)
        has_version0=`echo $out_f | awk 'NR==1{print $1 == 0}'`

	if [ "$exist" == "y" ];then
		[ "$out_f" == "" -o "$has_version0" == "1" ] && return
		echo "Metacopy xattr does not exist on ${target}. stdout=$out_f"
		return
	fi

	if [ "$out_f" == ""  -o "$has_version0" == "1" ];then
		echo "Metacopy xattr exists on ${target} unexpectedly."
		return
	fi

	target_f=`echo $target | _filter_scratch`
	msg="$target_f: trusted.overlay.metacopy: No such attribute"

	[ "$out_f" == "$msg" ] && return

	echo "Error while checking xattr on ${target}. stdout=$out"
}

# Check verity set in metacopy
check_verity()
{
	local target=$1 exist=$2
	local out_f target_f
	local msg

	out_f=$( { _getfattr --absolute-names --only-values -n $OVL_XATTR_METACOPY $target 2>&3 | od -A n -t x1 -w256 ; } 3>&1 | _filter_scratch)

	target_f=`echo $target | _filter_scratch`
	msg="$target_f: trusted.overlay.metacopy: No such attribute"
	has_digest=`echo $out_f | awk 'NR==1{print $4 == 1}'`

	if [ "$exist" == "y" ]; then
		[ "$out_f" == "$msg" -o "$has_digest" == "0" ] && echo "No verity on ${target}. stdout=$out_f"
		return
	fi

	[ "$out_f" == "$msg" -o "$has_digest" == "0" ] && return
	echo "Verity xattr exists on ${target} unexpectedly. stdout=$out_f"
}

# Check redirect xattr
check_redirect()
{
	local target=$1
	local expect=$2

	value=$(_getfattr --absolute-names --only-values -n \
		$OVL_XATTR_REDIRECT $target)

	[[ "$value" == "$expect" ]] || echo "Redirect xattr incorrect. Expected=\"$expect\", actual=\"$value\""
}

# Check size
check_file_size()
{
	local target=$1 expected_size=$2 actual_size

	actual_size=$(_get_filesize $target)

	[ "$actual_size" == "$expected_size" ] || echo "Expected file size of $target $expected_size but actual size is $actual_size"
}

check_file_contents()
{
	local target=$1 expected=$2
	local actual target_f

	target_f=`echo $target | _filter_scratch`

	read actual<$target

	[ "$actual" == "$expected" ] || echo "Expected file $target_f contents to be \"$expected\" but actual contents are \"$actual\""
}

check_file_size_contents()
{
	local target=$1 expected_size=$2 expected_content=$3

	check_file_size $target $expected_size
	check_file_contents $target $expected_content
}

check_io_error()
{
	local target=$1
	local actual target_f out_f

	target_f=`echo $target | _filter_scratch`
	out_f=`cat $target 2>&1 | _filter_scratch`
	msg="cat: $target_f: Input/output error"

	[ "$out_f" == "$msg" ] && return

	echo "$target_f unexpectedly has no I/O error"
}

create_basic_files()
{
	local subdir=$1

	_scratch_mkfs
	mkdir -p $lowerdir $lowerdir2 $upperdir $workdir $workdir2

	if [ "$subdir" != "" ]; then
	    mkdir $lowerdir/$subdir
	fi

	echo -n "$lowerdata" > $lowerdir/$subdir$verityname
	echo -n "$lowerdata2" > $lowerdir/$subdir$noverityname
	echo -n "$lowerdata3" > $lowerdir/$subdir$wrongverityname
	echo -n "$lowerdata4" > $lowerdir/$subdir$missingverityname

	for f in $verityname $noverityname $wrongverityname $missingverityname; do
		chmod 600 $lowerdir/$subdir$f

		if [ "$f" != "$noverityname" ]; then
			_fsv_enable $lowerdir/$subdir$f
		fi
        done
}

prepare_midlayer()
{
	local dataonlybase=$1

	subdir=""
	if [ "$dataonlybase" == "y" ]; then
	    subdir="base/"
	fi

	create_basic_files "$subdir"
	# Create midlayer
	_overlay_scratch_mount_dirs $lowerdir $lowerdir2 $workdir2 -o redirect_dir=on,index=on,verity=on,metacopy=on
	for f in $verityname $noverityname $wrongverityname $missingverityname; do
		if [ "$dataonlybase" == "y" ]; then
		    mv $SCRATCH_MNT/base/$f $SCRATCH_MNT/$f
		else
		    chmod 400 $SCRATCH_MNT/$f
		fi
	done
	umount_overlay

	if [ "$dataonlybase" == "y" ]; then
	    rm -rf $lowerdir2/base
	fi

	for f in $verityname $noverityname $wrongverityname $missingverityname; do
		# Ensure we have right metacopy and verity xattrs
		check_metacopy $lowerdir2/$f "y"

		if [ "$f" == "$noverityname" ]; then
		    check_verity $lowerdir2/$f "n"
		else
		    check_verity $lowerdir2/$f "y"
		fi

		if [ "$dataonlybase" == "y" ]; then
			check_redirect $lowerdir2/$f "/base/$f"
		fi

		check_file_size_contents $lowerdir2/$f $lowersize ""
	done

	# Fixup missing and wrong verity in lowerdir
	rm -f $lowerdir/$subdir$wrongverityname $lowerdir/$subdir$missingverityname
	echo -n "changed" > $lowerdir/$subdir$wrongverityname
	_fsv_enable $lowerdir/$subdir$wrongverityname
	echo "$lowerdata4" > $lowerdir/$subdir$missingverityname
}

test_common()
{
	local dataonlybase=$1
	local verity=$2

	if [ $dataonlybase == "y" ]; then
		mount_overlay "$lowerdir2::$lowerdir" $verity
	else
		mount_overlay "$lowerdir2:$lowerdir" $verity
	fi

	check_file_size_contents $SCRATCH_MNT/$verityname $lowersize "$lowerdata"

	if [ "$verity" == "require" ]; then
		check_io_error $SCRATCH_MNT/$noverityname
	else
		check_file_size_contents $SCRATCH_MNT/$noverityname $lowersize "$lowerdata2"
	fi

	if [ "$verity" == "off" ]; then
		check_file_size_contents $SCRATCH_MNT/$wrongverityname $lowersize "changed"
		check_file_size_contents $SCRATCH_MNT/$missingverityname $lowersize "$lowerdata4"
	else
		check_io_error $SCRATCH_MNT/$missingverityname
		check_io_error $SCRATCH_MNT/$wrongverityname
	fi

	umount_overlay
}

mount_overlay()
{
	local _lowerdir=$1
	local _verity=$2

	_overlay_scratch_mount_dirs "$_lowerdir" $upperdir $workdir -o redirect_dir=on,index=on,metacopy=on,verity=$_verity
}

umount_overlay()
{
	$UMOUNT_PROG $SCRATCH_MNT
}


echo -e "\n== Check fsverity validation =="

prepare_midlayer "n"
test_common "n" "off"
prepare_midlayer "n"
test_common "n" "on"

# Now with data-only layers
prepare_midlayer "y"
test_common "y" "off"
prepare_midlayer "y"
test_common "y" "on"

echo -e "\n== Check fsverity require =="

prepare_midlayer "n"
test_common "n" "require"

# Now with data-only layers
prepare_midlayer "y"
test_common "y" "require"

echo -e "\n== Check fsverity copy-up =="

# Ensure Second level metacopy sets verity xattr
prepare_midlayer "n"
mount_overlay "$lowerdir2:$lowerdir" "on"
chmod 200 $SCRATCH_MNT/$verityname
umount_overlay
check_metacopy $upperdir/$verityname "y"
check_verity $upperdir/$verityname "y"

# Ensure data copy up remove verity xattr
create_basic_files ""
mount_overlay "$lowerdir" "on"
echo foo >> $SCRATCH_MNT/$verityname
umount_overlay
check_metacopy $upperdir/$verityname "n"
check_verity $upperdir/$verityname "n"

# Ensure metacopy is only used if verity is enabled in lower for verity=require
create_basic_files ""
mount_overlay "$lowerdir" "require"
chmod 200 $SCRATCH_MNT/$verityname
chmod 200 $SCRATCH_MNT/$noverityname
umount_overlay
check_metacopy $upperdir/$verityname "y"
check_verity $upperdir/$verityname "y"
check_metacopy $upperdir/$noverityname "n"
check_verity $upperdir/$noverityname "n"

# success, all done
status=0
exit
