Skip to content

Commit

Permalink
optimize recv_fix_encryption_hierarchy()
Browse files Browse the repository at this point in the history
Signed-off-by: George Amanakis <[email protected]>
  • Loading branch information
gamanakis committed Jan 6, 2025
1 parent dc0324b commit c217393
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 62 deletions.
122 changes: 87 additions & 35 deletions lib/libzfs/libzfs_sendrecv.c
Original file line number Diff line number Diff line change
Expand Up @@ -3376,30 +3376,28 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
*/
static int
recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs,
nvlist_t *stream_nv)
nvlist_t *stream_nv, avl_tree_t *stream_avl)
{
int err;
nvpair_t *fselem = NULL;
nvlist_t *stream_fss;
nvpair_t *fselem = NULL, *nextfselem;
nvlist_t *local_nv, *stream_fss;
avl_tree_t *local_avl;
boolean_t recursive;
char fsname[ZFS_MAX_DATASET_NAME_LEN], *cp;

stream_fss = fnvlist_lookup_nvlist(stream_nv, "fss");
recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
ENOENT);

/*
* Go through the send stream and find a snapshot that exists
* locally.
*/
stream_fss = fnvlist_lookup_nvlist(stream_nv, "fss");
while ((fselem = nvlist_next_nvpair(stream_fss, fselem)) != NULL) {
zfs_handle_t *zhp = NULL;
uint64_t crypt;
nvlist_t *snaps, *props, *stream_nvfs = NULL;
nvlist_t *snaps, *stream_nvfs;
nvpair_t *snapel = NULL;
boolean_t is_encroot, is_clone, stream_encroot;
char *cp;
const char *stream_keylocation = NULL;
char keylocation[MAXNAMELEN];
char fsname[ZFS_MAX_DATASET_NAME_LEN];

keylocation[0] = '\0';
stream_nvfs = fnvpair_value_nvlist(fselem);
snaps = fnvlist_lookup_nvlist(stream_nvfs, "snaps");
props = fnvlist_lookup_nvlist(stream_nvfs, "props");
stream_encroot = nvlist_exists(stream_nvfs, "is_encroot");

/* find a snapshot from the stream that exists locally */
err = ENOENT;
Expand All @@ -3413,45 +3411,99 @@ recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs,
break;
}

if (err != 0)
continue;
if (err == 0)
break;
}

cp = strchr(fsname, '@');
if (cp != NULL)
*cp = '\0';
/* If we failed to find a snapshot that exists locally return. */
if (err != 0)
return (0);

zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
/* Convert the snapshot to fs name */
cp = strchr(fsname, '@');
if (cp != NULL)
*cp = '\0';

/* Using the fs name, gather the nvlists for all local filesystems. */
if ((err = gather_nvlist(hdl, fsname, NULL, NULL,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE,
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
return (err);

/*
* Go through the nvlists of the local filesystems and check for
* encryption roots.
*/
for (fselem = nvlist_next_nvpair(local_nv, NULL); fselem;
fselem = nextfselem) {
zfs_handle_t *zhp = NULL;
uint64_t crypt;
nvlist_t *stream_props, *snaps, *stream_nvfs = NULL,
*nvfs = NULL;
boolean_t is_encroot, is_clone, stream_encroot;
const char *stream_keylocation = NULL, *fs;
char keylocation[MAXNAMELEN];
nvpair_t *snapelem;

nextfselem = nvlist_next_nvpair(local_nv, fselem);

nvfs = fnvpair_value_nvlist(fselem);
snaps = fnvlist_lookup_nvlist(nvfs, "snaps");
fs = fnvlist_lookup_string(nvfs, "name");
zhp = zfs_open(hdl, fs, ZFS_TYPE_DATASET);
if (zhp == NULL) {
err = ENOENT;
goto error;
}

crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0';
(void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);

/* we don't need to do anything for unencrypted datasets */
crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
if (crypt == ZIO_CRYPT_OFF) {
zfs_close(zhp);
continue;
}

is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0';
(void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
keylocation[0] = '\0';

/*
* Go through the snapshots of the local filesystem and find
* the stream's filesystem.
*/
for (snapelem = nvlist_next_nvpair(snaps, NULL);
snapelem; snapelem = nvlist_next_nvpair(snaps, snapelem)) {
uint64_t thisguid;

thisguid = fnvpair_value_uint64(snapelem);
stream_nvfs = fsavl_find(stream_avl, thisguid, NULL);

if (stream_nvfs != NULL)
break;
}

if (stream_nvfs == NULL)
continue;

stream_props = fnvlist_lookup_nvlist(stream_nvfs, "props");
stream_encroot = nvlist_exists(stream_nvfs, "is_encroot");

/*
* If the dataset is flagged as an encryption root, was not
* received as a clone and is not currently an encryption root,
* force it to become one. Fixup the keylocation if necessary.
*/
if (stream_encroot) {
if (!is_clone && !is_encroot) {
err = lzc_change_key(fsname,
err = lzc_change_key(fs,
DCP_CMD_FORCE_NEW_KEY, NULL, NULL, 0);
if (err != 0) {
zfs_close(zhp);
goto error;
}
}

stream_keylocation = fnvlist_lookup_string(props,
stream_keylocation = fnvlist_lookup_string(stream_props,
zfs_prop_to_name(ZFS_PROP_KEYLOCATION));

/*
Expand Down Expand Up @@ -3485,8 +3537,8 @@ recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs,
* force-inherited.
*/
if (!stream_encroot && is_encroot &&
strcmp(top_zfs, fsname) != 0) {
err = lzc_change_key(fsname, DCP_CMD_FORCE_INHERIT,
strcmp(top_zfs, fs) != 0) {
err = lzc_change_key(fs, DCP_CMD_FORCE_INHERIT,
NULL, NULL, 0);
if (err != 0) {
zfs_close(zhp);
Expand Down Expand Up @@ -3518,14 +3570,14 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
boolean_t needagain, progress, recursive;
const char *s1, *s2;

if (flags->dryrun)
return (0);

fromsnap = fnvlist_lookup_string(stream_nv, "fromsnap");

recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
ENOENT);

if (flags->dryrun)
return (0);

again:
needagain = progress = B_FALSE;

Expand Down Expand Up @@ -3999,9 +4051,9 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
stream_nv, stream_avl, NULL);
}

if (raw && softerr == 0 && *top_zfs != NULL) {
if (raw && softerr == 0 && *top_zfs != NULL && !flags->dryrun) {
softerr = recv_fix_encryption_hierarchy(hdl, *top_zfs,
stream_nv);
stream_nv, stream_avl);
}

out:
Expand Down
112 changes: 85 additions & 27 deletions tests/zfs-tests/tests/functional/rsend/send_encrypted_hierarchy.ksh
Original file line number Diff line number Diff line change
Expand Up @@ -48,49 +48,107 @@ log_onexit cleanup

# Create the filesystem hierarchy
log_must cleanup_pool $POOL
log_must zfs create $POOL/fs1
log_must zfs create $POOL2/fs2
log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \
"-o keyformat=passphrase $POOL/$FS"
log_must zfs snapshot $POOL/$FS@snap
log_must zfs clone $POOL/$FS@snap $POOL/clone
log_must zfs create $POOL/$FS/child
"-o keyformat=passphrase $POOL/fs1/$FS"
log_must zfs snapshot $POOL/fs1/$FS@snap
log_must zfs clone $POOL/fs1/$FS@snap $POOL/clone
log_must zfs create $POOL/fs1/$FS/child
log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \
"-o keyformat=passphrase $POOL/fs1/a"
log_must zfs snapshot $POOL/fs1/a@snapa
log_must zfs clone $POOL/fs1/a@snapa $POOL/clonea
log_must zfs create $POOL/fs1/a/childa

# Back up the tree and verify the structure
log_must zfs snapshot -r $POOL@before
log_must eval "zfs send -wR $POOL@before > $BACKDIR/fs-before-R"
log_must eval "zfs receive -d -F $POOL2 < $BACKDIR/fs-before-R"
dstds=$(get_dst_ds $POOL/$FS $POOL2)
log_must cmp_ds_subs $POOL/$FS $dstds

log_must verify_encryption_root $POOL/$FS $POOL/$FS
log_must verify_keylocation $POOL/$FS "prompt"
log_must verify_origin $POOL/$FS "-"

log_must verify_encryption_root $POOL/clone $POOL/$FS
log_must verify_keylocation $POOL/clone "none"
log_must verify_origin $POOL/clone "$POOL/$FS@snap"

log_must verify_encryption_root $POOL/$FS/child $POOL/$FS
log_must verify_keylocation $POOL/$FS/child "none"
log_must eval "zfs receive -d -F $POOL2/fs2 < $BACKDIR/fs-before-R"
dstds=$(get_dst_ds $POOL/fs1/$FS $POOL2/fs2)
dstdsa=$(get_dst_ds $POOL/fs1/a $POOL2/fs2)
log_must cmp_ds_subs $POOL/fs1/$FS $dstds
log_must cmp_ds_subs $POOL/fs1/a $dstdsa

log_must verify_encryption_root $POOL2/fs2/fs1/$FS $POOL2/fs2/fs1/$FS
log_must verify_keylocation $POOL2/fs2/fs1/$FS "prompt"
log_must verify_origin $POOL2/fs2/fs1/$FS "-"
log_must verify_encryption_root $POOL2/fs2/fs1/a $POOL2/fs2/fs1/a
log_must verify_keylocation $POOL2/fs2/fs1/a "prompt"
log_must verify_origin $POOL2/fs2/fs1/a "-"

log_must verify_encryption_root $POOL2/fs2/clone $POOL2/fs2/fs1/$FS
log_must verify_keylocation $POOL2/fs2/clone "none"
log_must verify_origin $POOL2/fs2/clone "$POOL2/fs2/fs1/$FS@snap"
log_must verify_encryption_root $POOL2/fs2/clonea $POOL2/fs2/fs1/a
log_must verify_keylocation $POOL2/fs2/clonea "none"
log_must verify_origin $POOL2/fs2/clonea "$POOL2/fs2/fs1/a@snapa"

log_must verify_encryption_root $POOL/fs1/$FS/child $POOL/fs1/$FS
log_must verify_encryption_root $POOL2/fs2/fs1/$FS/child $POOL2/fs2/fs1/$FS
log_must verify_keylocation $POOL2/fs2/fs1/$FS/child "none"
log_must verify_encryption_root $POOL2/fs2/fs1/a/childa $POOL2/fs2/fs1/a
log_must verify_keylocation $POOL2/fs2/fs1/a/childa "none"

# Alter the hierarchy and re-send
log_must eval "echo $PASSPHRASE1 | zfs change-key -o keyformat=passphrase" \
"$POOL/$FS/child"
"$POOL/fs1/$FS/child"
log_must eval "echo $PASSPHRASE1 | zfs change-key -o keyformat=passphrase" \
"$POOL/fs1/a/childa"
log_must zfs promote $POOL/clone
log_must zfs promote $POOL/clonea
log_must zfs snapshot -r $POOL@after
log_must eval "zfs send -wR -i $POOL@before $POOL@after >" \
"$BACKDIR/fs-after-R"
log_must eval "zfs receive -d -F $POOL2 < $BACKDIR/fs-after-R"
log_must cmp_ds_subs $POOL/$FS $dstds
log_must eval "zfs receive -d -F $POOL2/fs2 < $BACKDIR/fs-after-R"
log_must cmp_ds_subs $POOL/fs1/$FS $dstds
log_must cmp_ds_subs $POOL/fs1/a $dstdsa

log_must verify_encryption_root $POOL/$FS $POOL/clone
log_must verify_keylocation $POOL/$FS "none"
log_must verify_origin $POOL/$FS "$POOL/clone@snap"
log_must verify_encryption_root $POOL/fs1/$FS $POOL/clone
log_must verify_keylocation $POOL/fs1/$FS "none"
log_must verify_origin $POOL/fs1/$FS "$POOL/clone@snap"
log_must verify_encryption_root $POOL/fs1/a $POOL/clonea
log_must verify_keylocation $POOL/fs1/a "none"
log_must verify_origin $POOL/fs1/a "$POOL/clonea@snapa"

log_must verify_encryption_root $POOL/clone $POOL/clone
log_must verify_keylocation $POOL/clone "prompt"
log_must verify_origin $POOL/clone "-"

log_must verify_encryption_root $POOL/$FS/child $POOL/$FS/child
log_must verify_keylocation $POOL/$FS/child "prompt"
log_must verify_encryption_root $POOL/clonea $POOL/clonea
log_must verify_keylocation $POOL/clonea "prompt"
log_must verify_origin $POOL/clonea "-"

log_must verify_encryption_root $POOL/fs1/$FS/child $POOL/fs1/$FS/child
log_must verify_keylocation $POOL/fs1/$FS/child "prompt"
log_must verify_encryption_root $POOL/fs1/a/childa $POOL/fs1/a/childa
log_must verify_keylocation $POOL/fs1/a/childa "prompt"

log_must verify_encryption_root $POOL2/fs2 "-"
log_must verify_encryption_root $POOL2/fs2/clone $POOL2/fs2/clone
log_must verify_encryption_root $POOL2/fs2/clonea $POOL2/fs2/clonea
log_must verify_encryption_root $POOL2/fs2/fs1 "-"
log_must verify_encryption_root $POOL2/fs2/fs1/a $POOL2/fs2/clonea
log_must verify_encryption_root $POOL2/fs2/fs1/a/childa $POOL2/fs2/fs1/a/childa
log_must verify_encryption_root $POOL2/fs2/fs1/$FS $POOL2/fs2/clone
log_must verify_encryption_root $POOL2/fs2/fs1/$FS/child $POOL2/fs2/fs1/$FS/child

log_must verify_keylocation $POOL2/fs2 "none"
log_must verify_keylocation $POOL2/fs2/clone "prompt"
log_must verify_keylocation $POOL2/fs2/clonea "prompt"
log_must verify_keylocation $POOL2/fs2/fs1 "none"
log_must verify_keylocation $POOL2/fs2/fs1/a "none"
log_must verify_keylocation $POOL2/fs2/fs1/a/childa "prompt"
log_must verify_keylocation $POOL2/fs2/fs1/$FS "none"
log_must verify_keylocation $POOL2/fs2/fs1/$FS/child "prompt"

log_must verify_origin $POOL2/fs2 "-"
log_must verify_origin $POOL2/fs2/clone "-"
log_must verify_origin $POOL2/fs2/clonea "-"
log_must verify_origin $POOL2/fs2/fs1 "-"
log_must verify_origin $POOL2/fs2/fs1/a "$POOL2/fs2/clonea@snapa"
log_must verify_origin $POOL2/fs2/fs1/a/childa "-"
log_must verify_origin $POOL2/fs2/fs1/$FS "$POOL2/fs2/clone@snap"
log_must verify_origin $POOL2/fs2/fs1/$FS/child "-"
log_must zfs list

log_pass "Raw recursive sends preserve filesystem structure."

0 comments on commit c217393

Please sign in to comment.