#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2021 Facebook, Inc. All Rights Reserved.
#
# FS QA Test 290
#
# Test btrfs support for fsverity.
# This test extends the generic fsverity testing by corrupting inline extents,
# preallocated extents, holes, and the Merkle descriptor in a btrfs-aware way.
#
. ./common/preamble
_begin_fstest auto quick verity prealloc

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

# Override the default cleanup function.
_cleanup()
{
	cd /
	_restore_fsverity_signatures
	rm -f $tmp.*
}

# real QA test starts here
_supported_fs btrfs
_require_scratch_verity
_require_scratch_nocheck
_require_odirect
_require_xfs_io_command "falloc"
_require_xfs_io_command "pread"
_require_xfs_io_command "pwrite"
_require_btrfs_corrupt_block
_disable_fsverity_signatures

get_ino() {
	local file=$1
	stat -c "%i" $file
}

validate() {
	local f=$1
	local sz=$(_get_filesize $f)
	# buffered io
	echo $(basename $f)
	$XFS_IO_PROG -rc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
	# direct io
	$XFS_IO_PROG -rdc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
}

# corrupt the data portion of an inline extent
corrupt_inline() {
	local f=$SCRATCH_MNT/inl
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 42" $f
	local ino=$(get_ino $f)
	_fsv_enable $f
	_scratch_unmount
	# inline data starts at disk_bytenr
	# overwrite the first u64 with random bogus junk
	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f disk_bytenr $SCRATCH_DEV > /dev/null 2>&1
	_scratch_mount
	validate $f
}

# preallocate a file, then corrupt it by changing it to a regular file
corrupt_prealloc_to_reg() {
	local f=$SCRATCH_MNT/prealloc
	$XFS_IO_PROG -fc "falloc 0 12k" $f
	local ino=$(get_ino $f)
	_fsv_enable $f
	_scratch_unmount
	# ensure non-zero at the pre-allocated region on disk
	# set extent type from prealloc (2) to reg (1)
	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 1 $SCRATCH_DEV >/dev/null 2>&1
	_scratch_mount
	# now that it's a regular file, reading actually looks at the previously
	# preallocated region, so ensure that has non-zero contents.
	head -c 5 /dev/zero | tr '\0' X | _fsv_scratch_corrupt_bytes $f 0
	validate $f
}

# corrupt a regular file by changing the type to preallocated
corrupt_reg_to_prealloc() {
	local f=$SCRATCH_MNT/reg
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
	local ino=$(get_ino $f)
	_fsv_enable $f
	_scratch_unmount
	# set type from reg (1) to prealloc (2)
	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 2 $SCRATCH_DEV >/dev/null 2>&1
	_scratch_mount
	validate $f
}

# corrupt a file by punching a hole
corrupt_punch_hole() {
	local f=$SCRATCH_MNT/punch
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
	local ino=$(get_ino $f)
	# make a new extent in the middle, sync so the writes don't coalesce
	$XFS_IO_PROG -c sync $SCRATCH_MNT
	$XFS_IO_PROG -fc "pwrite -q -S 0x59 4096 4096" $f
	_fsv_enable $f
	_scratch_unmount
	# change disk_bytenr to 0, representing a hole
	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 4096 -f disk_bytenr -v 0 $SCRATCH_DEV > /dev/null 2>&1
	_scratch_mount
	validate $f
}

# plug hole
corrupt_plug_hole() {
	local f=$SCRATCH_MNT/plug
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
	local ino=$(get_ino $f)
	$XFS_IO_PROG -fc "falloc 4k 4k" $f
	_fsv_enable $f
	_scratch_unmount
	# change disk_bytenr to some value, plugging the hole
	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 4096 -f disk_bytenr -v 13639680 $SCRATCH_DEV > /dev/null 2>&1
	_scratch_mount
	validate $f
}

# corrupt the fsverity descriptor item indiscriminately (causes EINVAL)
corrupt_verity_descriptor() {
	local f=$SCRATCH_MNT/desc
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
	local ino=$(get_ino $f)
	_fsv_enable $f
	_scratch_unmount
	# key for the descriptor item is <inode, BTRFS_VERITY_DESC_ITEM_KEY, 1>,
	# 88 is X. So we write 5 Xs to the start of the descriptor
	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 0 -b 5 $SCRATCH_DEV > /dev/null 2>&1
	_scratch_mount
	validate $f
}

# specifically target the root hash in the descriptor (causes EIO)
corrupt_root_hash() {
	local f=$SCRATCH_MNT/roothash
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
	local ino=$(get_ino $f)
	_fsv_enable $f
	_scratch_unmount
	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 16 -b 1 $SCRATCH_DEV > /dev/null 2>&1
	_scratch_mount
	validate $f
}

# corrupt the Merkle tree data itself
corrupt_merkle_tree() {
	local f=$SCRATCH_MNT/merkle
	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
	local ino=$(get_ino $f)
	_fsv_enable $f
	_scratch_unmount
	# key for the descriptor item is <inode, BTRFS_VERITY_MERKLE_ITEM_KEY, 0>,
	# 88 is X. So we write 5 Xs to somewhere in the middle of the first
	# merkle item
	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,37,0 -v 88 -o 100 -b 5 $SCRATCH_DEV > /dev/null 2>&1
	_scratch_mount
	validate $f
}

# real QA test starts here
_scratch_mkfs >/dev/null
_scratch_mount

corrupt_inline
corrupt_prealloc_to_reg
corrupt_reg_to_prealloc
corrupt_punch_hole
corrupt_plug_hole
corrupt_verity_descriptor
corrupt_root_hash
corrupt_merkle_tree

status=0
exit
