Permalink
Browse files

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 -k option is used
to skip receive of holds.

Signed-off-by: Paul Zuchowski <pzuchowski@datto.com>
  • Loading branch information...
PaulZ-98 committed Nov 9, 2018
1 parent 22448f0 commit 8b62bf3562728757715bbc86ab2f6a71459a8676
@@ -278,10 +278,10 @@ get_usage(zfs_help_t idx)
case HELP_PROMOTE:
return (gettext("\tpromote <clone-filesystem>\n"));
case HELP_RECEIVE:
return (gettext("\treceive [-vnsFu] "
return (gettext("\treceive [-vnsFku] "
"[-o <property>=<value>] ... [-x <property>] ...\n"
"\t <filesystem|volume|snapshot>\n"
"\treceive [-vnsFu] [-o <property>=<value>] ... "
"\treceive [-vnsFku] [-o <property>=<value>] ... "
"[-x <property>] ... \n"
"\t [-d | -e] <filesystem>\n"
"\treceive -A <filesystem|volume>\n"));
@@ -293,7 +293,7 @@ get_usage(zfs_help_t idx)
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
return (gettext("\tsend [-DnPpRvLecwb] [-[i|I] snapshot] "
return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] "
"<snapshot>\n"
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
"<filesystem|volume|snapshot>\n"
@@ -3971,11 +3971,12 @@ zfs_do_send(int argc, char **argv)
{"compressed", no_argument, NULL, 'c'},
{"raw", no_argument, NULL, 'w'},
{"backup", no_argument, NULL, 'b'},
{"holds", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};

/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLet:cwb", long_options,
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwb", long_options,
NULL)) != -1) {
switch (c) {
case 'i':
@@ -3998,6 +3999,9 @@ zfs_do_send(int argc, char **argv)
case 'b':
flags.backup = B_TRUE;
break;
case 'h':
flags.holds = B_TRUE;
break;
case 'P':
flags.parsable = B_TRUE;
flags.verbose = B_TRUE;
@@ -4120,7 +4124,7 @@ zfs_do_send(int argc, char **argv)
char frombuf[ZFS_MAX_DATASET_NAME_LEN];

if (flags.replicate || flags.doall || flags.props ||
flags.backup || flags.dedup ||
flags.backup || flags.dedup || flags.holds ||
(strchr(argv[0], '@') == NULL &&
(flags.dryrun || flags.verbose || flags.progress))) {
(void) fprintf(stderr, gettext("Error: "
@@ -4225,7 +4229,7 @@ zfs_do_receive(int argc, char **argv)
nomem();

/* check options */
while ((c = getopt(argc, argv, ":o:x:denuvFsA")) != -1) {
while ((c = getopt(argc, argv, ":o:x:deknuvFsA")) != -1) {
switch (c) {
case 'o':
if (!parseprop(props, optarg)) {
@@ -4257,6 +4261,9 @@ zfs_do_receive(int argc, char **argv)
}
flags.istail = B_TRUE;
break;
case 'k':
flags.skipholds = B_TRUE;
break;
case 'n':
flags.dryrun = B_TRUE;
break;
@@ -644,6 +644,9 @@ typedef struct sendflags {

/* only send received properties (ie. -b) */
boolean_t backup;

/* include snapshot holds in send stream */
boolean_t holds;
} sendflags_t;

typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);
@@ -706,6 +709,12 @@ typedef struct recvflags {

/* do not mount file systems as they are extracted (private) */
boolean_t nomount;

/* Was holds flag set in the compound header? */
boolean_t holds;

/* skip receive of snapshot holds */
boolean_t skipholds;
} recvflags_t;

extern int zfs_receive(libzfs_handle_t *, const char *, nvlist_t *,
@@ -106,6 +106,7 @@ typedef enum drr_headertype {
#define DMU_BACKUP_FEATURE_LARGE_DNODE (1 << 23)
#define DMU_BACKUP_FEATURE_RAW (1 << 24)
/* flag #25 is reserved for the ZSTD compression feature */
#define DMU_BACKUP_FEATURE_HOLDS (1 << 26)

/*
* Mask of all supported backup features
@@ -115,7 +116,7 @@ typedef enum drr_headertype {
DMU_BACKUP_FEATURE_EMBED_DATA | DMU_BACKUP_FEATURE_LZ4 | \
DMU_BACKUP_FEATURE_RESUMING | DMU_BACKUP_FEATURE_LARGE_BLOCKS | \
DMU_BACKUP_FEATURE_COMPRESSED | DMU_BACKUP_FEATURE_LARGE_DNODE | \
DMU_BACKUP_FEATURE_RAW)
DMU_BACKUP_FEATURE_RAW | DMU_BACKUP_FEATURE_HOLDS)

/* Are all features in the given flag word currently supported? */
#define DMU_STREAM_SUPPORTED(x) (!((x) & ~DMU_BACKUP_FEATURE_MASK))
@@ -29,6 +29,7 @@
* Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
* Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
* Copyright (c) 2017, 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 @@ 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,
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,15 +1919,17 @@ 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;
zio_cksum_t zc;

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 @@ 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, 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;
@@ -4176,6 +4225,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)) {
/*
@@ -4194,7 +4259,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);
@@ -4516,6 +4582,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)"));
Oops, something went wrong.

0 comments on commit 8b62bf3

Please sign in to comment.