Allow rewrite skip cloned and snapshotted blocks

Rewrite of cloned and snapshotted blocks can allocate additional
space, that may be undesired.  In some cases it may have sense
to still rewrite snapshotted blocks, expecting the snapshots to
rotate with time, freeing space.  In other cases rewrite of cloned
blocks may be acceptable, despite persistent space usage increase.
For this reason add them as separate flags to `zfs rewrite`.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Rob Norris <robn@despairlabs.com>
Reviewed-by: Ameer Hamza <ahamza@ixsystems.com>
Signed-off-by: Alexander Motin <alexander.motin@TrueNAS.com>
Closes #18179
This commit is contained in:
Alexander Motin
2026-02-09 13:17:56 -05:00
committed by GitHub
parent 15fbf534c6
commit 2646bd5585
13 changed files with 273 additions and 32 deletions
+2 -1
View File
@@ -309,7 +309,8 @@ tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos']
tags = ['functional', 'cli_root', 'zfs_reservation']
[tests/functional/cli_root/zfs_rewrite]
tests = ['zfs_rewrite', 'zfs_rewrite_physical']
tests = ['zfs_rewrite', 'zfs_rewrite_physical', 'zfs_rewrite_skip_clone',
'zfs_rewrite_skip_snapshot']
tags = ['functional', 'cli_root', 'zfs_rewrite']
[tests/functional/cli_root/zfs_rollback]
+2 -1
View File
@@ -195,7 +195,8 @@ tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos']
tags = ['functional', 'cli_root', 'zfs_reservation']
[tests/functional/cli_root/zfs_rewrite]
tests = ['zfs_rewrite', 'zfs_rewrite_physical']
tests = ['zfs_rewrite', 'zfs_rewrite_physical', 'zfs_rewrite_skip_clone',
'zfs_rewrite_skip_snapshot']
tags = ['functional', 'cli_root', 'zfs_rewrite']
[tests/functional/cli_root/zfs_rollback]
+22
View File
@@ -3943,4 +3943,26 @@ function pop_coredump_pattern
esac
}
#
# get_same_blocks dataset1 path/to/file1 dataset2 path/to/file2 [key]
#
# Returns a space-separated list of the indexes (starting at 0) of the L0
# blocks that are shared between both files (by first DVA and checksum).
#
function get_same_blocks # dataset1 file1 dataset2 file2 [key]
{
typeset KEY=$5
if [ ${#KEY} -gt 0 ]; then
KEY="--key=$KEY"
fi
typeset zdbout1=$(mktemp)
typeset zdbout2=$(mktemp)
zdb $KEY -vvvvv $1 -O $2 | \
awk '/ L0 / { print l++ " " $3 " " $7 }' > $zdbout1
zdb $KEY -vvvvv $3 -O $4 | \
awk '/ L0 / { print l++ " " $3 " " $7 }' > $zdbout2
echo $(sort -n $zdbout1 $zdbout2 | uniq -d | cut -f1 -d' ')
rm -f $zdbout1 $zdbout2
}
. ${STF_SUITE}/include/kstat.shlib
+2
View File
@@ -876,6 +876,8 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zfs_rewrite/setup.ksh \
functional/cli_root/zfs_rewrite/zfs_rewrite.ksh \
functional/cli_root/zfs_rewrite/zfs_rewrite_physical.ksh \
functional/cli_root/zfs_rewrite/zfs_rewrite_skip_clone.ksh \
functional/cli_root/zfs_rewrite/zfs_rewrite_skip_snapshot.ksh \
functional/cli_root/zfs_rollback/cleanup.ksh \
functional/cli_root/zfs_rollback/setup.ksh \
functional/cli_root/zfs_rollback/zfs_rollback_001_pos.ksh \
@@ -35,27 +35,3 @@ function have_same_content
log_must [ "$hash1" = "$hash2" ]
}
#
# get_same_blocks dataset1 path/to/file1 dataset2 path/to/file2
#
# Returns a space-separated list of the indexes (starting at 0) of the L0
# blocks that are shared between both files (by first DVA and checksum).
# Assumes that the two files have the same content, use have_same_content to
# confirm that.
#
function get_same_blocks
{
KEY=$5
if [ ${#KEY} -gt 0 ]; then
KEY="--key=$KEY"
fi
typeset zdbout1=$(mktemp)
typeset zdbout2=$(mktemp)
zdb $KEY -vvvvv $1 -O $2 | \
awk '/ L0 / { print l++ " " $3 " " $7 }' > $zdbout1
zdb $KEY -vvvvv $3 -O $4 | \
awk '/ L0 / { print l++ " " $3 " " $7 }' > $zdbout2
echo $(sort -n $zdbout1 $zdbout2 | uniq -d | cut -f1 -d' ')
rm -f $zdbout1 $zdbout2
}
@@ -0,0 +1,83 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# 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) 2026, iXsystems, Inc.
#
# DESCRIPTION:
# Verify zfs rewrite -C flag skips BRT-cloned blocks.
#
# STRATEGY:
# 1. Create a test file and sync it.
# 2. Clone the file using block cloning to share blocks via BRT.
# 3. Rewrite clone with -C flag and verify blocks are NOT rewritten.
# 4. Rewrite clone without -C flag and verify blocks ARE rewritten.
. $STF_SUITE/include/libtest.shlib
verify_runnable "global"
function cleanup
{
rm -rf $TESTDIR/*
}
log_assert "zfs rewrite -C flag skips BRT-cloned blocks"
log_onexit cleanup
log_must zfs set recordsize=128k $TESTPOOL/$TESTFS
# Create source file (4 x 128KB = 4 blocks)
log_must dd if=/dev/urandom of=$TESTDIR/source bs=128k count=4
log_must sync_pool $TESTPOOL
# Clone the file using block cloning
log_must clonefile -f $TESTDIR/source $TESTDIR/clone
log_must sync_pool $TESTPOOL
# Verify blocks are actually shared initially
typeset blocks=$(get_same_blocks $TESTPOOL/$TESTFS source \
$TESTPOOL/$TESTFS clone)
log_must [ "$blocks" = "0 1 2 3" ]
# Test 1: Rewrite clone WITH -C flag (should skip all cloned blocks)
log_must zfs rewrite -C $TESTDIR/clone
log_must sync_pool $TESTPOOL
# Blocks should still be shared (all blocks were skipped)
typeset blocks=$(get_same_blocks $TESTPOOL/$TESTFS source \
$TESTPOOL/$TESTFS clone)
log_must [ "$blocks" = "0 1 2 3" ]
# Test 2: Rewrite clone WITHOUT -C flag (should rewrite all blocks)
log_must zfs rewrite $TESTDIR/clone
log_must sync_pool $TESTPOOL
# No blocks should be shared (clone has new blocks)
typeset blocks=$(get_same_blocks $TESTPOOL/$TESTFS source \
$TESTPOOL/$TESTFS clone)
log_must [ -z "$blocks" ]
log_pass
@@ -0,0 +1,74 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# 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) 2026, iXsystems, Inc.
#
# DESCRIPTION:
# Verify zfs rewrite -S flag skips snapshot-shared blocks.
#
# STRATEGY:
# 1. Create a test file and sync it.
# 2. Take a snapshot to share the blocks.
# 3. Rewrite with -S flag and verify blocks are NOT rewritten.
# 4. Rewrite without -S flag and verify blocks ARE rewritten.
. $STF_SUITE/include/libtest.shlib
function cleanup
{
rm -rf $TESTDIR/*
zfs destroy -R $TESTPOOL/$TESTFS@snap1 2>/dev/null || true
}
log_assert "zfs rewrite -S flag skips snapshot-shared blocks"
log_onexit cleanup
log_must zfs set recordsize=128k $TESTPOOL/$TESTFS
# Create test file (4 x 128KB = 4 blocks) and snapshot
log_must dd if=/dev/urandom of=$TESTDIR/testfile bs=128k count=4
log_must sync_pool $TESTPOOL
log_must zfs snapshot $TESTPOOL/$TESTFS@snap1
# Test 1: Rewrite WITH -S flag (should skip all snapshot-shared blocks)
log_must zfs rewrite -S $TESTDIR/testfile
log_must sync_pool $TESTPOOL
# All blocks should still be shared (all blocks were skipped)
typeset blocks=$(get_same_blocks $TESTPOOL/$TESTFS testfile \
$TESTPOOL/$TESTFS@snap1 testfile)
log_must [ "$blocks" = "0 1 2 3" ]
# Test 2: Rewrite WITHOUT -S flag (should rewrite all blocks)
log_must zfs rewrite $TESTDIR/testfile
log_must sync_pool $TESTPOOL
# No blocks should be shared (all blocks were rewritten)
typeset blocks=$(get_same_blocks $TESTPOOL/$TESTFS testfile \
$TESTPOOL/$TESTFS@snap1 testfile)
log_must [ -z "$blocks" ]
log_pass