zfs should optionally send holds

Add -h switch to zfs send command to send dataset holds. If
holds are present in the stream, zfs receive will create them
on the target dataset, unless the zfs receive -h option is used
to skip receive of holds.

Reviewed-by: Alek Pinchuk <apinchuk@datto.com>
Reviewed-by: loli10K <ezomori.nozomu@gmail.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed by: Paul Dagnelie <pcd@delphix.com>
Signed-off-by: Paul Zuchowski <pzuchowski@datto.com>
Closes #7513
This commit is contained in:
Paul Zuchowski
2019-02-15 15:41:38 -05:00
committed by Brian Behlendorf
parent e73ab1b38c
commit 9c5e88b1de
10 changed files with 324 additions and 28 deletions
+84 -12
View File
@@ -29,6 +29,7 @@
* Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
* Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
* Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
* Copyright (c) 2018 Datto Inc.
*/
#include <assert.h>
@@ -616,6 +617,7 @@ typedef struct send_data {
nvlist_t *parent_snaps;
nvlist_t *fss;
nvlist_t *snapprops;
nvlist_t *snapholds; /* user holds */
/* send-receive configuration, does not change during traversal */
const char *fsname;
@@ -627,6 +629,8 @@ typedef struct send_data {
boolean_t verbose;
boolean_t seenfrom;
boolean_t seento;
boolean_t holds; /* were holds requested with send -h */
boolean_t props;
/*
* The header nvlist is of the following format:
@@ -642,6 +646,7 @@ typedef struct send_data {
* "props" -> { name -> value (only if set here) }
* "snaps" -> { name (lastname) -> number (guid) }
* "snapprops" -> { name (lastname) -> { name -> value } }
* "snapholds" -> { name (lastname) -> { holdname -> crtime } }
*
* "origin" -> number (guid) (if clone)
* "is_encroot" -> boolean
@@ -712,6 +717,15 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
send_iterate_prop(zhp, sd->backup, nv);
VERIFY(0 == nvlist_add_nvlist(sd->snapprops, snapname, nv));
nvlist_free(nv);
if (sd->holds) {
nvlist_t *holds = fnvlist_alloc();
int err = lzc_get_holds(zhp->zfs_name, &holds);
if (err == 0) {
VERIFY(0 == nvlist_add_nvlist(sd->snapholds,
snapname, holds));
}
fnvlist_free(holds);
}
zfs_close(zhp);
return (0);
@@ -893,9 +907,10 @@ 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, sd->backup, nv);
if (sd->props || sd->backup || sd->recursive) {
VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0));
send_iterate_prop(zhp, sd->backup, nv);
}
if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
boolean_t encroot;
@@ -925,17 +940,24 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
}
VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv));
if (nv != NULL)
VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv));
/* iterate over snaps, and set sd->parent_fromsnap_guid */
sd->parent_fromsnap_guid = 0;
VERIFY(0 == nvlist_alloc(&sd->parent_snaps, NV_UNIQUE_NAME, 0));
VERIFY(0 == nvlist_alloc(&sd->snapprops, NV_UNIQUE_NAME, 0));
if (sd->holds)
VERIFY(0 == nvlist_alloc(&sd->snapholds, NV_UNIQUE_NAME, 0));
(void) zfs_iter_snapshots_sorted(zhp, send_iterate_snap, sd);
VERIFY(0 == nvlist_add_nvlist(nvfs, "snaps", sd->parent_snaps));
VERIFY(0 == nvlist_add_nvlist(nvfs, "snapprops", sd->snapprops));
if (sd->holds)
VERIFY(0 == nvlist_add_nvlist(nvfs, "snapholds",
sd->snapholds));
nvlist_free(sd->parent_snaps);
nvlist_free(sd->snapprops);
nvlist_free(sd->snapholds);
/* add this fs to nvlist */
(void) snprintf(guidstring, sizeof (guidstring),
@@ -960,7 +982,8 @@ 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,
boolean_t backup, nvlist_t **nvlp, avl_tree_t **avlp)
boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp,
avl_tree_t **avlp)
{
zfs_handle_t *zhp;
send_data_t sd = { 0 };
@@ -978,6 +1001,8 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.raw = raw;
sd.verbose = verbose;
sd.backup = backup;
sd.holds = holds;
sd.props = props;
if ((error = send_iterate_fs(zhp, &sd)) != 0) {
nvlist_free(sd.fss);
@@ -1008,7 +1033,7 @@ typedef struct send_dump_data {
uint64_t prevsnap_obj;
boolean_t seenfrom, seento, replicate, doall, fromorigin;
boolean_t verbose, dryrun, parsable, progress, embed_data, std_out;
boolean_t large_block, compress, raw;
boolean_t large_block, compress, raw, holds;
int outfd;
boolean_t err;
nvlist_t *fss;
@@ -1864,6 +1889,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
}
if (flags->holds)
featureflags |= DMU_BACKUP_FEATURE_HOLDS;
/*
* Start the dedup thread if this is a dedup stream. We do not bother
* doing this if this a raw send of an encrypted dataset with dedup off
@@ -1891,7 +1919,8 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
}
if (flags->replicate || flags->doall || flags->props || flags->backup) {
if (flags->replicate || flags->doall || flags->props ||
flags->holds || flags->backup) {
dmu_replay_record_t drr = { 0 };
char *packbuf = NULL;
size_t buflen = 0;
@@ -1899,7 +1928,8 @@ 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 || flags->backup) {
if (flags->replicate || flags->props || flags->backup ||
flags->holds) {
nvlist_t *hdrnv;
VERIFY(0 == nvlist_alloc(&hdrnv, NV_UNIQUE_NAME, 0));
@@ -1918,7 +1948,8 @@ 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, flags->backup, &fss, &fsavl);
flags->verbose, flags->backup, flags->holds,
flags->props, &fss, &fsavl);
if (err)
goto err_out;
VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
@@ -1988,6 +2019,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
sdd.embed_data = flags->embed_data;
sdd.compress = flags->compress;
sdd.raw = flags->raw;
sdd.holds = flags->holds;
sdd.filter_cb = filter_func;
sdd.filter_cb_arg = cb_arg;
if (debugnvp)
@@ -2020,6 +2052,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
sdd.cleanup_fd = -1;
sdd.snapholds = NULL;
}
if (flags->verbose || sdd.snapholds != NULL) {
/*
* Do a verbose no-op dry run to get all the verbose output
@@ -2088,7 +2121,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
if (!flags->dryrun && (flags->replicate || flags->doall ||
flags->props || flags->backup)) {
flags->props || flags->backup || flags->holds)) {
/*
* write final end record. NB: want to do this even if
* there was some error, because it might not be totally
@@ -2820,7 +2853,8 @@ again:
VERIFY(0 == nvlist_alloc(&deleted, NV_UNIQUE_NAME, 0));
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
recursive, B_TRUE, B_FALSE, B_FALSE, &local_nv, &local_avl)) != 0)
recursive, B_TRUE, B_FALSE, B_FALSE, B_FALSE, B_TRUE, &local_nv,
&local_avl)) != 0)
return (error);
/*
@@ -3655,6 +3689,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
uint64_t parent_snapguid = 0;
prop_changelist_t *clp = NULL;
nvlist_t *snapprops_nvlist = NULL;
nvlist_t *snapholds_nvlist = NULL;
zprop_errflags_t prop_errflags;
nvlist_t *prop_errors = NULL;
boolean_t recursive;
@@ -3683,6 +3718,9 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
ENOENT);
/* Did the user request holds be skipped via zfs recv -k? */
boolean_t holds = flags->holds && !flags->skipholds;
if (stream_avl != NULL) {
char *keylocation = NULL;
nvlist_t *lookup = NULL;
@@ -3716,11 +3754,22 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (flags->canmountoff) {
VERIFY(0 == nvlist_add_uint64(rcvprops,
zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0));
} else if (newprops) { /* nothing in rcvprops, eliminate it */
nvlist_free(rcvprops);
rcvprops = NULL;
newprops = B_FALSE;
}
if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) {
VERIFY(0 == nvlist_lookup_nvlist(lookup,
snapname, &snapprops_nvlist));
}
if (holds) {
if (0 == nvlist_lookup_nvlist(fs, "snapholds",
&lookup)) {
VERIFY(0 == nvlist_lookup_nvlist(lookup,
snapname, &snapholds_nvlist));
}
}
}
cp = NULL;
@@ -4204,6 +4253,22 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zcmd_free_nvlists(&zc);
}
}
if (err == 0 && snapholds_nvlist) {
nvpair_t *pair;
nvlist_t *holds, *errors = NULL;
int cleanup_fd = -1;
VERIFY(0 == nvlist_alloc(&holds, 0, KM_SLEEP));
for (pair = nvlist_next_nvpair(snapholds_nvlist, NULL);
pair != NULL;
pair = nvlist_next_nvpair(snapholds_nvlist, pair)) {
VERIFY(0 == nvlist_add_string(holds, destsnap,
nvpair_name(pair)));
}
(void) lzc_hold(holds, cleanup_fd, &errors);
nvlist_free(snapholds_nvlist);
nvlist_free(holds);
}
if (err && (ioctl_errno == ENOENT || ioctl_errno == EEXIST)) {
/*
@@ -4222,7 +4287,8 @@ 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, B_FALSE, &local_nv, &local_avl) == 0) {
B_FALSE, B_FALSE, B_FALSE, B_TRUE, &local_nv, &local_avl)
== 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
fsavl_destroy(local_avl);
@@ -4544,6 +4610,12 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
}
/* Holds feature is set once in the compound stream header. */
boolean_t holds = (DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
DMU_BACKUP_FEATURE_HOLDS);
if (holds)
flags->holds = B_TRUE;
if (strchr(drrb->drr_toname, '@') == NULL) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
"stream (bad snapshot name)"));