diff --git a/cmd/zstream/zstream_dump.c b/cmd/zstream/zstream_dump.c index 6ccc57204c8e..7757ee3b1754 100644 --- a/cmd/zstream/zstream_dump.c +++ b/cmd/zstream/zstream_dump.c @@ -385,6 +385,20 @@ zstream_do_dump(int argc, char *argv[]) (void) ssread(buf, sz, &zc); if (ferror(send_stream)) perror("fread"); + + uint8_t *nv_header = (uint8_t *)buf; + boolean_t xdr = nv_header[0] == NV_ENCODE_XDR; + boolean_t big_endian = nv_header[1] == 0; + const char *nc; + if (xdr) { + nc = "NV_ENCODE_XDR"; + } else if (big_endian) { + nc = "NV_ENCODE_NATIVE (big-endian)"; + } else { + nc = "NV_ENCODE_NATIVE (little-endian)"; + } + printf("nvlist encoding = %s\n", nc); + err = nvlist_unpack(buf, sz, &nv, 0); if (err) { perror(strerror(err)); diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index 4c354722e4f8..d931d9432f04 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -2241,6 +2241,37 @@ setup_send_progress(struct dmu_send_params *dspp) return (dssp); } +/* + * Payloads must be multiples of 8 bytes for historical compatibility, but + * XDR-encoded nvlists are sized in multiples of 4 bytes and may need padding. + * + * Here we do the simplest possible thing and copy the data to a separate + * buffer. Not ideal in terms of performance and memory use, but most BEGIN + * nvlists are small or absent, the allocation is momentary, and we'll need + * to do this at most once per dataset. + * + * It's OK if there is extra data after a packed nvlist on the receiving + * side because packed nvlists have an internal end-of-list marker. + * + * The new buffer is allocated with kmem_alloc() and can be freed with + * fnvlist_pack_free(), like the original. + */ +static inline void +pad_packed_nvlist(char **buffer, size_t *size) +{ + size_t size_in = *size; + size_t extra_bytes = P2ROUNDUP(size_in, 8) - size_in; + if (extra_bytes != 0) { + size_t expanded_size = size_in + extra_bytes; + char *longbuf = kmem_alloc(expanded_size, KM_SLEEP); + memcpy(longbuf, *buffer, size_in); + memset(longbuf + size_in, 0, extra_bytes); + fnvlist_pack_free(*buffer, size_in); + *buffer = longbuf; + *size = expanded_size; + } +} + /* * Actually do the bulk of the work in a zfs send. * @@ -2474,7 +2505,7 @@ dmu_send_impl(struct dmu_send_params *dspp) dsl_pool_rele(dp, tag); - void *payload = NULL; + char *payload = NULL; size_t payload_len = 0; nvlist_t *nvl = fnvlist_alloc(); @@ -2548,7 +2579,9 @@ dmu_send_impl(struct dmu_send_params *dspp) } if (!nvlist_empty(nvl)) { - payload = fnvlist_pack(nvl, &payload_len); + VERIFY0(nvlist_pack(nvl, &payload, &payload_len, + NV_ENCODE_XDR, KM_SLEEP)); + pad_packed_nvlist(&payload, &payload_len); drr->drr_payloadlen = payload_len; } diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 14e4bd79f853..f18835da74bc 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -1025,6 +1025,15 @@ tests = ['scrub_mirror_001_pos', 'scrub_mirror_002_pos', 'scrub_mirror_003_pos', 'scrub_mirror_004_pos'] tags = ['functional', 'scrub_mirror'] +[tests/functional/send_xdr_encoding] +tests = ['xdr_bookmark_raw', 'xdr_bookmark_raw_with_write', + 'xdr_incr_from_bookmark', 'xdr_incr_from_redacted', 'xdr_raw', + 'xdr_redacted_full', 'xdr_redacted_received', + 'xdr_redacted_received_raw', 'xdr_replication', 'xdr_resume', + 'xdr_resume_bookmark_raw', 'xdr_resume_bookmark_raw_with_write', + 'xdr_resume_raw', 'xdr_resume_redacted'] +tags = ['functional', 'send_xdr_encoding'] + [tests/functional/slog] tests = ['slog_001_pos', 'slog_002_pos', 'slog_003_pos', 'slog_004_pos', 'slog_005_pos', 'slog_006_pos', 'slog_007_pos', 'slog_008_neg', diff --git a/tests/test-runner/bin/zts-report.py.in b/tests/test-runner/bin/zts-report.py.in index 29d2760ccb8f..2cbd2f02a318 100755 --- a/tests/test-runner/bin/zts-report.py.in +++ b/tests/test-runner/bin/zts-report.py.in @@ -253,6 +253,8 @@ maybe = { 'renameat2/setup': ['SKIP', renameat2_reason], 'reservation/reservation_008_pos': ['FAIL', 7741], 'reservation/reservation_018_pos': ['FAIL', 5642], + 'send_xdr_encoding/xdr_bookmark_raw_with_write': ['FAIL', 18491], + 'send_xdr_encoding/xdr_resume_bookmark_raw_with_write': ['FAIL', 18491], 'snapshot/clone_001_pos': ['FAIL', known_reason], 'snapshot/snapshot_006_pos': ['FAIL', known_reason], 'snapshot/snapshot_009_pos': ['FAIL', 7961], diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 28acc6f3af12..5dd350ece7c3 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -376,6 +376,8 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \ functional/rsend/rsend.kshlib \ functional/scrub_mirror/default.cfg \ functional/scrub_mirror/scrub_mirror_common.kshlib \ + functional/send_xdr_encoding/send_xdr_encoding.cfg \ + functional/send_xdr_encoding/send_xdr_encoding.kshlib \ functional/slog/slog.cfg \ functional/slog/slog.kshlib \ functional/snapshot/snapshot.cfg \ @@ -2129,6 +2131,22 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/scrub_mirror/scrub_mirror_003_pos.ksh \ functional/scrub_mirror/scrub_mirror_004_pos.ksh \ functional/scrub_mirror/setup.ksh \ + functional/send_xdr_encoding/cleanup.ksh \ + functional/send_xdr_encoding/setup.ksh \ + functional/send_xdr_encoding/xdr_bookmark_raw.ksh \ + functional/send_xdr_encoding/xdr_bookmark_raw_with_write.ksh \ + functional/send_xdr_encoding/xdr_incr_from_bookmark.ksh \ + functional/send_xdr_encoding/xdr_incr_from_redacted.ksh \ + functional/send_xdr_encoding/xdr_raw.ksh \ + functional/send_xdr_encoding/xdr_redacted_full.ksh \ + functional/send_xdr_encoding/xdr_redacted_received.ksh \ + functional/send_xdr_encoding/xdr_redacted_received_raw.ksh \ + functional/send_xdr_encoding/xdr_replication.ksh \ + functional/send_xdr_encoding/xdr_resume.ksh \ + functional/send_xdr_encoding/xdr_resume_bookmark_raw.ksh \ + functional/send_xdr_encoding/xdr_resume_bookmark_raw_with_write.ksh \ + functional/send_xdr_encoding/xdr_resume_raw.ksh \ + functional/send_xdr_encoding/xdr_resume_redacted.ksh \ functional/slog/cleanup.ksh \ functional/slog/setup.ksh \ functional/slog/slog_001_pos.ksh \ diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/cleanup.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/cleanup.ksh new file mode 100755 index 000000000000..8261885e6512 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/cleanup.ksh @@ -0,0 +1,27 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +destroy_pool $POOL +destroy_pool $POOL2 + +log_pass diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/send_xdr_encoding.cfg b/tests/zfs-tests/tests/functional/send_xdr_encoding/send_xdr_encoding.cfg new file mode 100644 index 000000000000..e4999a3ca29c --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/send_xdr_encoding.cfg @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +read -r DISK1 DISK2 _ <<<"$DISKS" +export DISK1 DISK2 + +export POOL=$TESTPOOL +export POOL2=$TESTPOOL2 diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib b/tests/zfs-tests/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib new file mode 100644 index 000000000000..8e36b748439f --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib @@ -0,0 +1,71 @@ +#!/bin/ksh +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.cfg + +# +# Verify that the DRR_BEGIN records in the given send stream encode their +# nvlist payloads with NV_ENCODE_XDR (and not NV_ENCODE_NATIVE). +# +# DRR_BEGIN records that carry an nvlist payload (raw sends, redacted sends, +# resumed sends, and combinations thereof) must encode that payload with +# NV_ENCODE_XDR so the resulting stream can be portably consumed across +# endianness. Encoding the payload with NV_ENCODE_NATIVE produces a stream +# that is unreadable on a receiver of the opposite endianness. +# +# zstream dump prints a single "nvlist encoding = ..." line per DRR_BEGIN +# record that carries an nvlist payload. The possible values are: +# +# NV_ENCODE_XDR +# NV_ENCODE_NATIVE (big-endian) +# NV_ENCODE_NATIVE (little-endian) +# +# Every test in this suite generates a stream whose DRR_BEGIN record +# carries an nvlist payload, so the pass criterion is: +# +# - At least one NV_ENCODE_XDR line appears, AND +# - No NV_ENCODE_NATIVE line appears. +# +# Requiring at least one XDR line catches the case where zstream dump +# itself fails before producing any encoding output. Asserting on dump +# content rather than dump exit status means a partial dump can still +# fail the test on an NV_ENCODE_NATIVE seen before the failure point. +# +function verify_xdr_nvlist_encoding +{ + typeset stream=$1 + typeset out + + [[ -f "$stream" ]] || \ + log_fail "verify_xdr_nvlist_encoding: stream not found: $stream" + + out=$(zstream dump "$stream" 2>/dev/null) + + if echo "$out" | grep -q 'NV_ENCODE_NATIVE'; then + log_fail "verify_xdr_nvlist_encoding: " \ + "NV_ENCODE_NATIVE found in $stream" + fi + if ! echo "$out" | grep -q 'NV_ENCODE_XDR'; then + log_fail "verify_xdr_nvlist_encoding: " \ + "no NV_ENCODE_XDR found in $stream" + fi +} diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/setup.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/setup.ksh new file mode 100755 index 000000000000..609acba3a224 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/setup.ksh @@ -0,0 +1,29 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +verify_disk_count "$DISKS" 2 + +create_pool $POOL $DISK1 +create_pool $POOL2 $DISK2 + +log_pass diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_bookmark_raw.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_bookmark_raw.ksh new file mode 100755 index 000000000000..9ba10d9e605a --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_bookmark_raw.ksh @@ -0,0 +1,93 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A raw incremental send from a redaction bookmark on an encrypted dataset +# (zfs send -w -i ds#book ds@snap) carries both BEGINNV_REDACT_FROM_SNAPS +# and crypt_keydata in its DRR_BEGIN nvlist payload. Verify that this +# combined payload is XDR-encoded and the stream can be received. +# +# Strategy: +# 1. Create an encrypted source dataset with a redaction bookmark and a +# later snapshot. +# 2. Establish a raw base on the receiver via zfs send -w of the bookmark's +# source snapshot. +# 3. zfs send -w -i sendfs#book sendfs@s1 to a file. +# 4. Verify that the resulting stream is XDR-encoded. +# 5. Verify that the zfs receive succeeds. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_bookmark_raw_src" +clonefs="$POOL/xdr_bookmark_raw_clone" +recvfs="$POOL2/xdr_bookmark_raw_recv" +keyfile="/$POOL/xdr_bookmark_raw.key" +full_stream="/$POOL/xdr_bookmark_raw_full.zsend" +incr_stream="/$POOL/xdr_bookmark_raw_incr.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $keyfile $full_stream $incr_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a raw incremental from a redaction bookmark is " \ + "XDR-encoded and receivable" + +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $sendfs + +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +# The clone inherits encryption from $sendfs. +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Take @s1 with no intervening writes. See xdr_bookmark_raw_with_write.ksh +# for a variant that includes a post-redact write; that variant exercises +# a known kernel-side issue (#18491) and may flake. +log_must zfs snapshot $sendfs@s1 + +# Establish a raw base on the receiver. +log_must eval "zfs send -w $sendfs@s0 > $full_stream" +log_must eval "zfs receive $recvfs < $full_stream" + +# Raw incremental from the redaction bookmark. This is the test focus. +log_must eval "zfs send -w -i $sendfs#redaction-bookmark $sendfs@s1 > \ + $incr_stream" +verify_xdr_nvlist_encoding $incr_stream +log_must eval "zfs receive $recvfs < $incr_stream" + +log_pass "BEGIN nvlist of a raw incremental from a redaction bookmark is " \ + "XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_bookmark_raw_with_write.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_bookmark_raw_with_write.ksh new file mode 100755 index 000000000000..c58735f04d40 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_bookmark_raw_with_write.ksh @@ -0,0 +1,107 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# This is the post-redact-write variant of xdr_bookmark_raw, separated out +# because of a known issue (#18491) that causes it to fail roughly 30% of +# the time. It's included here as a test for issue #18491 until the exact +# source of that problem can be pinned down more specifically. +# +# Known issue: openzfs/zfs#18491 +# +# On a freshly-created pool, `zfs send -w -i ds#book ds@snap` intermittently +# fails with EACCES whenever there is data-modifying activity between the +# `zfs redact` that created the bookmark and the subsequent send. This EACCES +# is surfaced to userspace as the misleading message "dataset key must be +# loaded," although the key remains loaded throughout. +# +# The reproducer script included in the issue report typically triggers the +# problem within about 10 iterations on a fresh pool. Disk-sync mitigations +# (zpool sync, with or without `-f`, with or without sleep, single or doubled, +# applied at any reasonable point) do not avert the problem. CI runs that +# include the test in this file reproduce the failure regularly (though +# intermittently) across multiple distributions. xdr_resume_bookmark_raw.ksh +# removes the post-redact write (which is not essential to the test) and +# therefore runs reliably. +# +# When this test fails, the failure marker is the libzfs warning +# "dataset key must be loaded" on stderr from the first `zfs send -w -i` +# line below (the one that produces the stream we then truncate), and a +# non-zero exit from that send. The test does not attempt to distinguish +# the known-issue failure from other possible failures. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_bookmark_raw_with_write_src" +clonefs="$POOL/xdr_bookmark_raw_with_write_clone" +recvfs="$POOL2/xdr_bookmark_raw_with_write_recv" +keyfile="/$POOL/xdr_bookmark_raw_with_write.key" +full_stream="/$POOL/xdr_bookmark_raw_with_write_full.zsend" +incr_stream="/$POOL/xdr_bookmark_raw_with_write_incr.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $keyfile $full_stream $incr_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a raw incremental from a redaction bookmark, " \ + "with a post-redact write, is XDR-encoded and receivable " \ + "(known to flake; see openzfs/zfs#18491)" + +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $sendfs + +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +# The clone inherits encryption from $sendfs. +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Post-redact write: the trigger for openzfs/zfs#18491. +log_must dd if=/dev/urandom of=/$sendfs/f3 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s1 + +# Establish a raw base on the receiver. +log_must eval "zfs send -w $sendfs@s0 > $full_stream" +log_must eval "zfs receive $recvfs < $full_stream" + +# The next line is what races. On failure it exits with EACCES rendered +# as "dataset key must be loaded". +log_must eval "zfs send -w -i $sendfs#redaction-bookmark $sendfs@s1 > \ + $incr_stream" +verify_xdr_nvlist_encoding $incr_stream +log_must eval "zfs receive $recvfs < $incr_stream" + +log_pass "BEGIN nvlist of a raw incremental from a redaction bookmark, " \ + "with a post-redact write, is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_incr_from_bookmark.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_incr_from_bookmark.ksh new file mode 100755 index 000000000000..ab04f6aa6033 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_incr_from_bookmark.ksh @@ -0,0 +1,88 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# An incremental send from a redaction bookmark (zfs send -i ds#book ds@snap) +# carries BEGINNV_REDACT_FROM_SNAPS in its DRR_BEGIN nvlist payload (via the +# from_rl path). Verify that this payload is XDR-encoded and the stream can +# be received. +# +# Strategy: +# 1. Create a source dataset with a redaction bookmark. +# 2. Send a redacted full stream from that bookmark's source snapshot +# and receive it into a second pool as a base. +# 3. Add data and a new snapshot on the source. +# 4. zfs send -i sendfs#redaction-bookmark sendfs@snap to a file. +# 5. Verify XDR encoding in the resulting stream. +# 6. Verify that zfs receive of the stream succeeds. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_incr_from_bookmark_src" +clonefs="$POOL/xdr_incr_from_bookmark_clone" +recvfs="$POOL2/xdr_incr_from_bookmark_recv" +full_stream="/$POOL/xdr_incr_from_bookmark_full.zsend" +incr_stream="/$POOL/xdr_incr_from_bookmark_incr.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $full_stream $incr_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of an incremental send from a redaction bookmark " \ + "is XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Establish a base on the receiver. +log_must eval "zfs send --redact redaction-bookmark $sendfs@s0 > $full_stream" +log_must eval "zfs receive $recvfs < $full_stream" + +# Add a new snapshot on the source for the incremental. +log_must dd if=/dev/urandom of=/$sendfs/f3 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s1 + +# Generate an incremental send from the redaction bookmark. This fires +# BEGINNV_REDACT_FROM_SNAPS via the from_rl path because the from-side +# is a redaction bookmark. +log_must eval "zfs send -i $sendfs#redaction-bookmark $sendfs@s1 > $incr_stream" +verify_xdr_nvlist_encoding $incr_stream +log_must eval "zfs receive $recvfs < $incr_stream" + +log_pass "BEGIN nvlist of an incremental send from a redaction bookmark " \ + "is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_incr_from_redacted.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_incr_from_redacted.ksh new file mode 100755 index 000000000000..fc4d34c43462 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_incr_from_redacted.ksh @@ -0,0 +1,96 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# An incremental send whose from-side is a snapshot of a previously-redacted +# dataset carries BEGINNV_REDACT_FROM_SNAPS in its DRR_BEGIN nvlist payload +# via a different code path than incrementals from a redaction bookmark +# (the dspp->numfromredactsnaps path). Verify that this payload is +# XDR-encoded and that the stream can be received. +# +# Strategy: +# 1. Produce a redacted dataset on a receiver via a redacted full send, +# leaving the receiver with a snapshot whose from-side will carry the +# SPA_FEATURE_REDACTED_DATASETS feature. +# 2. Establish the same base on a tertiary destination so we have somewhere +# to apply the incremental. +# 3. Create a new snapshot of the receiver-side redacted dataset. +# 4. zfs send -i mid@s0 mid@s1 to a file. +# 5. Verify that the stream is XDR encoded. +# 6. Verify that we can zfs receive the incremental onto the tertiary base. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_incr_from_redacted_src" +clonefs="$POOL/xdr_incr_from_redacted_clone" +midfs="$POOL2/xdr_incr_from_redacted_mid" +tertiary="$POOL/xdr_incr_from_redacted_tertiary" +full_stream="/$POOL/xdr_incr_from_redacted_full.zsend" +incr_stream="/$POOL/xdr_incr_from_redacted_incr.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $midfs && destroy_dataset $midfs -R + datasetexists $tertiary && destroy_dataset $tertiary -R + rm -f $full_stream $incr_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of an incremental from a previously-redacted " \ + "snapshot is XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Produce two receivers of the redacted full send: one we will re-send from +# (mid) and one we will receive the incremental into (tertiary). +log_must eval "zfs send --redact redaction-bookmark $sendfs@s0 > $full_stream" +log_must eval "zfs receive $midfs < $full_stream" +log_must eval "zfs receive $tertiary < $full_stream" + +# Create a fresh snapshot of the redacted receiver. The data has not changed +# (and cannot be modified without mounting), but the snapshot itself is +# enough to drive an incremental send and trigger the case-4 nvlist path. +log_must zfs snapshot $midfs@s1 + +# Create an incremental send from the redacted from-side. This fires +# BEGINNV_REDACT_FROM_SNAPS via the dspp->numfromredactsnaps path because +# $midfs@s0 has the SPA_FEATURE_REDACTED_DATASETS feature active. +log_must eval "zfs send -i $midfs@s0 $midfs@s1 > $incr_stream" +verify_xdr_nvlist_encoding $incr_stream +log_must eval "zfs receive $tertiary < $incr_stream" + +log_pass "BEGIN nvlist of an incremental from a previously-redacted snapshot " \ + "is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_raw.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_raw.ksh new file mode 100755 index 000000000000..c3a196650c6a --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_raw.ksh @@ -0,0 +1,67 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A raw send of an encrypted dataset (zfs send -w) carries a "crypt_keydata" +# nested nvlist in its DRR_BEGIN nvlist payload. Verify that this payload is +# XDR-encoded and that the stream can be received. +# +# Strategy: +# 1. Create an encrypted dataset with one snapshot. +# 2. zfs send -w to a file. +# 3. Verify that the stream is XDR-encoded. +# 4. Verify that zfs receive succeeds. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_raw_src" +recvfs="$POOL2/xdr_raw_recv" +keyfile="/$POOL/xdr_raw.key" +stream="/$POOL/xdr_raw.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -r + datasetexists $recvfs && destroy_dataset $recvfs -r + rm -f $keyfile $stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a raw send of an encrypted dataset is " \ + "XDR-encoded and receivable" + +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s1 + +log_must eval "zfs send -w $sendfs@s1 > $stream" + +verify_xdr_nvlist_encoding $stream +log_must eval "zfs receive $recvfs < $stream" + +log_pass "BEGIN nvlist of a raw send of an encrypted dataset is " \ + "XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_full.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_full.ksh new file mode 100755 index 000000000000..2bad9bebdaaf --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_full.ksh @@ -0,0 +1,72 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A redacted send (zfs send --redact ) carries BEGINNV_REDACT_SNAPS +# in its DRR_BEGIN nvlist payload. Verify that this payload is XDR-encoded and +# the stream can be received. +# +# Strategy: +# 1. Create a source dataset and a divergent clone. +# 2. Create a redaction bookmark on the source snapshot relative to the +# clone snapshot. +# 3. zfs send --redact sendfs@snap to a file. +# 4. verify_xdr_nvlist_encoding on the stream. +# 5. Verify that zfs receive succeeds. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_redacted_full_src" +clonefs="$POOL/xdr_redacted_full_clone" +recvfs="$POOL2/xdr_redacted_full_recv" +stream="/$POOL/xdr_redacted_full.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a redacted send is XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +log_must eval "zfs send --redact redaction-bookmark $sendfs@s0 > $stream" +verify_xdr_nvlist_encoding $stream +log_must eval "zfs receive $recvfs < $stream" + +log_pass "BEGIN nvlist of a redacted send is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_received.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_received.ksh new file mode 100755 index 000000000000..a18b1f405942 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_received.ksh @@ -0,0 +1,84 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# Sending a snapshot from a previously-redacted dataset (one with the +# SPA_FEATURE_REDACTED_DATASETS feature active, e.g., one that was received +# from a redacted send) carries BEGINNV_REDACT_SNAPS in its DRR_BEGIN +# nvlist payload via a different code path than the producer-side --redact +# flag. Verify that this payload is XDR-encoded and that the stream can be +# received. +# +# Strategy: +# 1. Produce a redacted dataset on a receiver via a redacted full send. +# 2. zfs send the received-redacted snapshot to a new dataset. +# 3. Verify XDR encoding on the new stream. +# 4. Verify that a zfs receive of the new stream succeeds. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_redacted_received_src" +clonefs="$POOL/xdr_redacted_received_clone" +midfs="$POOL2/xdr_redacted_received_mid" +recvfs="$POOL2/xdr_redacted_received_recv" +full_stream="/$POOL/xdr_redacted_received_full.zsend" +resend_stream="/$POOL/xdr_redacted_received_resend.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $midfs && destroy_dataset $midfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $full_stream $resend_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a send from a previously-redacted dataset is " \ + "XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Produce a previously-redacted dataset on the receiver. +log_must eval "zfs send --redact redaction-bookmark $sendfs@s0 > $full_stream" +log_must eval "zfs receive $midfs < $full_stream" + +# Send the received-redacted snapshot. This fires BEGINNV_REDACT_SNAPS via +# the SPA_FEATURE_REDACTED_DATASETS code path on to_ds. +log_must eval "zfs send $midfs@s0 > $resend_stream" +verify_xdr_nvlist_encoding $resend_stream +log_must eval "zfs receive $recvfs < $resend_stream" + +log_pass "BEGIN nvlist of a send from a previously-redacted dataset is " \ + "XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_received_raw.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_received_raw.ksh new file mode 100755 index 000000000000..2efcba32b9f3 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_redacted_received_raw.ksh @@ -0,0 +1,97 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# zfs send explicitly disallows the source-side combination of -w and +# --redact. However, the same nvlist combination (BEGINNV_REDACT_SNAPS +# together with crypt_keydata) can still be reached by: +# +# 1. Sending a redacted (non-raw) stream from an unencrypted source. +# 2. Receiving it with receiver-side encryption. +# 3. Re-sending the now-encrypted-and-redacted dataset with -w. +# +# The final stream's DRR_BEGIN nvlist contains both the redact-snaps array +# (via the SPA_FEATURE_REDACTED_DATASETS code path on to_ds) and +# crypt_keydata (via DMU_BACKUP_FEATURE_RAW). Verify that this combined +# payload is XDR-encoded and that the stream can be received. +# +# Strategy: +# 1. Create an unencrypted source dataset with a redaction bookmark. +# 2. zfs send --redact sendfs@snap to a file (no -w). +# 3. zfs receive into a new dataset with -o encryption=on (receiver-side +# encryption). +# 4. zfs send -w the received dataset to a second stream file. +# 5. Verify that this second stream is XDR-encoded. +# 6. Verify that the second stream can be zfs received successfully. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_redacted_received_raw_src" +clonefs="$POOL/xdr_redacted_received_raw_clone" +midfs="$POOL2/xdr_redacted_received_raw_mid" +recvfs="$POOL2/xdr_redacted_received_raw_recv" +keyfile="/$POOL/xdr_redacted_received_raw.key" +full_stream="/$POOL/xdr_redacted_received_raw_full.zsend" +resend_stream="/$POOL/xdr_redacted_received_raw_resend.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $midfs && destroy_dataset $midfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $keyfile $full_stream $resend_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a raw send of a received-redacted dataset is " \ + "XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=8 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Redacted send (non-raw) into a receiver that establishes its own encryption. +log_must eval "zfs send --redact redaction-bookmark $sendfs@s0 > $full_stream" +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must eval "zfs receive -o encryption=on -o keyformat=passphrase " \ + "-o keylocation=file://$keyfile $midfs < $full_stream" + +# Re-send the received stream as a raw (encrypted) stream. The DRR_BEGIN +# nvlist now carries both BEGINNV_REDACT_SNAPS data and crypt_keydata +# (DMU_BACKUP_FEATURE_RAW). +log_must eval "zfs send -w $midfs@s0 > $resend_stream" +verify_xdr_nvlist_encoding $resend_stream +log_must eval "zfs receive $recvfs < $resend_stream" + +log_pass "BEGIN nvlist of a raw send of a received-redacted dataset is " \ + "XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_replication.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_replication.ksh new file mode 100755 index 000000000000..22d0bf20410f --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_replication.ksh @@ -0,0 +1,90 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A replication send (zfs send -R) may emit two distinct categories of +# DRR_BEGIN record: +# +# 1. A wrapper BEGIN of type DMU_COMPOUNDSTREAM, generated in libzfs +# (lib/libzfs/libzfs_sendrecv.c), whose nvlist describes the package +# stream. This BEGIN has always been XDR-encoded and is not affected +# by the kernel-side encoding changes introduced in PR #18372. +# +# 2. One inner BEGIN record per dataset whose contents are included, +# generated in the kernel (module/zfs/dmu_send.c). These are the BEGIN +# records whose encoding the kernel-side change consolidates to XDR. +# +# All other tests in this suite exercise category (2). This test exercises +# both categories together: it verifies that no BEGIN record produced +# anywhere on the userspace+kernel send path is encoded with NV_ENCODE_NATIVE, +# so a future regression in either layer would be caught. +# +# Strategy: +# 1. Create an unencrypted parent dataset and an encrypted child filesystem +# underneath it, with some data in each. The encrypted child is what +# causes the kernel-side inner BEGIN to actually carry an nvlist payload +# (crypt_keydata) rather than passing through silently. +# 2. Snapshot recursively. +# 3. zfs send -wR parent@snap to a file. The resulting stream contains a +# libzfs-generated wrapper BEGIN with its compound-stream nvlist plus +# one kernel-generated inner BEGIN per dataset; the child's inner BEGIN +# carries crypt_keydata. +# 4. Verify the encoding for the whole stream — this checks every BEGIN +# nvlist line that zstream dump emits, so it covers both the wrapper +# and the encrypted child's inner record. +# 5. Verify that the stream can be zfs received successfully. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_replication_src" +childfs="$POOL/xdr_replication_src/child" +recvfs="$POOL2/xdr_replication_recv" +keyfile="/$POOL/xdr_replication.key" +stream="/$POOL/xdr_replication.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $keyfile $stream +} +log_onexit cleanup + +log_assert "BEGIN nvlists in a recursive replication stream (wrapper and inner) are XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $childfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=4 status=none +log_must dd if=/dev/urandom of=/$childfs/f1 bs=128k count=4 status=none +log_must zfs snapshot -r $sendfs@s0 + +log_must eval "zfs send -wR $sendfs@s0 > $stream" +verify_xdr_nvlist_encoding $stream +log_must eval "zfs receive $recvfs < $stream" + +log_pass "BEGIN nvlists in a recursive replication stream (wrapper and inner) are XDR-encoded and receivable" + diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume.ksh new file mode 100755 index 000000000000..e98de4c47f44 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume.ksh @@ -0,0 +1,73 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A token-resumed send (zfs send -t ) carries BEGINNV_RESUME_OBJECT +# and BEGINNV_RESUME_OFFSET in its DRR_BEGIN nvlist payload. Verify that +# this payload is XDR-encoded and that the resumed stream can be received. +# +# Strategy: +# 1. Create a small dataset with one snapshot. +# 2. zfs send the snapshot to a file, truncate it, then attempt receive +# so that a resume token is left behind. +# 3. zfs send -t to produce the resumed stream. +# 4. Verify that the resumed stream is XDR-encoded. +# 5. Verify that zfs receive -s on the resumed stream is successful. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_resume_src" +recvfs="$POOL2/xdr_resume_recv" +full_stream="/$POOL/xdr_resume_full.zsend" +resumed_stream="/$POOL/xdr_resume_resumed.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -r + datasetexists $recvfs && destroy_dataset $recvfs -r + rm -f $full_stream $resumed_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a token-resumed send is XDR-encoded and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=8 status=none +log_must zfs snapshot $sendfs@s1 + +log_must eval "zfs send $sendfs@s1 > $full_stream" +mess_send_file $full_stream +log_mustnot eval "zfs receive -s $recvfs < $full_stream" + +token=$(get_prop receive_resume_token $recvfs) +[[ -n "$token" && "$token" != "-" ]] || \ + log_fail "no resume token left behind by partial receive" +log_must eval "zfs send -t $token > $resumed_stream" + +verify_xdr_nvlist_encoding $resumed_stream +log_must eval "zfs receive -s $recvfs < $resumed_stream" + +log_pass "BEGIN nvlist of a token-resumed send is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_bookmark_raw.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_bookmark_raw.ksh new file mode 100755 index 000000000000..6645315fcd7b --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_bookmark_raw.ksh @@ -0,0 +1,103 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# The most populated DRR_BEGIN nvlist in the kernel: a token-resumed raw +# incremental from a redaction bookmark carries BEGINNV_REDACT_FROM_SNAPS, +# crypt_keydata, and BEGINNV_RESUME_{OBJECT,OFFSET}. Verify that this +# combined payload is XDR-encoded and the resumed stream can be received. +# +# Strategy: +# 1. Create an encrypted source with a redaction bookmark and a later +# snapshot, mirroring xdr_bookmark_raw. +# 2. Establish a raw base on the receiver. +# 3. zfs send -w -i sendfs#book sendfs@s1 to a file, truncate it, then +# attempt receive so that a resume token is left behind. +# 4. zfs send -t to produce the resumed stream. +# 5. Verify that the resumed stream is XDR-encoded. +# 6. Verify that zfs receive -s of the resumed stream is successful. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_resume_bookmark_raw_src" +clonefs="$POOL/xdr_resume_bookmark_raw_clone" +recvfs="$POOL2/xdr_resume_bookmark_raw_recv" +keyfile="/$POOL/xdr_resume_bookmark_raw.key" +full_stream="/$POOL/xdr_resume_bookmark_raw_full.zsend" +incr_stream="/$POOL/xdr_resume_bookmark_raw_incr.zsend" +resumed_stream="/$POOL/xdr_resume_bookmark_raw_resumed.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $keyfile $full_stream $incr_stream $resumed_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a token-resumed raw incremental from a redaction " \ + "bookmark is XDR-encoded and receivable" + +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $sendfs + +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=16 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=16 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=16 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Take @s1 with no intervening write. See xdr_resume_bookmark_raw_with_write.ksh +# for a variant that includes a post-redact write; that variant exercises +# a known kernel-side issue (#18491) and may flake. +log_must zfs snapshot $sendfs@s1 + +# Establish a raw base on the receiver. +log_must eval "zfs send -w $sendfs@s0 > $full_stream" +log_must eval "zfs receive $recvfs < $full_stream" + +# Truncate-and-resume on the raw incremental from the redaction bookmark. +log_must eval "zfs send -w -i $sendfs#redaction-bookmark $sendfs@s1 > \ + $incr_stream" +mess_send_file $incr_stream +log_mustnot eval "zfs receive -s $recvfs < $incr_stream" + +token=$(get_prop receive_resume_token $recvfs) +[[ -n "$token" && "$token" != "-" ]] || \ + log_fail "no resume token left behind by partial receive" +log_must eval "zfs send -t $token > $resumed_stream" + +verify_xdr_nvlist_encoding $resumed_stream +log_must eval "zfs receive -s $recvfs < $resumed_stream" + +log_pass "BEGIN nvlist of a token-resumed raw incremental from a redaction " \ + "bookmark is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_bookmark_raw_with_write.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_bookmark_raw_with_write.ksh new file mode 100755 index 000000000000..6c0b6b5b4ec3 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_bookmark_raw_with_write.ksh @@ -0,0 +1,116 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# This is the post-redact-write variant of xdr_resume_bookmark_raw, +# separated out because of a known issue (#18491) that causes it to fail +# roughly 30% of the time. It's included here as a test for issue #18491 +# until the exact source of that problem can be pinned down more specifically. +# +# Known issue: openzfs/zfs#18491 +# +# On a freshly-created pool, `zfs send -w -i ds#book ds@snap` intermittently +# fails with EACCES whenever there is data-modifying activity between the +# `zfs redact` that created the bookmark and the subsequent send. This EACCES +# is surfaced to userspace as the misleading message "dataset key must be +# loaded," although the key remains loaded throughout. +# +# The reproducer script included in the issue report typically triggers the +# problem within about 10 iterations on a fresh pool. Disk-sync mitigations +# (zpool sync, with or without `-f`, with or without sleep, single or doubled, +# applied at any reasonable point) do not avert the problem. CI runs that +# include the test in this file reproduce the failure regularly (though +# intermittently) across multiple distributions. xdr_resume_bookmark_raw.ksh +# removes the post-redact write (which is not essential to the test) and +# therefore runs reliably. +# +# When this test fails, the failure marker is the libzfs warning +# "dataset key must be loaded" on stderr from the first `zfs send -w -i` +# line below (the one that produces the stream we then truncate), and a +# non-zero exit from that send. The test does not attempt to distinguish +# the known-issue failure from other possible failures. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_resume_bookmark_raw_with_write_src" +clonefs="$POOL/xdr_resume_bookmark_raw_with_write_clone" +recvfs="$POOL2/xdr_resume_bookmark_raw_with_write_recv" +keyfile="/$POOL/xdr_resume_bookmark_raw_with_write.key" +full_stream="/$POOL/xdr_resume_bookmark_raw_with_write_full.zsend" +incr_stream="/$POOL/xdr_resume_bookmark_raw_with_write_incr.zsend" +resumed_stream="/$POOL/xdr_resume_bookmark_raw_with_write_resumed.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $keyfile $full_stream $incr_stream $resumed_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a token-resumed raw incremental from a redaction " \ + "bookmark, with a post-redact write, is XDR-encoded and receivable " \ + "(known to flake; see openzfs/zfs#18491)" + +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $sendfs + +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=16 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=16 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +# Post-redact write: the trigger for openzfs/zfs#18491. +log_must dd if=/dev/urandom of=/$sendfs/f3 bs=128k count=16 status=none +log_must zfs snapshot $sendfs@s1 + +# Establish a raw base on the receiver. +log_must eval "zfs send -w $sendfs@s0 > $full_stream" +log_must eval "zfs receive $recvfs < $full_stream" + +# The next line is what races. On failure it exits with EACCES rendered +# as "dataset key must be loaded". +log_must eval "zfs send -w -i $sendfs#redaction-bookmark $sendfs@s1 > \ + $incr_stream" +mess_send_file $incr_stream +log_mustnot eval "zfs receive -s $recvfs < $incr_stream" + +token=$(get_prop receive_resume_token $recvfs) +[[ -n "$token" && "$token" != "-" ]] || \ + log_fail "no resume token left behind by partial receive" +log_must eval "zfs send -t $token > $resumed_stream" + +verify_xdr_nvlist_encoding $resumed_stream +log_must eval "zfs receive -s $recvfs < $resumed_stream" + +log_pass "BEGIN nvlist of a token-resumed raw incremental from a redaction " \ + "bookmark, with a post-redact write, is XDR-encoded and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_raw.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_raw.ksh new file mode 100755 index 000000000000..a96df10b9454 --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_raw.ksh @@ -0,0 +1,79 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A resumed raw send (zfs send -t for a raw stream of an encrypted +# dataset) carries both BEGINNV_RESUME_{OBJECT,OFFSET} and the "crypt_keydata" +# nested nvlist in its DRR_BEGIN nvlist payload. Verify that this combined +# payload is XDR-encoded and the resumed stream can be received. +# +# Strategy: +# 1. Create an encrypted dataset with one snapshot. +# 2. zfs send -w to a file, truncate it, then attempt to zfs receive the +# stream so that a resume token is left behind. +# 3. zfs send -t to produce the resumed raw stream. +# 4. Verify that the resumed stream is XDR-encoded. +# 5. Verify that zfs receive -s receives the resumed stream successfully. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_resume_raw_src" +recvfs="$POOL2/xdr_resume_raw_recv" +keyfile="/$POOL/xdr_resume_raw.key" +full_stream="/$POOL/xdr_resume_raw_full.zsend" +resumed_stream="/$POOL/xdr_resume_raw_resumed.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -r + datasetexists $recvfs && destroy_dataset $recvfs -r + rm -f $keyfile $full_stream $resumed_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a token-resumed raw send is XDR-encoded " \ + "and receivable" + +log_must eval "echo 'thisisapassphrase' > $keyfile" +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=16 status=none +log_must zfs snapshot $sendfs@s1 + +log_must eval "zfs send -w $sendfs@s1 > $full_stream" +mess_send_file $full_stream +log_mustnot eval "zfs receive -s $recvfs < $full_stream" + +token=$(get_prop receive_resume_token $recvfs) +[[ -n "$token" && "$token" != "-" ]] || \ + log_fail "no resume token left behind by partial receive" +log_must eval "zfs send -t $token > $resumed_stream" + +verify_xdr_nvlist_encoding $resumed_stream +log_must eval "zfs receive -s $recvfs < $resumed_stream" + +log_pass "BEGIN nvlist of a token-resumed raw send is XDR-encoded " \ + "and receivable" diff --git a/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_redacted.ksh b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_redacted.ksh new file mode 100755 index 000000000000..6cee3e51a3dd --- /dev/null +++ b/tests/zfs-tests/tests/functional/send_xdr_encoding/xdr_resume_redacted.ksh @@ -0,0 +1,86 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Garth Snyder. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/tests/functional/send_xdr_encoding/send_xdr_encoding.kshlib + +# +# Description: +# A resumed redacted send (zfs send -t for a redacted stream) +# carries both BEGINNV_REDACT_SNAPS and BEGINNV_RESUME_{OBJECT,OFFSET} in +# its DRR_BEGIN nvlist payload. Verify that this combined payload is +# XDR-encoded and the resumed stream can be received. +# +# Strategy: +# 1. Create a source dataset with a redaction bookmark. +# 2. zfs send --redact sendfs@snap to a file, truncate it, then +# attempt zfs receive so that a resume token is left behind. +# 3. zfs send -t to produce a resumed redacted stream. +# 4. Verify that the resumed stream is XDR-encoded. +# 5. Verify that zfs receive -s of the resumed stream is successful. +# + +verify_runnable "both" + +sendfs="$POOL/xdr_resume_redacted_src" +clonefs="$POOL/xdr_resume_redacted_clone" +recvfs="$POOL2/xdr_resume_redacted_recv" +full_stream="/$POOL/xdr_resume_redacted_full.zsend" +resumed_stream="/$POOL/xdr_resume_redacted_resumed.zsend" + +function cleanup +{ + datasetexists $sendfs && destroy_dataset $sendfs -R + datasetexists $recvfs && destroy_dataset $recvfs -R + rm -f $full_stream $resumed_stream +} +log_onexit cleanup + +log_assert "BEGIN nvlist of a token-resumed redacted send is XDR-encoded " \ + "and receivable" + +log_must zfs create $sendfs +log_must dd if=/dev/urandom of=/$sendfs/f1 bs=128k count=16 status=none +log_must dd if=/dev/urandom of=/$sendfs/f2 bs=128k count=16 status=none +log_must zfs snapshot $sendfs@s0 + +log_must zfs clone $sendfs@s0 $clonefs +log_must dd if=/dev/urandom of=/$clonefs/f1 bs=128k count=16 conv=notrunc \ + status=none +log_must zfs snapshot $clonefs@s + +log_must zfs redact $sendfs@s0 redaction-bookmark $clonefs@s + +log_must eval "zfs send --redact redaction-bookmark $sendfs@s0 > $full_stream" +mess_send_file $full_stream +log_mustnot eval "zfs receive -s $recvfs < $full_stream" + +token=$(get_prop receive_resume_token $recvfs) +[[ -n "$token" && "$token" != "-" ]] || \ + log_fail "no resume token left behind by partial receive" +log_must eval "zfs send -t $token > $resumed_stream" + +verify_xdr_nvlist_encoding $resumed_stream +log_must eval "zfs receive -s $recvfs < $resumed_stream" + +log_pass "BEGIN nvlist of a token-resumed redacted send is XDR-encoded " \ + "and receivable" +