Skip to content

Commit 52cf371

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 8221bcf commit 52cf371

File tree

16 files changed

+1918
-71
lines changed

16 files changed

+1918
-71
lines changed

cmd/zfs/zfs_main.c

Lines changed: 36 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 [-Pnv] -S filesystem\n"));
310311
case HELP_SET:
311312
return (gettext("\tset <property=value> ... "
312313
"<filesystem|volume|snapshot> ...\n"));
@@ -4191,11 +4192,12 @@ zfs_do_send(int argc, char **argv)
41914192
{"raw", no_argument, NULL, 'w'},
41924193
{"backup", no_argument, NULL, 'b'},
41934194
{"holds", no_argument, NULL, 'h'},
4195+
{"partial", no_argument, NULL, 'S'},
41944196
{0, 0, 0, 0}
41954197
};
41964198

41974199
/* check options */
4198-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
4200+
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
41994201
long_options, NULL)) != -1) {
42004202
switch (c) {
42014203
case 'i':
@@ -4255,6 +4257,9 @@ zfs_do_send(int argc, char **argv)
42554257
flags.embed_data = B_TRUE;
42564258
flags.largeblock = B_TRUE;
42574259
break;
4260+
case 'S':
4261+
flags.partial = B_TRUE;
4262+
break;
42584263
case ':':
42594264
/*
42604265
* If a parameter was not passed, optopt contains the
@@ -4305,7 +4310,7 @@ zfs_do_send(int argc, char **argv)
43054310
if (resume_token != NULL) {
43064311
if (fromname != NULL || flags.replicate || flags.props ||
43074312
flags.backup || flags.dedup || flags.holds ||
4308-
redactbook != NULL) {
4313+
flags.partial || redactbook != NULL) {
43094314
(void) fprintf(stderr,
43104315
gettext("invalid flags combined with -t\n"));
43114316
usage(B_FALSE);
@@ -4326,6 +4331,24 @@ zfs_do_send(int argc, char **argv)
43264331
}
43274332
}
43284333

4334+
if (flags.partial) {
4335+
if (fromname != NULL || flags.replicate || flags.props ||
4336+
flags.doall || flags.backup || flags.dedup ||
4337+
flags.holds || flags.largeblock || flags.embed_data ||
4338+
flags.compress || flags.raw || redactbook != NULL) {
4339+
(void) fprintf(stderr,
4340+
gettext("incompatible flags combined with partial "
4341+
"send flag\n"));
4342+
usage(B_FALSE);
4343+
}
4344+
if (strchr(argv[0], '@') != NULL) {
4345+
(void) fprintf(stderr,
4346+
gettext("partial send cannot send full "
4347+
"snapshots\n"));
4348+
usage(B_FALSE);
4349+
}
4350+
}
4351+
43294352
if (flags.raw && redactbook != NULL) {
43304353
(void) fprintf(stderr,
43314354
gettext("Error: raw sends may not be redacted.\n"));
@@ -4339,7 +4362,16 @@ zfs_do_send(int argc, char **argv)
43394362
return (1);
43404363
}
43414364

4342-
if (resume_token != NULL) {
4365+
if (flags.partial) {
4366+
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
4367+
if (zhp == NULL)
4368+
return (1);
4369+
4370+
err = zfs_send_partial(zhp, &flags, STDOUT_FILENO,
4371+
resume_token);
4372+
zfs_close(zhp);
4373+
return (err != 0);
4374+
} else if (resume_token != NULL) {
43434375
return (zfs_send_resume(g_zfs, &flags, STDOUT_FILENO,
43444376
resume_token));
43454377
}

include/libzfs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,9 @@ typedef struct sendflags {
677677

678678
/* include snapshot holds in send stream */
679679
boolean_t holds;
680+
681+
/* stream represents a partially received dataset */
682+
boolean_t partial;
680683
} sendflags_t;
681684

682685
typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);
@@ -688,6 +691,7 @@ extern int zfs_send_one(zfs_handle_t *, const char *, int, sendflags_t *,
688691
extern int zfs_send_progress(zfs_handle_t *, int, uint64_t *, uint64_t *);
689692
extern int zfs_send_resume(libzfs_handle_t *, sendflags_t *, int outfd,
690693
const char *);
694+
extern int zfs_send_partial(zfs_handle_t *, sendflags_t *, int, const char *);
691695
extern nvlist_t *zfs_send_resume_token_to_nvlist(libzfs_handle_t *hdl,
692696
const char *token);
693697

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: 136 additions & 22 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

@@ -1981,9 +1983,9 @@ find_redact_book(libzfs_handle_t *hdl, const char *path,
19811983
return (0);
19821984
}
19831985

1984-
int
1985-
zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
1986-
const char *resume_token)
1986+
static int
1987+
zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
1988+
nvlist_t *resume_nvl)
19871989
{
19881990
char errbuf[1024];
19891991
char *toname;
@@ -2001,15 +2003,6 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
20012003
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
20022004
"cannot resume send"));
20032005

2004-
nvlist_t *resume_nvl =
2005-
zfs_send_resume_token_to_nvlist(hdl, resume_token);
2006-
if (resume_nvl == NULL) {
2007-
/*
2008-
* zfs_error_aux has already been set by
2009-
* zfs_send_resume_token_to_nvlist
2010-
*/
2011-
return (zfs_error(hdl, EZFS_FAULT, errbuf));
2012-
}
20132006
if (flags->verbosity != 0) {
20142007
(void) fprintf(fout, dgettext(TEXT_DOMAIN,
20152008
"resume token contents:\n"));
@@ -2036,19 +2029,28 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
20362029
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
20372030
if (flags->raw || nvlist_exists(resume_nvl, "rawok"))
20382031
lzc_flags |= LZC_SEND_FLAG_RAW;
2032+
if (flags->partial || nvlist_exists(resume_nvl, "partialok"))
2033+
lzc_flags |= LZC_SEND_FLAG_PARTIAL;
20392034

2040-
if (guid_to_name(hdl, toname, toguid, B_FALSE, name) != 0) {
2041-
if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
2042-
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2043-
"'%s' is no longer the same snapshot used in "
2044-
"the initial send"), toname);
2045-
} else {
2046-
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2047-
"'%s' used in the initial send no longer exists"),
2048-
toname);
2035+
if (flags->partial) {
2036+
(void) strcpy(name, toname);
2037+
} else {
2038+
error = guid_to_name(hdl, toname, toguid, B_FALSE, name);
2039+
if (error != 0) {
2040+
if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
2041+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2042+
"'%s' is no longer the same snapshot "
2043+
"used in the initial send"), toname);
2044+
} else {
2045+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2046+
"'%s' used in the initial send no "
2047+
"longer exists"),
2048+
toname);
2049+
}
2050+
return (zfs_error(hdl, EZFS_BADPATH, errbuf));
20492051
}
2050-
return (zfs_error(hdl, EZFS_BADPATH, errbuf));
20512052
}
2053+
20522054
zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
20532055
if (zhp == NULL) {
20542056
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -2199,6 +2201,118 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
21992201
return (error);
22002202
}
22012203

2204+
int
2205+
zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
2206+
const char *resume_token)
2207+
{
2208+
int ret;
2209+
char errbuf[1024];
2210+
nvlist_t *resume_nvl;
2211+
2212+
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
2213+
"cannot resume send"));
2214+
2215+
resume_nvl = zfs_send_resume_token_to_nvlist(hdl, resume_token);
2216+
if (resume_nvl == NULL) {
2217+
/*
2218+
* zfs_error_aux has already been set by
2219+
* zfs_send_resume_token_to_nvlist()
2220+
*/
2221+
return (zfs_error(hdl, EZFS_FAULT, errbuf));
2222+
}
2223+
2224+
ret = zfs_send_resume_impl(hdl, flags, outfd, resume_nvl);
2225+
nvlist_free(resume_nvl);
2226+
2227+
return (ret);
2228+
}
2229+
2230+
int
2231+
zfs_send_partial(zfs_handle_t *zhp, sendflags_t *flags, int outfd,
2232+
const char *resume_token)
2233+
{
2234+
int ret;
2235+
libzfs_handle_t *hdl = zhp->zfs_hdl;
2236+
nvlist_t *partial_nvl = NULL, *resume_nvl = NULL;
2237+
uint64_t partial_guid = 0, resume_guid = 0;
2238+
uint64_t obj = 0, off = 0, bytes = 0;
2239+
char token_buf[ZFS_MAXPROPLEN];
2240+
char errbuf[1024];
2241+
2242+
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
2243+
"partial send failed"));
2244+
2245+
ret = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
2246+
token_buf, sizeof (token_buf), NULL, NULL, 0, B_TRUE);
2247+
if (ret != 0)
2248+
goto out;
2249+
2250+
partial_nvl = zfs_send_resume_token_to_nvlist(hdl, token_buf);
2251+
if (partial_nvl == NULL) {
2252+
ret = zfs_error(hdl, EZFS_FAULT, errbuf);
2253+
goto out;
2254+
}
2255+
2256+
/*
2257+
* If a resume token is provided we use the object and offset
2258+
* from that instead of the default, which starts frm the
2259+
* beginning.
2260+
*/
2261+
if (resume_token != NULL) {
2262+
resume_nvl = zfs_send_resume_token_to_nvlist(hdl,
2263+
resume_token);
2264+
if (resume_nvl == NULL) {
2265+
ret = zfs_error(hdl, EZFS_FAULT, errbuf);
2266+
goto out;
2267+
}
2268+
2269+
if (nvlist_lookup_uint64(resume_nvl, "object", &obj) != 0 ||
2270+
nvlist_lookup_uint64(resume_nvl, "offset", &off) != 0 ||
2271+
nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 ||
2272+
nvlist_lookup_uint64(resume_nvl, "toguid",
2273+
&resume_guid) != 0) {
2274+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2275+
"provided resume token is corrupt"));
2276+
ret = zfs_error(hdl, EZFS_FAULT, errbuf);
2277+
goto out;
2278+
}
2279+
2280+
if (nvlist_lookup_uint64(partial_nvl, "toguid",
2281+
&partial_guid)) {
2282+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2283+
"dataset's resume token is corrupt"));
2284+
ret = zfs_error(hdl, EZFS_FAULT, errbuf);
2285+
goto out;
2286+
}
2287+
2288+
if (resume_guid != partial_guid) {
2289+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2290+
"provided resume token does not match dataset"));
2291+
ret = zfs_error(hdl, EZFS_BADBACKUP, errbuf);
2292+
goto out;
2293+
}
2294+
}
2295+
2296+
(void) nvlist_remove_all(partial_nvl, "object");
2297+
fnvlist_add_uint64(partial_nvl, "object", obj);
2298+
2299+
(void) nvlist_remove_all(partial_nvl, "offset");
2300+
fnvlist_add_uint64(partial_nvl, "offset", off);
2301+
2302+
(void) nvlist_remove_all(partial_nvl, "bytes");
2303+
fnvlist_add_uint64(partial_nvl, "bytes", bytes);
2304+
2305+
(void) nvlist_remove_all(partial_nvl, "toname");
2306+
fnvlist_add_string(partial_nvl, "toname", zhp->zfs_name);
2307+
2308+
ret = zfs_send_resume_impl(hdl, flags, outfd, partial_nvl);
2309+
2310+
out:
2311+
nvlist_free(partial_nvl);
2312+
nvlist_free(resume_nvl);
2313+
return (ret);
2314+
}
2315+
22022316
/*
22032317
* This function informs the target system that the recursive send is complete.
22042318
* The record is also expected in the case of a send -p.

lib/libzfs_core/libzfs_core.c

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

0 commit comments

Comments
 (0)