Skip to content

Commit f55cea9

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 c3fba90 commit f55cea9

File tree

12 files changed

+220
-32
lines changed

12 files changed

+220
-32
lines changed

cmd/zfs/zfs_main.c

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ get_usage(zfs_help_t idx)
302302
case HELP_SEND:
303303
return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] "
304304
"<snapshot>\n"
305-
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
305+
"\tsend [-nvPLecwS] [-i snapshot|bookmark] "
306306
"<filesystem|volume|snapshot>\n"
307-
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
307+
"\tsend [-DnPpvLecS] [-i bookmark|snapshot] "
308308
"--redact <bookmark> <snapshot>\n"
309309
"\tsend [-nvPe] -t <receive_resume_token>\n"));
310310
case HELP_SET:
@@ -4099,7 +4099,7 @@ zfs_do_send(int argc, char **argv)
40994099
char *toname = NULL;
41004100
char *resume_token = NULL;
41014101
char *cp;
4102-
zfs_handle_t *zhp;
4102+
zfs_handle_t *zhp = NULL;
41034103
sendflags_t flags = { 0 };
41044104
int c, err;
41054105
nvlist_t *dbgnv = NULL;
@@ -4120,11 +4120,12 @@ zfs_do_send(int argc, char **argv)
41204120
{"raw", no_argument, NULL, 'w'},
41214121
{"backup", no_argument, NULL, 'b'},
41224122
{"holds", no_argument, NULL, 'h'},
4123+
{"partial", no_argument, NULL, 'S'},
41234124
{0, 0, 0, 0}
41244125
};
41254126

41264127
/* check options */
4127-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
4128+
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
41284129
long_options, NULL)) != -1) {
41294130
switch (c) {
41304131
case 'i':
@@ -4184,6 +4185,9 @@ zfs_do_send(int argc, char **argv)
41844185
flags.embed_data = B_TRUE;
41854186
flags.largeblock = B_TRUE;
41864187
break;
4188+
case 'S':
4189+
flags.partial = B_TRUE;
4190+
break;
41874191
case ':':
41884192
/*
41894193
* If a parameter was not passed, optopt contains the
@@ -4234,7 +4238,7 @@ zfs_do_send(int argc, char **argv)
42344238
if (resume_token != NULL) {
42354239
if (fromname != NULL || flags.replicate || flags.props ||
42364240
flags.backup || flags.dedup || flags.holds ||
4237-
redactbook != NULL) {
4241+
flags.partial || redactbook != NULL) {
42384242
(void) fprintf(stderr,
42394243
gettext("invalid flags combined with -t\n"));
42404244
usage(B_FALSE);
@@ -4255,6 +4259,21 @@ zfs_do_send(int argc, char **argv)
42554259
}
42564260
}
42574261

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

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

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

42944333
if (fromname != NULL && (strchr(fromname, '#') == NULL &&
42954334
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
@@ -3805,6 +3805,12 @@ Note that if you do not use this flag for sending encrypted datasets, data will
38053805
be sent unencrypted and may be re-encrypted with a different encryption key on
38063806
the receiving system, which will disable the ability to do a raw send to that
38073807
system for incrementals.
3808+
.It Fl S, -partial
3809+
Send a dataset that has been partially transferred via a resumable receive.
3810+
This flag is compatible with the
3811+
.Fl i
3812+
flag, allowing the current state of the filesystem to be sent as an incremental
3813+
stream. Streams generated using this flag may also be received resumably.
38083814
.It Fl e, -embed
38093815
Generate a more compact stream by using
38103816
.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/zfs_ioctl.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5259,6 +5259,7 @@ zfs_ioc_send(zfs_cmd_t *zc)
52595259
boolean_t large_block_ok = (zc->zc_flags & 0x2);
52605260
boolean_t compressok = (zc->zc_flags & 0x4);
52615261
boolean_t rawok = (zc->zc_flags & 0x8);
5262+
boolean_t partialok = (zc->zc_flags & 0x10);
52625263

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

53315332
if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
53325333
fp->f_offset = off;
@@ -6232,6 +6233,7 @@ static const zfs_ioc_key_t zfs_keys_send_new[] = {
62326233
{"embedok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
62336234
{"compressok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
62346235
{"rawok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
6236+
{"partialok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL},
62356237
{"resume_object", DATA_TYPE_UINT64, ZK_OPTIONAL},
62366238
{"resume_offset", DATA_TYPE_UINT64, ZK_OPTIONAL},
62376239
{"redactbook", DATA_TYPE_STRING, ZK_OPTIONAL},
@@ -6250,6 +6252,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
62506252
boolean_t embedok;
62516253
boolean_t compressok;
62526254
boolean_t rawok;
6255+
boolean_t partialok;
62536256
uint64_t resumeobj = 0;
62546257
uint64_t resumeoff = 0;
62556258
char *redactbook = NULL;
@@ -6262,6 +6265,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
62626265
embedok = nvlist_exists(innvl, "embedok");
62636266
compressok = nvlist_exists(innvl, "compressok");
62646267
rawok = nvlist_exists(innvl, "rawok");
6268+
partialok = nvlist_exists(innvl, "partialok");
62656269

62666270
(void) nvlist_lookup_uint64(innvl, "resume_object", &resumeobj);
62676271
(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)
62766280
out.dso_outfunc = dump_bytes;
62776281
out.dso_arg = fp->f_vnode;
62786282
out.dso_dryrun = B_FALSE;
6279-
error = dmu_send(snapname, fromname, embedok, largeblockok, compressok,
6280-
rawok, resumeobj, resumeoff, redactbook, fd, &off, &out);
6283+
error = dmu_send(snapname, fromname, embedok, largeblockok,
6284+
compressok, rawok, partialok, resumeobj, resumeoff,
6285+
redactbook, fd, &off, &out);
62816286

62826287
if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
62836288
fp->f_offset = off;
@@ -6345,6 +6350,7 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
63456350
boolean_t embedok;
63466351
boolean_t compressok;
63476352
boolean_t rawok;
6353+
boolean_t partialok;
63486354
uint64_t space = 0;
63496355
boolean_t full_estimate = B_FALSE;
63506356
uint64_t resumeobj = 0;
@@ -6368,6 +6374,7 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
63686374
embedok = nvlist_exists(innvl, "embedok");
63696375
compressok = nvlist_exists(innvl, "compressok");
63706376
rawok = nvlist_exists(innvl, "rawok");
6377+
partialok = nvlist_exists(innvl, "partialok");
63716378
boolean_t from = (nvlist_lookup_string(innvl, "from", &fromname) == 0);
63726379
boolean_t altbook = (nvlist_lookup_string(innvl, "redactbook",
63736380
&redactlist_book) == 0);
@@ -6442,8 +6449,8 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl)
64426449
dsl_dataset_rele(tosnap, FTAG);
64436450
dsl_pool_rele(dp, FTAG);
64446451
error = dmu_send(snapname, fromname, embedok, largeblockok,
6445-
compressok, rawok, resumeobj, resumeoff, redactlist_book,
6446-
fd, &off, &out);
6452+
compressok, rawok, partialok, resumeobj, resumeoff,
6453+
redactlist_book, fd, &off, &out);
64476454
} else {
64486455
error = dmu_send_estimate_fast(tosnap, fromsnap,
64496456
(from && strchr(fromname, '#') != NULL ? &zbm : NULL),

0 commit comments

Comments
 (0)