aboutsummaryrefslogtreecommitdiffstats
path: root/lib/libzfs
diff options
context:
space:
mode:
authorTom Caputi <[email protected]>2020-01-10 13:16:58 -0500
committerBrian Behlendorf <[email protected]>2020-01-10 10:16:58 -0800
commitba0ba69e50efeda7190b59f27c23ada0edf6f36b (patch)
tree15a28e7c05e9265cd3c54c08c287abdf994ea507 /lib/libzfs
parent9ab6109fb51c88e9dc43622432b300efed036995 (diff)
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. Reviewed-by: Paul Dagnelie <[email protected]> Reviewed-by: Alek Pinchuk <[email protected]> Reviewed-by: Paul Zuchowski <[email protected]> Reviewed-by: Christian Schwarz <[email protected]> Reviewed-by: Matt Ahrens <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Tom Caputi <[email protected]> Closes #9007
Diffstat (limited to 'lib/libzfs')
-rw-r--r--lib/libzfs/libzfs_sendrecv.c161
1 files changed, 139 insertions, 22 deletions
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
index 20d29f48c..de3bfdbf4 100644
--- a/lib/libzfs/libzfs_sendrecv.c
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -1815,6 +1815,8 @@ lzc_flags_from_sendflags(const sendflags_t *flags)
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
if (flags->raw)
lzc_flags |= LZC_SEND_FLAG_RAW;
+ if (flags->saved)
+ lzc_flags |= LZC_SEND_FLAG_SAVED;
return (lzc_flags);
}
@@ -1981,9 +1983,9 @@ find_redact_book(libzfs_handle_t *hdl, const char *path,
return (0);
}
-int
-zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
- const char *resume_token)
+static int
+zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+ nvlist_t *resume_nvl)
{
char errbuf[1024];
char *toname;
@@ -2001,15 +2003,6 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot resume send"));
- nvlist_t *resume_nvl =
- zfs_send_resume_token_to_nvlist(hdl, resume_token);
- if (resume_nvl == NULL) {
- /*
- * zfs_error_aux has already been set by
- * zfs_send_resume_token_to_nvlist
- */
- return (zfs_error(hdl, EZFS_FAULT, errbuf));
- }
if (flags->verbosity != 0) {
(void) fprintf(fout, dgettext(TEXT_DOMAIN,
"resume token contents:\n"));
@@ -2036,19 +2029,27 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
if (flags->raw || nvlist_exists(resume_nvl, "rawok"))
lzc_flags |= LZC_SEND_FLAG_RAW;
+ if (flags->saved || nvlist_exists(resume_nvl, "savedok"))
+ lzc_flags |= LZC_SEND_FLAG_SAVED;
- if (guid_to_name(hdl, toname, toguid, B_FALSE, name) != 0) {
- if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "'%s' is no longer the same snapshot used in "
- "the initial send"), toname);
- } else {
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "'%s' used in the initial send no longer exists"),
- toname);
+ if (flags->saved) {
+ (void) strcpy(name, toname);
+ } else {
+ error = guid_to_name(hdl, toname, toguid, B_FALSE, name);
+ if (error != 0) {
+ if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is no longer the same snapshot "
+ "used in the initial send"), toname);
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' used in the initial send no "
+ "longer exists"), toname);
+ }
+ return (zfs_error(hdl, EZFS_BADPATH, errbuf));
}
- return (zfs_error(hdl, EZFS_BADPATH, errbuf));
}
+
zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
if (zhp == NULL) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -2199,6 +2200,122 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
return (error);
}
+int
+zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+ const char *resume_token)
+{
+ int ret;
+ char errbuf[1024];
+ nvlist_t *resume_nvl;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot resume send"));
+
+ resume_nvl = zfs_send_resume_token_to_nvlist(hdl, resume_token);
+ if (resume_nvl == NULL) {
+ /*
+ * zfs_error_aux has already been set by
+ * zfs_send_resume_token_to_nvlist()
+ */
+ return (zfs_error(hdl, EZFS_FAULT, errbuf));
+ }
+
+ ret = zfs_send_resume_impl(hdl, flags, outfd, resume_nvl);
+ nvlist_free(resume_nvl);
+
+ return (ret);
+}
+
+int
+zfs_send_saved(zfs_handle_t *zhp, sendflags_t *flags, int outfd,
+ const char *resume_token)
+{
+ int ret;
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvlist_t *saved_nvl = NULL, *resume_nvl = NULL;
+ uint64_t saved_guid = 0, resume_guid = 0;
+ uint64_t obj = 0, off = 0, bytes = 0;
+ char token_buf[ZFS_MAXPROPLEN];
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "saved send failed"));
+
+ ret = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ token_buf, sizeof (token_buf), NULL, NULL, 0, B_TRUE);
+ if (ret != 0)
+ goto out;
+
+ saved_nvl = zfs_send_resume_token_to_nvlist(hdl, token_buf);
+ if (saved_nvl == NULL) {
+ /*
+ * zfs_error_aux has already been set by
+ * zfs_send_resume_token_to_nvlist()
+ */
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ /*
+ * If a resume token is provided we use the object and offset
+ * from that instead of the default, which starts from the
+ * beginning.
+ */
+ if (resume_token != NULL) {
+ resume_nvl = zfs_send_resume_token_to_nvlist(hdl,
+ resume_token);
+ if (resume_nvl == NULL) {
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ if (nvlist_lookup_uint64(resume_nvl, "object", &obj) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "offset", &off) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "toguid",
+ &resume_guid) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "provided resume token is corrupt"));
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ if (nvlist_lookup_uint64(saved_nvl, "toguid",
+ &saved_guid)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset's resume token is corrupt"));
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ if (resume_guid != saved_guid) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "provided resume token does not match dataset"));
+ ret = zfs_error(hdl, EZFS_BADBACKUP, errbuf);
+ goto out;
+ }
+ }
+
+ (void) nvlist_remove_all(saved_nvl, "object");
+ fnvlist_add_uint64(saved_nvl, "object", obj);
+
+ (void) nvlist_remove_all(saved_nvl, "offset");
+ fnvlist_add_uint64(saved_nvl, "offset", off);
+
+ (void) nvlist_remove_all(saved_nvl, "bytes");
+ fnvlist_add_uint64(saved_nvl, "bytes", bytes);
+
+ (void) nvlist_remove_all(saved_nvl, "toname");
+ fnvlist_add_string(saved_nvl, "toname", zhp->zfs_name);
+
+ ret = zfs_send_resume_impl(hdl, flags, outfd, saved_nvl);
+
+out:
+ nvlist_free(saved_nvl);
+ nvlist_free(resume_nvl);
+ return (ret);
+}
+
/*
* This function informs the target system that the recursive send is complete.
* The record is also expected in the case of a send -p.