#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2014 Filipe Manana.  All Rights Reserved.
#
# FS QA Test No. btrfs/055
#
# Regression test for the btrfs ioctl clone operation when the source range
# contains hole(s) and the FS has the NO_HOLES feature enabled (file holes
# don't need file extent items in the btree to represent them).
#
# This issue is fixed by the following linux kernel btrfs patch:
#
#    Btrfs: fix clone to deal with holes when NO_HOLES feature is enabled
#
. ./common/preamble
_begin_fstest auto quick clone

# Override the default cleanup function.
_cleanup()
{
    rm -fr $tmp
}

. ./common/filter

_require_scratch
_require_cloner
_require_btrfs_fs_feature "no_holes"
_require_btrfs_mkfs_feature "no-holes"

test_btrfs_clone_with_holes()
{
	_scratch_mkfs "$1" >/dev/null 2>&1
	_scratch_mount

	BLOCK_SIZE=$(_get_block_size $SCRATCH_MNT)

	EXTENT_SIZE=$((2 * $BLOCK_SIZE))

	OFFSET=0

	# Create a file with 4 extents and 1 hole, all with 2 blocks each.
	# The hole is in the block range [4, 5[.
	$XFS_IO_PROG -s -f -c "pwrite -S 0x01 -b $EXTENT_SIZE $OFFSET $EXTENT_SIZE" \
		     $SCRATCH_MNT/foo | _filter_xfs_io_blocks_modified

	OFFSET=$(($OFFSET + $EXTENT_SIZE))
	$XFS_IO_PROG -s -f -c "pwrite -S 0x02 -b $EXTENT_SIZE $OFFSET $EXTENT_SIZE" \
		     $SCRATCH_MNT/foo | _filter_xfs_io_blocks_modified

	OFFSET=$(($OFFSET + 2 * $EXTENT_SIZE))
	$XFS_IO_PROG -s -f -c "pwrite -S 0x04 -b $EXTENT_SIZE $OFFSET $EXTENT_SIZE" \
		     $SCRATCH_MNT/foo | _filter_xfs_io_blocks_modified

	OFFSET=$(($OFFSET + $EXTENT_SIZE))
	$XFS_IO_PROG -s -f -c "pwrite -S 0x05 -b $EXTENT_SIZE $OFFSET $EXTENT_SIZE" \
		     $SCRATCH_MNT/foo | _filter_xfs_io_blocks_modified

	# Clone destination file, 1 extent of 24 blocks.
	EXTENT_SIZE=$((24 * $BLOCK_SIZE))
	$XFS_IO_PROG -s -f -c "pwrite -S 0xff -b $EXTENT_SIZE 0 $EXTENT_SIZE" \
		$SCRATCH_MNT/bar | _filter_xfs_io_blocks_modified

	# Clone 2nd extent, 2-blocks sized hole and 3rd extent of foo into bar.
	$CLONER_PROG -s $((2 * $BLOCK_SIZE)) -d 0 -l $((6 * $BLOCK_SIZE)) \
		     $SCRATCH_MNT/foo $SCRATCH_MNT/bar

	# Verify both extents and the hole were cloned.
	echo "1) Check both extents and the hole were cloned"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Cloning range starts at the middle of a hole.
	$CLONER_PROG -s $((5 * $BLOCK_SIZE)) -d $((8 * $BLOCK_SIZE)) \
		     -l $((3 * $BLOCK_SIZE)) $SCRATCH_MNT/foo $SCRATCH_MNT/bar

	# Verify that half of the hole and the following 2 block extent were cloned.
	echo "2) Check half hole and the following 2 block extent were cloned"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Cloning range ends at the middle of a hole.
	$CLONER_PROG -s 0 -d $((16 * $BLOCK_SIZE)) -l $((5 * $BLOCK_SIZE)) \
		     $SCRATCH_MNT/foo $SCRATCH_MNT/bar

	# Verify that 2 extents of 2 blocks size and a 1-block hole were cloned.
	echo "3) Check that 2 extents of 2 blocks each and a hole of 1 block were cloned"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Create a 6-block hole at the end of the source file (foo).
	$XFS_IO_PROG -c "truncate $((16 * $BLOCK_SIZE))" $SCRATCH_MNT/foo \
		| _filter_xfs_io_blocks_modified
	sync

	# Now clone a range that overlaps that hole at the end of the foo file.
	# It should clone the 10th block and the first two blocks of the hole
	# at the end of foo.
	$CLONER_PROG -s $((9 * $BLOCK_SIZE)) -d $((21 * $BLOCK_SIZE)) \
		     -l $((3 * $BLOCK_SIZE)) $SCRATCH_MNT/foo $SCRATCH_MNT/bar

	# Verify that the 9th block of foo and the first 2 blocks of the
	# 6-block hole of foo were cloned into bar.
	echo "4) Check that a block of 1 extent and 2 blocks of a hole were cloned"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Clone the same range as before, but clone it into a different offset
	# of the target (bar) such that it increases the size of the target
	# by 2 blocks.
	$CLONER_PROG -s $((9 * $BLOCK_SIZE)) -d $((23 * $BLOCK_SIZE)) \
		     -l $((3 * $BLOCK_SIZE)) $SCRATCH_MNT/foo $SCRATCH_MNT/bar

	# Verify that the 9th block of foo and the first 2 blocks of the 6-block
	# hole of foo were cloned into bar at bar's 23rd block and that bar's
	# size increased by 2 blocks.
	echo "5) Check that a block of 1 extent and 2 blocks of a hole were" \
	     "cloned and file size increased"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Create a new completely sparse file (no extents, it's a big hole).
	$XFS_IO_PROG -f -c "truncate $((25 * $BLOCK_SIZE))" $SCRATCH_MNT/qwerty \
		| _filter_xfs_io_blocks_modified
	sync

	# Test cloning a range from the sparse file to the bar file without
	# increasing bar's size.
	$CLONER_PROG -s $((1 * $BLOCK_SIZE)) -d 0 -l $((2 * $BLOCK_SIZE)) \
		     $SCRATCH_MNT/qwerty $SCRATCH_MNT/bar

	# First 2 blocks of bar should now be zeroes.
	echo "6) Check that 2 blocks of the hole were cloned"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Test cloning a range from the sparse file to the end of the bar file.
	# The bar file currently has 26 blocks.
	$CLONER_PROG -s 0 -d $((26 * $BLOCK_SIZE)) -l $((8 * $BLOCK_SIZE)) $SCRATCH_MNT/qwerty \
		$SCRATCH_MNT/bar

	# Verify bar's size increased to 26 + 8 blocks, and its
	# last 8 blocks are all zeroes.
	echo "7) Check that 8 blocks of the hole were cloned and the file size increased"
	od -t x1 $SCRATCH_MNT/bar | _filter_od

	# Verify that there are no consistency errors.
	_check_scratch_fs
}

# Regardless of the NO_HOLES feature being enabled or not, the test results
# should be exactly the same for both cases.

echo "Testing without the NO_HOLES feature"
# As of btrfs-progs 3.14.x, the no-holes feature isn't enabled by default.
# But explicitly disable it at mkfs time as it might be enabled by default
# in future versions.
test_btrfs_clone_with_holes "-O ^no-holes"

_scratch_unmount

echo "Testing with the NO_HOLES feature enabled"
test_btrfs_clone_with_holes "-O no-holes"

status=0
exit
