btrfs: test fsync after increasing file size with truncate
authorFilipe Manana <fdmanana@suse.com>
Tue, 6 Jul 2021 14:42:17 +0000 (15:42 +0100)
committerEryu Guan <guaneryu@gmail.com>
Sun, 18 Jul 2021 06:43:02 +0000 (14:43 +0800)
Test that if we explicitly fsync a file that was previously renamed and
its size was increased through a truncate operation, after a power failure
the file has the size set by the truncate operation. Also, in between the
truncation and the fsync, there was a rename of another file in the same
directory and that file was also fsynced before we fsynced the file that
was truncated.

This currently fails on a 5.13 kernel and on Linus' master branch. It is
fixed by a patch with the following subject:

  "btrfs: fix unpersisted i_size on fsync after expanding truncate"

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
tests/btrfs/243 [new file with mode: 0755]
tests/btrfs/243.out [new file with mode: 0644]

diff --git a/tests/btrfs/243 b/tests/btrfs/243
new file mode 100755 (executable)
index 0000000..750b432
--- /dev/null
@@ -0,0 +1,93 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 SUSE Linux Products GmbH.  All Rights Reserved.
+#
+# FS QA Test 243
+#
+# Test that if we explicitly fsync a file that was previously renamed and its
+# size was increased through a truncate operation, after a power failure the
+# file has the size set by truncate operation. In between the truncation and
+# the fsync, there was a rename of another file in the same directory and that
+# file was also fsynced before we fsynced the file that was truncated.
+#
+. ./common/preamble
+_begin_fstest auto quick log
+
+_cleanup()
+{
+       _cleanup_flakey
+       cd /
+       rm -r -f $tmp.*
+}
+
+. ./common/rc
+. ./common/filter
+. ./common/dmflakey
+
+# real QA test starts here
+
+_supported_fs btrfs
+_require_scratch
+_require_dm_target flakey
+
+rm -f $seqres.full
+
+_scratch_mkfs >>$seqres.full 2>&1
+_require_metadata_journaling $SCRATCH_DEV
+_init_flakey
+_mount_flakey
+
+# Create our test files.
+touch $SCRATCH_MNT/foo
+$XFS_IO_PROG -f -c "pwrite -S 0xab 0 1M" $SCRATCH_MNT/bar | _filter_xfs_io
+
+# Make them durably persisted.
+sync
+
+# Fsync bar, this will be a noop since the file has not yet been modified in
+# the current transaction. The goal here is to clear BTRFS_INODE_NEEDS_FULL_SYNC
+# from the inode's runtime flags.
+$XFS_IO_PROG -c "fsync" $SCRATCH_MNT/bar
+
+# Now rename both files, without changing their parent directory.
+mv $SCRATCH_MNT/bar $SCRATCH_MNT/bar2
+mv $SCRATCH_MNT/foo $SCRATCH_MNT/foo2
+
+# Increase the size of bar2 with a truncate operation.
+$XFS_IO_PROG -c "truncate 2M" $SCRATCH_MNT/bar2
+
+# Now fsync foo2, this results in logging its parent inode (the root directory),
+# and logging the parent results in logging the inode of file bar2 (its inode
+# item and the new name). The inode of file bar2 is logged with an i_size of 0
+# bytes since it's logged in LOG_INODE_EXISTS mode, meaning we are only logging
+# its names (and xattrs if it had any) and the i_size of the inode will not be
+# changed when the log is replayed.
+$XFS_IO_PROG -c "fsync" $SCRATCH_MNT/foo2
+
+# Now explicitly fsync bar2. This resulted in doing nothing, not logging the
+# inode with the new i_size of 2M and the hole from file offset 1M to 2M.
+# Because the inode did not have the flag BTRFS_INODE_NEEDS_FULL_SYNC set, when
+# it was logged through the fsync of file foo2, its last_log_commit field was
+# updated, resulting in this explicit of file bar2 not doing anything.
+$XFS_IO_PROG -c "fsync" $SCRATCH_MNT/bar2
+
+echo "File bar2 content before power failure:"
+od -A d -t x1 $SCRATCH_MNT/bar2
+
+# Simulate a power failure and then mount again the filesystem to replay the log
+# tree.
+_flakey_drop_and_remount
+
+echo "File bar2 content after power failure:"
+od -A d -t x1 $SCRATCH_MNT/bar2
+
+# While here, also check that the rename of foo to foo2 was durably persisted,
+# even if it's not the specific regression the test is checking for.
+[ -f $SCRATCH_MNT/foo2 ] || echo "File name foo2 does not exists"
+[ -f $SCRATCH_MNT/foo ] && echo "File name foo still exists"
+
+_unmount_flakey
+
+# success, all done
+status=0
+exit
diff --git a/tests/btrfs/243.out b/tests/btrfs/243.out
new file mode 100644 (file)
index 0000000..424fbce
--- /dev/null
@@ -0,0 +1,15 @@
+QA output created by 243
+wrote 1048576/1048576 bytes at offset 0
+XXX Bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+File bar2 content before power failure:
+0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
+*
+1048576 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+*
+2097152
+File bar2 content after power failure:
+0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
+*
+1048576 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+*
+2097152