Skip to content

Commit 4fc42f9

Browse files
author
Tom Caputi
committed
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 <[email protected]>
1 parent 681a85c commit 4fc42f9

File tree

13 files changed

+219
-35
lines changed

13 files changed

+219
-35
lines changed

cmd/zfs/zfs_main.c

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ get_usage(zfs_help_t idx)
301301
case HELP_SEND:
302302
return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] "
303303
"<snapshot>\n"
304-
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
304+
"\tsend [-nvPLecwS] [-i snapshot|bookmark] "
305305
"<filesystem|volume|snapshot>\n"
306-
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
306+
"\tsend [-DnPpvLecS] [-i bookmark|snapshot] "
307307
"--redact <bookmark> <snapshot>\n"
308308
"\tsend [-nvPe] -t <receive_resume_token>\n"));
309309
case HELP_SET:
@@ -4097,7 +4097,7 @@ zfs_do_send(int argc, char **argv)
40974097
char *toname = NULL;
40984098
char *resume_token = NULL;
40994099
char *cp;
4100-
zfs_handle_t *zhp;
4100+
zfs_handle_t *zhp = NULL;
41014101
sendflags_t flags = { 0 };
41024102
int c, err;
41034103
nvlist_t *dbgnv = NULL;
@@ -4118,11 +4118,12 @@ zfs_do_send(int argc, char **argv)
41184118
{"raw", no_argument, NULL, 'w'},
41194119
{"backup", no_argument, NULL, 'b'},
41204120
{"holds", no_argument, NULL, 'h'},
4121+
{"partial", no_argument, NULL, 'S'},
41214122
{0, 0, 0, 0}
41224123
};
41234124

41244125
/* check options */
4125-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
4126+
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
41264127
long_options, NULL)) != -1) {
41274128
switch (c) {
41284129
case 'i':
@@ -4182,6 +4183,9 @@ zfs_do_send(int argc, char **argv)
41824183
flags.embed_data = B_TRUE;
41834184
flags.largeblock = B_TRUE;
41844185
break;
4186+
case 'S':
4187+
flags.partial = B_TRUE;
4188+
break;
41854189
case ':':
41864190
/*
41874191
* If a parameter was not passed, optopt contains the
@@ -4232,7 +4236,7 @@ zfs_do_send(int argc, char **argv)
42324236
if (resume_token != NULL) {
42334237
if (fromname != NULL || flags.replicate || flags.props ||
42344238
flags.backup || flags.dedup || flags.holds ||
4235-
redactbook != NULL) {
4239+
flags.partial || redactbook != NULL) {
42364240
(void) fprintf(stderr,
42374241
gettext("invalid flags combined with -t\n"));
42384242
usage(B_FALSE);
@@ -4253,6 +4257,21 @@ zfs_do_send(int argc, char **argv)
42534257
}
42544258
}
42554259

4260+
if (flags.partial) {
4261+
if (flags.replicate || flags.props || flags.doall ||
4262+
flags.backup || flags.dedup || flags.holds ||
4263+
redactbook != NULL) {
4264+
(void) fprintf(stderr,
4265+
gettext("invalid flags combined with -S\n"));
4266+
usage(B_FALSE);
4267+
}
4268+
if (strchr(argv[0], '@') != NULL) {
4269+
(void) fprintf(stderr,
4270+
gettext("-S cannot send snapshots\n"));
4271+
usage(B_FALSE);
4272+
}
4273+
}
4274+
42564275
if (flags.raw && redactbook != NULL) {
42574276
(void) fprintf(stderr,
42584277
gettext("Error: raw sends may not be redacted.\n"));
@@ -4276,6 +4295,7 @@ zfs_do_send(int argc, char **argv)
42764295
*/
42774296
if (!(flags.replicate || flags.doall)) {
42784297
char frombuf[ZFS_MAX_DATASET_NAME_LEN];
4298+
char tobuf[ZFS_MAX_DATASET_NAME_LEN + 6];
42794299

42804300
if (redactbook != NULL) {
42814301
if (strchr(argv[0], '@') == NULL) {
@@ -4285,9 +4305,28 @@ zfs_do_send(int argc, char **argv)
42854305
}
42864306
}
42874307

4288-
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
4289-
if (zhp == NULL)
4290-
return (1);
4308+
/*
4309+
* If this is a partial send, the partial data will either
4310+
* live in the dataset itself (if the dataset did not
4311+
* previously exist) or it will live in the special '%recv'
4312+
* clone. To figure out which is correct we simply check both.
4313+
*/
4314+
if (flags.partial) {
4315+
(void) snprintf(tobuf, sizeof (tobuf), "%s/%%recv",
4316+
argv[0]);
4317+
if (zfs_dataset_exists(g_zfs, tobuf,
4318+
ZFS_TYPE_DATASET)) {
4319+
zhp = zfs_open(g_zfs, tobuf, ZFS_TYPE_DATASET);
4320+
if (zhp == NULL)
4321+
return (1);
4322+
}
4323+
}
4324+
4325+
if (zhp == NULL) {
4326+
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
4327+
if (zhp == NULL)
4328+
return (1);
4329+
}
42914330

42924331
if (fromname != NULL && (strchr(fromname, '#') == NULL &&
42934332
strchr(fromname, '@') == NULL)) {

include/libzfs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,9 @@ typedef struct sendflags {
668668

669669
/* include snapshot holds in send stream */
670670
boolean_t holds;
671+
672+
/* stream represents a partially received dataset */
673+
boolean_t partial;
671674
} sendflags_t;
672675

673676
typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);

include/libzfs_core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ enum lzc_send_flags {
7979
LZC_SEND_FLAG_LARGE_BLOCK = 1 << 1,
8080
LZC_SEND_FLAG_COMPRESS = 1 << 2,
8181
LZC_SEND_FLAG_RAW = 1 << 3,
82+
LZC_SEND_FLAG_PARTIAL = 1 << 4,
8283
};
8384

8485
int lzc_send(const char *, const char *, int, enum lzc_send_flags);

include/sys/dmu_send.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,16 @@ struct dmu_send_outparams;
5151
int
5252
dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
5353
boolean_t large_block_ok, boolean_t compressok, boolean_t rawok,
54-
uint64_t resumeobj, uint64_t resumeoff, const char *redactbook, int outfd,
55-
offset_t *off, struct dmu_send_outparams *dsop);
54+
boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff,
55+
const char *redactbook, int outfd, offset_t *off,
56+
struct dmu_send_outparams *dsop);
5657
int dmu_send_estimate_fast(struct dsl_dataset *ds, struct dsl_dataset *fromds,
5758
zfs_bookmark_phys_t *frombook, boolean_t stream_compressed,
5859
uint64_t *sizep);
5960
int dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
6061
boolean_t embedok, boolean_t large_block_ok, boolean_t compressok,
61-
boolean_t rawok, int outfd, offset_t *off, struct dmu_send_outparams *dso);
62+
boolean_t rawok, boolean_t partialok, int outfd, offset_t *off,
63+
struct dmu_send_outparams *dso);
6264

6365
typedef int (*dmu_send_outfunc_t)(objset_t *os, void *buf, int len, void *arg);
6466
typedef struct dmu_send_outparams {

lib/libzfs/libzfs_sendrecv.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,8 @@ lzc_flags_from_sendflags(const sendflags_t *flags)
18151815
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
18161816
if (flags->raw)
18171817
lzc_flags |= LZC_SEND_FLAG_RAW;
1818+
if (flags->partial)
1819+
lzc_flags |= LZC_SEND_FLAG_PARTIAL;
18181820
return (lzc_flags);
18191821
}
18201822

lib/libzfs_core/libzfs_core.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,8 @@ lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
674674
fnvlist_add_boolean(args, "compressok");
675675
if (flags & LZC_SEND_FLAG_RAW)
676676
fnvlist_add_boolean(args, "rawok");
677+
if (flags & LZC_SEND_FLAG_PARTIAL)
678+
fnvlist_add_boolean(args, "partialok");
677679
if (resumeobj != 0 || resumeoff != 0) {
678680
fnvlist_add_uint64(args, "resume_object", resumeobj);
679681
fnvlist_add_uint64(args, "resume_offset", resumeoff);

man/man8/zfs.8

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3806,6 +3806,12 @@ Note that if you do not use this flag for sending encrypted datasets, data will
38063806
be sent unencrypted and may be re-encrypted with a different encryption key on
38073807
the receiving system, which will disable the ability to do a raw send to that
38083808
system for incrementals.
3809+
.It Fl S, -partial
3810+
Send a dataset that has been partially transferred via a resumable receive.
3811+
This flag is compatible with the
3812+
.Fl i
3813+
flag, allowing the current state of the filesystem to be sent as an incremental
3814+
stream. Streams generated using this flag may also be received resumably.
38093815
.It Fl e, -embed
38103816
Generate a more compact stream by using
38113817
.Sy WRITE_EMBEDDED

module/zfs/dmu_send.c

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,6 +1898,8 @@ struct dmu_send_params {
18981898
boolean_t embedok;
18991899
boolean_t large_block_ok;
19001900
boolean_t compressok;
1901+
boolean_t rawok;
1902+
boolean_t partialok;
19011903
uint64_t resumeobj;
19021904
uint64_t resumeoff;
19031905
zfs_bookmark_phys_t *redactbook;
@@ -1907,7 +1909,6 @@ struct dmu_send_params {
19071909
/* Stream progress params */
19081910
offset_t *off;
19091911
int outfd;
1910-
boolean_t rawok;
19111912
};
19121913

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

19971998
dsl_dataset_name(to_ds, drrb->drr_toname);
1998-
if (!to_ds->ds_is_snapshot) {
1999+
if (dspp->partialok) {
2000+
objset_t *mos = dmu_objset_pool(os)->dp_meta_objset;
2001+
2002+
/*
2003+
* If we're sending a partially received dataset
2004+
* we need to get the toguid and toname from the
2005+
* receive resume token since to_ds is a
2006+
* non-snapshotted, inconsistent dataset.
2007+
*/
2008+
VERIFY0(zap_lookup(mos, dmu_objset_id(os),
2009+
DS_FIELD_RESUME_TOGUID, sizeof (drrb->drr_toguid), 1,
2010+
&drrb->drr_toguid));
2011+
VERIFY0(zap_lookup(mos, dmu_objset_id(os),
2012+
DS_FIELD_RESUME_TONAME, 1, sizeof (drrb->drr_toname),
2013+
drrb->drr_toname));
2014+
2015+
drrb->drr_flags &= ~DRR_FLAG_CLONE;
2016+
} else if (!to_ds->ds_is_snapshot) {
19992017
(void) strlcat(drrb->drr_toname, "@--head--",
20002018
sizeof (drrb->drr_toname));
20012019
}
@@ -2536,19 +2554,27 @@ dmu_send_impl(struct dmu_send_params *dspp)
25362554
goto out;
25372555
}
25382556

2539-
bzero(drr, sizeof (dmu_replay_record_t));
2540-
drr->drr_type = DRR_END;
2541-
drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc;
2542-
drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid;
2557+
/*
2558+
* Send the DRR_END record if this is not a partial stream.
2559+
* Otherwise, the omitted DRR_END record will signal to
2560+
* the receive side that the stream is incomplete.
2561+
*/
2562+
if (!dspp->partialok) {
2563+
bzero(drr, sizeof (dmu_replay_record_t));
2564+
drr->drr_type = DRR_END;
2565+
drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc;
2566+
drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid;
25432567

2544-
if (dump_record(&dsc, NULL, 0) != 0)
2545-
err = dsc.dsc_err;
2568+
if (dump_record(&dsc, NULL, 0) != 0)
2569+
err = dsc.dsc_err;
2570+
}
25462571
out:
25472572
mutex_enter(&to_ds->ds_sendstream_lock);
25482573
list_remove(&to_ds->ds_sendstreams, dssp);
25492574
mutex_exit(&to_ds->ds_sendstream_lock);
25502575

2551-
VERIFY(err != 0 || (dsc.dsc_sent_begin && dsc.dsc_sent_end));
2576+
VERIFY(err != 0 || (dsc.dsc_sent_begin &&
2577+
(dsc.dsc_sent_end || dspp->partialok)));
25522578

25532579
kmem_free(drr, sizeof (dmu_replay_record_t));
25542580
kmem_free(dssp, sizeof (dmu_sendstatus_t));
@@ -2574,7 +2600,8 @@ dmu_send_impl(struct dmu_send_params *dspp)
25742600
int
25752601
dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
25762602
boolean_t embedok, boolean_t large_block_ok, boolean_t compressok,
2577-
boolean_t rawok, int outfd, offset_t *off, dmu_send_outparams_t *dsop)
2603+
boolean_t rawok, boolean_t partialok, int outfd, offset_t *off,
2604+
dmu_send_outparams_t *dsop)
25782605
{
25792606
int err;
25802607
dsl_dataset_t *fromds;
@@ -2588,6 +2615,7 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
25882615
dspp.dso = dsop;
25892616
dspp.tag = FTAG;
25902617
dspp.rawok = rawok;
2618+
dspp.partialok = partialok;
25912619

25922620
err = dsl_pool_hold(pool, FTAG, &dspp.dp);
25932621
if (err != 0)
@@ -2654,8 +2682,9 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
26542682
int
26552683
dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
26562684
boolean_t large_block_ok, boolean_t compressok, boolean_t rawok,
2657-
uint64_t resumeobj, uint64_t resumeoff, const char *redactbook, int outfd,
2658-
offset_t *off, dmu_send_outparams_t *dsop)
2685+
boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff,
2686+
const char *redactbook, int outfd, offset_t *off,
2687+
dmu_send_outparams_t *dsop)
26592688
{
26602689
int err = 0;
26612690
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,
26742703
dspp.resumeobj = resumeobj;
26752704
dspp.resumeoff = resumeoff;
26762705
dspp.rawok = rawok;
2706+
dspp.partialok = partialok;
26772707

26782708
if (fromsnap != NULL && strpbrk(fromsnap, "@#") == NULL)
26792709
return (SET_ERROR(EINVAL));
@@ -2686,8 +2716,13 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
26862716
* We are sending a filesystem or volume. Ensure
26872717
* that it doesn't change by owning the dataset.
26882718
*/
2689-
err = dsl_dataset_own(dspp.dp, tosnap, dsflags, FTAG,
2690-
&dspp.to_ds);
2719+
if (partialok) {
2720+
err = dsl_dataset_own_force(dspp.dp, tosnap, dsflags,
2721+
FTAG, &dspp.to_ds);
2722+
} else {
2723+
err = dsl_dataset_own(dspp.dp, tosnap, dsflags,
2724+
FTAG, &dspp.to_ds);
2725+
}
26912726
owned = B_TRUE;
26922727
} else {
26932728
err = dsl_dataset_hold_flags(dspp.dp, tosnap, dsflags, FTAG,

module/zfs/dsl_dataset.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -968,10 +968,10 @@ dsl_dataset_tryown(dsl_dataset_t *ds, void *tag, boolean_t override)
968968

969969
ASSERT(dsl_pool_config_held(ds->ds_dir->dd_pool));
970970
mutex_enter(&ds->ds_lock);
971-
if (ds->ds_owner == NULL && (override || !(DS_IS_INCONSISTENT(ds) ||
972-
(dsl_dataset_feature_is_active(ds,
971+
if (ds->ds_owner == NULL && (override ||
972+
!(dsl_dataset_feature_is_active(ds,
973973
SPA_FEATURE_REDACTED_DATASETS) &&
974-
!zfs_allow_redacted_dataset_mount)))) {
974+
!zfs_allow_redacted_dataset_mount))) {
975975
ds->ds_owner = tag;
976976
dsl_dataset_long_hold(ds, tag);
977977
gotit = TRUE;

0 commit comments

Comments
 (0)