Normalize property names for zfs receive

It turns out, userland is much more happy with aliased property
names than the kernel is.

So let's normalize those to the expected names before we pass
them off.

Added a test case hacked up from the other recv -o/-x test that fails
on unpatched git and passes here.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Rich Ercolani <rincebrain@gmail.com>
Closes #12607 
Closes #12609
This commit is contained in:
Rich Ercolani 2021-10-29 18:38:10 -04:00 committed by GitHub
parent adeccfea17
commit 4476ccd906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 236 additions and 6 deletions

View File

@ -3958,6 +3958,19 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type,
const char *name = nvpair_name(nvp); const char *name = nvpair_name(nvp);
zfs_prop_t prop = zfs_name_to_prop(name); zfs_prop_t prop = zfs_name_to_prop(name);
/*
* It turns out, if we don't normalize "aliased" names
* e.g. compress= against the "real" names (e.g. compression)
* here, then setting/excluding them does not work as
* intended.
*
* But since user-defined properties wouldn't have a valid
* mapping here, we do this conditional dance.
*/
const char *newname = name;
if (prop >= ZFS_PROP_TYPE)
newname = zfs_prop_to_name(prop);
/* "origin" is processed separately, don't handle it here */ /* "origin" is processed separately, don't handle it here */
if (prop == ZFS_PROP_ORIGIN) if (prop == ZFS_PROP_ORIGIN)
continue; continue;
@ -4004,11 +4017,12 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type,
* locally-set, in which case its value will take * locally-set, in which case its value will take
* priority over the received anyway. * priority over the received anyway.
*/ */
if (nvlist_exists(origprops, name)) { if (nvlist_exists(origprops, newname)) {
nvlist_t *attrs; nvlist_t *attrs;
char *source = NULL; char *source = NULL;
attrs = fnvlist_lookup_nvlist(origprops, name); attrs = fnvlist_lookup_nvlist(origprops,
newname);
if (nvlist_lookup_string(attrs, if (nvlist_lookup_string(attrs,
ZPROP_SOURCE, &source) == 0 && ZPROP_SOURCE, &source) == 0 &&
strcmp(source, ZPROP_SOURCE_VAL_RECVD) != 0) strcmp(source, ZPROP_SOURCE_VAL_RECVD) != 0)
@ -4021,10 +4035,10 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type,
*/ */
if (!zfs_prop_inheritable(prop) && if (!zfs_prop_inheritable(prop) &&
!zfs_prop_user(name) && /* can be inherited too */ !zfs_prop_user(name) && /* can be inherited too */
nvlist_exists(recvprops, name)) nvlist_exists(recvprops, newname))
fnvlist_remove(recvprops, name); fnvlist_remove(recvprops, newname);
else else
fnvlist_add_nvpair(*oxprops, nvp); fnvlist_add_boolean(*oxprops, newname);
break; break;
case DATA_TYPE_STRING: /* -o property=value */ case DATA_TYPE_STRING: /* -o property=value */
/* /*
@ -4045,7 +4059,8 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type,
ret = zfs_error(hdl, EZFS_BADPROP, errbuf); ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error; goto error;
} }
fnvlist_add_nvpair(oprops, nvp); fnvlist_add_string(oprops, newname,
fnvpair_value_string(nvp));
break; break;
default: default:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,

View File

@ -232,6 +232,7 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos',
'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos',
'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos',
'zfs_receive_016_pos', 'receive-o-x_props_override', 'zfs_receive_016_pos', 'receive-o-x_props_override',
'receive-o-x_props_aliases',
'zfs_receive_from_encrypted', 'zfs_receive_to_encrypted', 'zfs_receive_from_encrypted', 'zfs_receive_to_encrypted',
'zfs_receive_raw', 'zfs_receive_raw_incremental', 'zfs_receive_-e', 'zfs_receive_raw', 'zfs_receive_raw_incremental', 'zfs_receive_-e',
'zfs_receive_raw_-d', 'zfs_receive_from_zstd', 'zfs_receive_new_props'] 'zfs_receive_raw_-d', 'zfs_receive_from_zstd', 'zfs_receive_new_props']

View File

@ -19,6 +19,7 @@ dist_pkgdata_SCRIPTS = \
zfs_receive_015_pos.ksh \ zfs_receive_015_pos.ksh \
zfs_receive_016_pos.ksh \ zfs_receive_016_pos.ksh \
receive-o-x_props_override.ksh \ receive-o-x_props_override.ksh \
receive-o-x_props_aliases.ksh \
zfs_receive_from_encrypted.ksh \ zfs_receive_from_encrypted.ksh \
zfs_receive_from_zstd.ksh \ zfs_receive_from_zstd.ksh \
zfs_receive_new_props.ksh \ zfs_receive_new_props.ksh \

View File

@ -0,0 +1,213 @@
#!/bin/ksh -p
#
# 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 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib
#
# DESCRIPTION:
# Verify ZFS property override (-o) and exclude (-x) options work when
# receiving a send stream, using property name aliases
#
# STRATEGY:
# 1. Create a filesystem with children.
# 2. Snapshot the filesystems.
# 3. Create various send streams (full, incremental, replication) and verify
# we can both override and exclude aliased properties.
#
verify_runnable "both"
function cleanup
{
log_must rm -f $streamfile_full
log_must rm -f $streamfile_incr
log_must rm -f $streamfile_repl
log_must rm -f $streamfile_trun
destroy_dataset "$orig" "-rf"
destroy_dataset "$dest" "-rf"
}
log_assert "ZFS receive property alias override and exclude options work as expected."
log_onexit cleanup
orig=$TESTPOOL/$TESTFS1
origsub=$orig/sub
dest=$TESTPOOL/$TESTFS2
destsub=$dest/sub
typeset streamfile_full=$TESTDIR/streamfile_full.$$
typeset streamfile_incr=$TESTDIR/streamfile_incr.$$
typeset streamfile_repl=$TESTDIR/streamfile_repl.$$
typeset streamfile_trun=$TESTDIR/streamfile_trun.$$
#
# 3.1 Verify we can't specify the same property in multiple -o or -x options
# or an invalid value was specified.
#
# Create a full send stream
log_must zfs create $orig
log_must zfs snapshot $orig@snap1
log_must eval "zfs send $orig@snap1 > $streamfile_full"
# Verify we reject invalid options
log_mustnot eval "zfs recv $dest -o compress < $streamfile_full"
log_mustnot eval "zfs recv $dest -x compress=off < $streamfile_full"
log_mustnot eval "zfs recv $dest -o compress=off -x compress < $streamfile_full"
log_mustnot eval "zfs recv $dest -o compress=off -o compress=on < $streamfile_full"
log_mustnot eval "zfs recv $dest -x compress -x compress < $streamfile_full"
log_mustnot eval "zfs recv $dest -o version=1 < $streamfile_full"
log_mustnot eval "zfs recv $dest -x version < $streamfile_full"
log_mustnot eval "zfs recv $dest -x normalization < $streamfile_full"
# Verify we also reject invalid ZVOL options
log_must zfs create -V 32K -s $orig/zvol
log_must eval "zfs send $orig@snap1 > $streamfile_full"
log_mustnot eval "zfs recv $dest -x volblock < $streamfile_full"
log_mustnot eval "zfs recv $dest -o volblock=32K < $streamfile_full"
# Cleanup
block_device_wait
log_must_busy zfs destroy -r -f $orig
#
# 3.2 Verify -o property=value works on streams without properties.
#
# Create a full send stream
log_must zfs create $orig
log_must zfs snapshot $orig@snap1
log_must eval "zfs send $orig@snap1 > $streamfile_full"
# Receive the full stream, override some properties
log_must eval "zfs recv -o compress=on -o '$userprop:dest'='$userval' "\
"$dest < $streamfile_full"
log_must eval "check_prop_source $dest compression on local"
log_must eval "check_prop_source $dest '$userprop:dest' '$userval' local"
# Cleanup
log_must zfs destroy -r -f $orig
log_must zfs destroy -r -f $dest
#
# 3.3 Verify -o property=value and -x work
# for an incremental replication send stream.
#
# Create a dataset tree and receive it
log_must zfs create $orig
log_must zfs create $origsub
log_must zfs snapshot -r $orig@snap1
log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
log_must eval "zfs recv $dest < $streamfile_repl"
# Fill the datasets with properties and create an incremental replication stream
log_must zfs snapshot -r $orig@snap2
log_must zfs snapshot -r $orig@snap3
log_must eval "zfs set copies=2 $orig"
log_must eval "zfs set dnsize=4k $orig"
log_must eval "zfs set compression=gzip $origsub"
log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr"
# Sets various combination of override and exclude options
log_must eval "zfs recv -F -o atime=off -o quota=123456789 -o checksum=sha512" \
" -o dnsize=2k -x compress $dest < $streamfile_incr"
# Verify we can correctly override and exclude properties
log_must eval "check_prop_source $dest copies 2 received"
log_must eval "check_prop_source $dest atime off local"
log_must eval "check_prop_source $dest quota 123456789 local"
log_must eval "check_prop_source $dest checksum sha512 local"
log_must eval "check_prop_source $dest dnodesize 2k local"
log_must eval "check_prop_inherit $destsub copies $dest"
log_must eval "check_prop_inherit $destsub atime $dest"
log_must eval "check_prop_inherit $destsub checksum $dest"
log_must eval "check_prop_source $destsub quota 0 default"
log_must eval "check_prop_source $destsub compression off default"
# Cleanup
log_must zfs destroy -r -f $orig
log_must zfs destroy -r -f $dest
#
# 3.4 Verify '-x property' does not remove existing local properties and a
# modified sent property is received and updated to the new value but can
# still be excluded.
#
# Create a dataset tree
log_must zfs create $orig
log_must zfs create $origsub
log_must zfs snapshot -r $orig@snap1
log_must eval "zfs set copies=2 $orig"
log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
log_must eval "zfs receive $dest < $streamfile_repl"
log_must eval "check_prop_source $dest copies 2 received"
log_must eval "check_prop_inherit $destsub copies $dest"
# Set new custom properties on both source and destination
log_must eval "zfs set copies=3 $orig"
log_must eval "zfs set compression=on $orig"
log_must eval "zfs set compression=lzjb $origsub"
log_must eval "zfs set compression=gzip $dest"
# Receive the new stream, verify we preserve locally set properties
log_must zfs snapshot -r $orig@snap2
log_must zfs snapshot -r $orig@snap3
log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr"
log_must eval "zfs recv -F -x copies -x compress $dest < $streamfile_incr"
log_must eval "check_prop_source $dest copies 1 default"
log_must eval "check_prop_received $dest copies 3"
log_must eval "check_prop_source $destsub copies 1 default"
log_must eval "check_prop_received $destsub copies '-'"
log_must eval "check_prop_source $dest compression gzip local"
log_must eval "check_prop_inherit $destsub compression $dest"
# Cleanup
log_must zfs destroy -r -f $orig
log_must zfs destroy -r -f $dest
#
# 3.6 Verify we correctly restore existing properties on a failed receive
#
# Receive a "clean" dataset tree
log_must zfs create $orig
log_must zfs create $origsub
log_must zfs snapshot -r $orig@snap1
log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
log_must eval "zfs receive $dest < $streamfile_repl"
# Set custom properties on the destination
log_must eval "zfs set compress=on $dest"
log_must eval "zfs set compress=lzjb $destsub"
# Create a truncated incremental replication stream
mntpnt=$(get_prop mountpoint $orig)
log_must eval "dd if=/dev/urandom of=$mntpnt/file bs=1024k count=10"
log_must zfs snapshot -r $orig@snap2
log_must zfs snapshot -r $orig@snap3
log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr"
log_must eval "dd if=$streamfile_incr of=$streamfile_trun bs=1024k count=9"
# Receive the truncated stream, verify original properties are kept
log_mustnot eval "zfs recv -F -o copies=3 -o compress=gzip "\
"$dest < $streamfile_trun"
log_must eval "check_prop_source $dest copies 1 default"
log_must eval "check_prop_source $destsub copies 1 default"
log_must eval "check_prop_source $dest compression on local"
log_must eval "check_prop_source $destsub compression lzjb local"
# Cleanup
log_must zfs destroy -r -f $orig
log_must zfs destroy -r -f $dest
#
# 3.7 Verify that we can't get around checking a property is readonly
# by using the alias or receiving a parent replication stream.
log_must zfs create $orig
log_must zfs create -V 128K -s $origsub
log_must zfs snapshot -r $orig@snap1
log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
log_mustnot eval "zfs receive -o volblock=64k $dest < $streamfile_repl"
# Cleanup
block_device_wait
log_must_busy zfs destroy -r -f $orig
log_pass "ZFS receive property alias override and exclude options passed."