#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
# Copyright (C) 2023 CTERA Networks. All Rights Reserved.
#
# FS QA Test No. 079
#
# Test data-only layers functionality.
#
. ./common/preamble
_begin_fstest auto quick metacopy redirect prealloc

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

# 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_xfs_io_command "falloc"

# remove all files from previous tests
_scratch_mkfs

# File size on lower
dataname="datafile"
sharedname="shared"
datacontent="data"
dataname2="datafile2"
datacontent2="data2"
datasize="4096"

# Number of blocks allocated by filesystem on lower. Will be queried later.
datarblocks=""
datarblocksize=""
estimated_datablocks=""

udirname="pureupper"
ufile="upperfile"

# Check metacopy xattr
check_metacopy()
{
	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>&1 | _filter_scratch)

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

	if [ "$out_f" == "" ];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 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 $expected_size but actual size is $actual_size"
}

check_file_blocks()
{
	local target=$1 expected_blocks=$2 nr_blocks

	nr_blocks=$(stat -c "%b" $target)

	[ "$nr_blocks" == "$expected_blocks" ] || echo "Expected $expected_blocks blocks but actual number of blocks is ${nr_blocks}."
}

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_no_file_contents()
{
	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: No such file or directory"

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

	echo "$target_f unexpectedly has content"
}


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
}

mount_overlay()
{
	local _lowerdir=$1 _datadir2=$2 _datadir=$3

	_overlay_scratch_mount_opts \
		-o"lowerdir=$_lowerdir::$_datadir2::$_datadir" \
		-o"upperdir=$upperdir,workdir=$workdir" \
		-o redirect_dir=on,metacopy=on
}

mount_ro_overlay()
{
	local _lowerdir=$1 _datadir2=$2 _datadir=$3

	_overlay_scratch_mount_opts \
		-o"lowerdir=$_lowerdir::$_datadir2::$_datadir" \
		-o redirect_dir=follow,metacopy=on
}

umount_overlay()
{
	$UMOUNT_PROG $SCRATCH_MNT
}

test_no_access()
{
	local _target=$1

	mount_ro_overlay "$lowerdir" "$datadir2" "$datadir"

	stat $SCRATCH_MNT/$_target >> $seqres.full 2>&1 || \
		echo "No access to lowerdata layer $_target"

	echo "Unmount and Mount rw"
	umount_overlay
	mount_overlay "$lowerdir" "$datadir2" "$datadir"
	stat $SCRATCH_MNT/$_target >> $seqres.full 2>&1 || \
		echo "No access to lowerdata layer $_target"
	umount_overlay
}

test_common()
{
	local _lowerdir=$1 _datadir2=$2 _datadir=$3
	local _target=$4 _size=$5 _blocks=$6 _data="$7"
	local _redirect=$8

	echo "Mount ro"
	mount_ro_overlay $_lowerdir $_datadir2 $_datadir

	# Check redirect xattr to lowerdata
	[ -n "$_redirect" ] && check_redirect $lowerdir/$_target "$_redirect"

	echo "check properties of metadata copied up file $_target"
	check_file_size_contents $SCRATCH_MNT/$_target $_size "$_data"
	check_file_blocks $SCRATCH_MNT/$_target $_blocks

	# Do a mount cycle and check size and contents again.
	echo "Unmount and Mount rw"
	umount_overlay
	mount_overlay $_lowerdir $_datadir2 $_datadir
	echo "check properties of metadata copied up file $_target"
	check_file_size_contents $SCRATCH_MNT/$_target $_size "$_data"
	check_file_blocks $SCRATCH_MNT/$_target $_blocks

	# Trigger metadata copy up and check existence of metacopy xattr.
	chmod 400 $SCRATCH_MNT/$_target
	umount_overlay
	check_metacopy $upperdir/$_target "y"
	check_file_size_contents $upperdir/$_target $_size ""

	# Trigger data copy up and check absence of metacopy xattr.
	mount_overlay $_lowerdir $_datadir2 $_datadir
	$XFS_IO_PROG -c "falloc 0 1" $SCRATCH_MNT/$_target >> $seqres.full
	echo "check properties of data copied up file $_target"
	check_file_size_contents $SCRATCH_MNT/$_target $_size "$_data"
	umount_overlay
	check_metacopy $upperdir/$_target "n"
	check_file_size_contents $upperdir/$_target $_size "$_data"
}

test_lazy()
{
	local _target=$1

	mount_overlay "$lowerdir" "$datadir2" "$datadir"

	# Metadata should be valid
	check_file_size $SCRATCH_MNT/$_target $datasize
	check_file_blocks $SCRATCH_MNT/$_target $estimated_datablocks

	# But have no content
	check_no_file_contents $SCRATCH_MNT/$_target

	umount_overlay
}

create_basic_files()
{
	_scratch_mkfs
	mkdir -p $datadir/subdir $datadir2/subdir $lowerdir $lowerdir2 $upperdir $workdir $workdir2
	mkdir -p $upperdir/$udirname
	echo "$datacontent" > $datadir/$dataname
	chmod 600 $datadir/$dataname
	echo "$datacontent2" > $datadir2/$dataname2
	chmod 600 $datadir2/$dataname2

	echo "$datacontent" > $datadir/$sharedname
	echo "$datacontent2" > $datadir2/$sharedname
	chmod 600 $datadir/$sharedname  $datadir2/$sharedname

	# Create files of size datasize.
	for f in $datadir/$dataname $datadir2/$dataname2 $datadir/$sharedname $datadir2/$sharedname; do
		$XFS_IO_PROG -c "falloc 0 $datasize" $f
		$XFS_IO_PROG -c "fsync" $f
	done

	# Query number of block
	datablocks=$(stat -c "%b" $datadir/$dataname)

	# For lazy lookup file the block count is estimated based on size and block size
	datablocksize=$(stat -c "%B" $datadir/$dataname)
	estimated_datablocks=$(( ($datasize + $datablocksize - 1)/$datablocksize ))
}

prepare_midlayer()
{
	local _redirect=$1

	_scratch_mkfs
	create_basic_files
	if [ -n "$_redirect" ]; then
		mv "$datadir/$dataname" "$datadir/$_redirect"
		mv "$datadir2/$dataname2" "$datadir2/$_redirect.2"
		mv "$datadir/$sharedname" "$datadir/$_redirect.shared"
		mv "$datadir2/$sharedname" "$datadir2/$_redirect.shared"
	fi
	# Create midlayer
	_overlay_scratch_mount_dirs $datadir2:$datadir $lowerdir $workdir2 -o redirect_dir=on,index=on,metacopy=on
	# Trigger a metacopy with or without redirect
	if [ -n "$_redirect" ]; then
		mv "$SCRATCH_MNT/$_redirect" "$SCRATCH_MNT/$dataname"
		mv "$SCRATCH_MNT/$_redirect.2" "$SCRATCH_MNT/$dataname2"
		mv "$SCRATCH_MNT/$_redirect.shared" "$SCRATCH_MNT/$sharedname"
	else
		chmod 400 $SCRATCH_MNT/$dataname
		chmod 400 $SCRATCH_MNT/$dataname2
		chmod 400 $SCRATCH_MNT/$sharedname
	fi
	umount_overlay
}

# Create test directories
datadir=$OVL_BASE_SCRATCH_MNT/data
datadir2=$OVL_BASE_SCRATCH_MNT/data2
lowerdir=$OVL_BASE_SCRATCH_MNT/lower
upperdir=$OVL_BASE_SCRATCH_MNT/upper
workdir=$OVL_BASE_SCRATCH_MNT/workdir
workdir2=$OVL_BASE_SCRATCH_MNT/workdir2

echo -e "\n== Check no follow to lowerdata layer without redirect =="
prepare_midlayer
test_no_access "$dataname"
test_no_access "$dataname2"
test_no_access "$sharedname"

echo -e "\n== Check no follow to lowerdata layer with relative redirect =="
prepare_midlayer "$dataname.renamed"
test_no_access "$dataname"
test_no_access "$dataname2"
test_no_access "$sharedname"

echo -e "\n== Check follow to lowerdata layer with absolute redirect =="
prepare_midlayer "/subdir/$dataname"
test_common "$lowerdir" "$datadir2" "$datadir" "$dataname" $datasize $datablocks \
		"$datacontent" "/subdir/$dataname"
test_common "$lowerdir" "$datadir2" "$datadir" "$dataname2" $datasize $datablocks \
		"$datacontent2" "/subdir/$dataname.2"
# Shared file should be picked from upper datadir
test_common "$lowerdir" "$datadir2" "$datadir" "$sharedname" $datasize $datablocks \
		"$datacontent2" "/subdir/$dataname.shared"

echo -e "\n== Check lazy follow to lowerdata layer =="

prepare_midlayer "/subdir/$dataname"
rm $datadir/subdir/$dataname
test_lazy $dataname


# success, all done
status=0
exit
