generic: test for file loss after mix of rename, fsync and inode eviction
authorFilipe Manana <>
Tue, 27 Jul 2021 10:24:59 +0000 (11:24 +0100)
committerEryu Guan <>
Sun, 1 Aug 2021 10:12:33 +0000 (18:12 +0800)
Test that if we fsync a directory A, evict A's inode, move one file
from directory A to directory B, fsync some other inode that is not
directory A, B or any inode inside these two directories, and then
power fail, the file that was moved is not lost.

This currently fails on btrfs and is fixed by commit ecc64fab7d49
("btrfs: fix lost inode on log replay after mix of fsync, rename and
inode eviction")

Signed-off-by: Filipe Manana <>
Reviewed-by: Eryu Guan <>
Signed-off-by: Eryu Guan <>
tests/generic/640 [new file with mode: 0755]
tests/generic/640.out [new file with mode: 0644]

diff --git a/tests/generic/640 b/tests/generic/640
new file mode 100755 (executable)
index 0000000..a9346d5
--- /dev/null
@@ -0,0 +1,101 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 SUSE Linux Products GmbH. All Rights Reserved.
+# FSQA Test No. 640
+# Test that if we fsync a directory A, evict A's inode, move one file from
+# directory A to a directory B, fsync some other inode that is not directory A,
+# B or any inode inside these two directories, and then power fail, the file
+# that was moved is not lost.
+. ./common/preamble
+_begin_fstest auto quick log
+# Override the default cleanup function.
+       _cleanup_flakey
+       cd /
+       rm -f $tmp.*
+# Import common functions.
+. ./common/filter
+. ./common/dmflakey
+# real QA test starts here
+_supported_fs generic
+_require_dm_target flakey
+_scratch_mkfs >>$seqres.full 2>&1
+_require_metadata_journaling $SCRATCH_DEV
+# Create two test directories, one with a file we will rename later.
+echo -n "hello world" > $SCRATCH_MNT/A/foo
+# Persist everything done so far.
+# Add some new file to directory A and fsync the directory.
+touch $SCRATCH_MNT/A/bar
+# Now evict all inodes from memory. To trigger the original problem on btrfs we
+# actually only needed to trigger eviction of A's inode, but there's no simple
+# way to evict a single inode, so evict everything.
+echo 2 > /proc/sys/vm/drop_caches
+# Now move file foo from directory A to directory B.
+# Now make an fsync to anything except A, B or any file inside them, like for
+# example create a file at the root directory and fsync this new file.
+touch $SCRATCH_MNT/baz
+$XFS_IO_PROG -c "fsync" $SCRATCH_MNT/baz
+# Simulate a power failure and then check file foo still exists.
+# Since file foo was not explicitly fsynced we can not guarantee that, for every
+# filesystem, after replaying the journal/log we have file foo inside directory A
+# or inside directory B. The file must exist however, and can only be found in
+# one of the directories, not on both.
+# At the moment of this writing, on f2fs file foo exists always at A/foo,
+# regardless of the fsync-mode mount option ("-o fsync_mode=posix" or
+# "-o fsync_mode=strict"). On ext4 and xfs it exists at B/foo. It is also
+# supposed to exist at B/foo on btrfs (at the moment it doesn't exist in
+# either directory due to a bug).
+if [ -f $SCRATCH_MNT/A/foo ]; then
+       echo "File foo data: $(cat $SCRATCH_MNT/A/foo)"
+       foo_in_a=1
+if [ -f $SCRATCH_MNT/B/foo ]; then
+       echo "File foo data: $(cat $SCRATCH_MNT/B/foo)"
+       foo_in_b=1
+if [ $foo_in_a -eq 1 ] && [ $foo_in_b -eq 1 ]; then
+       echo "File foo found in A/ and B/"
+elif [ $foo_in_a -eq 0 ] && [ $foo_in_b -eq 0 ]; then
+       echo "File foo is missing"
+# While here, also check that files bar and baz exist.
+[ -f $SCRATCH_MNT/A/bar ] || echo "File A/bar is missing"
+[ -f $SCRATCH_MNT/baz ] || echo "File baz is missing"
diff --git a/tests/generic/640.out b/tests/generic/640.out
new file mode 100644 (file)
index 0000000..a19db35
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 640
+File foo data: hello world