diff --git a/module/zfs/dmu_recv.c b/module/zfs/dmu_recv.c index 45c7af2bd..66ec6c1b0 100644 --- a/module/zfs/dmu_recv.c +++ b/module/zfs/dmu_recv.c @@ -1018,6 +1018,22 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx) dmu_buf_will_dirty(newds->ds_dbuf, tx); dsl_dataset_phys(newds)->ds_flags |= DS_FLAG_INCONSISTENT; + /* + * When receiving, we refuse to accept streams that are missing the + * large block feature flag if the large block is already active + * (see ZFS_ERR_STREAM_LARGE_BLOCK_MISMATCH). To prevent this + * check from being spuriously triggered, we always activate + * the large block feature if the feature flag is present in the + * stream. This covers the case where the sending side has the feature + * active, but has since deleted the file containing large blocks. + */ + if (featureflags & DMU_BACKUP_FEATURE_LARGE_BLOCKS && + !dsl_dataset_feature_is_active(newds, SPA_FEATURE_LARGE_BLOCKS)) { + dsl_dataset_activate_feature(newds->ds_object, + SPA_FEATURE_LARGE_BLOCKS, (void *)B_TRUE, tx); + newds->ds_feature[SPA_FEATURE_LARGE_BLOCKS] = (void *)B_TRUE; + } + /* * Activate longname feature if received */ diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 90b8c0a31..62c3cb0c0 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -997,6 +997,7 @@ tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos', 'send_freeobjects', 'send_realloc_files', 'send_realloc_encrypted_files', 'send_spill_block', 'send_holds', 'send_hole_birth', 'send_mixed_raw', 'send-wR_encrypted_zvol', 'send_partial_dataset', 'send_invalid', + 'send_large_blocks_incremental', 'send_large_blocks_initial', 'send_doall', 'send_raw_spill_block', 'send_raw_ashift', 'send_raw_large_blocks', 'send_leak_keymaps'] tags = ['functional', 'rsend'] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index ba1c9c4ee..9b298a374 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -2060,6 +2060,8 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/rsend/send_holds.ksh \ functional/rsend/send_hole_birth.ksh \ functional/rsend/send_invalid.ksh \ + functional/rsend/send_large_blocks_incremental.ksh \ + functional/rsend/send_large_blocks_initial.ksh \ functional/rsend/send_leak_keymaps.ksh \ functional/rsend/send-L_toggle.ksh \ functional/rsend/send_mixed_raw.ksh \ diff --git a/tests/zfs-tests/tests/functional/rsend/send_large_blocks_incremental.ksh b/tests/zfs-tests/tests/functional/rsend/send_large_blocks_incremental.ksh new file mode 100755 index 000000000..eff7db7cf --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/send_large_blocks_incremental.ksh @@ -0,0 +1,83 @@ +#!/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 Austin Wise. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/include/properties.shlib + +# +# Description: +# Verifies that an incremental receive activates the large block feature when the stream was sent +# from a dataset whose large block feature was activated. +# +# Strategy: +# 1. Create a dataset with 1MB recordsize. +# 2. Create a snapshot at where the feature is inactive. +# 3. Create and delete a large file to activate the large_blocks feature. +# 4. Create a snapshot where the feature is active. +# 5. Send the initial snapshot to a second dataset, where the large_blocks feature remains inactive. +# 6. Send the second snapshot to the dataset incrementally, which should activate the large_blocks feature. +# + +verify_runnable "both" + +log_assert "Verify incremental receive handles inactive large_blocks feature correctly." + +function cleanup +{ + cleanup_pool $POOL + cleanup_pool $POOL2 +} +log_onexit cleanup + +function assert_feature_state { + typeset pool=$1 + typeset expected_state=$2 + + typeset actual_state=$(zpool get -H -o value feature@large_blocks $pool) + log_note "Zpool $pool feature@large_blocks=$actual_state" + if [[ "$actual_state" != "$expected_state" ]]; then + log_fail "pool $pool feature@large_blocks=$actual_state (expected '$expected_state')" + fi +} + +typeset srcfs=$POOL/src +typeset destfs=$POOL2/dest + +# Create a dataset with a large recordsize (1MB) where the feature is inactive in the initial snapshot +# but active in a later snapshots. +log_must zfs create -o recordsize=1M $srcfs +typeset mntpnt=$(get_prop mountpoint $srcfs) +log_must zfs snapshot $srcfs@feature-inactive +log_must dd if=/dev/urandom of=$mntpnt/big.bin bs=1M count=1 +log_must zpool sync $POOL +log_must rm $mntpnt/big.bin +log_must zfs snapshot $srcfs@feature-active + +# Assert initial state of pools +assert_feature_state $POOL "active" +assert_feature_state $POOL2 "enabled" + +# Initial send does not activate feature. +log_must eval "zfs send -p -L $srcfs@feature-inactive | zfs receive $destfs" +assert_feature_state $POOL2 "enabled" + +# Incremental send activates feature. +log_must eval "zfs send -L -i $srcfs@feature-inactive $srcfs@feature-active | zfs receive $destfs" +assert_feature_state $POOL2 "active" + +log_pass "Feature activation propagated successfully." diff --git a/tests/zfs-tests/tests/functional/rsend/send_large_blocks_initial.ksh b/tests/zfs-tests/tests/functional/rsend/send_large_blocks_initial.ksh new file mode 100755 index 000000000..19f06d106 --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/send_large_blocks_initial.ksh @@ -0,0 +1,86 @@ +#!/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 Austin Wise. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/include/properties.shlib + +# +# Description: +# Verifies that creating a dataset from a send stream propagates the activation of the large blocks +# feature, even if the send stream contains no large blocks. +# Regression test for https://github.com/openzfs/zfs/issues/18101 +# +# Strategy: +# 1. Create a dataset with 1MB recordsize. +# 2. Create and delete a large file to activate the large_blocks feature. +# 3. Send a full stream to a second dataset. +# 4. Send an incremental send back to the original dataset. +# + +verify_runnable "both" + +log_assert "Verify incremental receive handles inactive large_blocks feature correctly." + +function cleanup +{ + cleanup_pool $POOL + cleanup_pool $POOL2 +} +log_onexit cleanup + +function assert_feature_state { + typeset pool=$1 + typeset expected_state=$2 + + typeset actual_state=$(zpool get -H -o value feature@large_blocks $pool) + log_note "Zpool $pool feature@large_blocks=$actual_state" + if [[ "$actual_state" != "$expected_state" ]]; then + log_fail "pool $pool feature@large_blocks=$actual_state (expected '$expected_state')" + fi +} + +typeset repro=$POOL/repro +typeset second=$POOL2/second + +# Create a dataset with a large recordsize (1MB) +log_must zfs create -o recordsize=1M $repro +typeset mntpnt=$(get_prop mountpoint $repro) + +# Activate the large_blocks feature by creating a large file, then delete it +# This leaves the feature 'active' on the dataset level even though large blocks no longer exist. +log_must dd if=/dev/urandom of=$mntpnt/big.bin bs=1M count=1 +log_must zpool sync $POOL +log_must rm $mntpnt/big.bin + +# Assert initial state of pools +assert_feature_state $POOL "active" +assert_feature_state $POOL2 "enabled" + +# Create initial snapshot and send to 'second' dataset. +# The send stream will have the large blocks feature flag active but not actually contain any large blocks. +log_must zfs snapshot $repro@initial +log_must eval "zfs send -p -L $repro@initial | zfs receive $second" +assert_feature_state $POOL2 "active" + +# Send an incremental stream back to the original dataset. +# The send stream should have the large_blocks feature flag despite no large blocks ever being +# born in the 'second' dataset. +log_must zfs snapshot $second@second +log_must eval "zfs send -L -i $second@initial $second@second | zfs receive -F $repro" + +log_pass "Feature activation propagated successfully."