Add --no-preserve-encryption flag

* Add an option to send datasets with params or replicate
without preserving encryption
* Add a test case for the new functionality

Reviewed-by: Paul Dagnelie <paul.dagnelie@klarasystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Signed-off-by: Chris Jacobs <idefix2020dev@gmail.com>
Closes #18240
This commit is contained in:
Idefix2020 2026-03-06 00:08:17 +01:00 committed by GitHub
parent c329530e6b
commit 5dad9459d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 62 additions and 22 deletions

View File

@ -344,10 +344,10 @@ get_usage(zfs_help_t idx)
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
return (gettext("\tsend [-DLPbcehnpsVvw] "
return (gettext("\tsend [-DLPbcehnpsUVvw] "
"[-i|-I snapshot]\n"
"\t [-R [-X dataset[,dataset]...]] <snapshot>\n"
"\tsend [-DnVvPLecw] [-i snapshot|bookmark] "
"\tsend [-DnVvPLecwU] [-i snapshot|bookmark] "
"<filesystem|volume|snapshot>\n"
"\tsend [-DnPpVvLec] [-i bookmark|snapshot] "
"--redact <bookmark> <snapshot>\n"
@ -4753,11 +4753,12 @@ zfs_do_send(int argc, char **argv)
{"holds", no_argument, NULL, 'h'},
{"saved", no_argument, NULL, 'S'},
{"exclude", required_argument, NULL, 'X'},
{"no-preserve-encryption", no_argument, NULL, 'U'},
{0, 0, 0, 0}
};
/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RsDpVvnPLeht:cwbd:SX:",
while ((c = getopt_long(argc, argv, ":i:I:RsDpVvnPLeht:cwbd:SX:U",
long_options, NULL)) != -1) {
switch (c) {
case 'X':
@ -4843,6 +4844,9 @@ zfs_do_send(int argc, char **argv)
case 'S':
flags.saved = B_TRUE;
break;
case 'U':
flags.no_preserve_encryption = B_TRUE;
break;
case ':':
/*
* If a parameter was not passed, optopt contains the

View File

@ -844,6 +844,9 @@ typedef struct sendflags {
/* stream represents a partially received dataset */
boolean_t saved;
/* allow sending datasets with props, without preserving encryption */
boolean_t no_preserve_encryption;
} sendflags_t;
typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);

View File

@ -258,6 +258,7 @@ typedef struct send_data {
boolean_t seento;
boolean_t holds; /* were holds requested with send -h */
boolean_t props;
boolean_t no_preserve_encryption;
/*
* The header nvlist is of the following format:
@ -587,20 +588,32 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
fnvlist_add_boolean(nvfs, "is_encroot");
/*
* Encrypted datasets can only be sent with properties if
* the raw flag is specified because the receive side doesn't
* currently have a mechanism for recursively asking the user
* for new encryption parameters.
* Encrypted datasets can only be sent with properties if the
* raw flag or the no-preserve-encryption flag are specified
* because the receive side doesn't currently have a mechanism
* for recursively asking the user for new encryption
* parameters.
* We allow sending the dataset unencrypted only if the user
* explicitly sets the no-preserve-encryption flag.
*/
if (!sd->raw) {
if (!sd->raw && !sd->no_preserve_encryption) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"cannot send %s@%s: encrypted dataset %s may not "
"be sent with properties without the raw flag\n"),
"be sent with properties without the raw flag or "
"no-preserve-encryption flag\n"),
sd->fsname, sd->tosnap, zhp->zfs_name);
rv = -1;
goto out;
}
/* If no-preserve-encryption flag is set, warn the user again */
if (!sd->raw && sd->no_preserve_encryption) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"WARNING: no-preserve-encryption flag set, sending "
"dataset %s without encryption\n"),
zhp->zfs_name);
}
}
/*
@ -683,8 +696,8 @@ static int
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall,
boolean_t replicate, boolean_t skipmissing, boolean_t verbose,
boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp,
avl_tree_t **avlp)
boolean_t backup, boolean_t holds, boolean_t props,
boolean_t no_preserve_encryption, nvlist_t **nvlp, avl_tree_t **avlp)
{
zfs_handle_t *zhp;
send_data_t sd = { 0 };
@ -707,6 +720,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.backup = backup;
sd.holds = holds;
sd.props = props;
sd.no_preserve_encryption = no_preserve_encryption;
if ((error = send_iterate_fs(zhp, &sd)) != 0) {
fnvlist_free(sd.fss);
@ -2199,7 +2213,7 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
boolean_t gather_props, boolean_t recursive, boolean_t verbose,
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t skipmissing,
boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall,
nvlist_t **fssp, avl_tree_t **fsavlp)
boolean_t no_preserve_encryption, nvlist_t **fssp, avl_tree_t **fsavlp)
{
int err = 0;
char *packbuf = NULL;
@ -2245,7 +2259,8 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
if (gather_nvlist(zhp->zfs_hdl, tofs,
from, tosnap, recursive, raw, doall, replicate, skipmissing,
verbose, backup, holds, props, &fss, fsavlp) != 0) {
verbose, backup, holds, props, no_preserve_encryption,
&fss, fsavlp) != 0) {
return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
errbuf));
}
@ -2392,7 +2407,7 @@ zfs_send_cb_impl(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
flags->replicate, flags->verbosity > 0, flags->dryrun,
flags->raw, flags->replicate, flags->skipmissing,
flags->backup, flags->holds, flags->props, flags->doall,
&fss, &fsavl);
flags->no_preserve_encryption, &fss, &fsavl);
zfs_close(tosnap);
if (err != 0)
goto err_out;
@ -2735,7 +2750,8 @@ zfs_send_one_cb_impl(zfs_handle_t *zhp, const char *from, int fd,
err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE,
flags->verbosity > 0, flags->dryrun, flags->raw,
flags->replicate, B_FALSE, flags->backup, flags->holds,
flags->props, flags->doall, NULL, NULL);
flags->props, flags->doall, flags->no_preserve_encryption,
NULL, NULL);
if (err != 0)
return (err);
}
@ -3392,7 +3408,7 @@ recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs,
/* Using top_zfs, gather the nvlists for all local filesystems. */
if ((err = gather_nvlist(hdl, top_zfs, NULL, NULL,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE,
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0)
return (err);
/*
@ -3547,7 +3563,7 @@ again:
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE,
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0)
return (error);
/*
@ -5138,7 +5154,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
*cp = '\0';
if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE,
B_TRUE, &local_nv, &local_avl) == 0) {
B_TRUE, B_FALSE, &local_nv, &local_avl) == 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
fsavl_destroy(local_avl);

View File

@ -31,7 +31,7 @@
.\" Copyright 2019 Joyent, Inc.
.\" Copyright (c) 2024, Klara, Inc.
.\"
.Dd August 29, 2025
.Dd February 20, 2026
.Dt ZFS-SEND 8
.Os
.
@ -41,13 +41,13 @@
.Sh SYNOPSIS
.Nm zfs
.Cm send
.Op Fl DLPVbcehnpsvw
.Op Fl DLPUVbcehnpsvw
.Op Fl R Op Fl X Ar dataset Ns Oo , Ns Ar dataset Oc Ns
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
.Ar snapshot
.Nm zfs
.Cm send
.Op Fl DLPVcensvw
.Op Fl DLPUVcensvw
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
.Nm zfs
@ -75,7 +75,7 @@
.It Xo
.Nm zfs
.Cm send
.Op Fl DLPVbcehnpsvw
.Op Fl DLPUVbcehnpsvw
.Op Fl R Op Fl X Ar dataset Ns Oo , Ns Ar dataset Oc Ns
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
.Ar snapshot
@ -146,6 +146,8 @@ If the
.Fl R
flag is used to send encrypted datasets, then
.Fl w
or
.Fl U
must also be specified.
.It Fl V , -proctitle
Set the process title to a per-second report of how much data has been sent.
@ -293,6 +295,8 @@ is specified.
The receiving system must also support this feature.
Sends of encrypted datasets must use
.Fl w
or
.Fl U
when using this flag.
.It Fl s , -skip-missing
Allows sending a replication stream even when there are snapshots missing in the
@ -303,6 +307,11 @@ belongs
and its descendants are skipped.
This flag can only be used in conjunction with
.Fl R .
.It Fl U , -no-preserve-encryption
Allow sending an encrypted dataset with properties, but without keeping
encryption.
When this flag is specified, encrypted datasets that would otherwise be blocked
from sending are sent as unencrypted data.
.It Fl v , -verbose
Print verbose information about the stream package generated.
This information includes a per-second report of how much data has been sent.

View File

@ -41,6 +41,7 @@
# encryption child
# 10. Verify that an unencrypted recursive send can be received as an
# encryption child
# 11. Verify an encrypted pool can be sent with props only when -U is set
#
verify_runnable "both"
@ -119,6 +120,13 @@ log_mustnot eval "zfs send -i $esnap $esnap2 |" \
"zfs recv -o pbkdf2iters=100k $TESTPOOL/recv"
log_must zfs destroy -r $TESTPOOL/recv
# The user has to explicitly allow sending a dataset unecrypted when sending
# an encrypted dataset with properties
log_note "Must not be able to send an encrypted dataset with props unless the -U flag is set"
log_mustnot eval "zfs send -p $esnap | zfs recv $TESTPOOL/recv"
log_must eval "zfs send -p -U $esnap | zfs recv $TESTPOOL/recv"
log_must zfs destroy -r $TESTPOOL/recv
# Test that we can receive a simple stream as an encryption root.
log_note "Must be able to receive stream as encryption root"
ds=$TESTPOOL/recv