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:
Alek P authored and GitHub committed 2026-07-14 09:56:07 -07:00
1 parent 733f048fd6
commit ffde77051c
5 files changed
+103 -7

No files matched your search

+12 -6
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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',
+1
View File
@@ -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"