#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2022 BingJing Chang.
#
# FS QA Test No. btrfs/278
#
# Regression test for btrfs incremental send issue when processing inodes
# with no links
#
# This issue is fixed by the following linux kernel btrfs patch:
#
#   commit 9ed0a72e5b355d ("btrfs: send: fix failures when processing
#   inodes with no links")
#
. ./common/preamble
_begin_fstest auto quick send

# real QA test starts here
_supported_fs btrfs
_fixed_by_kernel_commit 9ed0a72e5b355d \
	"btrfs: send: fix failures when processing inodes with no links"
_require_test
_require_scratch
_require_btrfs_command "property"
_require_fssum

send_files_dir=$TEST_DIR/btrfs-test-$seq

rm -fr $send_files_dir
mkdir $send_files_dir

_scratch_mkfs >>$seqres.full 2>&1
_scratch_mount
_run_btrfs_util_prog subvolume create $SCRATCH_MNT/vol

# Creating the first snapshot looks like:
#
# .                                                                  (ino 256)
# |--- deleted.file                                                  (ino 257)
# |--- deleted.dir/                                                  (ino 258)
# |--- changed_subcase1.file                                         (ino 259)
# |--- changed_subcase2.file                                         (ino 260)
# |--- changed_subcase1.dir/                                         (ino 261)
# |    |---- foo                                                     (ino 262)
# |--- changed_subcase2.dir/                                         (ino 263)
# |    |---- foo                                                     (ino 264)
#
touch $SCRATCH_MNT/vol/deleted.file
mkdir $SCRATCH_MNT/vol/deleted.dir
touch $SCRATCH_MNT/vol/changed_subcase1.file
touch $SCRATCH_MNT/vol/changed_subcase2.file
mkdir $SCRATCH_MNT/vol/changed_subcase1.dir
touch $SCRATCH_MNT/vol/changed_subcase1.dir/foo
mkdir $SCRATCH_MNT/vol/changed_subcase2.dir
touch $SCRATCH_MNT/vol/changed_subcase2.dir/foo
_run_btrfs_util_prog subvolume snapshot -r $SCRATCH_MNT/vol $SCRATCH_MNT/snap1

# Delete the deleted.*, create a new file and a new directory, and then
# take the second snapshot looks like:
#
# .                                                                  (ino 256)
# |--- changed_subcase1.file                                         (ino 259)
# |--- changed_subcase2.file                                         (ino 260)
# |--- changed_subcase1.dir/                                         (ino 261)
# |    |---- foo                                                     (ino 262)
# |--- changed_subcase2.dir/                                         (ino 263)
# |    |---- foo                                                     (ino 264)
# |--- new.file                                                      (ino 265)
# |--- new.dir/                                                      (ino 266)
#
unlink $SCRATCH_MNT/vol/deleted.file
rmdir $SCRATCH_MNT/vol/deleted.dir
touch $SCRATCH_MNT/vol/new.file
mkdir $SCRATCH_MNT/vol/new.dir
_run_btrfs_util_prog subvolume snapshot -r $SCRATCH_MNT/vol $SCRATCH_MNT/snap2

# Set the snapshot "snap1" to read-write mode and turn several inodes to
# orphans, so that the snapshot will look like this:
#
# .                                                                  (ino 256)
# |--- (orphan) deleted.file                                         (ino 257)
# |--- (orphan) deleted.dir/                                         (ino 258)
# |--- (orphan) changed_subcase1.file                                (ino 259)
# |--- changed_subcase2.file                                         (ino 260)
# |--- (orphan) changed_subcase1.dir/                                (ino 261)
# |--- changed_subcase2.dir/                                         (ino 263)
# |    |---- foo                                                     (ino 264)
#
# Note: To make an easy illustration, I just put a tag "(orphan)" in front of
# their original names to indicate that they're deleted, but their inodes can
# not be removed because of open file descriptors on them. Mention that orphan
# inodes don't have names(paths).
#
$BTRFS_UTIL_PROG property set $SCRATCH_MNT/snap1 ro false
exec 71<$SCRATCH_MNT/snap1/deleted.file
exec 72<$SCRATCH_MNT/snap1/deleted.dir
exec 73<$SCRATCH_MNT/snap1/changed_subcase1.file
exec 74<$SCRATCH_MNT/snap1/changed_subcase1.dir
unlink $SCRATCH_MNT/snap1/deleted.file
rmdir $SCRATCH_MNT/snap1/deleted.dir
unlink $SCRATCH_MNT/snap1/changed_subcase1.file
unlink $SCRATCH_MNT/snap1/changed_subcase1.dir/foo
rmdir $SCRATCH_MNT/snap1/changed_subcase1.dir

# Turn the snapshot "snap1" back to read-only mode.
$BTRFS_UTIL_PROG property set $SCRATCH_MNT/snap1 ro true

# Set the snapshot "snap2" to read-write mode and turn several inodes to
# orphans, so that the snapshot will look like this:
#
# .                                                                  (ino 256)
# |--- (orphan) changed_subcase1.file                                (ino 259)
# |--- (orphan) changed_subcase2.file                                (ino 260)
# |--- (orphan) changed_subcase1.dir/                                (ino 261)
# |--- (orphan) changed_subcase2.dir/                                (ino 263)
# |--- (orphan) new.file                                             (ino 265)
# |--- (orphan) new.dir/                                             (ino 266)
#
# Note: Same notice as above. Mention that orphan inodes don't have
# names(paths).
#
$BTRFS_UTIL_PROG property set $SCRATCH_MNT/snap2 ro false
exec 81<$SCRATCH_MNT/snap2/changed_subcase1.file
exec 82<$SCRATCH_MNT/snap2/changed_subcase1.dir
exec 83<$SCRATCH_MNT/snap2/changed_subcase2.file
exec 84<$SCRATCH_MNT/snap2/changed_subcase2.dir
exec 85<$SCRATCH_MNT/snap2/new.file
exec 86<$SCRATCH_MNT/snap2/new.dir
unlink $SCRATCH_MNT/snap2/changed_subcase1.file
unlink $SCRATCH_MNT/snap2/changed_subcase1.dir/foo
rmdir $SCRATCH_MNT/snap2/changed_subcase1.dir
unlink $SCRATCH_MNT/snap2/changed_subcase2.file
unlink $SCRATCH_MNT/snap2/changed_subcase2.dir/foo
rmdir $SCRATCH_MNT/snap2/changed_subcase2.dir
unlink $SCRATCH_MNT/snap2/new.file
rmdir $SCRATCH_MNT/snap2/new.dir

# Turn the snapshot "snap2" back to read-only mode.
$BTRFS_UTIL_PROG property set $SCRATCH_MNT/snap2 ro true

# Test that a full send operation can handle orphans with no paths
_run_btrfs_util_prog send -f $send_files_dir/1.snap $SCRATCH_MNT/snap1

# Test that an incremental send operation can handle orphans.
#
# Here're descriptions for the details:
#
# Case 1: new.file and new.dir (BTRFS_COMPARE_TREE_NEW)
#        |  send snapshot  | action
#  --------------------------------
#  nlink |        0        | ignore
#
# They are new inodes in the send snapshot ("snap2"), but they don't have
# paths because they have no links. Test that the send operation can ignore
# them in order not to generate the creation commands for them. Or it will
# fail, with -ENOENT, when trying to generate paths for them.
#
#
# Case 2: deleted.file and deleted.dir (BTRFS_COMPARE_TREE_DELETED)
#       | parent snapshot | action
# ----------------------------------
# nlink |        0        | as usual
#
# They're deleted in the parent snapshot ("snap1") but become orphans which
# have no paths. Test that no deletion commands will be generated as usual.
# This case didn't fail before.
#
#
# Case 3: changed_*.file and changed_*.dir (BTRFS_COMPARE_TREE_CHANGED)
#           |       | parent snapshot | send snapshot | action
# -----------------------------------------------------------------------
# subcase 1 | nlink |        0        |       0       | ignore
# subcase 2 | nlink |       >0        |       0       | new_gen(deletion)
#
# In subcase 1, test that the send operation can ignore them without trying
# to generate any commands.
#
# In subcase 2, test that the send operation can generate an unlink command
# for that file and test that it can generate a rename command for the
# non-empty directory first and a rmdir command to remove it finally. Or
# the receive operation will fail with a wrong unlink on a non-empty
# directory.
#
_run_btrfs_util_prog send -p $SCRATCH_MNT/snap1 -f $send_files_dir/2.snap \
		     $SCRATCH_MNT/snap2

$FSSUM_PROG -A -f -w $send_files_dir/1.fssum $SCRATCH_MNT/snap1
$FSSUM_PROG -A -f -w $send_files_dir/2.fssum \
	-x $SCRATCH_MNT/snap2/snap1 $SCRATCH_MNT/snap2

# Recreate the filesystem by receiving both send streams and verify we get
# the same content that the original filesystem had.
exec 71>&-
exec 72>&-
exec 73>&-
exec 74>&-
exec 81>&-
exec 82>&-
exec 83>&-
exec 84>&-
exec 85>&-
exec 86>&-
_scratch_unmount
_scratch_mkfs >>$seqres.full 2>&1
_scratch_mount

# Add the first snapshot to the new filesystem by applying the first send
# stream.
_run_btrfs_util_prog receive -f $send_files_dir/1.snap $SCRATCH_MNT

# Test the incremental send stream
_run_btrfs_util_prog receive -f $send_files_dir/2.snap $SCRATCH_MNT

$FSSUM_PROG -r $send_files_dir/1.fssum $SCRATCH_MNT/snap1
$FSSUM_PROG -r $send_files_dir/2.fssum $SCRATCH_MNT/snap2

status=0
exit
