summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/libzfs/libzfs_dataset.c27
-rw-r--r--lib/libzfs/libzfs_mount.c11
-rw-r--r--lib/libzfs/libzfs_sendrecv.c449
-rw-r--r--lib/libzfs_core/libzfs_core.c69
-rw-r--r--lib/libzpool/kernel.c2
5 files changed, 452 insertions, 106 deletions
diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c
index 07f2e75a6..80f75e256 100644
--- a/lib/libzfs/libzfs_dataset.c
+++ b/lib/libzfs/libzfs_dataset.c
@@ -1865,22 +1865,21 @@ getprop_uint64(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
return (value);
}
-static char *
+static const char *
getprop_string(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
{
nvlist_t *nv;
- char *value;
+ const char *value;
*source = NULL;
if (nvlist_lookup_nvlist(zhp->zfs_props,
zfs_prop_to_name(prop), &nv) == 0) {
- verify(nvlist_lookup_string(nv, ZPROP_VALUE, &value) == 0);
+ value = fnvlist_lookup_string(nv, ZPROP_VALUE);
(void) nvlist_lookup_string(nv, ZPROP_SOURCE, source);
} else {
verify(!zhp->zfs_props_table ||
zhp->zfs_props_table[prop] == B_TRUE);
- if ((value = (char *)zfs_prop_default_string(prop)) == NULL)
- value = "";
+ value = zfs_prop_default_string(prop);
*source = "";
}
@@ -2301,7 +2300,7 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
{
char *source = NULL;
uint64_t val;
- char *str;
+ const char *str;
const char *strval;
boolean_t received = zfs_is_recvd_props_mode(zhp);
@@ -2407,14 +2406,10 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
break;
case ZFS_PROP_ORIGIN:
- (void) strlcpy(propbuf, getprop_string(zhp, prop, &source),
- proplen);
- /*
- * If there is no parent at all, return failure to indicate that
- * it doesn't apply to this dataset.
- */
- if (propbuf[0] == '\0')
+ str = getprop_string(zhp, prop, &source);
+ if (str == NULL)
return (-1);
+ (void) strlcpy(propbuf, str, proplen);
break;
case ZFS_PROP_CLONES:
@@ -2596,8 +2591,10 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
break;
case PROP_TYPE_STRING:
- (void) strlcpy(propbuf,
- getprop_string(zhp, prop, &source), proplen);
+ str = getprop_string(zhp, prop, &source);
+ if (str == NULL)
+ return (-1);
+ (void) strlcpy(propbuf, str, proplen);
break;
case PROP_TYPE_INDEX:
diff --git a/lib/libzfs/libzfs_mount.c b/lib/libzfs/libzfs_mount.c
index 29907dc8b..83351c716 100644
--- a/lib/libzfs/libzfs_mount.c
+++ b/lib/libzfs/libzfs_mount.c
@@ -1051,6 +1051,17 @@ mount_cb(zfs_handle_t *zhp, void *data)
return (0);
}
+ /*
+ * If this filesystem is inconsistent and has a receive resume
+ * token, we can not mount it.
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) &&
+ zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ NULL, 0, NULL, NULL, 0, B_TRUE) == 0) {
+ zfs_close(zhp);
+ return (0);
+ }
+
libzfs_add_handle(cbp, zhp);
if (zfs_iter_filesystems(zhp, mount_cb, cbp) != 0) {
zfs_close(zhp);
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
index 46640f623..1bf8bf49a 100644
--- a/lib/libzfs/libzfs_sendrecv.c
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -21,7 +21,7 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2011, 2014 by Delphix. All rights reserved.
+ * Copyright (c) 2011, 2015 by Delphix. All rights reserved.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
* Copyright (c) 2012 Pawel Jakub Dawidek <[email protected]>.
* All rights reserved
@@ -56,6 +56,7 @@
#include "zfs_prop.h"
#include "zfs_fletcher.h"
#include "libzfs_impl.h"
+#include <zlib.h>
#include <sys/zio_checksum.h>
#include <sys/ddt.h>
#include <sys/socket.h>
@@ -66,6 +67,8 @@ extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *);
static int zfs_receive_impl(libzfs_handle_t *, const char *, const char *,
recvflags_t *, int, const char *, nvlist_t *, avl_tree_t *, char **, int,
uint64_t *);
+static int guid_to_name(libzfs_handle_t *, const char *,
+ uint64_t, boolean_t, char *);
static const zio_cksum_t zero_cksum = { { 0 } };
@@ -234,7 +237,7 @@ cksummer(void *arg)
{
dedup_arg_t *dda = arg;
char *buf = zfs_alloc(dda->dedup_hdl, SPA_MAXBLOCKSIZE);
- dmu_replay_record_t thedrr;
+ dmu_replay_record_t thedrr = { 0 };
dmu_replay_record_t *drr = &thedrr;
FILE *ofp;
int outfd;
@@ -283,8 +286,7 @@ cksummer(void *arg)
DMU_BACKUP_FEATURE_DEDUPPROPS);
DMU_SET_FEATUREFLAGS(drrb->drr_versioninfo, fflags);
- if (DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) ==
- DMU_COMPOUNDSTREAM && drr->drr_payloadlen != 0) {
+ if (drr->drr_payloadlen != 0) {
sz = drr->drr_payloadlen;
if (sz > SPA_MAXBLOCKSIZE) {
@@ -1013,17 +1015,14 @@ static void *
send_progress_thread(void *arg)
{
progress_arg_t *pa = arg;
-
zfs_cmd_t zc = {"\0"};
zfs_handle_t *zhp = pa->pa_zhp;
libzfs_handle_t *hdl = zhp->zfs_hdl;
unsigned long long bytes;
char buf[16];
-
time_t t;
struct tm *tm;
- assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
(void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
if (!pa->pa_parsable)
@@ -1056,6 +1055,51 @@ send_progress_thread(void *arg)
}
}
+static void
+send_print_verbose(FILE *fout, const char *tosnap, const char *fromsnap,
+ uint64_t size, boolean_t parsable)
+{
+ if (parsable) {
+ if (fromsnap != NULL) {
+ (void) fprintf(fout, "incremental\t%s\t%s",
+ fromsnap, tosnap);
+ } else {
+ (void) fprintf(fout, "full\t%s",
+ tosnap);
+ }
+ } else {
+ if (fromsnap != NULL) {
+ if (strchr(fromsnap, '@') == NULL &&
+ strchr(fromsnap, '#') == NULL) {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "send from @%s to %s"),
+ fromsnap, tosnap);
+ } else {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "send from %s to %s"),
+ fromsnap, tosnap);
+ }
+ } else {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "full send of %s"),
+ tosnap);
+ }
+ }
+
+ if (size != 0) {
+ if (parsable) {
+ (void) fprintf(fout, "\t%llu",
+ (longlong_t)size);
+ } else {
+ char buf[16];
+ zfs_nicenum(size, buf, sizeof (buf));
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ " estimated size is %s"), buf);
+ }
+ }
+ (void) fprintf(fout, "\n");
+}
+
static int
dump_snapshot(zfs_handle_t *zhp, void *arg)
{
@@ -1135,37 +1179,14 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
(sdd->fromorigin || sdd->replicate);
if (sdd->verbose) {
- uint64_t size;
- err = estimate_ioctl(zhp, sdd->prevsnap_obj,
+ uint64_t size = 0;
+ (void) estimate_ioctl(zhp, sdd->prevsnap_obj,
fromorigin, &size);
- if (sdd->parsable) {
- if (sdd->prevsnap[0] != '\0') {
- (void) fprintf(fout, "incremental\t%s\t%s",
- sdd->prevsnap, zhp->zfs_name);
- } else {
- (void) fprintf(fout, "full\t%s",
- zhp->zfs_name);
- }
- } else {
- (void) fprintf(fout, dgettext(TEXT_DOMAIN,
- "send from @%s to %s"),
- sdd->prevsnap, zhp->zfs_name);
- }
- if (err == 0) {
- if (sdd->parsable) {
- (void) fprintf(fout, "\t%llu\n",
- (longlong_t)size);
- } else {
- char buf[16];
- zfs_nicenum(size, buf, sizeof (buf));
- (void) fprintf(fout, dgettext(TEXT_DOMAIN,
- " estimated size is %s\n"), buf);
- }
- sdd->size += size;
- } else {
- (void) fprintf(fout, "\n");
- }
+ send_print_verbose(fout, zhp->zfs_name,
+ sdd->prevsnap[0] ? sdd->prevsnap : NULL,
+ size, sdd->parsable);
+ sdd->size += size;
}
if (!sdd->dryrun) {
@@ -1376,6 +1397,231 @@ again:
return (0);
}
+nvlist_t *
+zfs_send_resume_token_to_nvlist(libzfs_handle_t *hdl, const char *token)
+{
+ unsigned int version;
+ int nread, i;
+ unsigned long long checksum, packed_len;
+
+ /*
+ * Decode token header, which is:
+ * <token version>-<checksum of payload>-<uncompressed payload length>
+ * Note that the only supported token version is 1.
+ */
+ nread = sscanf(token, "%u-%llx-%llx-",
+ &version, &checksum, &packed_len);
+ if (nread != 3) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (invalid format)"));
+ return (NULL);
+ }
+
+ if (version != ZFS_SEND_RESUME_TOKEN_VERSION) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (invalid version %u)"),
+ version);
+ return (NULL);
+ }
+
+ /* convert hexadecimal representation to binary */
+ token = strrchr(token, '-') + 1;
+ int len = strlen(token) / 2;
+ unsigned char *compressed = zfs_alloc(hdl, len);
+ for (i = 0; i < len; i++) {
+ nread = sscanf(token + i * 2, "%2hhx", compressed + i);
+ if (nread != 1) {
+ free(compressed);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt "
+ "(payload is not hex-encoded)"));
+ return (NULL);
+ }
+ }
+
+ /* verify checksum */
+ zio_cksum_t cksum;
+ fletcher_4_native(compressed, len, &cksum);
+ if (cksum.zc_word[0] != checksum) {
+ free(compressed);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (incorrect checksum)"));
+ return (NULL);
+ }
+
+ /* uncompress */
+ void *packed = zfs_alloc(hdl, packed_len);
+ uLongf packed_len_long = packed_len;
+ if (uncompress(packed, &packed_len_long, compressed, len) != Z_OK ||
+ packed_len_long != packed_len) {
+ free(packed);
+ free(compressed);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (decompression failed)"));
+ return (NULL);
+ }
+
+ /* unpack nvlist */
+ nvlist_t *nv;
+ int error = nvlist_unpack(packed, packed_len, &nv, KM_SLEEP);
+ free(packed);
+ free(compressed);
+ if (error != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (nvlist_unpack failed)"));
+ return (NULL);
+ }
+ return (nv);
+}
+
+int
+zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+ const char *resume_token)
+{
+ char errbuf[1024];
+ char *toname;
+ char *fromname = NULL;
+ uint64_t resumeobj, resumeoff, toguid, fromguid, bytes;
+ zfs_handle_t *zhp;
+ int error = 0;
+ char name[ZFS_MAXNAMELEN];
+ enum lzc_send_flags lzc_flags = 0;
+
+ (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->verbose) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "resume token contents:\n"));
+ nvlist_print(stderr, resume_nvl);
+ }
+
+ if (nvlist_lookup_string(resume_nvl, "toname", &toname) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "object", &resumeobj) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "offset", &resumeoff) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "toguid", &toguid) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt"));
+ return (zfs_error(hdl, EZFS_FAULT, errbuf));
+ }
+ fromguid = 0;
+ (void) nvlist_lookup_uint64(resume_nvl, "fromguid", &fromguid);
+
+ if (flags->embed_data || nvlist_exists(resume_nvl, "embedok"))
+ lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+
+ 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);
+ }
+ 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,
+ "unable to access '%s'"), name);
+ return (zfs_error(hdl, EZFS_BADPATH, errbuf));
+ }
+
+ if (fromguid != 0) {
+ if (guid_to_name(hdl, toname, fromguid, B_TRUE, name) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental source %#llx no longer exists"),
+ (longlong_t)fromguid);
+ return (zfs_error(hdl, EZFS_BADPATH, errbuf));
+ }
+ fromname = name;
+ }
+
+ if (flags->verbose) {
+ uint64_t size = 0;
+ error = lzc_send_space(zhp->zfs_name, fromname, &size);
+ if (error == 0)
+ size = MAX(0, (int64_t)(size - bytes));
+ send_print_verbose(stderr, zhp->zfs_name, fromname,
+ size, flags->parsable);
+ }
+
+ if (!flags->dryrun) {
+ progress_arg_t pa = { 0 };
+ pthread_t tid;
+ /*
+ * If progress reporting is requested, spawn a new thread to
+ * poll ZFS_IOC_SEND_PROGRESS at a regular interval.
+ */
+ if (flags->progress) {
+ pa.pa_zhp = zhp;
+ pa.pa_fd = outfd;
+ pa.pa_parsable = flags->parsable;
+
+ error = pthread_create(&tid, NULL,
+ send_progress_thread, &pa);
+ if (error != 0) {
+ zfs_close(zhp);
+ return (error);
+ }
+ }
+
+ error = lzc_send_resume(zhp->zfs_name, fromname, outfd,
+ lzc_flags, resumeobj, resumeoff);
+
+ if (flags->progress) {
+ (void) pthread_cancel(tid);
+ (void) pthread_join(tid, NULL);
+ }
+
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "warning: cannot send '%s'"), zhp->zfs_name);
+
+ zfs_close(zhp);
+
+ switch (error) {
+ case 0:
+ return (0);
+ case EXDEV:
+ case ENOENT:
+ case EDQUOT:
+ case EFBIG:
+ case EIO:
+ case ENOLINK:
+ case ENOSPC:
+ case ENOSTR:
+ case ENXIO:
+ case EPIPE:
+ case ERANGE:
+ case EFAULT:
+ case EROFS:
+ zfs_error_aux(hdl, strerror(errno));
+ return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+ default:
+ return (zfs_standard_error(hdl, errno, errbuf));
+ }
+ }
+
+
+ zfs_close(zhp);
+
+ return (error);
+}
+
/*
* Generate a send stream for the dataset identified by the argument zhp.
*
@@ -1451,7 +1697,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
dmu_replay_record_t drr = { 0 };
char *packbuf = NULL;
size_t buflen = 0;
- zio_cksum_t zc = { { 0 } };
+ zio_cksum_t zc;
+
+ ZIO_SET_CHECKSUM(&zc, 0, 0, 0, 0);
if (flags->replicate || flags->props) {
nvlist_t *hdrnv;
@@ -1913,6 +2161,7 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
typedef struct guid_to_name_data {
uint64_t guid;
+ boolean_t bookmark_ok;
char *name;
char *skip;
} guid_to_name_data_t;
@@ -1921,20 +2170,25 @@ static int
guid_to_name_cb(zfs_handle_t *zhp, void *arg)
{
guid_to_name_data_t *gtnd = arg;
+ const char *slash;
int err;
if (gtnd->skip != NULL &&
- strcmp(zhp->zfs_name, gtnd->skip) == 0) {
+ (slash = strrchr(zhp->zfs_name, '/')) != NULL &&
+ strcmp(slash + 1, gtnd->skip) == 0) {
+ zfs_close(zhp);
return (0);
}
- if (zhp->zfs_dmustats.dds_guid == gtnd->guid) {
+ if (zfs_prop_get_int(zhp, ZFS_PROP_GUID) == gtnd->guid) {
(void) strcpy(gtnd->name, zhp->zfs_name);
zfs_close(zhp);
return (EEXIST);
}
err = zfs_iter_children(zhp, guid_to_name_cb, gtnd);
+ if (err != EEXIST && gtnd->bookmark_ok)
+ err = zfs_iter_bookmarks(zhp, guid_to_name_cb, gtnd);
zfs_close(zhp);
return (err);
}
@@ -1948,45 +2202,48 @@ guid_to_name_cb(zfs_handle_t *zhp, void *arg)
*/
static int
guid_to_name(libzfs_handle_t *hdl, const char *parent, uint64_t guid,
- char *name)
+ boolean_t bookmark_ok, char *name)
{
- /* exhaustive search all local snapshots */
char pname[ZFS_MAXNAMELEN];
guid_to_name_data_t gtnd;
- int err = 0;
- zfs_handle_t *zhp;
- char *cp;
gtnd.guid = guid;
+ gtnd.bookmark_ok = bookmark_ok;
gtnd.name = name;
gtnd.skip = NULL;
- (void) strlcpy(pname, parent, sizeof (pname));
-
/*
- * Search progressively larger portions of the hierarchy. This will
+ * Search progressively larger portions of the hierarchy, starting
+ * with the filesystem specified by 'parent'. This will
* select the "most local" version of the origin snapshot in the case
* that there are multiple matching snapshots in the system.
*/
- while ((cp = strrchr(pname, '/')) != NULL) {
-
+ (void) strlcpy(pname, parent, sizeof (pname));
+ char *cp = strrchr(pname, '@');
+ if (cp == NULL)
+ cp = strchr(pname, '\0');
+ for (; cp != NULL; cp = strrchr(pname, '/')) {
/* Chop off the last component and open the parent */
*cp = '\0';
- zhp = make_dataset_handle(hdl, pname);
+ zfs_handle_t *zhp = make_dataset_handle(hdl, pname);
if (zhp == NULL)
continue;
-
- err = zfs_iter_children(zhp, guid_to_name_cb, &gtnd);
+ int err = guid_to_name_cb(zfs_handle_dup(zhp), &gtnd);
+ if (err != EEXIST)
+ err = zfs_iter_children(zhp, guid_to_name_cb, &gtnd);
+ if (err != EEXIST && bookmark_ok)
+ err = zfs_iter_bookmarks(zhp, guid_to_name_cb, &gtnd);
zfs_close(zhp);
if (err == EEXIST)
return (0);
/*
- * Remember the dataset that we already searched, so we
- * skip it next time through.
+ * Remember the last portion of the dataset so we skip it next
+ * time through (as we've already searched that portion of the
+ * hierarchy).
*/
- gtnd.skip = pname;
+ gtnd.skip = strrchr(pname, '/') + 1;
}
return (ENOENT);
@@ -2586,11 +2843,9 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap)
switch (drr->drr_type) {
case DRR_BEGIN:
- /* NB: not to be used on v2 stream packages */
if (drr->drr_payloadlen != 0) {
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "invalid substream header"));
- return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
+ (void) recv_read(hdl, fd, buf,
+ drr->drr_payloadlen, B_FALSE, NULL);
}
break;
@@ -2651,6 +2906,40 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap)
return (-1);
}
+static void
+recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap,
+ boolean_t resumable)
+{
+ char target_fs[ZFS_MAXNAMELEN];
+
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "checksum mismatch or incomplete stream"));
+
+ if (!resumable)
+ return;
+ (void) strlcpy(target_fs, target_snap, sizeof (target_fs));
+ *strchr(target_fs, '@') = '\0';
+ zfs_handle_t *zhp = zfs_open(hdl, target_fs,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL)
+ return;
+
+ char token_buf[ZFS_MAXPROPLEN];
+ int error = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ token_buf, sizeof (token_buf),
+ NULL, NULL, 0, B_TRUE);
+ if (error == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "checksum mismatch or incomplete stream.\n"
+ "Partially received snapshot is saved.\n"
+ "A resuming stream can be generated on the sending "
+ "system by running:\n"
+ " zfs send -t %s"),
+ token_buf);
+ }
+ zfs_close(zhp);
+}
+
/*
* Restores a backup of tosnap from the file descriptor specified by infd.
*/
@@ -2799,7 +3088,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
*/
if (drrb->drr_flags & DRR_FLAG_CLONE) {
if (guid_to_name(hdl, zc.zc_value,
- drrb->drr_fromguid, zc.zc_string) != 0) {
+ drrb->drr_fromguid, B_FALSE, zc.zc_string) != 0) {
zcmd_free_nvlists(&zc);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"local origin for clone %s does not exist"),
@@ -2815,8 +3104,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zc.zc_string);
}
+ boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_RESUMING;
stream_wantsnewfs = (drrb->drr_fromguid == 0 ||
- (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap);
+ (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming;
if (stream_wantsnewfs) {
/*
@@ -2835,7 +3126,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
char suffix[ZFS_MAXNAMELEN];
(void) strcpy(suffix, strrchr(zc.zc_value, '/'));
if (guid_to_name(hdl, zc.zc_name, parent_snapguid,
- zc.zc_value) == 0) {
+ B_FALSE, zc.zc_value) == 0) {
*strchr(zc.zc_value, '@') = '\0';
(void) strcat(zc.zc_value, suffix);
}
@@ -2862,7 +3153,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
char snap[ZFS_MAXNAMELEN];
(void) strcpy(snap, strchr(zc.zc_value, '@'));
if (guid_to_name(hdl, zc.zc_name, drrb->drr_fromguid,
- zc.zc_value) == 0) {
+ B_FALSE, zc.zc_value) == 0) {
*strchr(zc.zc_value, '@') = '\0';
(void) strcat(zc.zc_value, snap);
}
@@ -2876,11 +3167,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zfs_handle_t *zhp;
/*
- * Destination fs exists. Therefore this should either
- * be an incremental, or the stream specifies a new fs
- * (full stream or clone) and they want us to blow it
- * away (and have therefore specified -F and removed any
- * snapshots).
+ * Destination fs exists. It must be one of these cases:
+ * - an incremental send stream
+ * - the stream specifies a new fs (full stream or clone)
+ * and they want us to blow away the existing fs (and
+ * have therefore specified -F and removed any snapshots)
+ * - we are resuming a failed receive.
*/
if (stream_wantsnewfs) {
if (!flags->force) {
@@ -2935,6 +3227,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
return (-1);
}
}
+
+ /*
+ * If we are resuming a newfs, set newfs here so that we will
+ * mount it if the recv succeeds this time. We can tell
+ * that it was a newfs on the first recv because the fs
+ * itself will be inconsistent (if the fs existed when we
+ * did the first recv, we would have received it into
+ * .../%recv).
+ */
+ if (resuming && zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT))
+ newfs = B_TRUE;
+
zfs_close(zhp);
} else {
/*
@@ -2967,9 +3271,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
newfs = B_TRUE;
}
- zc.zc_begin_record = drr_noswap->drr_u.drr_begin;
+ zc.zc_begin_record = *drr_noswap;
zc.zc_cookie = infd;
zc.zc_guid = flags->force;
+ zc.zc_resumable = flags->resumable;
if (flags->verbose) {
(void) printf("%s %s stream of %s into %s\n",
flags->dryrun ? "would receive" : "receiving",
@@ -3106,8 +3411,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
(void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
break;
case ECKSUM:
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "invalid stream (checksum mismatch)"));
+ recv_ecksum_set_aux(hdl, zc.zc_value, flags->resumable);
(void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
break;
case ENOTSUP:
@@ -3309,7 +3613,8 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
* Restores a backup of tosnap from the file descriptor specified by infd.
* Return 0 on total success, -2 if some things couldn't be
* destroyed/renamed/promoted, -1 if some things couldn't be received.
- * (-1 will override -2).
+ * (-1 will override -2, if -1 and the resumable flag was specified the
+ * transfer can be resumed if the sending side supports it).
*/
int
zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props,
diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c
index b706e6f6b..220792300 100644
--- a/lib/libzfs_core/libzfs_core.c
+++ b/lib/libzfs_core/libzfs_core.c
@@ -468,6 +468,13 @@ int
lzc_send(const char *snapname, const char *from, int fd,
enum lzc_send_flags flags)
{
+ return (lzc_send_resume(snapname, from, fd, flags, 0, 0));
+}
+
+int
+lzc_send_resume(const char *snapname, const char *from, int fd,
+ enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff)
+{
nvlist_t *args;
int err;
@@ -479,6 +486,10 @@ lzc_send(const char *snapname, const char *from, int fd,
fnvlist_add_boolean(args, "largeblockok");
if (flags & LZC_SEND_FLAG_EMBED_DATA)
fnvlist_add_boolean(args, "embedok");
+ if (resumeobj != 0 || resumeoff != 0) {
+ fnvlist_add_uint64(args, "resume_object", resumeobj);
+ fnvlist_add_uint64(args, "resume_offset", resumeoff);
+ }
err = lzc_ioctl(ZFS_IOC_SEND_NEW, snapname, args, NULL);
nvlist_free(args);
return (err);
@@ -536,22 +547,9 @@ recv_read(int fd, void *buf, int ilen)
return (0);
}
-/*
- * The simplest receive case: receive from the specified fd, creating the
- * specified snapshot. Apply the specified properties a "received" properties
- * (which can be overridden by locally-set properties). If the stream is a
- * clone, its origin snapshot must be specified by 'origin'. The 'force'
- * flag will cause the target filesystem to be rolled back or destroyed if
- * necessary to receive.
- *
- * Return 0 on success or an errno on failure.
- *
- * Note: this interface does not work on dedup'd streams
- * (those with DMU_BACKUP_FEATURE_DEDUP).
- */
-int
-lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
- boolean_t force, int fd)
+static int
+lzc_receive_impl(const char *snapname, nvlist_t *props, const char *origin,
+ boolean_t force, boolean_t resumable, int fd)
{
/*
* The receive ioctl is still legacy, so we need to construct our own
@@ -561,7 +559,6 @@ lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
char *atp;
char *packed = NULL;
size_t size;
- dmu_replay_record_t drr;
int error;
ASSERT3S(g_refcount, >, 0);
@@ -597,10 +594,9 @@ lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
(void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string));
/* zc_begin_record is non-byteswapped BEGIN record */
- error = recv_read(fd, &drr, sizeof (drr));
+ error = recv_read(fd, &zc.zc_begin_record, sizeof (zc.zc_begin_record));
if (error != 0)
goto out;
- zc.zc_begin_record = drr.drr_u.drr_begin;
/* zc_cookie is fd to read from */
zc.zc_cookie = fd;
@@ -608,6 +604,8 @@ lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
/* zc guid is force flag */
zc.zc_guid = force;
+ zc.zc_resumable = resumable;
+
/* zc_cleanup_fd is unused */
zc.zc_cleanup_fd = -1;
@@ -623,6 +621,39 @@ out:
}
/*
+ * The simplest receive case: receive from the specified fd, creating the
+ * specified snapshot. Apply the specified properties as "received" properties
+ * (which can be overridden by locally-set properties). If the stream is a
+ * clone, its origin snapshot must be specified by 'origin'. The 'force'
+ * flag will cause the target filesystem to be rolled back or destroyed if
+ * necessary to receive.
+ *
+ * Return 0 on success or an errno on failure.
+ *
+ * Note: this interface does not work on dedup'd streams
+ * (those with DMU_BACKUP_FEATURE_DEDUP).
+ */
+int
+lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
+ boolean_t force, int fd)
+{
+ return (lzc_receive_impl(snapname, props, origin, force, B_FALSE, fd));
+}
+
+/*
+ * Like lzc_receive, but if the receive fails due to premature stream
+ * termination, the intermediate state will be preserved on disk. In this
+ * case, ECKSUM will be returned. The receive may subsequently be resumed
+ * with a resuming send stream generated by lzc_send_resume().
+ */
+int
+lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
+ boolean_t force, int fd)
+{
+ return (lzc_receive_impl(snapname, props, origin, force, B_TRUE, fd));
+}
+
+/*
* Roll back this filesystem or volume to its most recent snapshot.
* If snapnamebuf is not NULL, it will be filled in with the name
* of the most recent snapshot.
diff --git a/lib/libzpool/kernel.c b/lib/libzpool/kernel.c
index 800cdd7d7..93bfb0403 100644
--- a/lib/libzpool/kernel.c
+++ b/lib/libzpool/kernel.c
@@ -1408,6 +1408,8 @@ spl_fstrans_check(void)
return (0);
}
+void *zvol_tag = "zvol_tag";
+
void
zvol_create_minors(spa_t *spa, const char *name, boolean_t async)
{