#! /bin/bash
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2019, Oracle and/or its affiliates.  All Rights Reserved.
#
# FS QA Test No. 544
#
# Ensure that we can reflink from a file with a higher inode number to a lower
# inode number and vice versa.  Mix it up by doing this test with inodes that
# already share blocks and inodes that don't share blocks.  This tests both
# double-inode locking order correctness as well as stressing things like ocfs2
# which have per-inode sharing groups and therefore have to check that we don't
# try to link data between disjoint sharing groups.
. ./common/preamble
_begin_fstest auto quick clone

# Import common functions.
. ./common/filter
. ./common/reflink

_require_scratch_reflink
_require_cp_reflink

echo "Format and mount"
_scratch_mkfs > $seqres.full 2>&1
_scratch_mount >> $seqres.full 2>&1

blksz=65536
_require_congruent_file_oplen $SCRATCH_MNT $blksz
nr=2
filesize=$((blksz * nr))
testdir=$SCRATCH_MNT/test-$seq
dummy_file=$testdir/dummy
low_file=$testdir/low
high_file=$testdir/high
scenario=1
mkdir $testdir

# Return inode number
inum() {
	stat -c '%i' $1
}

# Create two test files, make $low_file the file with the lower inode
# number, and make $high_file the file with the higher inode number.
create_files() {
	touch $testdir/file1
	touch $testdir/file2

	if [ "$(inum $testdir/file1)" -lt "$(inum $testdir/file2)" ]; then
		mv $testdir/file1 $low_file
		mv $testdir/file2 $high_file
	else
		mv $testdir/file2 $low_file
		mv $testdir/file1 $high_file
	fi

	_pwrite_byte 0x60 0 $filesize $low_file >> $seqres.full
	_pwrite_byte 0x61 0 $filesize $high_file >> $seqres.full
}

# Check md5sum of both files, but keep results sorted by inode order
check_files() {
	md5sum $low_file | _filter_scratch
	md5sum $high_file | _filter_scratch
}

# Test reflinking data from the first file to the second file
test_files() {
	local src="$1"
	local dest="$2"
	local off=$((filesize / 2))
	local sz=$((filesize / 2))

	check_files
	_reflink_range $src $off $dest $off $sz >> $seqres.full
	_scratch_cycle_mount
	check_files
}

# Make a file shared with a dummy file
dummy_share() {
	local which="$2"
	test -z "$which" && which=1
	local dummy=$dummy_file.$which

	rm -f $dummy
	_cp_reflink $1 $dummy
}

# Make two files share (different ranges) with a dummy file
mutual_dummy_share() {
	rm -f $dummy_file
	_cp_reflink $1 $dummy_file
	_reflink_range $2 0 $dummy_file $blksz $blksz >> $seqres.full
}

# Announce ourselves, remembering which scenario we've tried
ann() {
	echo "$scenario: $@" | tee -a $seqres.full
	scenario=$((scenario + 1))
}

# Scenario 1: low to high, neither file shares
ann "low to high, neither share"
create_files
test_files $low_file $high_file

# Scenario 2: high to low, neither file shares
ann "high to low, neither share"
create_files
test_files $high_file $low_file

# Scenario 3: low to high, only source file shares
ann "low to high, only source shares"
create_files
dummy_share $low_file
test_files $low_file $high_file

# Scenario 4: high to low, only source file shares
ann "high to low, only source shares"
create_files
dummy_share $high_file
test_files $high_file $low_file

# Scenario 5: low to high, only dest file shares
ann "low to high, only dest shares"
create_files
dummy_share $high_file
test_files $low_file $high_file

# Scenario 6: high to low, only dest file shares
ann "high to low, only dest shares"
create_files
dummy_share $low_file
test_files $high_file $low_file

# Scenario 7: low to high, both files share with each other
ann "low to high, both files share with each other"
create_files
_reflink_range $low_file 0 $high_file 0 $blksz >> $seqres.full
test_files $low_file $high_file

# Scenario 8: high to low, both files share with each other
ann "high to low, both files share with each other"
create_files
_reflink_range $low_file 0 $high_file 0 $blksz >> $seqres.full
test_files $high_file $low_file

# Scenario 9: low to high, both files share but not with each other
ann "low to high, both files share but not with each other"
create_files
# ocfs2 can only reflink between files sharing a refcount tree, so for
# this test (and #10) we skip the dummy file because we'd rather not split
# the test code just to mask off the /one/ weird fs like this...
if _supports_arbitrary_fileset_reflink; then
	dummy_share $low_file 1
	dummy_share $high_file 2
fi
test_files $low_file $high_file

# Scenario 10: high to low, both files share but not with each other
ann "high to low, both files share but not with each other"
create_files
if _supports_arbitrary_fileset_reflink; then
	dummy_share $low_file 1
	dummy_share $high_file 2
fi
test_files $high_file $low_file

# Scenario 11: low to high, both files share mutually
ann "low to high, both files share mutually"
create_files
mutual_dummy_share $low_file $high_file
test_files $low_file $high_file

# Scenario 12: high to low, both files share mutually
ann "high to low, both files share mutually"
create_files
mutual_dummy_share $low_file $high_file
test_files $high_file $low_file

# success, all done
status=0
exit
