]> git.proxmox.com Git - mirror_zfs.git/blobdiff - lib/libzfs/libzfs_sendrecv.c
Encryption patch follow-up
[mirror_zfs.git] / lib / libzfs / libzfs_sendrecv.c
index 448ee15ec21366e7f920def2caadc02e1239a623..934da54e1c1ea69c9598f58a01b8e372194c05b8 100644 (file)
@@ -27,6 +27,8 @@
  * All rights reserved
  * Copyright (c) 2013 Steven Hartland. All rights reserved.
  * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
  */
 
 #include <assert.h>
 #include "libzfs_impl.h"
 #include <zlib.h>
 #include <sys/zio_checksum.h>
+#include <sys/dsl_crypt.h>
 #include <sys/ddt.h>
 #include <sys/socket.h>
+#include <sys/sha2.h>
 
 /* in libzfs_dataset.c */
 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 *, const char *);
+    uint64_t *, const char *, nvlist_t *);
 static int guid_to_name(libzfs_handle_t *, const char *,
     uint64_t, boolean_t, char *);
 
@@ -244,12 +248,16 @@ cksummer(void *arg)
        int outfd;
        dedup_table_t ddt;
        zio_cksum_t stream_cksum;
-       uint64_t physmem = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE);
        uint64_t numbuckets;
 
+#ifdef _ILP32
+       ddt.max_ddt_size = SMALLEST_POSSIBLE_MAX_DDT_MB << 20;
+#else
+       uint64_t physmem = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE);
        ddt.max_ddt_size =
            MAX((physmem * MAX_DDT_PHYSMEM_PERCENT) / 100,
            SMALLEST_POSSIBLE_MAX_DDT_MB << 20);
+#endif
 
        numbuckets = ddt.max_ddt_size / (sizeof (dedup_entry_t));
 
@@ -258,7 +266,7 @@ cksummer(void *arg)
         * a power of 2 if necessary.
         */
        if (!ISP2(numbuckets))
-               numbuckets = 1 << high_order_bit(numbuckets);
+               numbuckets = 1ULL << high_order_bit(numbuckets);
 
        ddt.dedup_hash_array = calloc(numbuckets, sizeof (dedup_entry_t *));
        ddt.ddecache = umem_cache_create("dde", sizeof (dedup_entry_t), 0,
@@ -271,6 +279,15 @@ cksummer(void *arg)
        ofp = fdopen(dda->inputfd, "r");
        while (ssread(drr, sizeof (*drr), ofp) != 0) {
 
+               /*
+                * kernel filled in checksum, we are going to write same
+                * record, but need to regenerate checksum.
+                */
+               if (drr->drr_type != DRR_BEGIN) {
+                       bzero(&drr->drr_u.drr_checksum.drr_checksum,
+                           sizeof (drr->drr_u.drr_checksum.drr_checksum));
+               }
+
                switch (drr->drr_type) {
                case DRR_BEGIN:
                {
@@ -320,11 +337,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;
@@ -333,8 +348,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;
@@ -364,11 +379,13 @@ 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)) {
+                               SHA2_CTX ctx;
                                zio_cksum_t tmpsha256;
 
-                               zio_checksum_SHA256(buf,
-                                   payload_size, &tmpsha256);
+                               SHA2Init(SHA256, &ctx);
+                               SHA2Update(&ctx, buf, payload_size);
+                               SHA2Final(&tmpsha256, &ctx);
 
                                drrw->drr_key.ddk_cksum.zc_word[0] =
                                    BE_64(tmpsha256.zc_word[0]);
@@ -379,7 +396,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;
@@ -408,8 +425,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 =
@@ -448,6 +464,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);
@@ -574,13 +598,31 @@ fsavl_create(nvlist_t *fss)
  * Routines for dealing with the giant nvlist of fs-nvlists, etc.
  */
 typedef struct send_data {
+       /*
+        * assigned inside every recursive call,
+        * restored from *_save on return:
+        *
+        * guid of fromsnap snapshot in parent dataset
+        * txg of fromsnap snapshot in current dataset
+        * txg of tosnap snapshot in current dataset
+        */
+
        uint64_t parent_fromsnap_guid;
+       uint64_t fromsnap_txg;
+       uint64_t tosnap_txg;
+
+       /* the nvlists get accumulated during depth-first traversal */
        nvlist_t *parent_snaps;
        nvlist_t *fss;
        nvlist_t *snapprops;
+
+       /* send-receive configuration, does not change during traversal */
+       const char *fsname;
        const char *fromsnap;
        const char *tosnap;
+       boolean_t raw;
        boolean_t recursive;
+       boolean_t verbose;
        boolean_t seenfrom;
        boolean_t seento;
 
@@ -600,6 +642,7 @@ typedef struct send_data {
         *       "snapprops" -> { name (lastname) -> { name -> value } }
         *
         *       "origin" -> number (guid) (if clone)
+        *       "is_encroot" -> boolean
         *       "sent" -> boolean (not on-disk)
         *      }
         *   }
@@ -615,6 +658,7 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
 {
        send_data_t *sd = arg;
        uint64_t guid = zhp->zfs_dmustats.dds_guid;
+       uint64_t txg = zhp->zfs_dmustats.dds_creation_txg;
        char *snapname;
        nvlist_t *nv;
        boolean_t isfromsnap, istosnap, istosnapwithnofrom;
@@ -625,6 +669,17 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
        istosnap = (sd->tosnap != NULL && (strcmp(sd->tosnap, snapname) == 0));
        istosnapwithnofrom = (istosnap && sd->fromsnap == NULL);
 
+       if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
+               if (sd->verbose) {
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "skipping snapshot %s because it was created "
+                           "after the destination snapshot (%s)\n"),
+                           zhp->zfs_name, sd->tosnap);
+               }
+               zfs_close(zhp);
+               return (0);
+       }
+
        VERIFY(0 == nvlist_add_uint64(sd->parent_snaps, snapname, guid));
        /*
         * NB: if there is no fromsnap here (it's a newly created fs in
@@ -731,6 +786,31 @@ send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv)
        }
 }
 
+/*
+ * returns snapshot creation txg
+ * and returns 0 if the snapshot does not exist
+ */
+static uint64_t
+get_snap_txg(libzfs_handle_t *hdl, const char *fs, const char *snap)
+{
+       char name[ZFS_MAX_DATASET_NAME_LEN];
+       uint64_t txg = 0;
+
+       if (fs == NULL || fs[0] == '\0' || snap == NULL || snap[0] == '\0')
+               return (txg);
+
+       (void) snprintf(name, sizeof (name), "%s@%s", fs, snap);
+       if (zfs_dataset_exists(hdl, name, ZFS_TYPE_SNAPSHOT)) {
+               zfs_handle_t *zhp = zfs_open(hdl, name, ZFS_TYPE_SNAPSHOT);
+               if (zhp != NULL) {
+                       txg = zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG);
+                       zfs_close(zhp);
+               }
+       }
+
+       return (txg);
+}
+
 /*
  * recursively generate nvlists describing datasets.  See comment
  * for the data structure send_data_t above for description of contents
@@ -740,12 +820,51 @@ 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;
+       uint64_t tosnap_txg_save = sd->tosnap_txg;
+       uint64_t txg = zhp->zfs_dmustats.dds_creation_txg;
        uint64_t guid = zhp->zfs_dmustats.dds_guid;
+       uint64_t fromsnap_txg, tosnap_txg;
        char guidstring[64];
 
+       fromsnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->fromsnap);
+       if (fromsnap_txg != 0)
+               sd->fromsnap_txg = fromsnap_txg;
+
+       tosnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->tosnap);
+       if (tosnap_txg != 0)
+               sd->tosnap_txg = tosnap_txg;
+
+       /*
+        * on the send side, if the current dataset does not have tosnap,
+        * perform two additional checks:
+        *
+        * - skip sending the current dataset if it was created later than
+        *   the parent tosnap
+        * - return error if the current dataset was created earlier than
+        *   the parent tosnap
+        */
+       if (sd->tosnap != NULL && tosnap_txg == 0) {
+               if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
+                       if (sd->verbose) {
+                               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                                   "skipping dataset %s: snapshot %s does "
+                                   "not exist\n"), zhp->zfs_name, sd->tosnap);
+                       }
+               } else {
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "cannot send %s@%s%s: snapshot %s@%s does not "
+                           "exist\n"), sd->fsname, sd->tosnap, sd->recursive ?
+                           dgettext(TEXT_DOMAIN, " recursively") : "",
+                           zhp->zfs_name, sd->tosnap);
+                       rv = -1;
+               }
+               goto out;
+       }
+
        VERIFY(0 == nvlist_alloc(&nvfs, NV_UNIQUE_NAME, 0));
        VERIFY(0 == nvlist_add_string(nvfs, "name", zhp->zfs_name));
        VERIFY(0 == nvlist_add_uint64(nvfs, "parentfromsnap",
@@ -754,17 +873,50 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
        if (zhp->zfs_dmustats.dds_origin[0]) {
                zfs_handle_t *origin = zfs_open(zhp->zfs_hdl,
                    zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT);
-               if (origin == NULL)
-                       return (-1);
+               if (origin == NULL) {
+                       rv = -1;
+                       goto out;
+               }
                VERIFY(0 == nvlist_add_uint64(nvfs, "origin",
                    origin->zfs_dmustats.dds_guid));
+
+               zfs_close(origin);
        }
 
        /* 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;
@@ -780,13 +932,17 @@ 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)
                rv = zfs_iter_filesystems(zhp, send_iterate_fs, sd);
 
+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);
@@ -794,7 +950,8 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
 
 static int
 gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
-    const char *tosnap, boolean_t recursive, nvlist_t **nvlp, avl_tree_t **avlp)
+    const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t verbose,
+    nvlist_t **nvlp, avl_tree_t **avlp)
 {
        zfs_handle_t *zhp;
        send_data_t sd = { 0 };
@@ -805,9 +962,12 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
                return (EZFS_BADTYPE);
 
        VERIFY(0 == nvlist_alloc(&sd.fss, NV_UNIQUE_NAME, 0));
+       sd.fsname = fsname;
        sd.fromsnap = fromsnap;
        sd.tosnap = tosnap;
        sd.recursive = recursive;
+       sd.raw = raw;
+       sd.verbose = verbose;
 
        if ((error = send_iterate_fs(zhp, &sd)) != 0) {
                nvlist_free(sd.fss);
@@ -838,7 +998,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;
@@ -853,39 +1013,32 @@ typedef struct send_dump_data {
 } send_dump_data_t;
 
 static int
-estimate_ioctl(zfs_handle_t *zhp, uint64_t fromsnap_obj,
-    boolean_t fromorigin, enum lzc_send_flags flags, uint64_t *sizep)
+zfs_send_space(zfs_handle_t *zhp, const char *snapname, const char *from,
+    enum lzc_send_flags flags, uint64_t *spacep)
 {
-       zfs_cmd_t zc = {"\0"};
        libzfs_handle_t *hdl = zhp->zfs_hdl;
+       int error;
 
-       assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
-       assert(fromsnap_obj == 0 || !fromorigin);
+       assert(snapname != NULL);
+       error = lzc_send_space(snapname, from, flags, spacep);
 
-       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       zc.zc_obj = fromorigin;
-       zc.zc_sendobj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
-       zc.zc_fromobj = fromsnap_obj;
-       zc.zc_guid = 1;  /* estimate flag */
-       zc.zc_flags = flags;
-
-       if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND, &zc) != 0) {
+       if (error != 0) {
                char errbuf[1024];
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
-                   "warning: cannot estimate space for '%s'"), zhp->zfs_name);
+                   "warning: cannot estimate space for '%s'"), snapname);
 
-               switch (errno) {
+               switch (error) {
                case EXDEV:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "not an earlier snapshot from the same fs"));
                        return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
 
                case ENOENT:
-                       if (zfs_dataset_exists(hdl, zc.zc_name,
+                       if (zfs_dataset_exists(hdl, snapname,
                            ZFS_TYPE_SNAPSHOT)) {
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                                   "incremental source (@%s) does not exist"),
-                                   zc.zc_value);
+                                   "incremental source (%s) does not exist"),
+                                   snapname);
                        }
                        return (zfs_error(hdl, EZFS_NOENT, errbuf));
 
@@ -900,16 +1053,15 @@ estimate_ioctl(zfs_handle_t *zhp, uint64_t fromsnap_obj,
                case ERANGE:
                case EFAULT:
                case EROFS:
-                       zfs_error_aux(hdl, strerror(errno));
+               case EINVAL:
+                       zfs_error_aux(hdl, strerror(error));
                        return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
 
                default:
-                       return (zfs_standard_error(hdl, errno, errbuf));
+                       return (zfs_standard_error(hdl, error, errbuf));
                }
        }
 
-       *sizep = zc.zc_objset_type;
-
        return (0);
 }
 
@@ -960,6 +1112,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)) {
@@ -1046,7 +1203,7 @@ send_progress_thread(void *arg)
                            tm->tm_hour, tm->tm_min, tm->tm_sec,
                            bytes, zhp->zfs_name);
                } else {
-                       zfs_nicenum(bytes, buf, sizeof (buf));
+                       zfs_nicebytes(bytes, buf, sizeof (buf));
                        (void) fprintf(stderr, "%02d:%02d:%02d   %5s   %s\n",
                            tm->tm_hour, tm->tm_min, tm->tm_sec,
                            buf, zhp->zfs_name);
@@ -1085,16 +1242,14 @@ send_print_verbose(FILE *fout, const char *tosnap, const char *fromsnap,
                }
        }
 
-       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);
-               }
+       if (parsable) {
+               (void) fprintf(fout, "\t%llu",
+                   (longlong_t)size);
+       } else if (size != 0) {
+               char buf[16];
+               zfs_nicebytes(size, buf, sizeof (buf));
+               (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+                   " estimated size is %s"), buf);
        }
        (void) fprintf(fout, "\n");
 }
@@ -1120,7 +1275,8 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
        if (!sdd->seenfrom && isfromsnap) {
                gather_holds(zhp, sdd);
                sdd->seenfrom = B_TRUE;
-               (void) strcpy(sdd->prevsnap, thissnap);
+               (void) strlcpy(sdd->prevsnap, thissnap,
+                   sizeof (sdd->prevsnap));
                sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
                zfs_close(zhp);
                return (0);
@@ -1141,6 +1297,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) {
@@ -1187,12 +1345,21 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
 
        if (sdd->verbose) {
                uint64_t size = 0;
-               (void) estimate_ioctl(zhp, sdd->prevsnap_obj,
-                   fromorigin, flags, &size);
+               char fromds[ZFS_MAX_DATASET_NAME_LEN];
 
-               send_print_verbose(fout, zhp->zfs_name,
-                   sdd->prevsnap[0] ? sdd->prevsnap : NULL,
-                   size, sdd->parsable);
+               if (sdd->prevsnap[0] != '\0') {
+                       (void) strlcpy(fromds, zhp->zfs_name, sizeof (fromds));
+                       *(strchr(fromds, '@') + 1) = '\0';
+                       (void) strlcat(fromds, sdd->prevsnap, sizeof (fromds));
+               }
+               if (zfs_send_space(zhp, zhp->zfs_name,
+                   sdd->prevsnap[0] ? fromds : NULL, flags, &size) != 0) {
+                       size = 0; /* cannot estimate send space */
+               } else {
+                       send_print_verbose(fout, zhp->zfs_name,
+                           sdd->prevsnap[0] ? sdd->prevsnap : NULL,
+                           size, sdd->parsable);
+               }
                sdd->size += size;
        }
 
@@ -1207,7 +1374,7 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
                        pa.pa_parsable = sdd->parsable;
 
                        if ((err = pthread_create(&tid, NULL,
-                           send_progress_thread, &pa))) {
+                           send_progress_thread, &pa)) != 0) {
                                zfs_close(zhp);
                                return (err);
                        }
@@ -1487,6 +1654,7 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
        int error = 0;
        char name[ZFS_MAX_DATASET_NAME_LEN];
        enum lzc_send_flags lzc_flags = 0;
+       FILE *fout = (flags->verbose && flags->dryrun) ? stdout : stderr;
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot resume send"));
@@ -1501,9 +1669,9 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
                return (zfs_error(hdl, EZFS_FAULT, errbuf));
        }
        if (flags->verbose) {
-               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+               (void) fprintf(fout, dgettext(TEXT_DOMAIN,
                    "resume token contents:\n"));
-               nvlist_print(stderr, resume_nvl);
+               nvlist_print(fout, resume_nvl);
        }
 
        if (nvlist_lookup_string(resume_nvl, "toname", &toname) != 0 ||
@@ -1524,6 +1692,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)) {
@@ -1560,7 +1730,7 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
                    lzc_flags, &size);
                if (error == 0)
                        size = MAX(0, (int64_t)(size - bytes));
-               send_print_verbose(stderr, zhp->zfs_name, fromname,
+               send_print_verbose(fout, zhp->zfs_name, fromname,
                    size, flags->parsable);
        }
 
@@ -1601,6 +1771,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:
@@ -1679,10 +1854,17 @@ 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 = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd))) {
+               if ((err = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd)) != 0) {
                        zfs_error_aux(zhp->zfs_hdl, strerror(errno));
                        return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED,
                            errbuf));
@@ -1690,7 +1872,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                dda.outputfd = outfd;
                dda.inputfd = pipefd[1];
                dda.dedup_hdl = zhp->zfs_hdl;
-               if ((err = pthread_create(&tid, NULL, cksummer, &dda))) {
+               if ((err = pthread_create(&tid, NULL, cksummer, &dda)) != 0) {
                        (void) close(pipefd[0]);
                        (void) close(pipefd[1]);
                        zfs_error_aux(zhp->zfs_hdl, strerror(errno));
@@ -1720,9 +1902,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, &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));
@@ -1744,9 +1930,13 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                            drr_versioninfo, DMU_COMPOUNDSTREAM);
                        DMU_SET_FEATUREFLAGS(drr.drr_u.drr_begin.
                            drr_versioninfo, featureflags);
-                       (void) snprintf(drr.drr_u.drr_begin.drr_toname,
+                       if (snprintf(drr.drr_u.drr_begin.drr_toname,
                            sizeof (drr.drr_u.drr_begin.drr_toname),
-                           "%s@%s", zhp->zfs_name, tosnap);
+                           "%s@%s", zhp->zfs_name, tosnap) >=
+                           sizeof (drr.drr_u.drr_begin.drr_toname)) {
+                               err = EINVAL;
+                               goto stderr_out;
+                       }
                        drr.drr_payloadlen = buflen;
 
                        err = dump_record(&drr, packbuf, buflen, &zc, outfd);
@@ -1787,6 +1977,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)
@@ -1837,7 +2028,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                                    (longlong_t)sdd.size);
                        } else {
                                char buf[16];
-                               zfs_nicenum(sdd.size, buf, sizeof (buf));
+                               zfs_nicebytes(sdd.size, buf, sizeof (buf));
                                (void) fprintf(fout, dgettext(TEXT_DOMAIN,
                                    "total estimated size is %s\n"), buf);
                        }
@@ -1921,17 +2112,42 @@ err_out:
 }
 
 int
-zfs_send_one(zfs_handle_t *zhp, const char *from, int fd,
-    enum lzc_send_flags flags)
+zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t flags)
 {
-       int err;
+       int err = 0;
        libzfs_handle_t *hdl = zhp->zfs_hdl;
-
+       enum lzc_send_flags lzc_flags = 0;
+       FILE *fout = (flags.verbose && flags.dryrun) ? stdout : stderr;
        char errbuf[1024];
+
+       if (flags.largeblock)
+               lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK;
+       if (flags.embed_data)
+               lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+       if (flags.compress)
+               lzc_flags |= LZC_SEND_FLAG_COMPRESS;
+       if (flags.raw)
+               lzc_flags |= LZC_SEND_FLAG_RAW;
+
+       if (flags.verbose) {
+               uint64_t size = 0;
+               err = lzc_send_space(zhp->zfs_name, from, lzc_flags, &size);
+               if (err == 0) {
+                       send_print_verbose(fout, zhp->zfs_name, from, size,
+                           flags.parsable);
+               } else {
+                       (void) fprintf(stderr, "Cannot estimate send size: "
+                           "%s\n", strerror(errno));
+               }
+       }
+
+       if (flags.dryrun)
+               return (err);
+
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "warning: cannot send '%s'"), zhp->zfs_name);
 
-       err = lzc_send(zhp->zfs_name, from, fd, flags);
+       err = lzc_send(zhp->zfs_name, from, fd, lzc_flags);
        if (err != 0) {
                switch (errno) {
                case EXDEV:
@@ -1948,6 +2164,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, "
@@ -2038,6 +2259,63 @@ 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, zfs_cmd_t *zc)
+{
+       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 = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+       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 = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+       }
+
+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)
@@ -2045,20 +2323,23 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
        static int seq;
        zfs_cmd_t zc = {"\0"};
        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;
 
        zc.zc_objset_type = DMU_OST_ZFS;
        (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
@@ -2072,7 +2353,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
                        (void) printf("attempting rename %s to %s\n",
                            zc.zc_name, zc.zc_value);
                }
-               err = ioctl(hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+               err = recv_rename_impl(zhp, &zc);
                if (err == 0)
                        changelist_rename(clp, name, tryname);
        } else {
@@ -2090,7 +2371,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
                        (void) printf("failed - trying rename %s to %s\n",
                            zc.zc_name, zc.zc_value);
                }
-               err = ioctl(hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+               err = recv_rename_impl(zhp, &zc);
                if (err == 0)
                        changelist_rename(clp, name, newname);
                if (err && flags->verbose) {
@@ -2106,7 +2387,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);
 }
@@ -2308,6 +2644,149 @@ 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.
+ */
+static int
+recv_fix_encryption_heirarchy(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,
@@ -2337,7 +2816,7 @@ again:
        VERIFY(0 == nvlist_alloc(&deleted, NV_UNIQUE_NAME, 0));
 
        if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
-           recursive, &local_nv, &local_avl)) != 0)
+           recursive, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0)
                return (error);
 
        /*
@@ -2386,22 +2865,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;
@@ -2509,7 +2981,7 @@ again:
                        else
                                progress = B_TRUE;
                        sprintf(guidname, "%llu",
-                           (u_longlong_t) parent_fromsnap_guid);
+                           (u_longlong_t)parent_fromsnap_guid);
                        nvlist_add_boolean(deleted, guidname);
                        continue;
                }
@@ -2543,7 +3015,7 @@ again:
                    parent_fromsnap_guid != 0 &&
                    stream_parent_fromsnap_guid != parent_fromsnap_guid) {
                        sprintf(guidname, "%llu",
-                           (u_longlong_t) parent_fromsnap_guid);
+                           (u_longlong_t)parent_fromsnap_guid);
                        if (nvlist_exists(deleted, guidname)) {
                                progress = B_TRUE;
                                needagain = B_TRUE;
@@ -2617,13 +3089,14 @@ doagain:
                goto again;
        }
 
-       return (needagain);
+       return (needagain || error != 0);
 }
 
 static int
 zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
     recvflags_t *flags, dmu_replay_record_t *drr, zio_cksum_t *zc,
-    char **top_zfs, int cleanup_fd, uint64_t *action_handlep)
+    char **top_zfs, int cleanup_fd, uint64_t *action_handlep,
+    nvlist_t *cmdprops)
 {
        nvlist_t *stream_nv = NULL;
        avl_tree_t *stream_avl = NULL;
@@ -2637,7 +3110,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"));
@@ -2661,6 +3134,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,
@@ -2711,7 +3185,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                        goto out;
                }
 
-               if (fromsnap != NULL) {
+               if (fromsnap != NULL && recursive) {
                        nvlist_t *renamed = NULL;
                        nvpair_t *pair = NULL;
 
@@ -2738,7 +3212,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                                *strchr(tofs, '@') = '\0';
                        }
 
-                       if (recursive && !flags->dryrun && !flags->nomount) {
+                       if (!flags->dryrun && !flags->nomount) {
                                VERIFY(0 == nvlist_alloc(&renamed,
                                    NV_UNIQUE_NAME, 0));
                        }
@@ -2799,7 +3273,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                 */
                error = zfs_receive_impl(hdl, destname, NULL, flags, fd,
                    sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd,
-                   action_handlep, sendsnap);
+                   action_handlep, sendsnap, cmdprops);
                if (error == ENODATA) {
                        error = 0;
                        break;
@@ -2807,7 +3281,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                anyerr |= error;
        } while (error == 0);
 
-       if (drr->drr_payloadlen != 0 && fromsnap != NULL) {
+       if (drr->drr_payloadlen != 0 && recursive && fromsnap != NULL) {
                /*
                 * Now that we have the fs's they sent us, try the
                 * renames again.
@@ -2816,6 +3290,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_heirarchy(hdl, destname,
+                   stream_nv, stream_avl);
+       }
+
 out:
        fsavl_destroy(stream_avl);
        nvlist_free(stream_nv);
@@ -2920,6 +3399,7 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap)
                default:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "invalid record type"));
+                       free(buf);
                        return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
                }
        }
@@ -2962,6 +3442,131 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap,
        zfs_close(zhp);
 }
 
+/*
+ * Prepare a new nvlist of properties that are to override (-o) or be excluded
+ * (-x) from the received dataset
+ * recvprops: received properties from the send stream
+ * cmdprops: raw input properties from command line
+ * origprops: properties, both locally-set and received, currently set on the
+ *            target dataset if it exists, NULL otherwise.
+ * oxprops: valid output override (-o) and excluded (-x) properties
+ */
+static int
+zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
+    boolean_t recursive, boolean_t toplevel, nvlist_t *recvprops,
+    nvlist_t *cmdprops, nvlist_t *origprops, nvlist_t **oxprops,
+    const char *errbuf)
+{
+       nvpair_t *nvp;
+       nvlist_t *oprops, *voprops;
+       zfs_handle_t *zhp = NULL;
+       zpool_handle_t *zpool_hdl = NULL;
+       int ret = 0;
+
+       if (nvlist_empty(cmdprops))
+               return (0); /* No properties to override or exclude */
+
+       *oxprops = fnvlist_alloc();
+       oprops = fnvlist_alloc();
+
+       /*
+        * first iteration: process excluded (-x) properties now and gather
+        * added (-o) properties to be later processed by zfs_valid_proplist()
+        */
+       nvp = NULL;
+       while ((nvp = nvlist_next_nvpair(cmdprops, nvp)) != NULL) {
+               const char *name = nvpair_name(nvp);
+               zfs_prop_t prop = zfs_name_to_prop(name);
+
+               /* "origin" is processed separately, don't handle it here */
+               if (prop == ZFS_PROP_ORIGIN)
+                       continue;
+
+               /*
+                * we're trying to override or exclude a property that does not
+                * make sense for this type of dataset, but we don't want to
+                * fail if the receive is recursive: this comes in handy when
+                * the send stream contains, for instance, a child ZVOL and
+                * we're trying to receive it with "-o atime=on"
+                */
+               if (!zfs_prop_valid_for_type(prop, type, B_FALSE) &&
+                   !zfs_prop_user(name)) {
+                       if (recursive)
+                               continue;
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "property '%s' does not apply to datasets of this "
+                           "type"), name);
+                       ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+                       goto error;
+               }
+
+               switch (nvpair_type(nvp)) {
+               case DATA_TYPE_BOOLEAN: /* -x property */
+                       /*
+                        * DATA_TYPE_BOOLEAN is the way we're asked to "exclude"
+                        * a property: this is done by forcing an explicit
+                        * inherit on the destination so the effective value is
+                        * not the one we received from the send stream.
+                        * We do this only if the property is not already
+                        * locally-set, in which case its value will take
+                        * priority over the received anyway.
+                        */
+                       if (nvlist_exists(origprops, name)) {
+                               nvlist_t *attrs;
+
+                               attrs = fnvlist_lookup_nvlist(origprops, name);
+                               if (strcmp(fnvlist_lookup_string(attrs,
+                                   ZPROP_SOURCE), ZPROP_SOURCE_VAL_RECVD) != 0)
+                                       continue;
+                       }
+                       /*
+                        * We can't force an explicit inherit on non-inheritable
+                        * properties: if we're asked to exclude this kind of
+                        * values we remove them from "recvprops" input nvlist.
+                        */
+                       if (!zfs_prop_inheritable(prop) &&
+                           !zfs_prop_user(name) && /* can be inherited too */
+                           nvlist_exists(recvprops, name))
+                               fnvlist_remove(recvprops, name);
+                       else
+                               fnvlist_add_nvpair(*oxprops, nvp);
+                       break;
+               case DATA_TYPE_STRING: /* -o property=value */
+                       fnvlist_add_nvpair(oprops, nvp);
+                       break;
+               default:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "property '%s' must be a string or boolean"), name);
+                       ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+                       goto error;
+               }
+       }
+
+       if (toplevel) {
+               /* convert override strings properties to native */
+               if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET,
+                   oprops, zoned, zhp, zpool_hdl, B_FALSE, errbuf)) == NULL) {
+                       ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+                       goto error;
+               }
+
+               /* second pass: process "-o" properties */
+               fnvlist_merge(*oxprops, voprops);
+               fnvlist_free(voprops);
+       } else {
+               /* override props on child dataset are inherited */
+               nvp = NULL;
+               while ((nvp = nvlist_next_nvpair(oprops, nvp)) != NULL) {
+                       const char *name = nvpair_name(nvp);
+                       fnvlist_add_boolean(*oxprops, name);
+               }
+       }
+
+error:
+       fnvlist_free(oprops);
+       return (ret);
+}
+
 /*
  * Restores a backup of tosnap from the file descriptor specified by infd.
  */
@@ -2970,7 +3575,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
     const char *originsnap, recvflags_t *flags, dmu_replay_record_t *drr,
     dmu_replay_record_t *drr_noswap, const char *sendfs, nvlist_t *stream_nv,
     avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
-    uint64_t *action_handlep, const char *finalsnap)
+    uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops)
 {
        time_t begin_time;
        int ioctl_err, ioctl_errno, err;
@@ -2993,10 +3598,17 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        char destsnap[MAXPATHLEN * 2];
        char origin[MAXNAMELEN];
        char name[MAXPATHLEN];
-       nvlist_t *props = NULL;
+       char tmp_keylocation[MAXNAMELEN];
+       nvlist_t *rcvprops = NULL; /* props received from the send stream */
+       nvlist_t *oxprops = NULL; /* override (-o) and exclude (-x) props */
+       nvlist_t *origprops = NULL; /* original props (if destination exists) */
+       zfs_type_t type;
+       boolean_t toplevel;
+       boolean_t zoned = B_FALSE;
 
        begin_time = time(NULL);
        bzero(origin, MAXNAMELEN);
+       bzero(tmp_keylocation, MAXNAMELEN);
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot receive"));
@@ -3005,21 +3617,43 @@ 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);
 
                (void) nvlist_lookup_uint64(fs, "parentfromsnap",
                    &parent_snapguid);
-               err = nvlist_lookup_nvlist(fs, "props", &props);
+               err = nvlist_lookup_nvlist(fs, "props", &rcvprops);
                if (err) {
-                       VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0));
+                       VERIFY(0 == nvlist_alloc(&rcvprops, 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_heirarchy() 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(rcvprops,
+                   zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+               if (err == 0) {
+                       strcpy(tmp_keylocation, keylocation);
+                       (void) nvlist_remove_all(rcvprops,
+                           zfs_prop_to_name(ZFS_PROP_KEYLOCATION));
+               }
+
                if (flags->canmountoff) {
-                       VERIFY(0 == nvlist_add_uint64(props,
+                       VERIFY(0 == nvlist_add_uint64(rcvprops,
                            zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0));
                }
+               if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) {
+                       VERIFY(0 == nvlist_lookup_nvlist(lookup,
+                           snapname, &snapprops_nvlist));
+               }
        }
 
        cp = NULL;
@@ -3102,7 +3736,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        /*
         * Determine name of destination snapshot.
         */
-       (void) strcpy(destsnap, tosnap);
+       (void) strlcpy(destsnap, tosnap, sizeof (destsnap));
        (void) strlcat(destsnap, chopprefix, sizeof (destsnap));
        free(cp);
        if (!zfs_name_valid(destsnap, ZFS_TYPE_SNAPSHOT)) {
@@ -3113,7 +3747,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        /*
         * Determine the name of the origin snapshot.
         */
-       if (drrb->drr_flags & DRR_FLAG_CLONE) {
+       if (originsnap) {
+               (void) strncpy(origin, originsnap, sizeof (origin));
+               if (flags->verbose)
+                       (void) printf("using provided clone origin %s\n",
+                           origin);
+       } else if (drrb->drr_flags & DRR_FLAG_CLONE) {
                if (guid_to_name(hdl, destsnap,
                    drrb->drr_fromguid, B_FALSE, origin) != 0) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -3124,15 +3763,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                }
                if (flags->verbose)
                        (void) printf("found clone origin %s\n", origin);
-       } else if (originsnap) {
-               (void) strncpy(origin, originsnap, sizeof (origin));
-               if (flags->verbose)
-                       (void) printf("using provided clone origin %s\n",
-                           origin);
        }
 
        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 == 0 ||
            (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming;
 
@@ -3239,6 +3877,26 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        goto out;
                }
 
+               /*
+                * 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 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.
+                */
+               if (stream_wantsnewfs && flags->force &&
+                   zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) !=
+                   ZIO_CRYPT_OFF) {
+                       zfs_close(zhp);
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "zfs receive -F cannot be used to "
+                           "destroy an encrypted filesystem"));
+                       err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+                       goto out;
+               }
+
+
                if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
                    stream_wantsnewfs) {
                        /* We can't do online recv in this case */
@@ -3267,8 +3925,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                if (resuming && zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT))
                        newfs = B_TRUE;
 
+               /* we want to know if we're zoned when validating -o|-x props */
+               zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+
+               /* gather existing properties on destination */
+               origprops = fnvlist_alloc();
+               fnvlist_merge(origprops, zhp->zfs_props);
+               fnvlist_merge(origprops, zhp->zfs_user_props);
+
                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
@@ -3297,7 +3965,39 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        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 && rcvprops != NULL) {
+                       uint64_t crypt;
+
+                       zhp = zfs_open(hdl, 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 unenecrypted property"), name);
+                               err = zfs_error(hdl, EZFS_BADPROP, errbuf);
+                               goto out;
+                       }
+               }
+
                newfs = B_TRUE;
+               *cp = '/';
        }
 
        if (flags->verbose) {
@@ -3313,9 +4013,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                goto out;
        }
 
-       err = ioctl_err = lzc_receive_one(destsnap, props, origin,
-           flags->force, flags->resumable, infd, drr_noswap, cleanup_fd,
-           &read_bytes, &errflags, action_handlep, &prop_errors);
+       toplevel = chopprefix[0] != '/';
+       if (drrb->drr_type == DMU_OST_ZVOL) {
+               type = ZFS_TYPE_VOLUME;
+       } else if (drrb->drr_type == DMU_OST_ZFS) {
+               type = ZFS_TYPE_FILESYSTEM;
+       } else {
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                   "invalid record type: 0x%d"), drrb->drr_type);
+               err = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+               goto out;
+       }
+       if ((err = zfs_setup_cmdline_props(hdl, type, zoned, recursive,
+           toplevel, rcvprops, cmdprops, origprops, &oxprops, errbuf)) != 0)
+               goto out;
+
+       err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, oxprops,
+           origin, flags->force, flags->resumable, raw, infd, drr_noswap,
+           cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors);
        ioctl_errno = ioctl_err;
        prop_errflags = errflags;
 
@@ -3385,8 +4100,8 @@ 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, destsnap, NULL, NULL, B_FALSE,
-                   &local_nv, &local_avl) == 0) {
+               if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
+                   B_FALSE, &local_nv, &local_avl) == 0) {
                        *cp = '@';
                        fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
                        fsavl_destroy(local_avl);
@@ -3421,6 +4136,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                            "since most recent snapshot"), 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(destsnap, '@');
                        if (newfs) {
@@ -3439,6 +4168,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "kernel modules must be upgraded to "
                                    "receive this stream."));
+                       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:
@@ -3489,7 +4222,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        }
 
        if (clp) {
-               err |= changelist_postfix(clp);
+               if (!flags->nomount)
+                       err |= changelist_postfix(clp);
                changelist_free(clp);
        }
 
@@ -3516,10 +4250,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                time_t delta = time(NULL) - begin_time;
                if (delta == 0)
                        delta = 1;
-               zfs_nicenum(bytes, buf1, sizeof (buf1));
-               zfs_nicenum(bytes/delta, buf2, sizeof (buf1));
+               zfs_nicebytes(bytes, buf1, sizeof (buf1));
+               zfs_nicebytes(bytes/delta, buf2, sizeof (buf1));
 
-               (void) printf("received %sB stream in %lu seconds (%sB/sec)\n",
+               (void) printf("received %s stream in %lu seconds (%s/sec)\n",
                    buf1, delta, buf2);
        }
 
@@ -3528,17 +4262,71 @@ out:
        if (prop_errors != NULL)
                nvlist_free(prop_errors);
 
+       if (tmp_keylocation[0] != '\0') {
+               VERIFY(0 == nvlist_add_string(rcvprops,
+                   zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation));
+       }
+
        if (newprops)
-               nvlist_free(props);
+               nvlist_free(rcvprops);
+
+       nvlist_free(oxprops);
+       nvlist_free(origprops);
 
        return (err);
 }
 
+/*
+ * Check properties we were asked to override (both -o|-x)
+ */
+static boolean_t
+zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props,
+    const char *errbuf)
+{
+       nvpair_t *nvp;
+       zfs_prop_t prop;
+       const char *name;
+
+       nvp = NULL;
+       while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL) {
+               name = nvpair_name(nvp);
+               prop = zfs_name_to_prop(name);
+
+               if (prop == ZPROP_INVAL) {
+                       if (!zfs_prop_user(name)) {
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "invalid property '%s'"), name);
+                               return (B_FALSE);
+                       }
+                       continue;
+               }
+               /*
+                * "origin" is readonly but is used to receive datasets as
+                * clones so we don't raise an error here
+                */
+               if (prop == ZFS_PROP_ORIGIN)
+                       continue;
+
+               /*
+                * cannot override readonly, set-once and other specific
+                * settable properties
+                */
+               if (zfs_prop_readonly(prop) || prop == ZFS_PROP_VERSION ||
+                   prop == ZFS_PROP_VOLSIZE) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "invalid property '%s'"), name);
+                       return (B_FALSE);
+               }
+       }
+
+       return (B_TRUE);
+}
+
 static int
 zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
     const char *originsnap, recvflags_t *flags, int infd, const char *sendfs,
     nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
-    uint64_t *action_handlep, const char *finalsnap)
+    uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops)
 {
        int err;
        dmu_replay_record_t drr, drr_noswap;
@@ -3551,6 +4339,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot receive"));
 
+       /* check cmdline props, raise an error if they cannot be received */
+       if (!zfs_receive_checkprops(hdl, cmdprops, errbuf)) {
+               return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+       }
+
        if (flags->isprefix &&
            !zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs "
@@ -3639,12 +4432,12 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
                }
                return (zfs_receive_one(hdl, infd, tosnap, originsnap, flags,
                    &drr, &drr_noswap, sendfs, stream_nv, stream_avl, top_zfs,
-                   cleanup_fd, action_handlep, finalsnap));
+                   cleanup_fd, action_handlep, finalsnap, cmdprops));
        } else {
                assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) ==
                    DMU_COMPOUNDSTREAM);
                return (zfs_receive_package(hdl, infd, tosnap, flags, &drr,
-                   &zcksum, top_zfs, cleanup_fd, action_handlep));
+                   &zcksum, top_zfs, cleanup_fd, action_handlep, cmdprops));
        }
 }
 
@@ -3717,7 +4510,7 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props,
        VERIFY(cleanup_fd >= 0);
 
        err = zfs_receive_impl(hdl, tosnap, originsnap, flags, infd, NULL, NULL,
-           stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL);
+           stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL, props);
 
        VERIFY(0 == close(cleanup_fd));