Skip to content

Commit

Permalink
Add 'zfs send --partial' flag
Browse files Browse the repository at this point in the history
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
Tom Caputi authored and alek-p committed Jul 19, 2019
1 parent c3fba90 commit ab09006
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 28 deletions.
47 changes: 43 additions & 4 deletions cmd/zfs/zfs_main.c
Expand Up @@ -306,7 +306,8 @@ get_usage(zfs_help_t idx)
"<filesystem|volume|snapshot>\n"
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
"--redact <bookmark> <snapshot>\n"
"\tsend [-nvPe] -t <receive_resume_token>\n"));
"\tsend [-nvPe] -t <receive_resume_token>\n"
"\tsend [-PenvLw] -S [-i bookmark|snapshot] filesystem\n"));
case HELP_SET:
return (gettext("\tset <property=value> ... "
"<filesystem|volume|snapshot> ...\n"));
Expand Down Expand Up @@ -4120,11 +4121,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':
Expand Down Expand Up @@ -4184,6 +4186,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
Expand Down Expand Up @@ -4234,7 +4239,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);
Expand All @@ -4255,6 +4260,23 @@ 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("incompatible flags combined with partial "
"send flag\n"));
usage(B_FALSE);
}
if (strchr(argv[0], '@') != NULL) {
(void) fprintf(stderr,
gettext("partial send cannot send full "
"snapshots\n"));
usage(B_FALSE);
}
}

if (flags.raw && redactbook != NULL) {
(void) fprintf(stderr,
gettext("Error: raw sends may not be redacted.\n"));
Expand All @@ -4278,6 +4300,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) {
Expand All @@ -4287,7 +4310,23 @@ zfs_do_send(int argc, char **argv)
}
}

zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
/*
* 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.
*/
char *open_ds = argv[0];
if (flags.partial) {
(void) snprintf(tobuf, sizeof (tobuf), "%s/%%recv",
argv[0]);
if (zfs_dataset_exists(g_zfs, tobuf,
ZFS_TYPE_DATASET)) {
open_ds = tobuf;
}
}

zhp = zfs_open(g_zfs, open_ds, ZFS_TYPE_DATASET);
if (zhp == NULL)
return (1);

Expand Down
3 changes: 3 additions & 0 deletions include/libzfs.h
Expand Up @@ -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 *);
Expand Down
1 change: 1 addition & 0 deletions include/libzfs_core.h
Expand Up @@ -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);
Expand Down
8 changes: 5 additions & 3 deletions include/sys/dmu_send.h
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions lib/libzfs/libzfs_sendrecv.c
Expand Up @@ -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);
}

Expand Down
2 changes: 2 additions & 0 deletions lib/libzfs_core/libzfs_core.c
Expand Up @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions man/man8/zfs.8
Expand Up @@ -215,6 +215,12 @@
.Op Fl Penv
.Fl t Ar receive_resume_token
.Nm
.Cm send
.Op Fl PenvLw
.Oo Fl i Ar snapshot Ns | Ns Ar bookmark
.Oc
.Fl S Ar filesystem
.Nm
.Cm receive
.Op Fl Fhnsuv
.Op Fl o Sy origin Ns = Ns Ar snapshot
Expand Down Expand Up @@ -3970,6 +3976,25 @@ See the documentation for
for more details.
.It Xo
.Nm
.Cm send
.Op Fl PenvLw
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
.Fl S
.Ar filesystem
.Xc
Send a dataset that has been partially transferred via a resumable receive.
.Bl -tag -width "-L"
.It Fl S, -partial
This flag requires that the specified filesystem previously received a resumable
send that did not finish and was interrupted. In such scenarios this flag
enables us to send this partially received state. This flag is compatible with
the
.Fl i
flag, allowing the partial state of the dataset to be sent as an incremental
stream.
.El
.It Xo
.Nm
.Cm receive
.Op Fl Fhnsuv
.Op Fl o Sy origin Ns = Ns Ar snapshot
Expand Down
98 changes: 84 additions & 14 deletions module/zfs/dmu_send.c
Expand Up @@ -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;
Expand All @@ -1907,7 +1909,6 @@ struct dmu_send_params {
/* Stream progress params */
offset_t *off;
int outfd;
boolean_t rawok;
};

static int
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -2315,6 +2333,40 @@ dmu_send_impl(struct dmu_send_params *dspp)
dsl_pool_rele(dp, tag);
return (err);
}

/*
* If we're sending partial state resume fields must exist and we will
* need to setup the dspp resume fields.
*/
if (dspp->partialok) {
struct drr_begin drrb;
objset_t *mos = dmu_objset_pool(os)->dp_meta_objset;
err = zap_lookup(mos, dmu_objset_id(os),
DS_FIELD_RESUME_TOGUID, sizeof (drrb.drr_toguid), 1,
&drrb.drr_toguid);
err += zap_lookup(mos, dmu_objset_id(os),
DS_FIELD_RESUME_TONAME, 1, sizeof (drrb.drr_toname),
drrb.drr_toname);
if (err != 0) {
dsl_pool_rele(dp, tag);
return (SET_ERROR(EINVAL));
}
err = zap_lookup(mos, dmu_objset_id(os),
DS_FIELD_RESUME_OBJECT, sizeof (dspp->resumeobj), 1,
&dspp->resumeobj);
if (err != 0) {
dsl_pool_rele(dp, tag);
return (SET_ERROR(EINVAL));
}
err = zap_lookup(mos, dmu_objset_id(os),
DS_FIELD_RESUME_OFFSET, sizeof (dspp->resumeoff), 1,
&dspp->resumeoff);
if (err != 0) {
dsl_pool_rele(dp, tag);
return (SET_ERROR(EINVAL));
}
}

/*
* If this is a non-raw send of an encrypted ds, we can ensure that
* the objset_phys_t is authenticated. This is safe because this is
Expand Down Expand Up @@ -2536,19 +2588,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));
Expand All @@ -2574,7 +2634,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;
Expand All @@ -2588,6 +2649,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)
Expand Down Expand Up @@ -2654,8 +2716,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;
Expand All @@ -2674,20 +2737,27 @@ 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));

err = dsl_pool_hold(tosnap, FTAG, &dspp.dp);
if (err != 0)
return (err);

if (strchr(tosnap, '@') == NULL && spa_writeable(dspp.dp->dp_spa)) {
/*
* 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,
Expand Down

0 comments on commit ab09006

Please sign in to comment.