aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorUmer Saleem <[email protected]>2024-01-17 02:15:10 +0500
committerBrian Behlendorf <[email protected]>2024-01-19 12:28:02 -0800
commitd2f7b2e55767f8b84bcca79cf508f89c0471a92a (patch)
tree5c38634fcbbac2d570a6bf01a2017a25f387405b /tests
parent83c0ccc7cf5494090621ab7038386b8a4750e560 (diff)
ZTS: Test for clone, mmap and write for block cloning
For block cloning, if we mmap the cloned file and write from the map into the file, it triggers a panic in dbuf_redirty() on Linux. The same scenario causes data corruption on FreeBSD. Both these issues are fixed under PR#15656 and PR#15665. It would be good to add a test for this scenario in ZTS. The test program and issue was produced by @robn. Reviewed-by: Pawel Jakub Dawidek <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Alexander Motin <[email protected]> Reviewed-by: Ameer Hamza <[email protected]> Signed-off-by: Umer Saleem <[email protected]> Closes #15717
Diffstat (limited to 'tests')
-rw-r--r--tests/runfiles/common.run2
-rwxr-xr-xtests/test-runner/bin/zts-report.py.in2
-rw-r--r--tests/zfs-tests/cmd/.gitignore1
-rw-r--r--tests/zfs-tests/cmd/Makefile.am1
-rw-r--r--tests/zfs-tests/cmd/clone_mmap_write.c123
-rw-r--r--tests/zfs-tests/include/commands.cfg1
-rw-r--r--tests/zfs-tests/tests/Makefile.am1
-rwxr-xr-xtests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_write.ksh79
8 files changed, 209 insertions, 1 deletions
diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run
index 13d831283..f320c5423 100644
--- a/tests/runfiles/common.run
+++ b/tests/runfiles/common.run
@@ -79,7 +79,7 @@ tests = ['block_cloning_copyfilerange', 'block_cloning_copyfilerange_partial',
'block_cloning_cross_enc_dataset',
'block_cloning_copyfilerange_fallback_same_txg',
'block_cloning_replay', 'block_cloning_replay_encrypted',
- 'block_cloning_lwb_buffer_overflow']
+ 'block_cloning_lwb_buffer_overflow', 'block_cloning_clone_mmap_write']
tags = ['functional', 'block_cloning']
[tests/functional/bootfs]
diff --git a/tests/test-runner/bin/zts-report.py.in b/tests/test-runner/bin/zts-report.py.in
index 7bf4d05d5..c84f75cd8 100755
--- a/tests/test-runner/bin/zts-report.py.in
+++ b/tests/test-runner/bin/zts-report.py.in
@@ -287,6 +287,8 @@ elif sys.platform.startswith('linux'):
'bclone/bclone_samefs_data': ['SKIP', cfr_reason],
'bclone/bclone_samefs_embedded': ['SKIP', cfr_reason],
'bclone/bclone_samefs_hole': ['SKIP', cfr_reason],
+ 'block_cloning/block_cloning_clone_mmap_write':
+ ['SKIP', cfr_reason],
'block_cloning/block_cloning_copyfilerange':
['SKIP', cfr_reason],
'block_cloning/block_cloning_copyfilerange_cross_dataset':
diff --git a/tests/zfs-tests/cmd/.gitignore b/tests/zfs-tests/cmd/.gitignore
index 5f53b6871..a696fd387 100644
--- a/tests/zfs-tests/cmd/.gitignore
+++ b/tests/zfs-tests/cmd/.gitignore
@@ -2,6 +2,7 @@
/btree_test
/chg_usr_exec
/clonefile
+/clone_mmap_write
/devname2devid
/dir_rd_update
/draid
diff --git a/tests/zfs-tests/cmd/Makefile.am b/tests/zfs-tests/cmd/Makefile.am
index 1b915ae98..379dc5e23 100644
--- a/tests/zfs-tests/cmd/Makefile.am
+++ b/tests/zfs-tests/cmd/Makefile.am
@@ -3,6 +3,7 @@ scripts_zfs_tests_bindir = $(datadir)/$(PACKAGE)/zfs-tests/bin
scripts_zfs_tests_bin_PROGRAMS = %D%/chg_usr_exec
scripts_zfs_tests_bin_PROGRAMS += %D%/clonefile
+scripts_zfs_tests_bin_PROGRAMS += %D%/clone_mmap_write
scripts_zfs_tests_bin_PROGRAMS += %D%/cp_files
scripts_zfs_tests_bin_PROGRAMS += %D%/ctime
scripts_zfs_tests_bin_PROGRAMS += %D%/dir_rd_update
diff --git a/tests/zfs-tests/cmd/clone_mmap_write.c b/tests/zfs-tests/cmd/clone_mmap_write.c
new file mode 100644
index 000000000..6a5cd8721
--- /dev/null
+++ b/tests/zfs-tests/cmd/clone_mmap_write.c
@@ -0,0 +1,123 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or https://opensource.org/licenses/CDDL-1.0.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * This program clones the file, mmap it, and writes from the map into
+ * file. This scenario triggers a panic on Linux in dbuf_redirty(),
+ * which is fixed under PR#15656. On FreeBSD, the same test causes data
+ * corruption, which is fixed by PR#15665.
+ *
+ * It would be good to test for this scenario in ZTS. This program and
+ * issue was initially produced by @robn.
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#ifdef __FreeBSD__
+#define loff_t off_t
+#endif
+
+ssize_t
+copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int)
+ __attribute__((weak));
+
+static int
+open_file(const char *source)
+{
+ int fd;
+ if ((fd = open(source, O_RDWR | O_APPEND)) < 0) {
+ (void) fprintf(stderr, "Error opening %s\n", source);
+ exit(1);
+ }
+ sync();
+ return (fd);
+}
+
+static int
+clone_file(int sfd, long long size, const char *dest)
+{
+ int dfd;
+
+ if ((dfd = open(dest, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
+ (void) fprintf(stderr, "Error opening %s\n", dest);
+ exit(1);
+ }
+
+ if (copy_file_range(sfd, 0, dfd, 0, size, 0) < 0) {
+ (void) fprintf(stderr, "copy_file_range failed\n");
+ exit(1);
+ }
+
+ return (dfd);
+}
+
+static void *
+map_file(int fd, long long size)
+{
+ void *p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (p == MAP_FAILED) {
+ (void) fprintf(stderr, "mmap failed\n");
+ exit(1);
+ }
+
+ return (p);
+}
+
+static void
+map_write(void *p, int fd)
+{
+ if (pwrite(fd, p, 1024*128, 0) < 0) {
+ (void) fprintf(stderr, "write failed\n");
+ exit(1);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int sfd, dfd;
+ void *p;
+ struct stat sb;
+ if (argc != 3) {
+ (void) printf("usage: %s <input source file> "
+ "<clone destination file>\n", argv[0]);
+ exit(1);
+ }
+ sfd = open_file(argv[1]);
+ if (fstat(sfd, &sb) == -1) {
+ (void) fprintf(stderr, "fstat failed\n");
+ exit(1);
+ }
+ dfd = clone_file(sfd, sb.st_size, argv[2]);
+ p = map_file(dfd, sb.st_size);
+ map_write(p, dfd);
+ return (0);
+}
diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg
index c6f74cd81..797078ed3 100644
--- a/tests/zfs-tests/include/commands.cfg
+++ b/tests/zfs-tests/include/commands.cfg
@@ -185,6 +185,7 @@ export ZFSTEST_FILES='badsend
btree_test
chg_usr_exec
clonefile
+ clone_mmap_write
devname2devid
dir_rd_update
draid
diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am
index 33e97d22b..aeff66627 100644
--- a/tests/zfs-tests/tests/Makefile.am
+++ b/tests/zfs-tests/tests/Makefile.am
@@ -461,6 +461,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/bclone/setup.ksh \
functional/block_cloning/cleanup.ksh \
functional/block_cloning/setup.ksh \
+ functional/block_cloning/block_cloning_clone_mmap_write.ksh \
functional/block_cloning/block_cloning_copyfilerange_cross_dataset.ksh \
functional/block_cloning/block_cloning_copyfilerange_fallback.ksh \
functional/block_cloning/block_cloning_copyfilerange_fallback_same_txg.ksh \
diff --git a/tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_write.ksh b/tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_write.ksh
new file mode 100755
index 000000000..6215b3178
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_write.ksh
@@ -0,0 +1,79 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/block_cloning/block_cloning.kshlib
+
+#
+# DESCRIPTION:
+# A PANIC is triggered in dbuf_redirty() if we clone a file, mmap it
+# and write from the map into the file. PR#15656 fixes this scenario.
+# This scenario also causes data corruption on FreeBSD, which is fixed
+# by PR#15665.
+#
+# STRATEGY:
+# 1. Create a pool
+# 2. Create a test file
+# 3. Clone, mmap and write to the file using clone_mmap_write
+# 5. Synchronize cached writes
+# 6. Verfiy data is correctly written to the disk
+#
+
+verify_runnable "global"
+
+if is_linux && [[ $(linux_version) -lt $(linux_version "4.5") ]]; then
+ log_unsupported "copy_file_range not available before Linux 4.5"
+fi
+
+VDIR=$TEST_BASE_DIR/disk-bclone
+VDEV="$VDIR/a"
+
+function cleanup
+{
+ datasetexists $TESTPOOL && destroy_pool $TESTPOOL
+ rm -rf $VDIR
+}
+
+log_onexit cleanup
+
+log_assert "Test for clone, mmap and write scenario"
+
+log_must rm -rf $VDIR
+log_must mkdir -p $VDIR
+log_must truncate -s 1G $VDEV
+
+log_must zpool create -o feature@block_cloning=enabled $TESTPOOL $VDEV
+log_must zfs create $TESTPOOL/$TESTFS
+
+log_must dd if=/dev/urandom of=/$TESTPOOL/$TESTFS/file bs=1M count=512
+log_must clone_mmap_write /$TESTPOOL/$TESTFS/file /$TESTPOOL/$TESTFS/clone
+
+sync_pool $TESTPOOL
+log_must sync
+
+log_must have_same_content /$TESTPOOL/$TESTFS/file /$TESTPOOL/$TESTFS/clone
+blocks=$(get_same_blocks $TESTPOOL/$TESTFS file $TESTPOOL/$TESTFS clone)
+# FreeBSD's seq(1) leaves a trailing space, remove it with sed(1).
+log_must [ "$blocks" = "$(seq -s " " 1 4095 | sed 's/ $//')" ]
+
+log_pass "Clone, mmap and write does not cause data corruption or " \
+ "trigger panic"