]> git-server-git.apps.pok.os.sepia.ceph.com Git - xfstests-dev.git/commitdiff
generic: test fsync against a removed directory
authorFilipe Manana <fdmanana@suse.com>
Thu, 9 Apr 2026 15:22:27 +0000 (16:22 +0100)
committerZorro Lang <zlang@kernel.org>
Mon, 13 Apr 2026 19:41:25 +0000 (03:41 +0800)
Test that if we fsync a directory that became empty after removing it
and we have a power failure, we are able to mount the filesystem and the
directory does not exists anymore.

This exercises a bug found in btrfs which is fixed by the following patch:

  btrfs: fix missing last_unlink_trans update when removing a directory

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Zorro Lang <zlang@kernel.org>
src/unlink-fsync.c
tests/generic/792 [new file with mode: 0755]
tests/generic/792.out [new file with mode: 0644]

index ce008c6b8d190b42bd06d12c9bb67aa74431a827..364f92265d367348b0300bd07c39fb6b3e139282 100644 (file)
@@ -4,10 +4,12 @@
  */
 
 /*
- * Utility to open an existing file, unlink it while holding an open fd on it
- * and then fsync the file before closing the fd.
+ * Utility to open an existing file or directory, unlink or rmdir it while
+ * holding an open fd on it and then fsync it before closing the fd.
  */
 
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 int main(int argc, char *argv[])
 {
+       struct stat path_stat;
        int fd;
        int ret;
 
        if (argc != 2) {
-               fprintf(stderr, "Use: %s <file path>\n", argv[0]);
+               fprintf(stderr, "Use: %s <file or dir path>\n", argv[0]);
                return 1;
        }
 
-       fd = open(argv[1], O_WRONLY, 0666);
+       ret = stat(argv[1], &path_stat);
+       if (ret == -1) {
+               perror("stat failed");
+               exit(1);
+       }
+
+       if (S_ISDIR(path_stat.st_mode))
+               fd = open(argv[1], O_RDONLY | O_DIRECTORY);
+       else
+               fd = open(argv[1], O_WRONLY, 0666);
+
        if (fd == -1) {
-               perror("failed to open file");
+               perror("failed to open file/dir");
                exit(1);
        }
 
-       ret = unlink(argv[1]);
-       if (ret == -1) {
-               perror("unlink failed");
-               exit(2);
+       if (S_ISDIR(path_stat.st_mode)) {
+               ret = rmdir(argv[1]);
+               if (ret == -1) {
+                       perror("rmdir failed");
+                       exit(2);
+               }
+       } else {
+               ret = unlink(argv[1]);
+               if (ret == -1) {
+                       perror("unlink failed");
+                       exit(2);
+               }
        }
 
        ret = fsync(fd);
diff --git a/tests/generic/792 b/tests/generic/792
new file mode 100755 (executable)
index 0000000..892ca2d
--- /dev/null
@@ -0,0 +1,69 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 SUSE S.A.  All Rights Reserved.
+#
+# FS QA Test 792
+#
+# Test that if we fsync a directory that became empty after removing it and we
+# have a power failure, we are able to mount the filesystem and the directory
+# does not exists anymore.
+#
+. ./common/preamble
+_begin_fstest auto quick log
+
+_cleanup()
+{
+       _cleanup_flakey
+       cd /
+       rm -r -f $tmp.*
+}
+
+. ./common/filter
+. ./common/dmflakey
+
+_require_scratch
+_require_test_program unlink-fsync
+_require_dm_target flakey
+
+_fixed_by_fs_commit btrfs xxxxxxxxxxxx \
+       "btrfs: fix missing last_unlink_trans update when removing a directory"
+
+_scratch_mkfs >> $seqres.full 2>&1 || _fail "mkfs failed"
+_require_metadata_journaling $SCRATCH_DEV
+_init_flakey
+_scratch_mount
+
+# Create our first directory and define its permissions (we will later change
+# them).
+mkdir $SCRATCH_MNT/dir1
+chmod a+rwx $SCRATCH_MNT/dir1
+
+# Created another directory inside that first directoy and then another one at
+# the top level.
+mkdir $SCRATCH_MNT/dir1/dir2
+mkdir $SCRATCH_MNT/dir3
+
+# Persist everything done so far.
+_scratch_sync
+
+# Do some change to the first directory, like change its permissions, fsync it.
+chmod o-rwx $SCRATCH_MNT/dir1
+$XFS_IO_PROG -c "fsync" $SCRATCH_MNT/dir1
+
+# Now move the second directory from dir1 to dir3, so that dir1 becomes empty.
+mv $SCRATCH_MNT/dir1/dir2 $SCRATCH_MNT/dir3/
+
+# Now open an fd for dir1, rmdir it and fsync it before closing the fd.
+$here/src/unlink-fsync $SCRATCH_MNT/dir1
+
+# Simulate a power failure and replay the log/journal.
+# The mount should succeed, dir1 must not exist anymore and dir2 must now be
+# inside dir3.
+_flakey_drop_and_remount
+
+echo -e "Filesystem content after power failure:\n"
+# Exclude 'lost+found' dir from ext4 and last line if it's blank (due to removal
+# of 'lost+found').
+ls -R $SCRATCH_MNT/ | grep -v 'lost+found' | sed -e '${/^$/d;}' | _filter_scratch
+
+_exit 0
diff --git a/tests/generic/792.out b/tests/generic/792.out
new file mode 100644 (file)
index 0000000..f806258
--- /dev/null
@@ -0,0 +1,10 @@
+QA output created by 792
+Filesystem content after power failure:
+
+SCRATCH_MNT/:
+dir3
+
+SCRATCH_MNT/dir3:
+dir2
+
+SCRATCH_MNT/dir3/dir2: