#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2022 SUSE Linux Products GmbH. All Rights Reserved.
#
# FS QA Test 276
#
# Verify that fiemap correctly reports the sharedness of extents for a file with
# a very large number of extents, spanning many b+tree leaves in the fs tree,
# and when the file's subvolume was snapshoted.
#
. ./common/preamble
_begin_fstest auto snapshot fiemap remount

. ./common/filter
. ./common/filter.btrfs
. ./common/attr

_require_scratch
_require_xfs_io_command "fiemap" "ranged"
_require_attrs
_require_odirect

_scratch_mkfs >> $seqres.full 2>&1
_scratch_mount

fiemap_test_file()
{
	local offset=$1
	local len=$2

	# Skip the first two lines of xfs_io's fiemap output (file path and
	# header describing the output columns) as well as holes.
	$XFS_IO_PROG -c "fiemap -v $offset $len" $SCRATCH_MNT/foo | \
		grep -v 'hole' | tail -n +3
}

# Count the number of shared extents for the whole test file or just for a given
# range.
count_shared_extents()
{
	local offset=$1
	local len=$2

	# Column 5 (from xfs_io's "fiemap -v" command) is the flags (hex field).
	# 0x2000 is the value for the FIEMAP_EXTENT_SHARED flag.
	fiemap_test_file $offset $len | \
		$AWK_PROG --source 'BEGIN { cnt = 0 }' \
			  --source '{ if (and(strtonum($5), 0x2000)) cnt++ }' \
			  --source 'END { print cnt }'
}

# Count the number of non shared extents for the whole test file or just for a
# given range.
count_not_shared_extents()
{
	local offset=$1
	local len=$2

	# Column 5 (from xfs_io's "fiemap -v" command) is the flags (hex field).
	# 0x2000 is the value for the FIEMAP_EXTENT_SHARED flag.
	fiemap_test_file $offset $len | \
		$AWK_PROG --source 'BEGIN { cnt = 0 }' \
			  --source '{ if (!and(strtonum($5), 0x2000)) cnt++ }' \
			  --source 'END { print cnt }'
}

# Create a file with 2000 extents, and a fs tree with a height of at least 3
# (root node at level 2). We want to verify later that fiemap correctly reports
# the sharedness of each extent, even when it needs to switch from one leaf to
# the next one and from a node at level 1 to the next node at level 1.
# To speedup creating a fs tree of height >= 3, add several large xattrs.
ext_size=$(( 64 * 1024 ))
file_size=$(( 2000 * 2 * $ext_size )) # about 250M
nr_cpus=$("$here/src/feature" -o)
workers=0
for (( i = 0; i < $file_size; i += 2 * $ext_size )); do
	$XFS_IO_PROG -f -d -c "pwrite -b $ext_size $i $ext_size" \
		$SCRATCH_MNT/foo > /dev/null &
	workers=$(( workers + 1 ))
	if [ "$workers" -ge "$nr_cpus" ]; then
		workers=0
		wait
	fi
done
wait

workers=0
xattr_value=$(printf '%0.sX' $(seq 1 3900))
for (( i = 1; i <= 29000; i++ )); do
	echo -n > $SCRATCH_MNT/filler_$i
	$SETFATTR_PROG -n 'user.x1' -v $xattr_value $SCRATCH_MNT/filler_$i &
	workers=$(( workers + 1 ))
	if [ "$workers" -ge "$nr_cpus" ]; then
		workers=0
		wait
	fi
done
wait

# Make sure every ordered extent completed and therefore updated the fs tree.
sync

# All extents should be reported as non shared (2000 extents).
echo "Number of non-shared extents in the whole file: $(count_not_shared_extents)"

# Creating a snapshot.
_btrfs subvolume snapshot $SCRATCH_MNT $SCRATCH_MNT/snap

# We have a snapshot, so now all extents should be reported as shared.
echo "Number of shared extents in the whole file: $(count_shared_extents)"

# Now COW two file ranges, of 64K each, in the snapshot's file.
# So 2 extents should become non-shared after this. Each file extent item is in
# different leaf of the snapshot tree.
#
$XFS_IO_PROG -d -c "pwrite -b $ext_size 512K $ext_size" \
	     -d -c "pwrite -b $ext_size 249M $ext_size" \
	     $SCRATCH_MNT/snap/foo | _filter_xfs_io

# Wait for ordered extents to complete and commit current transaction to make
# sure fiemap will see all extents in the subvolume and extent trees.
sync

# Now we should have 2 non-shared extents and 1998 shared extents.
echo "Number of non-shared extents in the whole file: $(count_not_shared_extents)"
echo "Number of shared extents in the whole file: $(count_shared_extents)"

# Check that the non-shared extents are indeed in the expected file ranges.
echo "Number of non-shared extents in range [512K, 512K + 64K): $(count_not_shared_extents 512K 64K)"
echo "Number of non-shared extents in range [249M, 249M + 64K): $(count_not_shared_extents 249M 64K)"

# Now delete the snapshot.
$BTRFS_UTIL_PROG subvolume delete -c $SCRATCH_MNT/snap | _filter_btrfs_subvol_delete

# We deleted the snapshot and committed the transaction used to delete it (-c),
# but all its extents (both metadata and data) are actually only deleted in the
# background, by the cleaner kthread. So remount, which wakes up the cleaner
# kthread, with a commit interval of 1 second and sleep for 1.1 seconds - after
# this we are guaranteed all extents of the snapshot were deleted.
_scratch_remount commit=1
sleep 1.1

# Now all extents should be reported as not shared (2000 extents).
echo "Number of non-shared extents in the whole file: $(count_not_shared_extents)"

# success, all done
status=0
exit
