Want 'zfs send -b'

This change implements 'zfs send -b' which can be used to send only
received property values whether or not they are overridden by local
settings.

This can be very useful during "restore" operations from a backup pool
because it allows to send only the property values originally sent
from the backup source, even though they were later modified on the
destination either by a 'zfs set' operation, explicit 'zfs inherit' or
overridden during the receive process via 'zfs receive -o|-x'.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: loli10K <ezomori.nozomu@gmail.com>
Closes #7156
This commit is contained in:
LOLi 2018-02-21 21:32:06 +01:00 committed by Brian Behlendorf
parent b0918402dc
commit faa97c1619
9 changed files with 257 additions and 120 deletions

View file

@ -288,9 +288,9 @@ get_usage(zfs_help_t idx)
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
return (gettext("\tsend [-DnPpRvLecr] [-[i|I] snapshot] "
return (gettext("\tsend [-DnPpRvLecwb] [-[i|I] snapshot] "
"<snapshot>\n"
"\tsend [-Lecr] [-i snapshot|bookmark] "
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
"<filesystem|volume|snapshot>\n"
"\tsend [-nvPe] -t <receive_resume_token>\n"));
case HELP_SET:
@ -3944,11 +3944,12 @@ zfs_do_send(int argc, char **argv)
{"resume", required_argument, NULL, 't'},
{"compressed", no_argument, NULL, 'c'},
{"raw", no_argument, NULL, 'w'},
{"backup", no_argument, NULL, 'b'},
{0, 0, 0, 0}
};
/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLet:cw", long_options,
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLet:cwb", long_options,
NULL)) != -1) {
switch (c) {
case 'i':
@ -3968,6 +3969,9 @@ zfs_do_send(int argc, char **argv)
case 'p':
flags.props = B_TRUE;
break;
case 'b':
flags.backup = B_TRUE;
break;
case 'P':
flags.parsable = B_TRUE;
flags.verbose = B_TRUE;
@ -4048,7 +4052,7 @@ zfs_do_send(int argc, char **argv)
if (resume_token != NULL) {
if (fromname != NULL || flags.replicate || flags.props ||
flags.dedup) {
flags.backup || flags.dedup) {
(void) fprintf(stderr,
gettext("invalid flags combined with -t\n"));
usage(B_FALSE);
@ -4090,7 +4094,8 @@ zfs_do_send(int argc, char **argv)
char frombuf[ZFS_MAX_DATASET_NAME_LEN];
if (flags.replicate || flags.doall || flags.props ||
flags.dedup || (strchr(argv[0], '@') == NULL &&
flags.backup || flags.dedup ||
(strchr(argv[0], '@') == NULL &&
(flags.dryrun || flags.verbose || flags.progress))) {
(void) fprintf(stderr, gettext("Error: "
"Unsupported flag with filesystem or bookmark.\n"));

View file

@ -673,6 +673,9 @@ typedef struct sendflags {
/* raw encrypted records are permitted */
boolean_t raw;
/* only send received properties (ie. -b) */
boolean_t backup;
} sendflags_t;
typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);

View file

@ -621,6 +621,7 @@ typedef struct send_data {
const char *fromsnap;
const char *tosnap;
boolean_t raw;
boolean_t backup;
boolean_t recursive;
boolean_t verbose;
boolean_t seenfrom;
@ -651,7 +652,8 @@ typedef struct send_data {
*/
} send_data_t;
static void send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv);
static void
send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv);
static int
send_iterate_snap(zfs_handle_t *zhp, void *arg)
@ -706,7 +708,7 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
}
VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0));
send_iterate_prop(zhp, nv);
send_iterate_prop(zhp, sd->backup, nv);
VERIFY(0 == nvlist_add_nvlist(sd->snapprops, snapname, nv));
nvlist_free(nv);
@ -715,11 +717,17 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
}
static void
send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv)
send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv)
{
nvlist_t *props = NULL;
nvpair_t *elem = NULL;
while ((elem = nvlist_next_nvpair(zhp->zfs_props, elem)) != NULL) {
if (received_only)
props = zfs_get_recvd_props(zhp);
else
props = zhp->zfs_props;
while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
char *propname = nvpair_name(elem);
zfs_prop_t prop = zfs_name_to_prop(propname);
nvlist_t *propnv;
@ -885,7 +893,7 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
/* iterate over props */
VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0));
send_iterate_prop(zhp, nv);
send_iterate_prop(zhp, sd->backup, nv);
if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
boolean_t encroot;
@ -951,7 +959,7 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
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 verbose,
nvlist_t **nvlp, avl_tree_t **avlp)
boolean_t backup, nvlist_t **nvlp, avl_tree_t **avlp)
{
zfs_handle_t *zhp;
send_data_t sd = { 0 };
@ -968,6 +976,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.recursive = recursive;
sd.raw = raw;
sd.verbose = verbose;
sd.backup = backup;
if ((error = send_iterate_fs(zhp, &sd)) != 0) {
nvlist_free(sd.fss);
@ -1881,7 +1890,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
}
if (flags->replicate || flags->doall || flags->props) {
if (flags->replicate || flags->doall || flags->props || flags->backup) {
dmu_replay_record_t drr = { 0 };
char *packbuf = NULL;
size_t buflen = 0;
@ -1889,7 +1898,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
ZIO_SET_CHECKSUM(&zc, 0, 0, 0, 0);
if (flags->replicate || flags->props) {
if (flags->replicate || flags->props || flags->backup) {
nvlist_t *hdrnv;
VERIFY(0 == nvlist_alloc(&hdrnv, NV_UNIQUE_NAME, 0));
@ -1908,7 +1917,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
fromsnap, tosnap, flags->replicate, flags->raw,
flags->verbose, &fss, &fsavl);
flags->verbose, flags->backup, &fss, &fsavl);
if (err)
goto err_out;
VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
@ -2078,7 +2087,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
if (!flags->dryrun && (flags->replicate || flags->doall ||
flags->props)) {
flags->props || flags->backup)) {
/*
* write final end record. NB: want to do this even if
* there was some error, because it might not be totally
@ -2816,7 +2825,7 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
VERIFY(0 == nvlist_alloc(&deleted, NV_UNIQUE_NAME, 0));
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
recursive, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0)
recursive, B_TRUE, B_FALSE, B_FALSE, &local_nv, &local_avl)) != 0)
return (error);
/*
@ -4121,7 +4130,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, &local_nv, &local_avl) == 0) {
B_FALSE, B_FALSE, &local_nv, &local_avl) == 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
fsavl_destroy(local_avl);

View file

@ -194,7 +194,7 @@
.Ar snapshot bookmark
.Nm
.Cm send
.Op Fl DLPRcenpvw
.Op Fl DLPRbcenpvw
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
.Ar snapshot
.Nm
@ -3321,7 +3321,7 @@ feature.
.It Xo
.Nm
.Cm send
.Op Fl DLPRcenpvw
.Op Fl DLPRbcenpvw
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
.Ar snapshot
.Xc
@ -3417,6 +3417,14 @@ See
for details on ZFS feature flags and the
.Sy embedded_data
feature.
.It Fl b, -backup
Sends only received property values whether or not they are overridden by local
settings, but only if the dataset has ever been received. Use this option when
you want
.Nm zfs Cm receive
to restore received properties backed up on the sent dataset and to avoid
sending local settings that may have nothing to do with the source dataset,
but only with how the data is backed up.
.It Fl c, -compressed
Generate a more compact stream by using compressed WRITE records for blocks
which are compressed on disk and in memory

View file

@ -223,7 +223,7 @@ tags = ['functional', 'cli_root', 'zfs_rollback']
tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos',
'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos',
'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw',
'zfs_send_sparse']
'zfs_send_sparse', 'zfs_send-b']
tags = ['functional', 'cli_root', 'zfs_send']
[tests/functional/cli_root/zfs_set]

View file

@ -45,105 +45,6 @@ function cleanup
log_must zfs destroy -r -f $dest
}
#
# Verify property $2 is set from source $4 on dataset $1 and has value $3.
#
# $1 checked dataset
# $2 user property
# $3 property value
# $4 source
#
function check_prop_source
{
typeset dataset="$1"
typeset prop="$2"
typeset value="$3"
typeset source="$4"
typeset chk_value=$(get_prop "$prop" "$dataset")
typeset chk_source=$(get_source "$prop" "$dataset")
if [[ "$chk_value" != "$value" || "$chk_source" != "$4" ]]
then
return 1
else
return 0
fi
}
#
# Verify target dataset $1 inherit property $2 from dataset $3.
#
# $1 checked dataset
# $2 property
# $3 inherited dataset
#
function check_prop_inherit
{
typeset checked_dtst="$1"
typeset prop="$2"
typeset inherited_dtst="$3"
typeset inherited_value=$(get_prop "$prop" "$inherited_dtst")
typeset value=$(get_prop "$prop" "$checked_dtst")
typeset source=$(get_source "$prop" "$checked_dtst")
if [[ "$value" != "$inherited_value" || \
"$source" != "inherited from $inherited_dtst" ]]
then
return 1
else
return 0
fi
}
#
# Verify property $2 received value on dataset $1 has value $3
#
# $1 checked dataset
# $2 property name
# $3 checked value
#
function check_prop_received
{
typeset dataset="$1"
typeset prop="$2"
typeset value="$3"
received=$(zfs get -H -o received "$prop" "$dataset")
if (($? != 0)); then
log_fail "Unable to get $prop received value for dataset " \
"$dataset"
fi
if [[ "$received" == "$value" ]]
then
return 0
else
return 1
fi
}
#
# Verify user property $2 is not set on dataset $1
#
# $1 checked dataset
# $2 property name
#
function check_prop_missing
{
typeset dataset="$1"
typeset prop="$2"
value=$(zfs get -H -o value "$prop" "$dataset")
if (($? != 0)); then
log_fail "Unable to get $prop value for dataset $dataset"
fi
if [[ "-" == "$value" ]]
then
return 0
else
return 1
fi
}
log_assert "ZFS receive property override and exclude options work as expected."
log_onexit cleanup

View file

@ -12,4 +12,5 @@ dist_pkgdata_SCRIPTS = \
zfs_send_007_pos.ksh \
zfs_send_encrypted.ksh \
zfs_send_raw.ksh \
zfs_send_sparse.ksh
zfs_send_sparse.ksh \
zfs_send-b.ksh

View file

@ -0,0 +1,103 @@
#!/bin/ksh -p
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source. A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#
#
# Copyright 2018, 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:
# 'zfs send -b' should works as expected.
#
# STRATEGY:
# 1. Create a source dataset and set some properties
# 2. Verify command line options interact with '-b' correctly
# 3. Send the dataset and its properties to a new "backup" destination
# 4. Set some properties on the new "backup" dataset
# 5. Restore the "backup" dataset to a new destination
# 6. Verify only original (received) properties are sent from "backup"
#
verify_runnable "both"
function cleanup
{
for ds in "$SENDFS" "$BACKUP" "$RESTORE"; do
datasetexists $ds && log_must zfs destroy -r $ds
done
}
log_assert "'zfs send -b' should work as expected."
log_onexit cleanup
SENDFS="$TESTPOOL/sendfs"
BACKUP="$TESTPOOL/backup"
RESTORE="$TESTPOOL/restore"
# 1. Create a source dataset and set some properties
log_must zfs create $SENDFS
log_must zfs snapshot "$SENDFS@s1"
log_must zfs bookmark "$SENDFS@s1" "$SENDFS#bm"
log_must zfs snapshot "$SENDFS@s2"
log_must zfs set "compression=gzip" $SENDFS
log_must zfs set "org.zfsonlinux:prop=val" $SENDFS
log_must zfs set "org.zfsonlinux:snapprop=val" "$SENDFS@s1"
# 2. Verify command line options interact with '-b' correctly
typeset opts=("" "p" "Rp" "cew" "nv" "D" "DLPRcenpvw")
for opt in ${opts[@]}; do
log_must eval "zfs send -b$opt $SENDFS@s1 > /dev/null"
log_must eval "zfs send -b$opt -i $SENDFS@s1 $SENDFS@s2 > /dev/null"
log_must eval "zfs send -b$opt -I $SENDFS@s1 $SENDFS@s2 > /dev/null"
done
for opt in ${opts[@]}; do
log_mustnot eval "zfs send -b$opt $SENDFS > /dev/null"
log_mustnot eval "zfs send -b$opt $SENDFS#bm > /dev/null"
log_mustnot eval "zfs send -b$opt -i $SENDFS#bm $SENDFS@s2 > /dev/null"
done
# Do 3..6 in a loop to verify various combination of "zfs send" options
typeset opts=("" "p" "R" "pR" "cew")
for opt in ${opts[@]}; do
# 3. Send the dataset and its properties to a new "backup" destination
# NOTE: only need to send properties (-p) here
log_must eval "zfs send -p $SENDFS@s1 | zfs recv $BACKUP"
# 4. Set some properties on the new "backup" dataset
# NOTE: override "received" values and set some new properties as well
log_must zfs set "compression=lz4" $BACKUP
log_must zfs set "exec=off" $BACKUP
log_must zfs set "org.zfsonlinux:prop=newval" $BACKUP
log_must zfs set "org.zfsonlinux:newprop=newval" $BACKUP
log_must zfs set "org.zfsonlinux:snapprop=newval" "$BACKUP@s1"
log_must zfs set "org.zfsonlinux:newsnapprop=newval" "$BACKUP@s1"
# 5. Restore the "backup" dataset to a new destination
log_must eval "zfs send -b$opt $BACKUP@s1 | zfs recv $RESTORE"
# 6. Verify only original (received) properties are sent from "backup"
log_must eval "check_prop_source $RESTORE compression gzip received"
log_must eval "check_prop_source $RESTORE org.zfsonlinux:prop val received"
log_must eval "check_prop_source $RESTORE@s1 org.zfsonlinux:snapprop val received"
log_must eval "check_prop_source $RESTORE exec on default"
log_must eval "check_prop_missing $RESTORE org.zfsonlinux:newprop"
log_must eval "check_prop_missing $RESTORE@s1 org.zfsonlinux:newsnapprop"
# cleanup
log_must zfs destroy -r $BACKUP
log_must zfs destroy -r $RESTORE
done
log_pass "'zfs send -b' works as expected."

View file

@ -267,3 +267,110 @@ function get_source
echo "$source"
}
#
# Verify property $2 is set from source $4 on dataset $1 and has value $3.
#
# $1 checked dataset
# $2 user property
# $3 property value
# $4 source
#
# Returns: 0 if both expected source and value match, 1 otherwise
#
function check_prop_source
{
typeset dataset="$1"
typeset prop="$2"
typeset value="$3"
typeset source="$4"
typeset chk_value=$(get_prop "$prop" "$dataset")
typeset chk_source=$(get_source "$prop" "$dataset")
if [[ "$chk_value" != "$value" || "$chk_source" != "$4" ]]
then
return 1
else
return 0
fi
}
#
# Verify target dataset $1 inherit property $2 from dataset $3.
#
# $1 checked dataset
# $2 property
# $3 inherited dataset
#
# Returns: 0 if property has expected value and is inherited, 1 otherwise
#
function check_prop_inherit
{
typeset checked_dtst="$1"
typeset prop="$2"
typeset inherited_dtst="$3"
typeset inherited_value=$(get_prop "$prop" "$inherited_dtst")
typeset value=$(get_prop "$prop" "$checked_dtst")
typeset source=$(get_source "$prop" "$checked_dtst")
if [[ "$value" != "$inherited_value" || \
"$source" != "inherited from $inherited_dtst" ]]
then
return 1
else
return 0
fi
}
#
# Verify property $2 received value on dataset $1 has value $3
#
# $1 checked dataset
# $2 property name
# $3 checked value
#
# Returns: 0 if property has expected value and is received, 1 otherwise
#
function check_prop_received
{
typeset dataset="$1"
typeset prop="$2"
typeset value="$3"
received=$(zfs get -H -o received "$prop" "$dataset")
if (($? != 0)); then
log_fail "Unable to get $prop received value for dataset " \
"$dataset"
fi
if [[ "$received" == "$value" ]]
then
return 0
else
return 1
fi
}
#
# Verify user property $2 is not set on dataset $1
#
# $1 checked dataset
# $2 property name
#
# Returns: 0 if property is missing (not set), 1 otherwise
#
function check_prop_missing
{
typeset dataset="$1"
typeset prop="$2"
value=$(zfs get -H -o value "$prop" "$dataset")
if (($? != 0)); then
log_fail "Unable to get $prop value for dataset $dataset"
fi
if [[ "-" == "$value" ]]
then
return 0
else
return 1
fi
}