mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
Fix deduce_nblkptr for large dnodes on receive
In Pre-SA filesystems (version < 5) deduce_nblkptr() derived nblkptr from DN_OLD_MAX_BONUSLEN, the bonus capacity of a single-slot dnode. The issue is receive_handle_existing_object() uses the value returned by deduce_nblkptr() to decide whether the object was reallocated. With the existing formula, multi-slot dnodes get an underestimated nblkptr so a non-raw incremental receive could free still-needed contents. Resolve this by passing dn_slots into deduce_nblkptr() and use the same bonus-capacity formula as dnode_(re)allocate(). Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Alexander Motin <[email protected]> Signed-off-by: Alek Pinchuk <[email protected]> Closes #18773
This commit is contained in:
1 parent
733f048fd6
commit
ffde77051c
5 files changed
+103
-7
No files matched your search
+12
-6
@@ -1622,14 +1622,20 @@ receive_read(dmu_recv_cookie_t *drc, int len, void *buf)
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
deduce_nblkptr(dmu_object_type_t bonus_type, uint64_t bonus_size)
|
||||
deduce_nblkptr(dmu_object_type_t bonus_type, uint64_t bonus_size,
|
||||
uint8_t dn_slots)
|
||||
{
|
||||
if (bonus_type == DMU_OT_SA) {
|
||||
return (1);
|
||||
} else {
|
||||
return (1 +
|
||||
((DN_OLD_MAX_BONUSLEN -
|
||||
MIN(DN_OLD_MAX_BONUSLEN, bonus_size)) >> SPA_BLKPTRSHIFT));
|
||||
/*
|
||||
* Match dnode_allocate() / dnode_reallocate(): nblkptr is
|
||||
* derived from the dnode's bonus capacity for dn_slots.
|
||||
*/
|
||||
return (MIN(DN_MAX_NBLKPTR,
|
||||
1 + ((DN_SLOTS_TO_BONUSLEN(dn_slots) -
|
||||
MIN(DN_SLOTS_TO_BONUSLEN(dn_slots), bonus_size)) >>
|
||||
SPA_BLKPTRSHIFT)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1706,10 +1712,10 @@ receive_handle_existing_object(const struct receive_writer_arg *rwa,
|
||||
{
|
||||
uint32_t indblksz = drro->drr_indblkshift ?
|
||||
1ULL << drro->drr_indblkshift : 0;
|
||||
int nblkptr = deduce_nblkptr(drro->drr_bonustype,
|
||||
drro->drr_bonuslen);
|
||||
uint8_t dn_slots = drro->drr_dn_slots != 0 ?
|
||||
drro->drr_dn_slots : DNODE_MIN_SLOTS;
|
||||
int nblkptr = deduce_nblkptr(drro->drr_bonustype,
|
||||
drro->drr_bonuslen, dn_slots);
|
||||
boolean_t do_free_range = B_FALSE;
|
||||
int err;
|
||||
|
||||
|
||||
+8
-1
@@ -785,6 +785,9 @@ dnode_allocate(dnode_t *dn, dmu_object_type_t ot, int blocksize, int ibs,
|
||||
if (bonustype == DMU_OT_SA) /* Maximize bonus space for SA */
|
||||
dn->dn_nblkptr = 1;
|
||||
else {
|
||||
/*
|
||||
* Keep in sync with deduce_nblkptr() in dmu_recv.c.
|
||||
*/
|
||||
dn->dn_nblkptr = MIN(DN_MAX_NBLKPTR,
|
||||
1 + ((DN_SLOTS_TO_BONUSLEN(dn_slots) - bonuslen) >>
|
||||
SPA_BLKPTRSHIFT));
|
||||
@@ -853,10 +856,14 @@ dnode_reallocate(dnode_t *dn, dmu_object_type_t ot, int blocksize,
|
||||
|
||||
if (bonustype == DMU_OT_SA) /* Maximize bonus space for SA */
|
||||
nblkptr = 1;
|
||||
else
|
||||
else {
|
||||
/*
|
||||
* Keep in sync with deduce_nblkptr() in dmu_recv.c.
|
||||
*/
|
||||
nblkptr = MIN(DN_MAX_NBLKPTR,
|
||||
1 + ((DN_SLOTS_TO_BONUSLEN(dn_slots) - bonuslen) >>
|
||||
SPA_BLKPTRSHIFT));
|
||||
}
|
||||
if (dn->dn_bonustype != bonustype)
|
||||
dn->dn_next_bonustype[tx->tx_txg & TXG_MASK] = bonustype;
|
||||
if (dn->dn_nblkptr != nblkptr)
|
||||
|
||||
@@ -1011,6 +1011,7 @@ tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos',
|
||||
'send_encrypted_freeobjects', 'send_encrypted_hierarchy',
|
||||
'send_encrypted_props', 'send_encrypted_truncated_files',
|
||||
'send_freeobjects', 'send_realloc_files', 'send_realloc_encrypted_files',
|
||||
'send_realloc_dnode_nblkptr',
|
||||
'send_spill_block', 'send_split_large_block', 'send_holds',
|
||||
'send_hole_birth', 'send_mixed_raw',
|
||||
'send-wR_encrypted_zvol',
|
||||
|
||||
@@ -2173,6 +2173,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/rsend/send_raw_ashift.ksh \
|
||||
functional/rsend/send_raw_spill_block.ksh \
|
||||
functional/rsend/send_raw_large_blocks.ksh \
|
||||
functional/rsend/send_realloc_dnode_nblkptr.ksh \
|
||||
functional/rsend/send_realloc_dnode_size.ksh \
|
||||
functional/rsend/send_realloc_encrypted_files.ksh \
|
||||
functional/rsend/send_realloc_files.ksh \
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
|
||||
#
|
||||
# This file and its contents are supplied under the terms of the
|
||||
# Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
# You may only use this file in accordance with the terms of version
|
||||
# 1.0 of the CDDL.
|
||||
#
|
||||
# A full copy of the text of the CDDL should have accompanied this
|
||||
# source. A copy of the CDDL is also available via the Internet at
|
||||
# http://www.illumos.org/license/CDDL.
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2026 by Connectwise
|
||||
#
|
||||
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
. $STF_SUITE/tests/functional/rsend/rsend.kshlib
|
||||
|
||||
#
|
||||
# Description:
|
||||
# Verify incremental receive of multi-slot (large) dnodes with non-SA bonus
|
||||
# buffers does not mis-detect reallocation via deduce_nblkptr().
|
||||
#
|
||||
# The rsend/send_realloc_{files,encrypted_files,dnode_size} tests exercise
|
||||
# reallocation with modern SA bonuses. For DMU_OT_SA, deduce_nblkptr()
|
||||
# always returns 1, matching allocation, so those tests cannot catch a
|
||||
# slots-unaware deduce_nblkptr() that still used DN_OLD_MAX_BONUSLEN.
|
||||
#
|
||||
# Pre-SA filesystems store znodes as DMU_OT_ZNODE with a fixed 0x108-byte
|
||||
# bonus. On a multi-slot dnode that yields nblkptr=3 at allocate time,
|
||||
# while the old deduce formula returned 1. On non-raw incremental receive
|
||||
# that made receive_handle_existing_object() free still-needed object
|
||||
# contents.
|
||||
#
|
||||
# Strategy:
|
||||
# 1. Create a version=4 filesystem with dnodesize=1k (non-SA, multi-slot).
|
||||
# 2. Write a multi-block file, snapshot, and fully receive it.
|
||||
# 3. Overwrite only the first record (OBJECT + partial WRITEs), snapshot,
|
||||
# and incrementally receive; verify contents match.
|
||||
#
|
||||
|
||||
verify_runnable "both"
|
||||
|
||||
log_assert "Incremental receive preserves large-dnode non-SA object contents"
|
||||
|
||||
typeset sendfile=$BACKDIR/fs-nblkptr
|
||||
|
||||
function cleanup
|
||||
{
|
||||
rm -f $sendfile $sendfile.incr
|
||||
datasetexists $POOL/fs && destroy_dataset $POOL/fs -rR
|
||||
datasetexists $POOL/newfs && destroy_dataset $POOL/newfs -rR
|
||||
}
|
||||
|
||||
log_onexit cleanup
|
||||
|
||||
#
|
||||
# Underestimated nblkptr must not free unchanged blocks on incr receive.
|
||||
#
|
||||
log_must zfs create -o version=4 -o dnodesize=1k -o recordsize=128k $POOL/fs
|
||||
log_must dd if=/dev/urandom of=/$POOL/fs/file bs=128k count=8
|
||||
log_must zfs snapshot $POOL/fs@a
|
||||
log_must eval "zfs send $POOL/fs@a > $sendfile"
|
||||
log_must eval "zfs recv $POOL/newfs < $sendfile"
|
||||
log_must zfs set atime=off $POOL/newfs
|
||||
|
||||
log_must dd if=/dev/urandom of=/$POOL/fs/file bs=128k count=1 conv=notrunc
|
||||
expected_cksum=$(recursive_cksum /$POOL/fs)
|
||||
log_must zfs snapshot $POOL/fs@b
|
||||
log_must eval "zfs send -i $POOL/fs@a $POOL/fs@b > $sendfile.incr"
|
||||
log_must eval "zfs recv $POOL/newfs < $sendfile.incr"
|
||||
|
||||
actual_cksum=$(recursive_cksum /$POOL/newfs)
|
||||
if [[ "$expected_cksum" != "$actual_cksum" ]]; then
|
||||
log_fail "Checksums differ ($expected_cksum != $actual_cksum)"
|
||||
fi
|
||||
|
||||
log_pass "Incremental receive preserves large-dnode non-SA object contents"
|
||||
Reference in new issue
Block a user