summaryrefslogtreecommitdiff
path: root/usr/src/lib/libzfs/common/libzfs_sendrecv.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libzfs/common/libzfs_sendrecv.c')
-rw-r--r--usr/src/lib/libzfs/common/libzfs_sendrecv.c622
1 files changed, 555 insertions, 67 deletions
diff --git a/usr/src/lib/libzfs/common/libzfs_sendrecv.c b/usr/src/lib/libzfs/common/libzfs_sendrecv.c
index 7ed81fd0d1..c933a24e89 100644
--- a/usr/src/lib/libzfs/common/libzfs_sendrecv.c
+++ b/usr/src/lib/libzfs/common/libzfs_sendrecv.c
@@ -55,6 +55,7 @@
#include <zlib.h>
#include <sha2.h>
#include <sys/zio_checksum.h>
+#include <sys/dsl_crypt.h>
#include <sys/ddt.h>
/* in libzfs_dataset.c */
@@ -324,11 +325,9 @@ cksummer(void *arg)
struct drr_object *drro = &drr->drr_u.drr_object;
if (drro->drr_bonuslen > 0) {
(void) ssread(buf,
- P2ROUNDUP((uint64_t)drro->drr_bonuslen, 8),
- ofp);
+ DRR_OBJECT_PAYLOAD_SIZE(drro), ofp);
}
- if (dump_record(drr, buf,
- P2ROUNDUP((uint64_t)drro->drr_bonuslen, 8),
+ if (dump_record(drr, buf, DRR_OBJECT_PAYLOAD_SIZE(drro),
&stream_cksum, outfd) != 0)
goto out;
break;
@@ -337,8 +336,8 @@ cksummer(void *arg)
case DRR_SPILL:
{
struct drr_spill *drrs = &drr->drr_u.drr_spill;
- (void) ssread(buf, drrs->drr_length, ofp);
- if (dump_record(drr, buf, drrs->drr_length,
+ (void) ssread(buf, DRR_SPILL_PAYLOAD_SIZE(drrs), ofp);
+ if (dump_record(drr, buf, DRR_SPILL_PAYLOAD_SIZE(drrs),
&stream_cksum, outfd) != 0)
goto out;
break;
@@ -368,7 +367,7 @@ cksummer(void *arg)
if (ZIO_CHECKSUM_EQUAL(drrw->drr_key.ddk_cksum,
zero_cksum) ||
- !DRR_IS_DEDUP_CAPABLE(drrw->drr_checksumflags)) {
+ !DRR_IS_DEDUP_CAPABLE(drrw->drr_flags)) {
SHA256_CTX ctx;
zio_cksum_t tmpsha256;
@@ -384,7 +383,7 @@ cksummer(void *arg)
drrw->drr_key.ddk_cksum.zc_word[3] =
BE_64(tmpsha256.zc_word[3]);
drrw->drr_checksumtype = ZIO_CHECKSUM_SHA256;
- drrw->drr_checksumflags = DRR_CHECKSUM_DEDUP;
+ drrw->drr_flags |= DRR_CHECKSUM_DEDUP;
}
dataref.ref_guid = drrw->drr_toguid;
@@ -413,8 +412,7 @@ cksummer(void *arg)
wbr_drrr->drr_checksumtype =
drrw->drr_checksumtype;
- wbr_drrr->drr_checksumflags =
- drrw->drr_checksumtype;
+ wbr_drrr->drr_flags = drrw->drr_flags;
wbr_drrr->drr_key.ddk_cksum =
drrw->drr_key.ddk_cksum;
wbr_drrr->drr_key.ddk_prop =
@@ -453,6 +451,14 @@ cksummer(void *arg)
break;
}
+ case DRR_OBJECT_RANGE:
+ {
+ if (dump_record(drr, NULL, 0, &stream_cksum,
+ outfd) != 0)
+ goto out;
+ break;
+ }
+
default:
(void) fprintf(stderr, "INVALID record type 0x%x\n",
drr->drr_type);
@@ -601,6 +607,7 @@ typedef struct send_data {
const char *fsname;
const char *fromsnap;
const char *tosnap;
+ boolean_t raw;
boolean_t recursive;
boolean_t verbose;
@@ -620,6 +627,7 @@ typedef struct send_data {
* "snapprops" -> { name (lastname) -> { name -> value } }
*
* "origin" -> number (guid) (if clone)
+ * "is_encroot" -> boolean
* "sent" -> boolean (not on-disk)
* }
* }
@@ -778,7 +786,7 @@ static int
send_iterate_fs(zfs_handle_t *zhp, void *arg)
{
send_data_t *sd = arg;
- nvlist_t *nvfs, *nv;
+ nvlist_t *nvfs = NULL, *nv = NULL;
int rv = 0;
uint64_t parent_fromsnap_guid_save = sd->parent_fromsnap_guid;
uint64_t fromsnap_txg_save = sd->fromsnap_txg;
@@ -842,8 +850,37 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
/* iterate over props */
VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0));
send_iterate_prop(zhp, nv);
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
+ boolean_t encroot;
+
+ /* determine if this dataset is an encryption root */
+ if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0) {
+ rv = -1;
+ goto out;
+ }
+
+ if (encroot)
+ VERIFY(0 == nvlist_add_boolean(nvfs, "is_encroot"));
+
+ /*
+ * Encrypted datasets can only be sent with properties if
+ * the raw flag is specified because the receive side doesn't
+ * currently have a mechanism for recursively asking the user
+ * for new encryption parameters.
+ */
+ if (!sd->raw) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "cannot send %s@%s: encrypted dataset %s may not "
+ "be sent with properties without the raw flag\n"),
+ sd->fsname, sd->tosnap, zhp->zfs_name);
+ rv = -1;
+ goto out;
+ }
+
+ }
+
VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv));
- nvlist_free(nv);
/* iterate over snaps, and set sd->parent_fromsnap_guid */
sd->parent_fromsnap_guid = 0;
@@ -859,7 +896,6 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
(void) snprintf(guidstring, sizeof (guidstring),
"0x%llx", (longlong_t)guid);
VERIFY(0 == nvlist_add_nvlist(sd->fss, guidstring, nvfs));
- nvlist_free(nvfs);
/* iterate over children */
if (sd->recursive)
@@ -869,6 +905,8 @@ out:
sd->parent_fromsnap_guid = parent_fromsnap_guid_save;
sd->fromsnap_txg = fromsnap_txg_save;
sd->tosnap_txg = tosnap_txg_save;
+ nvlist_free(nv);
+ nvlist_free(nvfs);
zfs_close(zhp);
return (rv);
@@ -876,7 +914,7 @@ out:
static int
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
- const char *tosnap, boolean_t recursive, boolean_t verbose,
+ const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t verbose,
nvlist_t **nvlp, avl_tree_t **avlp)
{
zfs_handle_t *zhp;
@@ -892,6 +930,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.fromsnap = fromsnap;
sd.tosnap = tosnap;
sd.recursive = recursive;
+ sd.raw = raw;
sd.verbose = verbose;
if ((error = send_iterate_fs(zhp, &sd)) != 0) {
@@ -923,7 +962,7 @@ typedef struct send_dump_data {
uint64_t prevsnap_obj;
boolean_t seenfrom, seento, replicate, doall, fromorigin;
boolean_t verbose, dryrun, parsable, progress, embed_data, std_out;
- boolean_t large_block, compress;
+ boolean_t large_block, compress, raw;
int outfd;
boolean_t err;
nvlist_t *fss;
@@ -965,6 +1004,11 @@ estimate_ioctl(zfs_handle_t *zhp, uint64_t fromsnap_obj,
"not an earlier snapshot from the same fs"));
return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case ENOENT:
if (zfs_dataset_exists(hdl, zc.zc_name,
ZFS_TYPE_SNAPSHOT)) {
@@ -1045,6 +1089,11 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
"not an earlier snapshot from the same fs"));
return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case ENOENT:
if (zfs_dataset_exists(hdl, zc.zc_name,
ZFS_TYPE_SNAPSHOT)) {
@@ -1226,6 +1275,8 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
flags |= LZC_SEND_FLAG_EMBED_DATA;
if (sdd->compress)
flags |= LZC_SEND_FLAG_COMPRESS;
+ if (sdd->raw)
+ flags |= LZC_SEND_FLAG_RAW;
if (!sdd->doall && !isfromsnap && !istosnap) {
if (sdd->replicate) {
@@ -1610,6 +1661,8 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
if (flags->compress || nvlist_exists(resume_nvl, "compressok"))
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
+ if (flags->raw || nvlist_exists(resume_nvl, "rawok"))
+ lzc_flags |= LZC_SEND_FLAG_RAW;
if (guid_to_name(hdl, toname, toguid, B_FALSE, name) != 0) {
if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
@@ -1687,6 +1740,11 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
switch (error) {
case 0:
return (0);
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case EXDEV:
case ENOENT:
case EDQUOT:
@@ -1765,7 +1823,14 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
}
- if (flags->dedup && !flags->dryrun) {
+ /*
+ * Start the dedup thread if this is a dedup stream. We do not bother
+ * doing this if this a raw send of an encrypted dataset with dedup off
+ * because normal encrypted blocks won't dedup.
+ */
+ if (flags->dedup && !flags->dryrun && !(flags->raw &&
+ zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF &&
+ zfs_prop_get_int(zhp, ZFS_PROP_DEDUP) == ZIO_CHECKSUM_OFF)) {
featureflags |= (DMU_BACKUP_FEATURE_DEDUP |
DMU_BACKUP_FEATURE_DEDUPPROPS);
if ((err = pipe(pipefd)) != 0) {
@@ -1804,10 +1869,13 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
VERIFY(0 == nvlist_add_boolean(hdrnv,
"not_recursive"));
}
+ if (flags->raw) {
+ VERIFY(0 == nvlist_add_boolean(hdrnv, "raw"));
+ }
err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
- fromsnap, tosnap, flags->replicate, flags->verbose,
- &fss, &fsavl);
+ fromsnap, tosnap, flags->replicate, flags->raw,
+ flags->verbose, &fss, &fsavl);
if (err)
goto err_out;
VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
@@ -1872,6 +1940,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
sdd.large_block = flags->largeblock;
sdd.embed_data = flags->embed_data;
sdd.compress = flags->compress;
+ sdd.raw = flags->raw;
sdd.filter_cb = filter_func;
sdd.filter_cb_arg = cb_arg;
if (debugnvp)
@@ -2033,6 +2102,11 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd,
}
return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case EBUSY:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"target is busy; if a filesystem, "
@@ -2123,26 +2197,86 @@ recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp,
return (0);
}
+/*
+ * Returns the grand origin (origin of origin of origin...) of a given handle.
+ * If this dataset is not a clone, it simply returns a copy of the original
+ * handle.
+ */
+static zfs_handle_t *
+recv_open_grand_origin(zfs_handle_t *zhp)
+{
+ char origin[ZFS_MAX_DATASET_NAME_LEN];
+ zprop_source_t src;
+ zfs_handle_t *ozhp = zfs_handle_dup(zhp);
+
+ while (ozhp != NULL) {
+ if (zfs_prop_get(ozhp, ZFS_PROP_ORIGIN, origin,
+ sizeof (origin), &src, NULL, 0, B_FALSE) != 0)
+ break;
+
+ (void) zfs_close(ozhp);
+ ozhp = zfs_open(zhp->zfs_hdl, origin, ZFS_TYPE_FILESYSTEM);
+ }
+
+ return (ozhp);
+}
+
+static int
+recv_rename_impl(zfs_handle_t *zhp, const char *source, const char *target)
+{
+ int err;
+ zfs_handle_t *ozhp = NULL;
+
+ /*
+ * Attempt to rename the dataset. If it fails with EACCES we have
+ * attempted to rename the dataset outside of its encryption root.
+ * Force the dataset to become an encryption root and try again.
+ */
+ err = lzc_rename(source, target);
+ if (err == EACCES) {
+ ozhp = recv_open_grand_origin(zhp);
+ if (ozhp == NULL) {
+ err = ENOENT;
+ goto out;
+ }
+
+ err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY,
+ NULL, NULL, 0);
+ if (err != 0)
+ goto out;
+
+ err = lzc_rename(source, target);
+ }
+
+out:
+ if (ozhp != NULL)
+ zfs_close(ozhp);
+ return (err);
+}
+
static int
recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
int baselen, char *newname, recvflags_t *flags)
{
static int seq;
int err;
- prop_changelist_t *clp;
- zfs_handle_t *zhp;
+ prop_changelist_t *clp = NULL;
+ zfs_handle_t *zhp = NULL;
zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
- if (zhp == NULL)
- return (-1);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ }
clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
flags->force ? MS_FORCE : 0);
- zfs_close(zhp);
- if (clp == NULL)
- return (-1);
+ if (clp == NULL) {
+ err = -1;
+ goto out;
+ }
err = changelist_prefix(clp);
if (err)
- return (err);
+ goto out;
if (tryname) {
(void) strcpy(newname, tryname);
@@ -2150,7 +2284,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
(void) printf("attempting rename %s to %s\n",
name, newname);
}
- err = lzc_rename(name, newname);
+ err = recv_rename_impl(zhp, name, newname);
if (err == 0)
changelist_rename(clp, name, tryname);
} else {
@@ -2166,7 +2300,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
(void) printf("failed - trying rename %s to %s\n",
name, newname);
}
- err = lzc_rename(name, newname);
+ err = recv_rename_impl(zhp, name, newname);
if (err == 0)
changelist_rename(clp, name, newname);
if (err && flags->verbose) {
@@ -2182,7 +2316,62 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
}
(void) changelist_postfix(clp);
- changelist_free(clp);
+
+out:
+ if (clp != NULL)
+ changelist_free(clp);
+ if (zhp != NULL)
+ zfs_close(zhp);
+
+ return (err);
+}
+
+static int
+recv_promote(libzfs_handle_t *hdl, const char *fsname,
+ const char *origin_fsname, recvflags_t *flags)
+{
+ int err;
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *zhp = NULL, *ozhp = NULL;
+
+ if (flags->verbose)
+ (void) printf("promoting %s\n", fsname);
+
+ (void) strlcpy(zc.zc_value, origin_fsname, sizeof (zc.zc_value));
+ (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name));
+
+ /*
+ * Attempt to promote the dataset. If it fails with EACCES the
+ * promotion would cause this dataset to leave its encryption root.
+ * Force the origin to become an encryption root and try again.
+ */
+ err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ if (err == EACCES) {
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ ozhp = recv_open_grand_origin(zhp);
+ if (ozhp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY,
+ NULL, NULL, 0);
+ if (err != 0)
+ goto out;
+
+ err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ }
+
+out:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ if (ozhp != NULL)
+ zfs_close(ozhp);
return (err);
}
@@ -2386,6 +2575,150 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
return (rv);
}
+/*
+ * This function reestablishes the heirarchy of encryption roots after a
+ * recursive incremental receive has completed. This must be done after the
+ * second call to recv_incremental_replication() has renamed and promoted all
+ * sent datasets to their final locations in the dataset heriarchy.
+ */
+/* ARGSUSED */
+static int
+recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *destname,
+ nvlist_t *stream_nv, avl_tree_t *stream_avl)
+{
+ int err;
+ nvpair_t *fselem = NULL;
+ nvlist_t *stream_fss;
+ char *cp;
+ char top_zfs[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) strcpy(top_zfs, destname);
+ cp = strrchr(top_zfs, '@');
+ if (cp != NULL)
+ *cp = '\0';
+
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nv, "fss", &stream_fss));
+
+ while ((fselem = nvlist_next_nvpair(stream_fss, fselem)) != NULL) {
+ zfs_handle_t *zhp = NULL;
+ uint64_t crypt;
+ nvlist_t *snaps, *props, *stream_nvfs = NULL;
+ nvpair_t *snapel = NULL;
+ boolean_t is_encroot, is_clone, stream_encroot;
+ char *cp;
+ char *stream_keylocation = NULL;
+ char keylocation[MAXNAMELEN];
+ char fsname[ZFS_MAX_DATASET_NAME_LEN];
+
+ keylocation[0] = '\0';
+ VERIFY(0 == nvpair_value_nvlist(fselem, &stream_nvfs));
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "snaps", &snaps));
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "props", &props));
+ stream_encroot = nvlist_exists(stream_nvfs, "is_encroot");
+
+ /* find a snapshot from the stream that exists locally */
+ err = ENOENT;
+ while ((snapel = nvlist_next_nvpair(snaps, snapel)) != NULL) {
+ uint64_t guid;
+
+ VERIFY(0 == nvpair_value_uint64(snapel, &guid));
+ err = guid_to_name(hdl, destname, guid, B_FALSE,
+ fsname);
+ if (err == 0)
+ break;
+ }
+
+ if (err != 0)
+ continue;
+
+ cp = strchr(fsname, '@');
+ if (cp != NULL)
+ *cp = '\0';
+
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = ENOENT;
+ goto error;
+ }
+
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0';
+ (void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+
+ /* we don't need to do anything for unencrypted filesystems */
+ if (crypt == ZIO_CRYPT_OFF) {
+ zfs_close(zhp);
+ continue;
+ }
+
+ /*
+ * If the dataset is flagged as an encryption root, was not
+ * received as a clone and is not currently an encryption root,
+ * force it to become one. Fixup the keylocation if necessary.
+ */
+ if (stream_encroot) {
+ if (!is_clone && !is_encroot) {
+ err = lzc_change_key(fsname,
+ DCP_CMD_FORCE_NEW_KEY, NULL, NULL, 0);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+
+ VERIFY(0 == nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ &stream_keylocation));
+
+ /*
+ * Refresh the properties in case the call to
+ * lzc_change_key() changed the value.
+ */
+ zfs_refresh_properties(zhp);
+ err = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
+ keylocation, sizeof (keylocation), NULL, NULL,
+ 0, B_TRUE);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+
+ if (strcmp(keylocation, stream_keylocation) != 0) {
+ err = zfs_prop_set(zhp,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ stream_keylocation);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+ }
+
+ /*
+ * If the dataset is not flagged as an encryption root and is
+ * currently an encryption root, force it to inherit from its
+ * parent. The root of a raw send should never be
+ * force-inherited.
+ */
+ if (!stream_encroot && is_encroot &&
+ strcmp(top_zfs, fsname) != 0) {
+ err = lzc_change_key(fsname, DCP_CMD_FORCE_INHERIT,
+ NULL, NULL, 0);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+
+ zfs_close(zhp);
+ }
+
+ return (0);
+
+error:
+ return (err);
+}
+
static int
recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl,
@@ -2412,7 +2745,7 @@ again:
needagain = progress = B_FALSE;
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
- recursive, B_FALSE, &local_nv, &local_avl)) != 0)
+ recursive, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0)
return (error);
/*
@@ -2461,22 +2794,15 @@ again:
stream_originguid, originguid)) {
case 1: {
/* promote it! */
- zfs_cmd_t zc = { 0 };
nvlist_t *origin_nvfs;
char *origin_fsname;
- if (flags->verbose)
- (void) printf("promoting %s\n", fsname);
-
origin_nvfs = fsavl_find(local_avl, originguid,
NULL);
VERIFY(0 == nvlist_lookup_string(origin_nvfs,
"name", &origin_fsname));
- (void) strlcpy(zc.zc_value, origin_fsname,
- sizeof (zc.zc_value));
- (void) strlcpy(zc.zc_name, fsname,
- sizeof (zc.zc_name));
- error = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ error = recv_promote(hdl, fsname, origin_fsname,
+ flags);
if (error == 0)
progress = B_TRUE;
break;
@@ -2665,7 +2991,7 @@ again:
goto again;
}
- return (needagain);
+ return (needagain || error != 0);
}
static int
@@ -2685,7 +3011,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
int error;
boolean_t anyerr = B_FALSE;
boolean_t softerr = B_FALSE;
- boolean_t recursive;
+ boolean_t recursive, raw;
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive"));
@@ -2709,6 +3035,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
ENOENT);
+ raw = (nvlist_lookup_boolean(stream_nv, "raw") == 0);
if (recursive && strchr(destname, '@')) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -2864,6 +3191,11 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
stream_nv, stream_avl, NULL);
}
+ if (raw && softerr == 0) {
+ softerr = recv_fix_encryption_hierarchy(hdl, destname,
+ stream_nv, stream_avl);
+ }
+
out:
fsavl_destroy(stream_avl);
nvlist_free(stream_nv);
@@ -3030,14 +3362,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
const char *chopprefix;
boolean_t newfs = B_FALSE;
boolean_t stream_wantsnewfs;
+ boolean_t newprops = B_FALSE;
uint64_t parent_snapguid = 0;
prop_changelist_t *clp = NULL;
nvlist_t *snapprops_nvlist = NULL;
zprop_errflags_t prop_errflags;
boolean_t recursive;
char *snapname = NULL;
+ nvlist_t *props = NULL;
+ char tmp_keylocation[MAXNAMELEN];
begin_time = time(NULL);
+ bzero(tmp_keylocation, MAXNAMELEN);
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive"));
@@ -3046,32 +3382,50 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
ENOENT);
if (stream_avl != NULL) {
+ char *keylocation = NULL;
+ nvlist_t *lookup = NULL;
nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid,
&snapname);
- nvlist_t *props;
int ret;
(void) nvlist_lookup_uint64(fs, "parentfromsnap",
&parent_snapguid);
err = nvlist_lookup_nvlist(fs, "props", &props);
- if (err)
+ if (err) {
VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0));
+ newprops = B_TRUE;
+ }
+ /*
+ * The keylocation property may only be set on encryption roots,
+ * but this dataset might not become an encryption root until
+ * recv_fix_encryption_hierarchy() is called. That function
+ * will fixup the keylocation anyway, so we temporarily unset
+ * the keylocation for now to avoid any errors from the receive
+ * ioctl.
+ */
+ err = nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+ if (err == 0) {
+ (void) strcpy(tmp_keylocation, keylocation);
+ (void) nvlist_remove_all(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION));
+ }
if (flags->canmountoff) {
VERIFY(0 == nvlist_add_uint64(props,
zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0));
}
ret = zcmd_write_src_nvlist(hdl, &zc, props);
- if (err)
- nvlist_free(props);
- if (0 == nvlist_lookup_nvlist(fs, "snapprops", &props)) {
- VERIFY(0 == nvlist_lookup_nvlist(props,
+ if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) {
+ VERIFY(0 == nvlist_lookup_nvlist(lookup,
snapname, &snapprops_nvlist));
}
- if (ret != 0)
- return (-1);
+ if (ret != 0) {
+ err = -1;
+ goto out;
+ }
}
cp = NULL;
@@ -3092,7 +3446,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (strchr(tosnap, '@')) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
"argument - snapshot not allowed with -e"));
- return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto out;
}
chopprefix = strrchr(sendfs, '/');
@@ -3119,7 +3474,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (strchr(tosnap, '@')) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
"argument - snapshot not allowed with -d"));
- return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto out;
}
chopprefix = strchr(drrb->drr_toname, '/');
@@ -3137,7 +3493,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"cannot specify snapshot name for multi-snapshot "
"stream"));
- return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
+ err = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
}
chopprefix = drrb->drr_toname + strlen(drrb->drr_toname);
}
@@ -3156,7 +3513,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
free(cp);
if (!zfs_name_valid(zc.zc_value, ZFS_TYPE_SNAPSHOT)) {
zcmd_free_nvlists(&zc);
- return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto out;
}
/*
@@ -3174,7 +3532,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"local origin for clone %s does not exist"),
zc.zc_value);
- return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ goto out;
}
if (flags->verbose)
(void) printf("found clone origin %s\n", zc.zc_string);
@@ -3182,6 +3541,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
DMU_BACKUP_FEATURE_RESUMING;
+ boolean_t raw = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_RAW;
+ boolean_t embedded = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_EMBED_DATA;
stream_wantsnewfs = (drrb->drr_fromguid == NULL ||
(drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming;
@@ -3241,6 +3604,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
zfs_handle_t *zhp;
+ boolean_t encrypted;
/*
* Destination fs exists. It must be one of these cases:
@@ -3257,7 +3621,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"destination '%s' exists\n"
"must specify -F to overwrite it"),
zc.zc_name);
- return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
}
if (ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT,
&zc) == 0) {
@@ -3266,14 +3631,16 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"destination has snapshots (eg. %s)\n"
"must destroy them to overwrite it"),
zc.zc_name);
- return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
}
}
if ((zhp = zfs_open(hdl, zc.zc_name,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) {
zcmd_free_nvlists(&zc);
- return (-1);
+ err = -1;
+ goto out;
}
if (stream_wantsnewfs &&
@@ -3284,7 +3651,42 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"destination '%s' is a clone\n"
"must destroy it to overwrite it"),
zc.zc_name);
- return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
+ }
+
+ /*
+ * Raw sends can not be performed as an incremental on top
+ * of existing unencrypted datasets. zfs recv -F cant be
+ * used to blow away an existing encrypted filesystem. This
+ * is because it would require the dsl dir to point to the
+ * new key (or lack of a key) and the old key at the same
+ * time. The -F flag may still be used for deleting
+ * intermediate snapshots that would otherwise prevent the
+ * receive from working.
+ */
+ encrypted = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) !=
+ ZIO_CRYPT_OFF;
+ if (!stream_wantsnewfs && !encrypted && raw) {
+ zfs_close(zhp);
+ zcmd_free_nvlists(&zc);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot perform raw receive on top of "
+ "existing unencrypted dataset"));
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+ if (stream_wantsnewfs && flags->force &&
+ ((raw && !encrypted) || encrypted)) {
+ zfs_close(zhp);
+ zcmd_free_nvlists(&zc);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "zfs receive -F cannot be used to destroy an "
+ "encrypted filesystem or overwrite an "
+ "unencrypted one with an encrypted one"));
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
}
if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
@@ -3294,13 +3696,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (clp == NULL) {
zfs_close(zhp);
zcmd_free_nvlists(&zc);
- return (-1);
+ err = -1;
+ goto out;
}
if (changelist_prefix(clp) != 0) {
changelist_free(clp);
zfs_close(zhp);
zcmd_free_nvlists(&zc);
- return (-1);
+ err = -1;
+ goto out;
}
}
@@ -3317,6 +3721,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zfs_close(zhp);
} else {
+ zfs_handle_t *zhp;
+
/*
* Destination filesystem does not exist. Therefore we better
* be creating a new filesystem (either from a full backup, or
@@ -3329,7 +3735,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zcmd_free_nvlists(&zc);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"destination '%s' does not exist"), zc.zc_name);
- return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ goto out;
}
/*
@@ -3341,10 +3748,45 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (flags->isprefix && !flags->istail && !flags->dryrun &&
create_parents(hdl, zc.zc_value, strlen(tosnap)) != 0) {
zcmd_free_nvlists(&zc);
- return (zfs_error(hdl, EZFS_BADRESTORE, errbuf));
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+ /*
+ * It is invalid to receive a properties stream that was
+ * unencrypted on the send side as a child of an encrypted
+ * parent. Technically there is nothing preventing this, but
+ * it would mean that the encryption=off property which is
+ * locally set on the send side would not be received correctly.
+ * We can infer encryption=off if the stream is not raw and
+ * properties were included since the send side will only ever
+ * send the encryption property in a raw nvlist header.
+ */
+ if (!raw && props != NULL) {
+ uint64_t crypt;
+
+ zhp = zfs_open(hdl, zc.zc_name, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = zfs_error(hdl, EZFS_BADRESTORE,
+ errbuf);
+ goto out;
+ }
+
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ zfs_close(zhp);
+
+ if (crypt != ZIO_CRYPT_OFF) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "parent '%s' must not be encrypted to "
+ "receive unencrypted property"),
+ zc.zc_name);
+ err = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto out;
+ }
}
newfs = B_TRUE;
+ *cp = '/';
}
zc.zc_begin_record = *drr_noswap;
@@ -3361,7 +3803,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (flags->dryrun) {
zcmd_free_nvlists(&zc);
- return (recv_skip(hdl, infd, flags->byteswap));
+ err = recv_skip(hdl, infd, flags->byteswap);
+ goto out;
}
zc.zc_nvlist_dst = (uint64_t)(uintptr_t)prop_errbuf;
@@ -3448,7 +3891,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
* get a strange "does not exist" error message.
*/
*cp = '\0';
- if (gather_nvlist(hdl, zc.zc_value, NULL, NULL, B_FALSE,
+ if (gather_nvlist(hdl, zc.zc_value, NULL, NULL, B_FALSE, B_TRUE,
B_FALSE, &local_nv, &local_avl) == 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
@@ -3484,6 +3927,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"since most recent snapshot"), zc.zc_name);
(void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
break;
+ case EACCES:
+ if (raw && stream_wantsnewfs) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "failed to create encryption key"));
+ } else if (raw && !stream_wantsnewfs) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption key does not match "
+ "existing key"));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "inherited key must be loaded"));
+ }
+ (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
+ break;
case EEXIST:
cp = strchr(zc.zc_value, '@');
if (newfs) {
@@ -3498,6 +3955,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
*cp = '@';
break;
case EINVAL:
+ if (embedded && !raw)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incompatible embedded data stream "
+ "feature with encrypted receive."));
(void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
break;
case ECKSUM:
@@ -3514,6 +3975,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"destination %s space quota exceeded"), zc.zc_name);
(void) zfs_error(hdl, EZFS_NOSPC, errbuf);
break;
+ case ZFS_ERR_FROM_IVSET_GUID_MISSING:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "IV set guid missing. See errata %u at"
+ "http://zfsonlinux.org/msg/ZFS-8000-ER"),
+ ZPOOL_ERRATA_ZOL_8308_ENCRYPTION);
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case ZFS_ERR_FROM_IVSET_GUID_MISMATCH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "IV set guid mismatch. See the 'zfs receive' "
+ "man page section\n discussing the limitations "
+ "of raw encrypted send streams."));
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
default:
(void) zfs_standard_error(hdl, ioctl_errno, errbuf);
}
@@ -3566,8 +4041,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
(void) fprintf(stderr, "\n");
}
- if (err || ioctl_err)
- return (-1);
+ if (err || ioctl_err) {
+ err = -1;
+ goto out;
+ }
*action_handlep = zc.zc_action_handle;
@@ -3585,7 +4062,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
buf1, delta, buf2);
}
- return (0);
+ err = 0;
+out:
+
+ if (tmp_keylocation[0] != '\0') {
+ VERIFY(0 == nvlist_add_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation));
+ }
+
+ if (newprops)
+ nvlist_free(props);
+
+ return (err);
}
static int