Skip to content

Commit 538875f

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 e6cebbf commit 538875f

File tree

16 files changed

+315
-35
lines changed

16 files changed

+315
-35
lines changed

cmd/zfs/zfs_main.c

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ get_usage(zfs_help_t idx)
306306
"<filesystem|volume|snapshot>\n"
307307
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
308308
"--redact <bookmark> <snapshot>\n"
309-
"\tsend [-nvPe] -t <receive_resume_token>\n"));
309+
"\tsend [-nvPe] -t <receive_resume_token>\n"
310+
"\tsend [-PenvLw] -S filesystem\n"));
310311
case HELP_SET:
311312
return (gettext("\tset <property=value> ... "
312313
"<filesystem|volume|snapshot> ...\n"));
@@ -4190,11 +4191,12 @@ zfs_do_send(int argc, char **argv)
41904191
{"raw", no_argument, NULL, 'w'},
41914192
{"backup", no_argument, NULL, 'b'},
41924193
{"holds", no_argument, NULL, 'h'},
4194+
{"partial", no_argument, NULL, 'S'},
41934195
{0, 0, 0, 0}
41944196
};
41954197

41964198
/* check options */
4197-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
4199+
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
41984200
long_options, NULL)) != -1) {
41994201
switch (c) {
42004202
case 'i':
@@ -4254,6 +4256,9 @@ zfs_do_send(int argc, char **argv)
42544256
flags.embed_data = B_TRUE;
42554257
flags.largeblock = B_TRUE;
42564258
break;
4259+
case 'S':
4260+
flags.partial = B_TRUE;
4261+
break;
42574262
case ':':
42584263
/*
42594264
* If a parameter was not passed, optopt contains the
@@ -4304,7 +4309,7 @@ zfs_do_send(int argc, char **argv)
43044309
if (resume_token != NULL) {
43054310
if (fromname != NULL || flags.replicate || flags.props ||
43064311
flags.backup || flags.dedup || flags.holds ||
4307-
redactbook != NULL) {
4312+
flags.partial || redactbook != NULL) {
43084313
(void) fprintf(stderr,
43094314
gettext("invalid flags combined with -t\n"));
43104315
usage(B_FALSE);
@@ -4325,6 +4330,23 @@ zfs_do_send(int argc, char **argv)
43254330
}
43264331
}
43274332

4333+
if (flags.partial) {
4334+
if (fromname != NULL || flags.replicate || flags.props ||
4335+
flags.doall || flags.backup || flags.dedup ||
4336+
flags.holds || redactbook != NULL) {
4337+
(void) fprintf(stderr,
4338+
gettext("incompatible flags combined with partial "
4339+
"send flag\n"));
4340+
usage(B_FALSE);
4341+
}
4342+
if (strchr(argv[0], '@') != NULL) {
4343+
(void) fprintf(stderr,
4344+
gettext("partial send cannot send full "
4345+
"snapshots\n"));
4346+
usage(B_FALSE);
4347+
}
4348+
}
4349+
43284350
if (flags.raw && redactbook != NULL) {
43294351
(void) fprintf(stderr,
43304352
gettext("Error: raw sends may not be redacted.\n"));
@@ -4348,6 +4370,7 @@ zfs_do_send(int argc, char **argv)
43484370
*/
43494371
if (!(flags.replicate || flags.doall)) {
43504372
char frombuf[ZFS_MAX_DATASET_NAME_LEN];
4373+
char tobuf[ZFS_MAX_DATASET_NAME_LEN + 6];
43514374

43524375
if (redactbook != NULL) {
43534376
if (strchr(argv[0], '@') == NULL) {
@@ -4357,10 +4380,38 @@ zfs_do_send(int argc, char **argv)
43574380
}
43584381
}
43594382

4360-
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
4383+
/*
4384+
* If this is a partial send, the partial data to send will
4385+
* either live in the dataset itself (if the dataset did not
4386+
* previously exist) or it will live in the special '%recv'
4387+
* clone. To figure out which is correct we simply check both.
4388+
*/
4389+
char *open_ds = argv[0];
4390+
if (flags.partial) {
4391+
(void) snprintf(tobuf, sizeof (tobuf), "%s/%%recv",
4392+
argv[0]);
4393+
if (zfs_dataset_exists(g_zfs, tobuf,
4394+
ZFS_TYPE_DATASET)) {
4395+
open_ds = tobuf;
4396+
}
4397+
}
4398+
4399+
zhp = zfs_open(g_zfs, open_ds, ZFS_TYPE_DATASET);
43614400
if (zhp == NULL)
43624401
return (1);
43634402

4403+
/*
4404+
* We always use the previous snapshot as the source of a
4405+
* partial send. This avoid complications that result if
4406+
* a user attempts to do a partial send with a different
4407+
* "from" snap / bookmark than the original send.
4408+
*/
4409+
if (flags.partial && zfs_prop_get(zhp, ZFS_PROP_PREV_SNAP,
4410+
frombuf, ZFS_MAX_DATASET_NAME_LEN, NULL, NULL,
4411+
0, B_TRUE) == 0) {
4412+
fromname = frombuf;
4413+
}
4414+
43644415
if (fromname != NULL && (strchr(fromname, '#') == NULL &&
43654416
strchr(fromname, '@') == NULL)) {
43664417
/*

include/libzfs.h

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

670670
/* include snapshot holds in send stream */
671671
boolean_t holds;
672+
673+
/* stream represents a partially received dataset */
674+
boolean_t partial;
672675
} sendflags_t;
673676

674677
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: 3 additions & 1 deletion
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

@@ -2006,7 +2008,7 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
20062008
if (resume_nvl == NULL) {
20072009
/*
20082010
* zfs_error_aux has already been set by
2009-
* zfs_send_resume_token_to_nvlist
2011+
* zfs_send_resume_token_to_nvlist()
20102012
*/
20112013
return (zfs_error(hdl, EZFS_FAULT, errbuf));
20122014
}

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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,12 @@
215215
.Op Fl Penv
216216
.Fl t Ar receive_resume_token
217217
.Nm
218+
.Cm send
219+
.Op Fl PenvLw
220+
.Oo Fl i Ar snapshot Ns | Ns Ar bookmark
221+
.Oc
222+
.Fl S Ar filesystem
223+
.Nm
218224
.Cm receive
219225
.Op Fl Fhnsuv
220226
.Op Fl o Sy origin Ns = Ns Ar snapshot
@@ -4058,6 +4064,22 @@ See the documentation for
40584064
for more details.
40594065
.It Xo
40604066
.Nm
4067+
.Cm send
4068+
.Op Fl PenvLw
4069+
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
4070+
.Fl S
4071+
.Ar filesystem
4072+
.Xc
4073+
Generate a send stream from a dataset that has been partially received.
4074+
.Bl -tag -width "-L"
4075+
.It Fl S, -partial
4076+
This flag requires that the specified filesystem previously received a resumable
4077+
send that did not finish and was interrupted. In such scenarios this flag
4078+
enables us to send this partially received state. Using this flag will always
4079+
use the last fully received snapshot as the incremental source if it exists.
4080+
.El
4081+
.It Xo
4082+
.Nm
40614083
.Cm receive
40624084
.Op Fl Fhnsuv
40634085
.Op Fl o Sy origin Ns = Ns Ar snapshot

module/zfs/dmu_recv.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,6 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
882882
drba->drba_cookie->drc_raw = B_TRUE;
883883
}
884884

885-
886885
if (featureflags & DMU_BACKUP_FEATURE_REDACTED) {
887886
uint64_t *redact_snaps;
888887
uint_t numredactsnaps;

module/zfs/dmu_send.c

Lines changed: 75 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
}
@@ -2315,6 +2333,31 @@ dmu_send_impl(struct dmu_send_params *dspp)
23152333
dsl_pool_rele(dp, tag);
23162334
return (err);
23172335
}
2336+
2337+
/*
2338+
* If we're sending partial state resume fields must exist.
2339+
*/
2340+
if (dspp->partialok) {
2341+
struct drr_begin drrb;
2342+
objset_t *mos = dmu_objset_pool(os)->dp_meta_objset;
2343+
2344+
err = zap_lookup(mos, dmu_objset_id(os),
2345+
DS_FIELD_RESUME_TOGUID, sizeof (drrb.drr_toguid), 1,
2346+
&drrb.drr_toguid);
2347+
if (err != 0) {
2348+
dsl_pool_rele(dp, tag);
2349+
return (SET_ERROR(EINVAL));
2350+
}
2351+
2352+
err = zap_lookup(mos, dmu_objset_id(os),
2353+
DS_FIELD_RESUME_TONAME, 1, sizeof (drrb.drr_toname),
2354+
drrb.drr_toname);
2355+
if (err != 0) {
2356+
dsl_pool_rele(dp, tag);
2357+
return (SET_ERROR(EINVAL));
2358+
}
2359+
}
2360+
23182361
/*
23192362
* If this is a non-raw send of an encrypted ds, we can ensure that
23202363
* the objset_phys_t is authenticated. This is safe because this is
@@ -2536,19 +2579,27 @@ dmu_send_impl(struct dmu_send_params *dspp)
25362579
goto out;
25372580
}
25382581

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;
2582+
/*
2583+
* Send the DRR_END record if this is not a partial stream.
2584+
* Otherwise, the omitted DRR_END record will signal to
2585+
* the receive side that the stream is incomplete.
2586+
*/
2587+
if (!dspp->partialok) {
2588+
bzero(drr, sizeof (dmu_replay_record_t));
2589+
drr->drr_type = DRR_END;
2590+
drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc;
2591+
drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid;
25432592

2544-
if (dump_record(&dsc, NULL, 0) != 0)
2545-
err = dsc.dsc_err;
2593+
if (dump_record(&dsc, NULL, 0) != 0)
2594+
err = dsc.dsc_err;
2595+
}
25462596
out:
25472597
mutex_enter(&to_ds->ds_sendstream_lock);
25482598
list_remove(&to_ds->ds_sendstreams, dssp);
25492599
mutex_exit(&to_ds->ds_sendstream_lock);
25502600

2551-
VERIFY(err != 0 || (dsc.dsc_sent_begin && dsc.dsc_sent_end));
2601+
VERIFY(err != 0 || (dsc.dsc_sent_begin &&
2602+
(dsc.dsc_sent_end || dspp->partialok)));
25522603

25532604
kmem_free(drr, sizeof (dmu_replay_record_t));
25542605
kmem_free(dssp, sizeof (dmu_sendstatus_t));
@@ -2574,7 +2625,8 @@ dmu_send_impl(struct dmu_send_params *dspp)
25742625
int
25752626
dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
25762627
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)
2628+
boolean_t rawok, boolean_t partialok, int outfd, offset_t *off,
2629+
dmu_send_outparams_t *dsop)
25782630
{
25792631
int err;
25802632
dsl_dataset_t *fromds;
@@ -2588,6 +2640,7 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
25882640
dspp.dso = dsop;
25892641
dspp.tag = FTAG;
25902642
dspp.rawok = rawok;
2643+
dspp.partialok = partialok;
25912644

25922645
err = dsl_pool_hold(pool, FTAG, &dspp.dp);
25932646
if (err != 0)
@@ -2654,8 +2707,9 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
26542707
int
26552708
dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
26562709
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)
2710+
boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff,
2711+
const char *redactbook, int outfd, offset_t *off,
2712+
dmu_send_outparams_t *dsop)
26592713
{
26602714
int err = 0;
26612715
ds_hold_flags_t dsflags = (rawok) ? 0 : DS_HOLD_FLAG_DECRYPT;
@@ -2674,20 +2728,27 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
26742728
dspp.resumeobj = resumeobj;
26752729
dspp.resumeoff = resumeoff;
26762730
dspp.rawok = rawok;
2731+
dspp.partialok = partialok;
26772732

26782733
if (fromsnap != NULL && strpbrk(fromsnap, "@#") == NULL)
26792734
return (SET_ERROR(EINVAL));
26802735

26812736
err = dsl_pool_hold(tosnap, FTAG, &dspp.dp);
26822737
if (err != 0)
26832738
return (err);
2739+
26842740
if (strchr(tosnap, '@') == NULL && spa_writeable(dspp.dp->dp_spa)) {
26852741
/*
26862742
* We are sending a filesystem or volume. Ensure
26872743
* that it doesn't change by owning the dataset.
26882744
*/
2689-
err = dsl_dataset_own(dspp.dp, tosnap, dsflags, FTAG,
2690-
&dspp.to_ds);
2745+
if (partialok) {
2746+
err = dsl_dataset_own_force(dspp.dp, tosnap, dsflags,
2747+
FTAG, &dspp.to_ds);
2748+
} else {
2749+
err = dsl_dataset_own(dspp.dp, tosnap, dsflags,
2750+
FTAG, &dspp.to_ds);
2751+
}
26912752
owned = B_TRUE;
26922753
} else {
26932754
err = dsl_dataset_hold_flags(dspp.dp, tosnap, dsflags, FTAG,

0 commit comments

Comments
 (0)