]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Fix cloning into mmaped and cached file.
authorPawel Jakub Dawidek <pawel@dawidek.net>
Wed, 17 Jan 2024 16:51:07 +0000 (08:51 -0800)
committerGitHub <noreply@github.com>
Wed, 17 Jan 2024 16:51:07 +0000 (08:51 -0800)
If the destination file is mmaped and the mmaped region was already
read, so it is cached, we need to update mmaped pages after successful
clone using update_pages().

Reviewed-by: Alexander Motin <mav@FreeBSD.org>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Pointed out by: Ka Ho Ng <khng@freebsd.org>
Signed-off-by: Pawel Jakub Dawidek <pawel@dawidek.net>
Closes #15772

module/zfs/zfs_vnops.c
tests/runfiles/common.run
tests/test-runner/bin/zts-report.py.in
tests/zfs-tests/cmd/.gitignore
tests/zfs-tests/cmd/Makefile.am
tests/zfs-tests/cmd/clone_mmap_cached.c [new file with mode: 0644]
tests/zfs-tests/include/commands.cfg
tests/zfs-tests/tests/Makefile.am
tests/zfs-tests/tests/functional/block_cloning/block_cloning_clone_mmap_cached.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/block_cloning/setup.ksh

index 5377da401126cab7829778af3b101a8c0113d3ef..c8ff7b6432fdfebf0599f0712650d713191bcae6 100644 (file)
@@ -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);
 
index 47f8de0dd4c116d82ef446b144a6895870dc59af..7f0cce25237214fe98c924c1c389d9a51eea7d88 100644 (file)
@@ -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',
index c84f75cd806b163dc289803a7a0106c5c9783e49..ae4aa62754653bceed7868be822948d37f2d8ce0 100755 (executable)
@@ -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':
index a696fd387111c7def04cf276c38d684515c81074..0ed0a69eb013b48c885340cdddbb960bb56629ce 100644 (file)
@@ -2,6 +2,7 @@
 /btree_test
 /chg_usr_exec
 /clonefile
+/clone_mmap_cached
 /clone_mmap_write
 /devname2devid
 /dir_rd_update
index 379dc5e236c591b8e3689e8b9472adabed6876be..23848a82ffbd11acb42efa6abced0effd1982dcf 100644 (file)
@@ -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 (file)
index 0000000..c1cdf79
--- /dev/null
@@ -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);
+}
index 797078ed3ab6a853b1620c73f830d9e74a50f1fd..daa7945516825853f6f6f793cd76d5e9c8a7914e 100644 (file)
@@ -185,6 +185,7 @@ export ZFSTEST_FILES='badsend
     btree_test
     chg_usr_exec
     clonefile
+    clone_mmap_cached
     clone_mmap_write
     devname2devid
     dir_rd_update
index 2fc54301bbeb63324caf523286e1e194599dac81..4040e60434a79c8b4781c6efdddabf2e783fb771 100644 (file)
@@ -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 (executable)
index 0000000..b0ef8ec
--- /dev/null
@@ -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"
index 58441bf8f3ad5a7bae1b0417c6f6389b5ad3d3aa..a9b13f062a4e868a56e62c52bac57df3af412ca6 100755 (executable)
@@ -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"