]> granicus.if.org Git - zfs/commitdiff
Want 'zfs send -b'
authorLOLi <loli10K@users.noreply.github.com>
Wed, 21 Feb 2018 20:32:06 +0000 (21:32 +0100)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Wed, 21 Feb 2018 20:32:06 +0000 (12:32 -0800)
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

cmd/zfs/zfs_main.c
include/libzfs.h
lib/libzfs/libzfs_sendrecv.c
man/man8/zfs.8
tests/runfiles/linux.run
tests/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh
tests/zfs-tests/tests/functional/cli_root/zfs_send/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib

index b9c3a5cf3f73ee407f7cadb7351ad56913149445..718ceea50c0e6deefe39ac4ff6a8c0a78ee4d9ba 100644 (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"));
index 1244bf0e49d1cebe0da4158260b4bfca69c022df..71a588325f14b5cd9fc5eacf26acc91f27db8bc9 100644 (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 *);
index c850c8ba645ff6536edba309658dfd0c6f5e296b..1623a75b4bbb45ae88c638d9fb0fd06d3c0881f2 100644 (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 @@ out:
 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 @@ again:
        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);
index f428513285069a869bdb38668d58abac0b766ca5..ea585b5331a964729675dc2f944b03cb50467368 100644 (file)
 .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
index da9c791f9b6cb1e53ce2abf85e2431757533f1d2..b80788e22fd598db6e730c742ff7ee2809394371 100644 (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]
index e4e69851f3857e62b780c1c5fc8ac36bdf949ee2..3d5d08db207957cf5ec44d93c310cc2c7902cad9 100755 (executable)
@@ -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
 
index e82df61c7364155d791145741f0502003aca5583..682569da48bbabf33bc8a7777af3468331a79ec7 100644 (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
diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b.ksh
new file mode 100755 (executable)
index 0000000..cd87984
--- /dev/null
@@ -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."
index d4e51dcbdfa11b7835926325d493b9065793bb00..084a4a0a82ac67bfce0e6f1eabc963f28d299383 100644 (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
+}