Skip to content

Commit 76ac939

Browse files
author
Tom Caputi
committed
Add 'zfs send --saved' flag
This commit adds the --saved (-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 saved send 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 c24fa4b commit 76ac939

File tree

16 files changed

+484
-76
lines changed

16 files changed

+484
-76
lines changed

cmd/zfs/zfs_main.c

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ get_usage(zfs_help_t idx)
318318
"<filesystem|volume|snapshot>\n"
319319
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
320320
"--redact <bookmark> <snapshot>\n"
321-
"\tsend [-nvPe] -t <receive_resume_token>\n"));
321+
"\tsend [-nvPe] -t <receive_resume_token>\n"
322+
"\tsend [-Pnv] --saved filesystem\n"));
322323
case HELP_SET:
323324
return (gettext("\tset <property=value> ... "
324325
"<filesystem|volume|snapshot> ...\n"));
@@ -4207,11 +4208,12 @@ zfs_do_send(int argc, char **argv)
42074208
{"raw", no_argument, NULL, 'w'},
42084209
{"backup", no_argument, NULL, 'b'},
42094210
{"holds", no_argument, NULL, 'h'},
4211+
{"saved", no_argument, NULL, 'S'},
42104212
{0, 0, 0, 0}
42114213
};
42124214

42134215
/* check options */
4214-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
4216+
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
42154217
long_options, NULL)) != -1) {
42164218
switch (c) {
42174219
case 'i':
@@ -4271,6 +4273,9 @@ zfs_do_send(int argc, char **argv)
42714273
flags.embed_data = B_TRUE;
42724274
flags.largeblock = B_TRUE;
42734275
break;
4276+
case 'S':
4277+
flags.saved = B_TRUE;
4278+
break;
42744279
case ':':
42754280
/*
42764281
* If a parameter was not passed, optopt contains the
@@ -4321,7 +4326,7 @@ zfs_do_send(int argc, char **argv)
43214326
if (resume_token != NULL) {
43224327
if (fromname != NULL || flags.replicate || flags.props ||
43234328
flags.backup || flags.dedup || flags.holds ||
4324-
redactbook != NULL) {
4329+
flags.saved || redactbook != NULL) {
43254330
(void) fprintf(stderr,
43264331
gettext("invalid flags combined with -t\n"));
43274332
usage(B_FALSE);
@@ -4342,6 +4347,23 @@ zfs_do_send(int argc, char **argv)
43424347
}
43434348
}
43444349

4350+
if (flags.saved) {
4351+
if (fromname != NULL || flags.replicate || flags.props ||
4352+
flags.doall || flags.backup || flags.dedup ||
4353+
flags.holds || flags.largeblock || flags.embed_data ||
4354+
flags.compress || flags.raw || redactbook != NULL) {
4355+
(void) fprintf(stderr, gettext("incompatible flags "
4356+
"combined with saved send flag\n"));
4357+
usage(B_FALSE);
4358+
}
4359+
if (strchr(argv[0], '@') != NULL) {
4360+
(void) fprintf(stderr, gettext("saved send must "
4361+
"specify the dataset with partially-received "
4362+
"state\n"));
4363+
usage(B_FALSE);
4364+
}
4365+
}
4366+
43454367
if (flags.raw && redactbook != NULL) {
43464368
(void) fprintf(stderr,
43474369
gettext("Error: raw sends may not be redacted.\n"));
@@ -4355,7 +4377,16 @@ zfs_do_send(int argc, char **argv)
43554377
return (1);
43564378
}
43574379

4358-
if (resume_token != NULL) {
4380+
if (flags.saved) {
4381+
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
4382+
if (zhp == NULL)
4383+
return (1);
4384+
4385+
err = zfs_send_saved(zhp, &flags, STDOUT_FILENO,
4386+
resume_token);
4387+
zfs_close(zhp);
4388+
return (err != 0);
4389+
} else if (resume_token != NULL) {
43594390
return (zfs_send_resume(g_zfs, &flags, STDOUT_FILENO,
43604391
resume_token));
43614392
}

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 saved;
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_saved(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_SAVED = 1 << 4,
8283
};
8384

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

include/sys/dmu_send.h

Lines changed: 6 additions & 4 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 savedok, 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,
58-
uint64_t *sizep);
59+
boolean_t saved, 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 savedok, 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: 139 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->saved)
1819+
lzc_flags |= LZC_SEND_FLAG_SAVED;
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,27 @@ 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->saved || nvlist_exists(resume_nvl, "savedok"))
2033+
lzc_flags |= LZC_SEND_FLAG_SAVED;
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->saved) {
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"), toname);
2048+
}
2049+
return (zfs_error(hdl, EZFS_BADPATH, errbuf));
20492050
}
2050-
return (zfs_error(hdl, EZFS_BADPATH, errbuf));
20512051
}
2052+
20522053
zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
20532054
if (zhp == NULL) {
20542055
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -2199,6 +2200,122 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
21992200
return (error);
22002201
}
22012202

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

man/man8/zfs-send.8

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
.Fl t
6161
.Ar receive_resume_token
6262
.Nm
63+
.Cm send
64+
.Op Fl Pnv
65+
.Fl S Ar filesystem
66+
.Nm
6367
.Cm redact
6468
.Ar snapshot redaction_bookmark
6569
.Ar redaction_snapshot Ns ...
@@ -503,6 +507,23 @@ See the documentation for
503507
for more details.
504508
.It Xo
505509
.Nm
510+
.Cm send
511+
.Op Fl Pnv
512+
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
513+
.Fl S
514+
.Ar filesystem
515+
.Xc
516+
Generate a send stream from a dataset that has been partially received.
517+
.Bl -tag -width "-L"
518+
.It Fl S, -saved
519+
This flag requires that the specified filesystem previously received a resumable
520+
send that did not finish and was interrupted. In such scenarios this flag
521+
enables the user to send this partially received state. Using this flag will
522+
always use the last fully received snapshot as the incremental source if it
523+
exists.
524+
.El
525+
.It Xo
526+
.Nm
506527
.Cm redact
507528
.Ar snapshot redaction_bookmark
508529
.Ar redaction_snapshot Ns ...

module/zfs/dmu_recv.c

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

890-
891890
if (featureflags & DMU_BACKUP_FEATURE_REDACTED) {
892891
uint64_t *redact_snaps;
893892
uint_t numredactsnaps;

0 commit comments

Comments
 (0)