aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorBrian Behlendorf <[email protected]>2024-02-05 16:44:45 -0800
committerGitHub <[email protected]>2024-02-05 16:44:45 -0800
commit6dccdf501ea47bb8a45f00e4904d26efcb917ad4 (patch)
treec3a3f8525e4202edc8c8a97759fc354e5673664c /tests
parenta0d3fe72bf78e9035d231b00583cc5408c5cc63d (diff)
BRT: Fix FICLONE/FICLONERANGE shortened copy
On Linux the ioctl_ficlonerange() and ioctl_ficlone() system calls are expected to either fully clone the specified range or return an error. The range may be for an entire file. While internally ZFS supports cloning partial ranges there's no way to return the length cloned to the caller so we need to make this all or nothing. As part of this change support for the REMAP_FILE_CAN_SHORTEN flag has been added. When REMAP_FILE_CAN_SHORTEN is set zfs_clone_range() will return a shortened range when encountering pending dirty records. When it's clear zfs_clone_range() will block and wait for the records to be written out allowing the blocks to be cloned. Furthermore, the file range lock is held over the region being cloned to prevent it from being modified while cloning. This doesn't quite provide an atomic semantics since if an error is encountered only a portion of the range may be cloned. This will be converted to an error if REMAP_FILE_CAN_SHORTEN was not provided and returned to the caller. However, the destination file range is left in an undefined state. A test case has been added which exercises this functionality by verifying that `cp --reflink=never|auto|always` works correctly. Reviewed-by: Alexander Motin <[email protected]> Signed-off-by: Brian Behlendorf <[email protected]> Closes #15728 Closes #15842
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/include/tunables.cfg1
-rw-r--r--tests/zfs-tests/tests/Makefile.am1
-rwxr-xr-xtests/zfs-tests/tests/functional/cp_files/cp_files_002_pos.ksh161
5 files changed, 166 insertions, 1 deletions
diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run
index 7e0990b5d..05e8cdc8f 100644
--- a/tests/runfiles/common.run
+++ b/tests/runfiles/common.run
@@ -631,7 +631,7 @@ tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos',
tags = ['functional', 'compression']
[tests/functional/cp_files]
-tests = ['cp_files_001_pos', 'cp_stress']
+tests = ['cp_files_001_pos', 'cp_files_002_pos', 'cp_stress']
tags = ['functional', 'cp_files']
[tests/functional/crtime]
diff --git a/tests/test-runner/bin/zts-report.py.in b/tests/test-runner/bin/zts-report.py.in
index ae4aa6275..edfdd47ee 100755
--- a/tests/test-runner/bin/zts-report.py.in
+++ b/tests/test-runner/bin/zts-report.py.in
@@ -176,6 +176,7 @@ if sys.platform.startswith('freebsd'):
'cli_root/zpool_wait/zpool_wait_trim_cancel': ['SKIP', trim_reason],
'cli_root/zpool_wait/zpool_wait_trim_flag': ['SKIP', trim_reason],
'cli_root/zfs_unshare/zfs_unshare_008_pos': ['SKIP', na_reason],
+ 'cp_files/cp_files_002_pos': ['SKIP', na_reason],
'link_count/link_count_001': ['SKIP', na_reason],
'casenorm/mixed_create_failure': ['FAIL', 13215],
'mmap/mmap_sync_001_pos': ['SKIP', na_reason],
@@ -312,6 +313,7 @@ elif sys.platform.startswith('linux'):
['SKIP', cfr_reason],
'cli_root/zfs_rename/zfs_rename_002_pos': ['FAIL', known_reason],
'cli_root/zpool_reopen/zpool_reopen_003_pos': ['FAIL', known_reason],
+ 'cp_files/cp_files_002_pos': ['SKIP', cfr_reason],
'fault/auto_online_002_pos': ['FAIL', 11889],
'fault/auto_replace_001_pos': ['FAIL', 14851],
'fault/auto_spare_002_pos': ['FAIL', 11889],
diff --git a/tests/zfs-tests/include/tunables.cfg b/tests/zfs-tests/include/tunables.cfg
index e4e380aa7..a619b846d 100644
--- a/tests/zfs-tests/include/tunables.cfg
+++ b/tests/zfs-tests/include/tunables.cfg
@@ -94,6 +94,7 @@ VOL_MODE vol.mode zvol_volmode
VOL_RECURSIVE vol.recursive UNSUPPORTED
VOL_USE_BLK_MQ UNSUPPORTED zvol_use_blk_mq
BCLONE_ENABLED zfs_bclone_enabled zfs_bclone_enabled
+BCLONE_WAIT_DIRTY zfs_bclone_wait_dirty zfs_bclone_wait_dirty
XATTR_COMPAT xattr_compat zfs_xattr_compat
ZEVENT_LEN_MAX zevent.len_max zfs_zevent_len_max
ZEVENT_RETAIN_MAX zevent.retain_max zfs_zevent_retain_max
diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am
index 4040e6043..44fa0bf68 100644
--- a/tests/zfs-tests/tests/Makefile.am
+++ b/tests/zfs-tests/tests/Makefile.am
@@ -1394,6 +1394,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/compression/setup.ksh \
functional/cp_files/cleanup.ksh \
functional/cp_files/cp_files_001_pos.ksh \
+ functional/cp_files/cp_files_002_pos.ksh \
functional/cp_files/cp_stress.ksh \
functional/cp_files/setup.ksh \
functional/crtime/cleanup.ksh \
diff --git a/tests/zfs-tests/tests/functional/cp_files/cp_files_002_pos.ksh b/tests/zfs-tests/tests/functional/cp_files/cp_files_002_pos.ksh
new file mode 100755
index 000000000..60817449a
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cp_files/cp_files_002_pos.ksh
@@ -0,0 +1,161 @@
+#! /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
+#
+
+#
+# Copyright (c) 2024 by Lawrence Livermore National Security, LLC.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/bclone/bclone_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify all cp --reflink modes work with modified file.
+#
+# STRATEGY:
+# 1. Verify "cp --reflink=never|auto|always" behaves as expected.
+# Two different modes of operation are tested.
+#
+# a. zfs_bclone_wait_dirty=0: FICLONE and FICLONERANGE fail with EINVAL
+# when there are dirty blocks which cannot be immediately cloned.
+# This is the default behavior.
+#
+# b. zfs_bclone_wait_dirty=1: FICLONE and FICLONERANGE wait for
+# dirty blocks to be written to disk allowing the clone to succeed.
+# The downside to this is it may be slow which depending on the
+# situtation may defeat the point of making a clone.
+#
+
+verify_runnable "global"
+verify_block_cloning
+
+if ! is_linux; then
+ log_unsupported "cp --reflink is a GNU coreutils option"
+fi
+
+function cleanup
+{
+ datasetexists $TESTPOOL/cp-reflink && \
+ destroy_dataset $$TESTPOOL/cp-reflink -f
+ log_must set_tunable32 BCLONE_WAIT_DIRTY 0
+}
+
+function verify_copy
+{
+ src_cksum=$(sha256digest $1)
+ dst_cksum=$(sha256digest $2)
+
+ if [[ "$src_cksum" != "$dst_cksum" ]]; then
+ log_must ls -l $CP_TESTDIR
+ log_fail "checksum mismatch ($src_cksum != $dst_cksum)"
+ fi
+}
+
+log_assert "Verify all cp --reflink modes work with modified file"
+
+log_onexit cleanup
+
+SRC_FILE=src.data
+DST_FILE=dst.data
+SRC_SIZE=$(($RANDOM % 2048))
+
+# A smaller recordsize is used merely to speed up the test.
+RECORDSIZE=4096
+
+log_must zfs create -o recordsize=$RECORDSIZE $TESTPOOL/cp-reflink
+CP_TESTDIR=$(get_prop mountpoint $TESTPOOL/cp-reflink)
+
+log_must cd $CP_TESTDIR
+
+# Never wait on dirty blocks (zfs_bclone_wait_dirty=0)
+log_must set_tunable32 BCLONE_WAIT_DIRTY 0
+
+for mode in "never" "auto" "always"; do
+ log_note "Checking 'cp --reflink=$mode'"
+
+ # Create a new file and immediately copy it.
+ log_must dd if=/dev/urandom of=$SRC_FILE bs=$RECORDSIZE count=$SRC_SIZE
+
+ if [[ "$mode" == "always" ]]; then
+ log_mustnot cp --reflink=$mode $SRC_FILE $DST_FILE
+ log_must ls -l $CP_TESTDIR
+ else
+ log_must cp --reflink=$mode $SRC_FILE $DST_FILE
+ verify_copy $SRC_FILE $DST_FILE
+ fi
+ log_must rm -f $DST_FILE
+
+ # Append to an existing file and immediately copy it.
+ sync_pool $TESTPOOL
+ log_must dd if=/dev/urandom of=$SRC_FILE bs=$RECORDSIZE seek=$SRC_SIZE \
+ count=1 conv=notrunc
+ if [[ "$mode" == "always" ]]; then
+ log_mustnot cp --reflink=$mode $SRC_FILE $DST_FILE
+ log_must ls -l $CP_TESTDIR
+ else
+ log_must cp --reflink=$mode $SRC_FILE $DST_FILE
+ verify_copy $SRC_FILE $DST_FILE
+ fi
+ log_must rm -f $DST_FILE
+
+ # Overwrite a random range of an existing file and immediately copy it.
+ sync_pool $TESTPOOL
+ log_must dd if=/dev/urandom of=$SRC_FILE bs=$((RECORDSIZE / 2)) \
+ seek=$(($RANDOM % $SRC_SIZE)) count=$(($RANDOM % 16)) conv=notrunc
+ if [[ "$mode" == "always" ]]; then
+ log_mustnot cp --reflink=$mode $SRC_FILE $DST_FILE
+ log_must ls -l $CP_TESTDIR
+ else
+ log_must cp --reflink=$mode $SRC_FILE $DST_FILE
+ verify_copy $SRC_FILE $DST_FILE
+ fi
+ log_must rm -f $SRC_FILE $DST_FILE
+done
+
+# Wait on dirty blocks (zfs_bclone_wait_dirty=1)
+log_must set_tunable32 BCLONE_WAIT_DIRTY 1
+
+for mode in "never" "auto" "always"; do
+ log_note "Checking 'cp --reflink=$mode'"
+
+ # Create a new file and immediately copy it.
+ log_must dd if=/dev/urandom of=$SRC_FILE bs=$RECORDSIZE count=$SRC_SIZE
+ log_must cp --reflink=$mode $SRC_FILE $DST_FILE
+ verify_copy $SRC_FILE $DST_FILE
+ log_must rm -f $DST_FILE
+
+ # Append to an existing file and immediately copy it.
+ log_must dd if=/dev/urandom of=$SRC_FILE bs=$RECORDSIZE seek=$SRC_SIZE \
+ count=1 conv=notrunc
+ log_must cp --reflink=$mode $SRC_FILE $DST_FILE
+ verify_copy $SRC_FILE $DST_FILE
+ log_must rm -f $DST_FILE
+
+ # Overwrite a random range of an existing file and immediately copy it.
+ log_must dd if=/dev/urandom of=$SRC_FILE bs=$((RECORDSIZE / 2)) \
+ seek=$(($RANDOM % $SRC_SIZE)) count=$(($RANDOM % 16)) conv=notrunc
+ log_must cp --reflink=$mode $SRC_FILE $DST_FILE
+ verify_copy $SRC_FILE $DST_FILE
+ log_must rm -f $SRC_FILE $DST_FILE
+done
+
+log_pass