From: Filipe Manana Date: Thu, 9 Apr 2026 15:22:27 +0000 (+0100) Subject: generic: test fsync against a removed directory X-Git-Tag: v2026.04.20~4 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c4e03688706d008211c8bb9faeef8fbab3a45615;p=xfstests-dev.git generic: test fsync against a removed directory 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 Reviewed-by: Boris Burkov Signed-off-by: Zorro Lang --- diff --git a/src/unlink-fsync.c b/src/unlink-fsync.c index ce008c6b..364f9226 100644 --- a/src/unlink-fsync.c +++ b/src/unlink-fsync.c @@ -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 +#include #include #include #include @@ -15,24 +17,43 @@ int main(int argc, char *argv[]) { + struct stat path_stat; int fd; int ret; if (argc != 2) { - fprintf(stderr, "Use: %s \n", argv[0]); + fprintf(stderr, "Use: %s \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 index 00000000..892ca2d8 --- /dev/null +++ b/tests/generic/792 @@ -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 index 00000000..f806258c --- /dev/null +++ b/tests/generic/792.out @@ -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: