aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--module/zfs/zfs_vnops.c4
-rw-r--r--tests/runfiles/common.run4
-rwxr-xr-xtests/test-runner/bin/zts-report.py.in1
-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_cached.c146
-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_cached.ksh86
-rwxr-xr-xtests/zfs-tests/tests/functional/block_cloning/setup.ksh3
10 files changed, 247 insertions, 1 deletions
diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c
index 5377da401..c8ff7b643 100644
--- a/module/zfs/zfs_vnops.c
+++ b/module/zfs/zfs_vnops.c
@@ -1349,6 +1349,10 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp,
break;
}
+ if (zn_has_cached_data(outzp, outoff, outoff + size - 1)) {
+ update_pages(outzp, outoff, size, outos);
+ }
+
zfs_clear_setid_bits_if_necessary(outzfsvfs, outzp, cr,
&clear_setid_bits_txg, tx);
diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run
index 47f8de0dd..7f0cce252 100644
--- a/tests/runfiles/common.run
+++ b/tests/runfiles/common.run
@@ -72,7 +72,9 @@ tags = ['functional', 'bclone']
timeout = 7200
[tests/functional/block_cloning]
-tests = ['block_cloning_copyfilerange', 'block_cloning_copyfilerange_partial',
+tests = ['block_cloning_clone_mmap_cached',
+ 'block_cloning_copyfilerange',
+ 'block_cloning_copyfilerange_partial',
'block_cloning_copyfilerange_fallback',
'block_cloning_disabled_copyfilerange',
'block_cloning_copyfilerange_cross_dataset',
diff --git a/tests/test-runner/bin/zts-report.py.in b/tests/test-runner/bin/zts-report.py.in
index c84f75cd8..ae4aa6275 100755
--- a/tests/test-runner/bin/zts-report.py.in
+++ b/tests/test-runner/bin/zts-report.py.in
@@ -287,6 +287,7 @@ 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_cached': ['SKIP', cfr_reason],
'block_cloning/block_cloning_clone_mmap_write':
['SKIP', cfr_reason],
'block_cloning/block_cloning_copyfilerange':
diff --git a/tests/zfs-tests/cmd/.gitignore b/tests/zfs-tests/cmd/.gitignore
index a696fd387..0ed0a69eb 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_cached
/clone_mmap_write
/devname2devid
/dir_rd_update
diff --git a/tests/zfs-tests/cmd/Makefile.am b/tests/zfs-tests/cmd/Makefile.am
index 379dc5e23..23848a82f 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_cached
scripts_zfs_tests_bin_PROGRAMS += %D%/clone_mmap_write
scripts_zfs_tests_bin_PROGRAMS += %D%/cp_files
scripts_zfs_tests_bin_PROGRAMS += %D%/ctime
diff --git a/tests/zfs-tests/cmd/clone_mmap_cached.c b/tests/zfs-tests/cmd/clone_mmap_cached.c
new file mode 100644
index 000000000..c1cdf796c
--- /dev/null
+++ b/tests/zfs-tests/cmd/clone_mmap_cached.c
@@ -0,0 +1,146 @@
+/*
+ * 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
+ */
+
+/*
+ * Copyright (c) 2024 by Pawel Jakub Dawidek
+ */
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.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 void *
+mmap_file(int fd, size_t size)
+{
+ void *p;
+
+ p = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (p == MAP_FAILED) {
+ (void) fprintf(stderr, "mmap failed: %s\n", strerror(errno));
+ exit(2);
+ }
+
+ return (p);
+}
+
+static void
+usage(const char *progname)
+{
+
+ /*
+ * -i cache input before copy_file_range(2).
+ * -o cache input before copy_file_range(2).
+ */
+ (void) fprintf(stderr, "usage: %s [-io] <input> <output>\n", progname);
+ exit(3);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int dfd, sfd;
+ size_t dsize, ssize;
+ void *dmem, *smem, *ptr;
+ off_t doff, soff;
+ struct stat sb;
+ bool cache_input, cache_output;
+ const char *progname;
+ int c;
+
+ progname = argv[0];
+ cache_input = cache_output = false;
+
+ while ((c = getopt(argc, argv, "io")) != -1) {
+ switch (c) {
+ case 'i':
+ cache_input = true;
+ break;
+ case 'o':
+ cache_output = true;
+ break;
+ default:
+ usage(progname);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2) {
+ usage(progname);
+ }
+
+ sfd = open(argv[0], O_RDONLY);
+ if (fstat(sfd, &sb) == -1) {
+ (void) fprintf(stderr, "fstat failed: %s\n", strerror(errno));
+ exit(2);
+ }
+ ssize = sb.st_size;
+ smem = mmap_file(sfd, ssize);
+
+ dfd = open(argv[1], O_RDWR);
+ if (fstat(dfd, &sb) == -1) {
+ (void) fprintf(stderr, "fstat failed: %s\n", strerror(errno));
+ exit(2);
+ }
+ dsize = sb.st_size;
+ dmem = mmap_file(dfd, dsize);
+
+ /*
+ * Hopefully it won't be compiled out.
+ */
+ if (cache_input) {
+ ptr = malloc(ssize);
+ assert(ptr != NULL);
+ memcpy(ptr, smem, ssize);
+ free(ptr);
+ }
+ if (cache_output) {
+ ptr = malloc(ssize);
+ assert(ptr != NULL);
+ memcpy(ptr, dmem, dsize);
+ free(ptr);
+ }
+
+ soff = doff = 0;
+ if (copy_file_range(sfd, &soff, dfd, &doff, ssize, 0) < 0) {
+ (void) fprintf(stderr, "copy_file_range failed: %s\n",
+ strerror(errno));
+ exit(2);
+ }
+
+ exit(memcmp(smem, dmem, ssize) == 0 ? 0 : 1);
+}
diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg
index 797078ed3..daa794551 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_cached
clone_mmap_write
devname2devid
dir_rd_update
diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am
index 2fc54301b..4040e6043 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_cached.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 \
diff --git a/tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_cached.ksh b/tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_cached.ksh
new file mode 100755
index 000000000..b0ef8ec99
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_cached.ksh
@@ -0,0 +1,86 @@
+#!/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:
+# When the destination file is mmaped and is already cached we need to
+# update mmaped pages after successful clone.
+#
+# STRATEGY:
+# 1. Create a pool.
+# 2. Create a two test files with random content.
+# 3. mmap the files, read them and clone from one to the other using
+# clone_mmap_cached.
+# 4. clone_mmap_cached also verifies if the content of the destination
+# file was updated while reading it from mmaped memory.
+#
+
+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 into mmaped and cached file"
+
+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
+
+for opts in "--" "-i" "-o" "-io"
+do
+ log_must dd if=/dev/urandom of=/$TESTPOOL/$TESTFS/src bs=1M count=1
+ log_must dd if=/dev/urandom of=/$TESTPOOL/$TESTFS/dst bs=1M count=1
+
+ # Clear cache.
+ log_must zpool export $TESTPOOL
+ log_must zpool import -d $VDIR $TESTPOOL
+
+ log_must clone_mmap_cached $opts /$TESTPOOL/$TESTFS/src /$TESTPOOL/$TESTFS/dst
+
+ sync_pool $TESTPOOL
+ log_must sync
+
+ log_must have_same_content /$TESTPOOL/$TESTFS/src /$TESTPOOL/$TESTFS/dst
+ blocks=$(get_same_blocks $TESTPOOL/$TESTFS src $TESTPOOL/$TESTFS dst)
+ # FreeBSD's seq(1) leaves a trailing space, remove it with sed(1).
+ log_must [ "$blocks" = "$(seq -s " " 0 7 | sed 's/ $//')" ]
+done
+
+log_pass "Clone properly updates mmapped and cached pages"
diff --git a/tests/zfs-tests/tests/functional/block_cloning/setup.ksh b/tests/zfs-tests/tests/functional/block_cloning/setup.ksh
index 58441bf8f..a9b13f062 100755
--- a/tests/zfs-tests/tests/functional/block_cloning/setup.ksh
+++ b/tests/zfs-tests/tests/functional/block_cloning/setup.ksh
@@ -30,6 +30,9 @@
if ! command -v clonefile > /dev/null ; then
log_unsupported "clonefile program required to test block cloning"
fi
+if ! command -v clone_mmap_cached > /dev/null ; then
+ log_unsupported "clone_mmap_cached program required to test block cloning"
+fi
verify_runnable "global"