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:
{"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':
case 'p':
flags.props = B_TRUE;
break;
+ case 'b':
+ flags.backup = B_TRUE;
+ break;
case 'P':
flags.parsable = B_TRUE;
flags.verbose = B_TRUE;
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);
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"));
/* 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 *);
const char *fromsnap;
const char *tosnap;
boolean_t raw;
+ boolean_t backup;
boolean_t recursive;
boolean_t verbose;
boolean_t seenfrom;
*/
} 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)
}
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);
}
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;
/* 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;
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 };
sd.recursive = recursive;
sd.raw = raw;
sd.verbose = verbose;
+ sd.backup = backup;
if ((error = send_iterate_fs(zhp, &sd)) != 0) {
nvlist_free(sd.fss);
}
}
- 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;
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));
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));
}
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
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);
/*
*/
*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);
.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
.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
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
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]
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
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
--- /dev/null
+#!/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."
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
+}