diff mbox series

[v2] generic: test fsync of file with no more hard links

Message ID 9a9c54e662293fb01591c256dd32243b1a149fe2.1742905343.git.fdmanana@suse.com (mailing list archive)
State New
Headers show
Series [v2] generic: test fsync of file with no more hard links | expand

Commit Message

Filipe Manana March 25, 2025, 12:24 p.m. UTC
From: Filipe Manana <fdmanana@suse.com>

Test that if we fsync a file that has no more hard links, power fail and
then mount the filesystem, after the journal/log is replayed, the file
doesn't exists anymore.

This exercises a bug recently found and fixed by the following patch for
the linux kernel:

   btrfs: fix fsync of files with no hard links not persisting deletion

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---

V2: Use the src/multi_open_unlink.c program with added options to sync
    filesystem after creating files and fsync files after the unlink.

 src/multi_open_unlink.c | 24 ++++++++++++++++++--
 tests/generic/764       | 50 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/764.out   |  2 ++
 3 files changed, 74 insertions(+), 2 deletions(-)
 create mode 100755 tests/generic/764
 create mode 100644 tests/generic/764.out

Comments

Zorro Lang March 26, 2025, 12:05 p.m. UTC | #1
On Tue, Mar 25, 2025 at 12:24:40PM +0000, fdmanana@kernel.org wrote:
> From: Filipe Manana <fdmanana@suse.com>
> 
> Test that if we fsync a file that has no more hard links, power fail and
> then mount the filesystem, after the journal/log is replayed, the file
> doesn't exists anymore.
> 
> This exercises a bug recently found and fixed by the following patch for
> the linux kernel:
> 
>    btrfs: fix fsync of files with no hard links not persisting deletion
> 
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
> ---
> 
> V2: Use the src/multi_open_unlink.c program with added options to sync
>     filesystem after creating files and fsync files after the unlink.

This version is good to me,

Reviewed-by: Zorro Lang <zlang@redhat.com>

If no more review points this week, I'll merge it this week.

Thanks,
Zorro

> 
>  src/multi_open_unlink.c | 24 ++++++++++++++++++--
>  tests/generic/764       | 50 +++++++++++++++++++++++++++++++++++++++++
>  tests/generic/764.out   |  2 ++
>  3 files changed, 74 insertions(+), 2 deletions(-)
>  create mode 100755 tests/generic/764
>  create mode 100644 tests/generic/764.out
> 
> diff --git a/src/multi_open_unlink.c b/src/multi_open_unlink.c
> index c221d39e..fb054e87 100644
> --- a/src/multi_open_unlink.c
> +++ b/src/multi_open_unlink.c
> @@ -28,7 +28,7 @@
>  void
>  usage(char *prog)
>  {
> -	fprintf(stderr, "Usage: %s [-e num-eas] [-f path_prefix] [-n num_files] [-s sleep_time] [-v ea-valuesize] \n", prog);
> +	fprintf(stderr, "Usage: %s [-e num-eas] [-f path_prefix] [-F] [-n num_files] [-s sleep_time] [-S] [-v ea-valuesize] \n", prog);
>  	exit(1);
>  }
>  
> @@ -44,8 +44,10 @@ main(int argc, char *argv[])
>  	int value_size = MAX_VALUELEN;
>  	int fd = -1;
>  	int i,j,c;
> +	int fsync_files = 0;
> +	int sync_fs = 0;
>  
> -	while ((c = getopt(argc, argv, "e:f:n:s:v:")) != EOF) {
> +	while ((c = getopt(argc, argv, "e:f:Fn:s:Sv:")) != EOF) {
>  		switch (c) {
>  			case 'e':   /* create eas */
>  				num_eas = atoi(optarg);
> @@ -53,12 +55,18 @@ main(int argc, char *argv[])
>  			case 'f':   /* file prefix */
>  				given_path = optarg;
>  				break;
> +			case 'F':   /* fsync files after unlink */
> +				fsync_files = 1;
> +				break;
>  			case 'n':   /* number of files */
>  				num_files = atoi(optarg);
>  				break;
>  			case 's':   /* sleep time */
>  				sleep_time = atoi(optarg);
>  				break;
> +			case 'S':   /* sync fs after creating files */
> +				sync_fs = 1;
> +				break;
>  			case 'v':  /* value size on eas */
>  				value_size = atoi(optarg);
>  				break;
> @@ -83,6 +91,12 @@ main(int argc, char *argv[])
>  			return 1;
>  		}
>  
> +		if (sync_fs && syncfs(fd) == -1) {
> +			fprintf(stderr, "%s: failed to sync filesystem: %s\n",
> +				prog, strerror(errno));
> +			return 1;
> +		}
> +
>  		/* set the EAs */
>  		for (j = 0; j < num_eas; j++) {
>  			int sts;
> @@ -111,6 +125,12 @@ main(int argc, char *argv[])
>  				prog, path, strerror(errno));
>  			return 1;
>  		}
> +
> +		if (fsync_files && fsync(fd) == -1) {
> +			fprintf(stderr, "%s: failed to fsync \"%s\": %s\n",
> +				prog, path, strerror(errno));
> +			return 1;
> +		}
>  	}
>  
>  	sleep(sleep_time);
> diff --git a/tests/generic/764 b/tests/generic/764
> new file mode 100755
> index 00000000..1b21bc02
> --- /dev/null
> +++ b/tests/generic/764
> @@ -0,0 +1,50 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2025 SUSE Linux Products GmbH.  All Rights Reserved.
> +#
> +# FS QA Test 764
> +#
> +# Test that if we fsync a file that has no more hard links, power fail and then
> +# mount the filesystem, after the journal/log is replayed, the file doesn't
> +# exists anymore.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick log
> +
> +_cleanup()
> +{
> +	_cleanup_flakey
> +	cd /
> +	rm -r -f $tmp.*
> +}
> +
> +. ./common/dmflakey
> +
> +[ "$FSTYP" = "btrfs" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \
> +	"btrfs: fix fsync of files with no hard links not persisting deletion"
> +
> +_require_scratch
> +_require_dm_target flakey
> +_require_test_program "multi_open_unlink"
> +
> +_scratch_mkfs >>$seqres.full 2>&1 || _fail "mkfs failed"
> +_require_metadata_journaling $SCRATCH_DEV
> +_init_flakey
> +_mount_flakey
> +
> +mkdir $SCRATCH_MNT/testdir
> +$here/src/multi_open_unlink -f $SCRATCH_MNT/testdir/foo -F -S -n 1 -s 0
> +
> +# Simulate a power failure and then mount again the filesystem to replay the
> +# journal/log.
> +_flakey_drop_and_remount
> +
> +# We don't expect the file to exist anymore, since it was fsynced when it had no
> +# more hard links.
> +ls $SCRATCH_MNT/testdir
> +
> +_unmount_flakey
> +
> +echo "Silence is golden"
> +status=0
> +exit
> diff --git a/tests/generic/764.out b/tests/generic/764.out
> new file mode 100644
> index 00000000..bb58e5b8
> --- /dev/null
> +++ b/tests/generic/764.out
> @@ -0,0 +1,2 @@
> +QA output created by 764
> +Silence is golden
> -- 
> 2.45.2
> 
>
diff mbox series

Patch

diff --git a/src/multi_open_unlink.c b/src/multi_open_unlink.c
index c221d39e..fb054e87 100644
--- a/src/multi_open_unlink.c
+++ b/src/multi_open_unlink.c
@@ -28,7 +28,7 @@ 
 void
 usage(char *prog)
 {
-	fprintf(stderr, "Usage: %s [-e num-eas] [-f path_prefix] [-n num_files] [-s sleep_time] [-v ea-valuesize] \n", prog);
+	fprintf(stderr, "Usage: %s [-e num-eas] [-f path_prefix] [-F] [-n num_files] [-s sleep_time] [-S] [-v ea-valuesize] \n", prog);
 	exit(1);
 }
 
@@ -44,8 +44,10 @@  main(int argc, char *argv[])
 	int value_size = MAX_VALUELEN;
 	int fd = -1;
 	int i,j,c;
+	int fsync_files = 0;
+	int sync_fs = 0;
 
-	while ((c = getopt(argc, argv, "e:f:n:s:v:")) != EOF) {
+	while ((c = getopt(argc, argv, "e:f:Fn:s:Sv:")) != EOF) {
 		switch (c) {
 			case 'e':   /* create eas */
 				num_eas = atoi(optarg);
@@ -53,12 +55,18 @@  main(int argc, char *argv[])
 			case 'f':   /* file prefix */
 				given_path = optarg;
 				break;
+			case 'F':   /* fsync files after unlink */
+				fsync_files = 1;
+				break;
 			case 'n':   /* number of files */
 				num_files = atoi(optarg);
 				break;
 			case 's':   /* sleep time */
 				sleep_time = atoi(optarg);
 				break;
+			case 'S':   /* sync fs after creating files */
+				sync_fs = 1;
+				break;
 			case 'v':  /* value size on eas */
 				value_size = atoi(optarg);
 				break;
@@ -83,6 +91,12 @@  main(int argc, char *argv[])
 			return 1;
 		}
 
+		if (sync_fs && syncfs(fd) == -1) {
+			fprintf(stderr, "%s: failed to sync filesystem: %s\n",
+				prog, strerror(errno));
+			return 1;
+		}
+
 		/* set the EAs */
 		for (j = 0; j < num_eas; j++) {
 			int sts;
@@ -111,6 +125,12 @@  main(int argc, char *argv[])
 				prog, path, strerror(errno));
 			return 1;
 		}
+
+		if (fsync_files && fsync(fd) == -1) {
+			fprintf(stderr, "%s: failed to fsync \"%s\": %s\n",
+				prog, path, strerror(errno));
+			return 1;
+		}
 	}
 
 	sleep(sleep_time);
diff --git a/tests/generic/764 b/tests/generic/764
new file mode 100755
index 00000000..1b21bc02
--- /dev/null
+++ b/tests/generic/764
@@ -0,0 +1,50 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 SUSE Linux Products GmbH.  All Rights Reserved.
+#
+# FS QA Test 764
+#
+# Test that if we fsync a file that has no more hard links, power fail and then
+# mount the filesystem, after the journal/log is replayed, the file doesn't
+# exists anymore.
+#
+. ./common/preamble
+_begin_fstest auto quick log
+
+_cleanup()
+{
+	_cleanup_flakey
+	cd /
+	rm -r -f $tmp.*
+}
+
+. ./common/dmflakey
+
+[ "$FSTYP" = "btrfs" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \
+	"btrfs: fix fsync of files with no hard links not persisting deletion"
+
+_require_scratch
+_require_dm_target flakey
+_require_test_program "multi_open_unlink"
+
+_scratch_mkfs >>$seqres.full 2>&1 || _fail "mkfs failed"
+_require_metadata_journaling $SCRATCH_DEV
+_init_flakey
+_mount_flakey
+
+mkdir $SCRATCH_MNT/testdir
+$here/src/multi_open_unlink -f $SCRATCH_MNT/testdir/foo -F -S -n 1 -s 0
+
+# Simulate a power failure and then mount again the filesystem to replay the
+# journal/log.
+_flakey_drop_and_remount
+
+# We don't expect the file to exist anymore, since it was fsynced when it had no
+# more hard links.
+ls $SCRATCH_MNT/testdir
+
+_unmount_flakey
+
+echo "Silence is golden"
+status=0
+exit
diff --git a/tests/generic/764.out b/tests/generic/764.out
new file mode 100644
index 00000000..bb58e5b8
--- /dev/null
+++ b/tests/generic/764.out
@@ -0,0 +1,2 @@ 
+QA output created by 764
+Silence is golden