Skip to content
Permalink
Browse files

Add 'zfs send --partial' flag

This commit adds the --partial (-S) to the 'zfs send' command.
This flag allows a user to send a partially received dataset,
which can be useful when migrating a backup server to new
hardware. This flag is compatible with resumable receives, so
even if the partial transfer is interrupted, it can be resumed.
The flag does not require any user / kernel ABI changes or any
new feature flags in the send stream format.

Signed-off-by: Tom Caputi <tcaputi@datto.com>
  • Loading branch information...
tcaputi committed Jul 5, 2019
1 parent c3fba90 commit f55cea990a2b5c7163cfbba7557338bcb86e40fd
@@ -302,9 +302,9 @@ get_usage(zfs_help_t idx)
case HELP_SEND:
return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] "
"<snapshot>\n"
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
"\tsend [-nvPLecwS] [-i snapshot|bookmark] "
"<filesystem|volume|snapshot>\n"
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
"\tsend [-DnPpvLecS] [-i bookmark|snapshot] "
"--redact <bookmark> <snapshot>\n"
"\tsend [-nvPe] -t <receive_resume_token>\n"));
case HELP_SET:
@@ -4099,7 +4099,7 @@ zfs_do_send(int argc, char **argv)
char *toname = NULL;
char *resume_token = NULL;
char *cp;
zfs_handle_t *zhp;
zfs_handle_t *zhp = NULL;
sendflags_t flags = { 0 };
int c, err;
nvlist_t *dbgnv = NULL;
@@ -4120,11 +4120,12 @@ zfs_do_send(int argc, char **argv)
{"raw", no_argument, NULL, 'w'},
{"backup", no_argument, NULL, 'b'},
{"holds", no_argument, NULL, 'h'},
{"partial", no_argument, NULL, 'S'},
{0, 0, 0, 0}
};

/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
long_options, NULL)) != -1) {
switch (c) {
case 'i':
@@ -4184,6 +4185,9 @@ zfs_do_send(int argc, char **argv)
flags.embed_data = B_TRUE;
flags.largeblock = B_TRUE;
break;
case 'S':
flags.partial = B_TRUE;
break;
case ':':
/*
* If a parameter was not passed, optopt contains the
@@ -4234,7 +4238,7 @@ zfs_do_send(int argc, char **argv)
if (resume_token != NULL) {
if (fromname != NULL || flags.replicate || flags.props ||
flags.backup || flags.dedup || flags.holds ||
redactbook != NULL) {
flags.partial || redactbook != NULL) {
(void) fprintf(stderr,
gettext("invalid flags combined with -t\n"));
usage(B_FALSE);
@@ -4255,6 +4259,21 @@ zfs_do_send(int argc, char **argv)
}
}

if (flags.partial) {
if (flags.replicate || flags.props || flags.doall ||
flags.backup || flags.dedup || flags.holds ||
redactbook != NULL) {
(void) fprintf(stderr,
gettext("invalid flags combined with -S\n"));
usage(B_FALSE);
}
if (strchr(argv[0], '@') != NULL) {
(void) fprintf(stderr,
gettext("-S cannot send snapshots\n"));
usage(B_FALSE);
}
}

if (flags.raw && redactbook != NULL) {
(void) fprintf(stderr,
gettext("Error: raw sends may not be redacted.\n"));
@@ -4278,6 +4297,7 @@ zfs_do_send(int argc, char **argv)
*/
if (!(flags.replicate || flags.doall)) {
char frombuf[ZFS_MAX_DATASET_NAME_LEN];
char tobuf[ZFS_MAX_DATASET_NAME_LEN + 6];

if (redactbook != NULL) {
if (strchr(argv[0], '@') == NULL) {
@@ -4287,9 +4307,28 @@ zfs_do_send(int argc, char **argv)
}
}

zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
if (zhp == NULL)
return (1);
/*
* If this is a partial send, the partial data will either
* live in the dataset itself (if the dataset did not
* previously exist) or it will live in the special '%recv'
* clone. To figure out which is correct we simply check both.
*/
if (flags.partial) {
(void) snprintf(tobuf, sizeof (tobuf), "%s/%%recv",
argv[0]);
if (zfs_dataset_exists(g_zfs, tobuf,
ZFS_TYPE_DATASET)) {
zhp = zfs_open(g_zfs, tobuf, ZFS_TYPE_DATASET);
if (zhp == NULL)
return (1);
}
}

if (zhp == NULL) {
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
if (zhp == NULL)
return (1);
}

if (fromname != NULL && (strchr(fromname, '#') == NULL &&
strchr(fromname, '@') == NULL)) {
@@ -668,6 +668,9 @@ typedef struct sendflags {

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

/* stream represents a partially received dataset */
boolean_t partial;
} sendflags_t;

typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);
@@ -79,6 +79,7 @@ enum lzc_send_flags {
LZC_SEND_FLAG_LARGE_BLOCK = 1 << 1,
LZC_SEND_FLAG_COMPRESS = 1 << 2,
LZC_SEND_FLAG_RAW = 1 << 3,
LZC_SEND_FLAG_PARTIAL = 1 << 4,
};

int lzc_send(const char *, const char *, int, enum lzc_send_flags);
@@ -51,14 +51,16 @@ struct dmu_send_outparams;
int
dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
boolean_t large_block_ok, boolean_t compressok, boolean_t rawok,
uint64_t resumeobj, uint64_t resumeoff, const char *redactbook, int outfd,
offset_t *off, struct dmu_send_outparams *dsop);
boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff,
const char *redactbook, int outfd, offset_t *off,
struct dmu_send_outparams *dsop);
int dmu_send_estimate_fast(struct dsl_dataset *ds, struct dsl_dataset *fromds,
zfs_bookmark_phys_t *frombook, boolean_t stream_compressed,
uint64_t *sizep);
int dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
boolean_t embedok, boolean_t large_block_ok, boolean_t compressok,
boolean_t rawok, int outfd, offset_t *off, struct dmu_send_outparams *dso);
boolean_t rawok, boolean_t partialok, int outfd, offset_t *off,
struct dmu_send_outparams *dso);

typedef int (*dmu_send_outfunc_t)(objset_t *os, void *buf, int len, void *arg);
typedef struct dmu_send_outparams {
@@ -1815,6 +1815,8 @@ lzc_flags_from_sendflags(const sendflags_t *flags)
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
if (flags->raw)
lzc_flags |= LZC_SEND_FLAG_RAW;
if (flags->partial)
lzc_flags |= LZC_SEND_FLAG_PARTIAL;
return (lzc_flags);
}

@@ -674,6 +674,8 @@ lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
fnvlist_add_boolean(args, "compressok");
if (flags & LZC_SEND_FLAG_RAW)
fnvlist_add_boolean(args, "rawok");
if (flags & LZC_SEND_FLAG_PARTIAL)
fnvlist_add_boolean(args, "partialok");
if (resumeobj != 0 || resumeoff != 0) {
fnvlist_add_uint64(args, "resume_object", resumeobj);
fnvlist_add_uint64(args, "resume_offset", resumeoff);
@@ -3805,6 +3805,12 @@ Note that if you do not use this flag for sending encrypted datasets, data will
be sent unencrypted and may be re-encrypted with a different encryption key on
the receiving system, which will disable the ability to do a raw send to that
system for incrementals.
.It Fl S, -partial
Send a dataset that has been partially transferred via a resumable receive.
This flag is compatible with the
.Fl i
flag, allowing the current state of the filesystem to be sent as an incremental
stream. Streams generated using this flag may also be received resumably.
.It Fl e, -embed
Generate a more compact stream by using
.Sy WRITE_EMBEDDED
@@ -1898,6 +1898,8 @@ struct dmu_send_params {
boolean_t embedok;
boolean_t large_block_ok;
boolean_t compressok;
boolean_t rawok;
boolean_t partialok;
uint64_t resumeobj;
uint64_t resumeoff;
zfs_bookmark_phys_t *redactbook;
@@ -1907,7 +1909,6 @@ struct dmu_send_params {
/* Stream progress params */
offset_t *off;
int outfd;
boolean_t rawok;
};

static int
@@ -1995,7 +1996,24 @@ create_begin_record(struct dmu_send_params *dspp, objset_t *os,
drr->drr_u.drr_begin.drr_flags |= DRR_FLAG_SPILL_BLOCK;

dsl_dataset_name(to_ds, drrb->drr_toname);
if (!to_ds->ds_is_snapshot) {
if (dspp->partialok) {
objset_t *mos = dmu_objset_pool(os)->dp_meta_objset;

/*
* If we're sending a partially received dataset
* we need to get the toguid and toname from the
* receive resume token since to_ds is a
* non-snapshotted, inconsistent dataset.
*/
VERIFY0(zap_lookup(mos, dmu_objset_id(os),
DS_FIELD_RESUME_TOGUID, sizeof (drrb->drr_toguid), 1,
&drrb->drr_toguid));
VERIFY0(zap_lookup(mos, dmu_objset_id(os),
DS_FIELD_RESUME_TONAME, 1, sizeof (drrb->drr_toname),
drrb->drr_toname));

drrb->drr_flags &= ~DRR_FLAG_CLONE;
} else if (!to_ds->ds_is_snapshot) {
(void) strlcat(drrb->drr_toname, "@--head--",
sizeof (drrb->drr_toname));
}
@@ -2536,19 +2554,27 @@ dmu_send_impl(struct dmu_send_params *dspp)
goto out;
}

bzero(drr, sizeof (dmu_replay_record_t));
drr->drr_type = DRR_END;
drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc;
drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid;
/*
* Send the DRR_END record if this is not a partial stream.
* Otherwise, the omitted DRR_END record will signal to
* the receive side that the stream is incomplete.
*/
if (!dspp->partialok) {
bzero(drr, sizeof (dmu_replay_record_t));
drr->drr_type = DRR_END;
drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc;
drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid;

if (dump_record(&dsc, NULL, 0) != 0)
err = dsc.dsc_err;
if (dump_record(&dsc, NULL, 0) != 0)
err = dsc.dsc_err;
}
out:
mutex_enter(&to_ds->ds_sendstream_lock);
list_remove(&to_ds->ds_sendstreams, dssp);
mutex_exit(&to_ds->ds_sendstream_lock);

VERIFY(err != 0 || (dsc.dsc_sent_begin && dsc.dsc_sent_end));
VERIFY(err != 0 || (dsc.dsc_sent_begin &&
(dsc.dsc_sent_end || dspp->partialok)));

kmem_free(drr, sizeof (dmu_replay_record_t));
kmem_free(dssp, sizeof (dmu_sendstatus_t));
@@ -2574,7 +2600,8 @@ dmu_send_impl(struct dmu_send_params *dspp)
int
dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
boolean_t embedok, boolean_t large_block_ok, boolean_t compressok,
boolean_t rawok, int outfd, offset_t *off, dmu_send_outparams_t *dsop)
boolean_t rawok, boolean_t partialok, int outfd, offset_t *off,
dmu_send_outparams_t *dsop)
{
int err;
dsl_dataset_t *fromds;
@@ -2588,6 +2615,7 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
dspp.dso = dsop;
dspp.tag = FTAG;
dspp.rawok = rawok;
dspp.partialok = partialok;

err = dsl_pool_hold(pool, FTAG, &dspp.dp);
if (err != 0)
@@ -2654,8 +2682,9 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
int
dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
boolean_t large_block_ok, boolean_t compressok, boolean_t rawok,
uint64_t resumeobj, uint64_t resumeoff, const char *redactbook, int outfd,
offset_t *off, dmu_send_outparams_t *dsop)
boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff,
const char *redactbook, int outfd, offset_t *off,
dmu_send_outparams_t *dsop)
{
int err = 0;
ds_hold_flags_t dsflags = (rawok) ? 0 : DS_HOLD_FLAG_DECRYPT;
@@ -2674,6 +2703,7 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
dspp.resumeobj = resumeobj;
dspp.resumeoff = resumeoff;
dspp.rawok = rawok;
dspp.partialok = partialok;

if (fromsnap != NULL && strpbrk(fromsnap, "@#") == NULL)
return (SET_ERROR(EINVAL));
@@ -2686,8 +2716,13 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
* We are sending a filesystem or volume. Ensure
* that it doesn't change by owning the dataset.
*/
err = dsl_dataset_own(dspp.dp, tosnap, dsflags, FTAG,
&dspp.to_ds);
if (partialok) {
err = dsl_dataset_own_force(dspp.dp, tosnap, dsflags,
FTAG, &dspp.to_ds);
} else {
err = dsl_dataset_own(dspp.dp, tosnap, dsflags,
FTAG, &dspp.to_ds);
}
owned = B_TRUE;
} else {
err = dsl_dataset_hold_flags(dspp.dp, tosnap, dsflags, FTAG,
@@ -5259,6 +5259,7 @@ zfs_ioc_send(zfs_cmd_t *zc)
boolean_t large_block_ok = (zc->zc_flags & 0x2);
boolean_t compressok = (zc->zc_flags & 0x4);
boolean_t rawok = (zc->zc_flags & 0x8);
boolean_t partialok = (zc->zc_flags & 0x10);

if (zc->zc_obj != 0) {
dsl_pool_t *dp;
@@ -5325,8 +5326,8 @@ zfs_ioc_send(zfs_cmd_t *zc)
out.dso_arg = fp->f_vnode;
out.dso_dryrun = B_FALSE;
error = dmu_send_obj(zc->zc_name, zc->zc_sendobj,
zc->zc_fromobj, embedok, large_block_ok, compressok, rawok,
zc->zc_cookie, &off, &out);
zc->zc_fromobj, embedok, large_block_ok, compressok,
rawok, partialok, zc->zc_cookie, &off, &out);

if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
fp->f_offset = off;
@@ -6232,6 +6233,7 @@ static const zfs_ioc_key_t zfs_keys_send_new[] = {
{"embedok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
{"compressok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
{"rawok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
{"partialok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
{"resume_object", DATA_TYPE_UINT64, ZK_OPTIONAL},
{"resume_offset", DATA_TYPE_UINT64, ZK_OPTIONAL},
{"redactbook", DATA_TYPE_STRING, ZK_OPTIONAL},
@@ -6250,6 +6252,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
boolean_t embedok;
boolean_t compressok;
boolean_t rawok;
boolean_t partialok;
uint64_t resumeobj = 0;
uint64_t resumeoff = 0;
char *redactbook = NULL;
@@ -6262,6 +6265,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
embedok = nvlist_exists(innvl, "embedok");
compressok = nvlist_exists(innvl, "compressok");
rawok = nvlist_exists(innvl, "rawok");
partialok = nvlist_exists(innvl, "partialok");

(void) nvlist_lookup_uint64(innvl, "resume_object", &resumeobj);
(void) nvlist_lookup_uint64(innvl, "resume_offset", &resumeoff);
@@ -6276,8 +6280,9 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
out.dso_outfunc = dump_bytes;
out.dso_arg = fp->f_vnode;
out.dso_dryrun = B_FALSE;
error = dmu_send(snapname, fromname, embedok, largeblockok, compressok,
rawok, resumeobj, resumeoff, redactbook, fd, &off, &out);
error = dmu_send(snapname, fromname, embedok, largeblockok,
compressok, rawok, partialok, resumeobj, resumeoff,
redactbook, fd, &off, &out);

if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
fp->f_offset = off;
@@ -6345,6 +6350,7 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
boolean_t embedok;
boolean_t compressok;
boolean_t rawok;
boolean_t partialok;
uint64_t space = 0;
boolean_t full_estimate = B_FALSE;
uint64_t resumeobj = 0;
@@ -6368,6 +6374,7 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
embedok = nvlist_exists(innvl, "embedok");
compressok = nvlist_exists(innvl, "compressok");
rawok = nvlist_exists(innvl, "rawok");
partialok = nvlist_exists(innvl, "partialok");
boolean_t from = (nvlist_lookup_string(innvl, "from", &fromname) == 0);
boolean_t altbook = (nvlist_lookup_string(innvl, "redactbook",
&redactlist_book) == 0);
@@ -6442,8 +6449,8 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
dsl_dataset_rele(tosnap, FTAG);
dsl_pool_rele(dp, FTAG);
error = dmu_send(snapname, fromname, embedok, largeblockok,
compressok, rawok, resumeobj, resumeoff, redactlist_book,
fd, &off, &out);
compressok, rawok, partialok, resumeobj, resumeoff,
redactlist_book, fd, &off, &out);
} else {
error = dmu_send_estimate_fast(tosnap, fromsnap,
(from && strchr(fromname, '#') != NULL ? &zbm : NULL),

0 comments on commit f55cea9

Please sign in to comment.
You can’t perform that action at this time.