#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2021 Facebook, Inc. All Rights Reserved.
#
# FS QA Test 291
#
# Test btrfs consistency after each FUA while enabling verity on a file
# This test works by following the pattern in log-writes/replay-individual.sh:
# 1. run a workload (verity + sync) while logging to the log device
# 2. replay an entry to the replay device
# 3. snapshot the replay device to the snapshot device
# 4. run destructive tests on the snapshot device (e.g. mount with orphans)
# 5. goto 2
#
. ./common/preamble
_begin_fstest auto verity recoveryloop

# Override the default cleanup function.
_cleanup()
{
	cd /
	_log_writes_cleanup &> /dev/null
	$LVM_PROG vgremove -f -y $vgname >>$seqres.full 2>&1
	_udev_wait --removed /dev/mapper/$vgname-$lvname
	losetup -d $loop_dev >>$seqres.full 2>&1
	rm -f $img
	_restore_fsverity_signatures
}

. ./common/filter
. ./common/attr
. ./common/dmlogwrites
. ./common/verity


_require_scratch
_require_test
_require_loop
_require_log_writes
_require_dm_target snapshot
_require_command $LVM_PROG lvm
_require_scratch_verity
_require_btrfs_command inspect-internal dump-tree
_require_test_program "log-writes/replay-log"
_disable_fsverity_signatures

sync_loop() {
	i=$1
	[ -z "$i" ] && _fail "sync loop needs a number of iterations"
	while [ $i -gt 0 ]
	do
		$XFS_IO_PROG -c sync $SCRATCH_MNT
		let i-=1
	done
}

dump_tree() {
	local dev=$1
	$BTRFS_UTIL_PROG inspect-internal dump-tree $dev
}

count_item() {
	local dev=$1
	local item=$2
	dump_tree $dev | grep -c "$item"
}

count_merkle_items() {
	local dev=$1
	count_item $dev 'VERITY_\(DESC\|MERKLE\)_ITEM'
}

_log_writes_init $SCRATCH_DEV
_log_writes_mkfs
_log_writes_mount

f=$SCRATCH_MNT/fsv
MB=$((1024 * 1024))
img=$TEST_DIR/$$.img
$XFS_IO_PROG -fc "pwrite -q 0 $((10 * $MB))" $f
$XFS_IO_PROG -c sync $SCRATCH_MNT
sync_loop 10 &
sync_proc=$!
_fsv_enable $f
$XFS_IO_PROG -c sync $SCRATCH_MNT
wait $sync_proc

_log_writes_unmount
_log_writes_remove

# the snapshot and the replay will each be the size of the log writes dev
# so we create a loop device of size 2 * logwrites and then split it into
# replay and snapshot with lvm.
log_writes_blocks=$(blockdev --getsz $LOGWRITES_DEV)
replay_bytes=$((512 * $log_writes_blocks))
img_bytes=$((2 * $replay_bytes))

$XFS_IO_PROG -fc "pwrite -q -S 0 $img_bytes $MB" $img >>$seqres.full 2>&1 || \
	_fail "failed to create image for loop device"
loop_dev=$(losetup -f --show $img)
vgname=vg_replay
lvname=lv_replay
replay_dev=/dev/mapper/vg_replay-lv_replay
snapname=lv_snap
snap_dev=/dev/mapper/vg_replay-$snapname

$LVM_PROG vgcreate -f $vgname $loop_dev >>$seqres.full 2>&1 || _fail "failed to vgcreate $vgname"
$LVM_PROG lvcreate -L "$replay_bytes"B -n $lvname $vgname -y >>$seqres.full 2>&1 || \
	_fail "failed to lvcreate $lvname"
_udev_wait /dev/mapper/$vgname-$lvname

replay_log_prog=$here/src/log-writes/replay-log
num_entries=$($replay_log_prog --log $LOGWRITES_DEV --num-entries)
entry=$($replay_log_prog --log $LOGWRITES_DEV --replay $replay_dev --find --end-mark mkfs | cut -d@ -f1)
prev=$(_log_writes_mark_to_entry_number mkfs)
[ -z "$prev" ] && _fail "failed to locate entry mark 'mkfs'"
cur=$(_log_writes_find_next_fua $prev)

# state = 0: verity hasn't started
# state = 1: verity underway
# state = 2: verity done
state=0
while [ ! -z "$cur" ];
do
	_log_writes_replay_log_range $cur $replay_dev >> $seqres.full

	$LVM_PROG lvcreate -s -L 4M -n $snapname $vgname/$lvname >>$seqres.full 2>&1 || \
		_fail "Failed to create snapshot"
	_udev_wait /dev/mapper/$vgname-$snapname

	orphan=$(count_item $snap_dev ORPHAN)
	[ $state -eq 0 ] && [ $orphan -gt 0 ] && state=1

	pre_mount=$(count_merkle_items $snap_dev)
	_mount $snap_dev $SCRATCH_MNT || _fail "mount failed at entry $cur"
	fsverity measure $SCRATCH_MNT/fsv >>$seqres.full 2>&1
	measured=$?
	umount $SCRATCH_MNT
	[ $state -eq 1 ] && [ $measured -eq 0 ] && state=2
	[ $state -eq 2 ] && ([ $measured -eq 0 ] || _fail "verity done, but measurement failed at entry $cur")
	post_mount=$(count_merkle_items $snap_dev)

	echo "entry: $cur, state: $state, orphan: $orphan, pre_mount: $pre_mount, post_mount: $post_mount" >> $seqres.full

	if [ $state -eq 1 ]; then
		[ $post_mount -eq 0 ] || \
			_fail "mount failed to clear under-construction merkle items pre: $pre_mount, post: $post_mount at entry $cur";
	fi
	if [ $state -eq 2 ]; then
		[ $pre_mount -gt 0 ] || \
			_fail "expected to have verity items before mount at entry $cur"
		[ $pre_mount -eq $post_mount ] || \
			_fail "mount cleared merkle items after verity was enabled $pre_mount vs $post_mount at entry $cur";
	fi

	$LVM_PROG lvremove $vgname/$snapname -y >>$seqres.full

	prev=$cur
	cur=$(_log_writes_find_next_fua $(($cur + 1)))
done

[ $state -eq 2 ] || _fail "expected to reach verity done state"

echo "Silence is golden"

# success, all done
status=0
exit
