]> granicus.if.org Git - zfs/commitdiff
Implement zfs_ioc_recv_new() for OpenZFS 2605
authorBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 10 Jun 2016 00:04:12 +0000 (17:04 -0700)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 28 Jun 2016 20:47:03 +0000 (13:47 -0700)
Adds ZFS_IOC_RECV_NEW for resumable streams and preserves the legacy
ZFS_IOC_RECV user/kernel interface.  The new interface supports all
stream options but is currently only used for resumable streams.
This way updated user space utilities will interoperate with older
kernel modules.

ZFS_IOC_RECV_NEW is modeled after the existing ZFS_IOC_SEND_NEW
handler.  Non-Linux OpenZFS platforms have opted to change the
legacy interface in an incompatible fashion instead of adding a
new ioctl.

Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
include/libzfs_core.h
include/sys/fs/zfs.h
include/sys/zfs_ioctl.h
lib/libzfs/libzfs_sendrecv.c
lib/libzfs_core/libzfs_core.c
module/zfs/zfs_ioctl.c

index d2d79175f6a4dd010fcc635c83c609a0ba7e9859..9e761004aa36a44e5d31b72e26f5318cd0b8e581 100644 (file)
@@ -69,6 +69,9 @@ int lzc_receive_resumable(const char *, nvlist_t *, const char *,
     boolean_t, int);
 int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t,
     boolean_t, int, const struct dmu_replay_record *);
+int lzc_receive_one(const char *, nvlist_t *, const char *, boolean_t,
+    boolean_t, int, const struct dmu_replay_record *, int, uint64_t *,
+    uint64_t *, uint64_t *, nvlist_t **);
 
 boolean_t lzc_exists(const char *);
 
index 8a581eee5d1c58c52652be5dcbda941f38443a3b..e802454b9febabf3a0a0f59960ef31bd0da520ce 100644 (file)
@@ -232,6 +232,7 @@ typedef enum {
 
 #define        ZPROP_SOURCE_VAL_RECVD  "$recvd"
 #define        ZPROP_N_MORE_ERRORS     "N_MORE_ERRORS"
+
 /*
  * Dataset flag implemented as a special entry in the props zap object
  * indicating that the dataset has received properties on or after
@@ -923,7 +924,7 @@ typedef struct ddt_histogram {
  */
 typedef enum zfs_ioc {
        /*
-        * Illumos - 70/128 numbers reserved.
+        * Illumos - 71/128 numbers reserved.
         */
        ZFS_IOC_FIRST = ('Z' << 8),
        ZFS_IOC = ZFS_IOC_FIRST,
@@ -997,6 +998,7 @@ typedef enum zfs_ioc {
        ZFS_IOC_BOOKMARK,
        ZFS_IOC_GET_BOOKMARKS,
        ZFS_IOC_DESTROY_BOOKMARKS,
+       ZFS_IOC_RECV_NEW,
 
        /*
         * Linux - 3/64 numbers reserved.
index 7b751fea696e21f52575c44a3dec0b92bbdae27f..5157c6704f4b722f6e9999cd9e4da2890a622303 100644 (file)
@@ -389,15 +389,14 @@ typedef struct zfs_cmd {
        uint64_t        zc_iflags;              /* internal to zfs(7fs) */
        zfs_share_t     zc_share;
        dmu_objset_stats_t zc_objset_stats;
-       dmu_replay_record_t zc_begin_record;
+       struct drr_begin zc_begin_record;
        zinject_record_t zc_inject_record;
        uint32_t        zc_defer_destroy;
        uint32_t        zc_flags;
        uint64_t        zc_action_handle;
        int             zc_cleanup_fd;
        uint8_t         zc_simple;
-       boolean_t       zc_resumable;
-       uint8_t         zc_pad[2];              /* alignment */
+       uint8_t         zc_pad[3];              /* alignment */
        uint64_t        zc_sendobj;
        uint64_t        zc_fromobj;
        uint64_t        zc_createtxg;
index 6231cf0e47846232dbfc9813c3353c77159a695f..fdf9a115f1498a0bb2b859829336210bf0831930 100644 (file)
@@ -2960,24 +2960,31 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
     avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
     uint64_t *action_handlep, const char *finalsnap)
 {
-       zfs_cmd_t zc = {"\0"};
        time_t begin_time;
        int ioctl_err, ioctl_errno, err;
        char *cp;
        struct drr_begin *drrb = &drr->drr_u.drr_begin;
        char errbuf[1024];
-       char prop_errbuf[1024];
        const char *chopprefix;
        boolean_t newfs = B_FALSE;
        boolean_t stream_wantsnewfs;
+       boolean_t newprops = B_FALSE;
+       uint64_t read_bytes = 0;
+       uint64_t errflags = 0;
        uint64_t parent_snapguid = 0;
        prop_changelist_t *clp = NULL;
        nvlist_t *snapprops_nvlist = NULL;
        zprop_errflags_t prop_errflags;
+       nvlist_t *prop_errors = NULL;
        boolean_t recursive;
        char *snapname = NULL;
+       char destsnap[MAXPATHLEN * 2];
+       char origin[MAXNAMELEN];
+       char name[MAXPATHLEN];
+       nvlist_t *props = NULL;
 
        begin_time = time(NULL);
+       bzero(origin, MAXNAMELEN);
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot receive"));
@@ -2988,24 +2995,19 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        if (stream_avl != 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;
+               }
 
                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 (ret != 0)
-                       return (-1);
        }
 
        cp = NULL;
@@ -3026,7 +3028,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, '/');
@@ -3053,7 +3056,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, '/');
@@ -3071,7 +3075,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);
        }
@@ -3083,35 +3088,35 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
            chopprefix[0] == '\0');
 
        /*
-        * Determine name of destination snapshot, store in zc_value.
+        * Determine name of destination snapshot.
         */
-       (void) strcpy(zc.zc_value, tosnap);
-       (void) strlcat(zc.zc_value, chopprefix, sizeof (zc.zc_value));
+       (void) strcpy(destsnap, tosnap);
+       (void) strlcat(destsnap, chopprefix, sizeof (destsnap));
        free(cp);
-       if (!zfs_name_valid(zc.zc_value, ZFS_TYPE_SNAPSHOT)) {
-               zcmd_free_nvlists(&zc);
-               return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+       if (!zfs_name_valid(destsnap, ZFS_TYPE_SNAPSHOT)) {
+               err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+               goto out;
        }
 
        /*
-        * Determine the name of the origin snapshot, store in zc_string.
+        * Determine the name of the origin snapshot.
         */
        if (drrb->drr_flags & DRR_FLAG_CLONE) {
-               if (guid_to_name(hdl, zc.zc_value,
-                   drrb->drr_fromguid, B_FALSE, zc.zc_string) != 0) {
-                       zcmd_free_nvlists(&zc);
+               if (guid_to_name(hdl, destsnap,
+                   drrb->drr_fromguid, B_FALSE, origin) != 0) {
                        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));
+                           destsnap);
+                       err = zfs_error(hdl, EZFS_NOENT, errbuf);
+                       goto out;
                }
                if (flags->verbose)
-                       (void) printf("found clone origin %s\n", zc.zc_string);
+                       (void) printf("found clone origin %s\n", origin);
        } else if (originsnap) {
-               (void) strncpy(zc.zc_string, originsnap, ZFS_MAXNAMELEN);
+               (void) strncpy(origin, originsnap, ZFS_MAXNAMELEN);
                if (flags->verbose)
                        (void) printf("using provided clone origin %s\n",
-                           zc.zc_string);
+                           origin);
        }
 
        boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
@@ -3127,18 +3132,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot receive new filesystem stream"));
 
-               (void) strcpy(zc.zc_name, zc.zc_value);
-               cp = strrchr(zc.zc_name, '/');
+               (void) strcpy(name, destsnap);
+               cp = strrchr(name, '/');
                if (cp)
                        *cp = '\0';
                if (cp &&
-                   !zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
+                   !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
                        char suffix[ZFS_MAXNAMELEN];
-                       (void) strcpy(suffix, strrchr(zc.zc_value, '/'));
-                       if (guid_to_name(hdl, zc.zc_name, parent_snapguid,
-                           B_FALSE, zc.zc_value) == 0) {
-                               *strchr(zc.zc_value, '@') = '\0';
-                               (void) strcat(zc.zc_value, suffix);
+                       (void) strcpy(suffix, strrchr(destsnap, '/'));
+                       if (guid_to_name(hdl, name, parent_snapguid,
+                           B_FALSE, destsnap) == 0) {
+                               *strchr(destsnap, '@') = '\0';
+                               (void) strcat(destsnap, suffix);
                        }
                }
        } else {
@@ -3149,8 +3154,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot receive incremental stream"));
 
-               (void) strcpy(zc.zc_name, zc.zc_value);
-               *strchr(zc.zc_name, '@') = '\0';
+               (void) strcpy(name, destsnap);
+               *strchr(name, '@') = '\0';
 
                /*
                 * If the exact receive path was specified and this is the
@@ -3159,23 +3164,26 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 */
                if ((flags->isprefix || (*(chopprefix = drrb->drr_toname +
                    strlen(sendfs)) != '\0' && *chopprefix != '@')) &&
-                   !zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
+                   !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
                        char snap[ZFS_MAXNAMELEN];
-                       (void) strcpy(snap, strchr(zc.zc_value, '@'));
-                       if (guid_to_name(hdl, zc.zc_name, drrb->drr_fromguid,
-                           B_FALSE, zc.zc_value) == 0) {
-                               *strchr(zc.zc_value, '@') = '\0';
-                               (void) strcat(zc.zc_value, snap);
+                       (void) strcpy(snap, strchr(destsnap, '@'));
+                       if (guid_to_name(hdl, name, drrb->drr_fromguid,
+                           B_FALSE, destsnap) == 0) {
+                               *strchr(destsnap, '@') = '\0';
+                               (void) strcat(destsnap, snap);
                        }
                }
        }
 
-       (void) strcpy(zc.zc_name, zc.zc_value);
-       *strchr(zc.zc_name, '@') = '\0';
+       (void) strcpy(name, destsnap);
+       *strchr(name, '@') = '\0';
 
-       if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) {
+       if (zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
+               zfs_cmd_t zc = {"\0"};
                zfs_handle_t *zhp;
 
+               (void) strcpy(zc.zc_name, name);
+
                /*
                 * Destination fs exists.  It must be one of these cases:
                 *  - an incremental send stream
@@ -3186,39 +3194,37 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 */
                if (stream_wantsnewfs) {
                        if (!flags->force) {
-                               zcmd_free_nvlists(&zc);
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "destination '%s' exists\n"
-                                   "must specify -F to overwrite it"),
-                                   zc.zc_name);
-                               return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+                                   "must specify -F to overwrite it"), name);
+                               err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+                               goto out;
                        }
                        if (ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT,
                            &zc) == 0) {
-                               zcmd_free_nvlists(&zc);
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "destination has snapshots (eg. %s)\n"
                                    "must destroy them to overwrite it"),
-                                   zc.zc_name);
-                               return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+                                   name);
+                               err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+                               goto out;
                        }
                }
 
-               if ((zhp = zfs_open(hdl, zc.zc_name,
+               if ((zhp = zfs_open(hdl, name,
                    ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) {
-                       zcmd_free_nvlists(&zc);
-                       return (-1);
+                       err = -1;
+                       goto out;
                }
 
                if (stream_wantsnewfs &&
                    zhp->zfs_dmustats.dds_origin[0]) {
-                       zcmd_free_nvlists(&zc);
                        zfs_close(zhp);
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "destination '%s' is a clone\n"
-                           "must destroy it to overwrite it"),
-                           zc.zc_name);
-                       return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+                           "must destroy it to overwrite it"), name);
+                       err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+                       goto out;
                }
 
                if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
@@ -3227,14 +3233,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0);
                        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;
                        }
                }
 
@@ -3259,11 +3265,11 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 * contained no slash character).
                 */
                if (!stream_wantsnewfs ||
-                   (cp = strrchr(zc.zc_name, '/')) == NULL) {
-                       zcmd_free_nvlists(&zc);
+                   (cp = strrchr(name, '/')) == NULL) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                           "destination '%s' does not exist"), zc.zc_name);
-                       return (zfs_error(hdl, EZFS_NOENT, errbuf));
+                           "destination '%s' does not exist"), name);
+                       err = zfs_error(hdl, EZFS_NOENT, errbuf);
+                       goto out;
                }
 
                /*
@@ -3273,45 +3279,34 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                *cp = '\0';
 
                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));
+                   create_parents(hdl, destsnap, strlen(tosnap)) != 0) {
+                       err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+                       goto out;
                }
 
                newfs = B_TRUE;
        }
 
-       zc.zc_begin_record = *drr_noswap;
-       zc.zc_cookie = infd;
-       zc.zc_guid = flags->force;
-       zc.zc_resumable = flags->resumable;
        if (flags->verbose) {
                (void) printf("%s %s stream of %s into %s\n",
                    flags->dryrun ? "would receive" : "receiving",
                    drrb->drr_fromguid ? "incremental" : "full",
-                   drrb->drr_toname, zc.zc_value);
+                   drrb->drr_toname, destsnap);
                (void) fflush(stdout);
        }
 
        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;
-       zc.zc_nvlist_dst_size = sizeof (prop_errbuf);
-       zc.zc_cleanup_fd = cleanup_fd;
-       zc.zc_action_handle = *action_handlep;
-
-       err = ioctl_err = zfs_ioctl(hdl, ZFS_IOC_RECV, &zc);
-       ioctl_errno = errno;
-       prop_errflags = (zprop_errflags_t)zc.zc_obj;
+       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);
+       ioctl_errno = ioctl_err;
+       prop_errflags = errflags;
 
        if (err == 0) {
-               nvlist_t *prop_errors;
-               VERIFY(0 == nvlist_unpack((void *)(uintptr_t)zc.zc_nvlist_dst,
-                   zc.zc_nvlist_dst_size, &prop_errors, 0));
-
                nvpair_t *prop_err = NULL;
 
                while ((prop_err = nvlist_next_nvpair(prop_errors,
@@ -3344,25 +3339,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                                (void) snprintf(tbuf, sizeof (tbuf),
                                    dgettext(TEXT_DOMAIN,
                                    "cannot receive %s property on %s"),
-                                   nvpair_name(prop_err), zc.zc_name);
+                                   nvpair_name(prop_err), name);
                                zfs_setprop_error(hdl, prop, intval, tbuf);
                        }
                }
-               nvlist_free(prop_errors);
        }
 
-       zc.zc_nvlist_dst = 0;
-       zc.zc_nvlist_dst_size = 0;
-       zcmd_free_nvlists(&zc);
-
        if (err == 0 && snapprops_nvlist) {
-               zfs_cmd_t zc2 = {"\0"};
+               zfs_cmd_t zc = {"\0"};
 
-               (void) strcpy(zc2.zc_name, zc.zc_value);
-               zc2.zc_cookie = B_TRUE; /* received */
-               if (zcmd_write_src_nvlist(hdl, &zc2, snapprops_nvlist) == 0) {
-                       (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc2);
-                       zcmd_free_nvlists(&zc2);
+               (void) strcpy(zc.zc_name, destsnap);
+               zc.zc_cookie = B_TRUE; /* received */
+               if (zcmd_write_src_nvlist(hdl, &zc, snapprops_nvlist) == 0) {
+                       (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
+                       zcmd_free_nvlists(&zc);
                }
        }
 
@@ -3374,7 +3364,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 */
                avl_tree_t *local_avl;
                nvlist_t *local_nv, *fs;
-               cp = strchr(zc.zc_value, '@');
+               cp = strchr(destsnap, '@');
 
                /*
                 * XXX Do this faster by just iterating over snaps in
@@ -3382,7 +3372,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, destsnap, NULL, NULL, B_FALSE,
                    &local_nv, &local_avl) == 0) {
                        *cp = '@';
                        fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
@@ -3392,7 +3382,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        if (fs != NULL) {
                                if (flags->verbose) {
                                        (void) printf("snap %s already exists; "
-                                           "ignoring\n", zc.zc_value);
+                                           "ignoring\n", destsnap);
                                }
                                err = ioctl_err = recv_skip(hdl, infd,
                                    flags->byteswap);
@@ -3404,22 +3394,22 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        if (ioctl_err != 0) {
                switch (ioctl_errno) {
                case ENODEV:
-                       cp = strchr(zc.zc_value, '@');
+                       cp = strchr(destsnap, '@');
                        *cp = '\0';
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "most recent snapshot of %s does not\n"
-                           "match incremental source"), zc.zc_value);
+                           "match incremental source"), destsnap);
                        (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
                        *cp = '@';
                        break;
                case ETXTBSY:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "destination %s has been modified\n"
-                           "since most recent snapshot"), zc.zc_name);
+                           "since most recent snapshot"), name);
                        (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
                        break;
                case EEXIST:
-                       cp = strchr(zc.zc_value, '@');
+                       cp = strchr(destsnap, '@');
                        if (newfs) {
                                /* it's the containing fs that exists */
                                *cp = '\0';
@@ -3428,14 +3418,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                            "destination already exists"));
                        (void) zfs_error_fmt(hdl, EZFS_EXISTS,
                            dgettext(TEXT_DOMAIN, "cannot restore to %s"),
-                           zc.zc_value);
+                           destsnap);
                        *cp = '@';
                        break;
                case EINVAL:
+                       if (flags->resumable)
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "kernel modules must be upgraded to "
+                                   "receive this stream."));
                        (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
                        break;
                case ECKSUM:
-                       recv_ecksum_set_aux(hdl, zc.zc_value, flags->resumable);
+                       recv_ecksum_set_aux(hdl, destsnap, flags->resumable);
                        (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
                        break;
                case ENOTSUP:
@@ -3445,7 +3439,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        break;
                case EDQUOT:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                           "destination %s space quota exceeded"), zc.zc_name);
+                           "destination %s space quota exceeded"), name);
                        (void) zfs_error(hdl, EZFS_NOSPC, errbuf);
                        break;
                default:
@@ -3458,12 +3452,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
         * children of the target filesystem if we did a replication
         * receive (indicated by stream_avl being non-NULL).
         */
-       cp = strchr(zc.zc_value, '@');
+       cp = strchr(destsnap, '@');
        if (cp && (ioctl_err == 0 || !newfs)) {
                zfs_handle_t *h;
 
                *cp = '\0';
-               h = zfs_open(hdl, zc.zc_value,
+               h = zfs_open(hdl, destsnap,
                    ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
                if (h != NULL) {
                        if (h->zfs_type == ZFS_TYPE_VOLUME) {
@@ -3474,7 +3468,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                                 * for mounting and sharing later.
                                 */
                                if (top_zfs && *top_zfs == NULL)
-                                       *top_zfs = zfs_strdup(hdl, zc.zc_value);
+                                       *top_zfs = zfs_strdup(hdl, destsnap);
                        }
                        zfs_close(h);
                }
@@ -3488,26 +3482,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
 
        if (prop_errflags & ZPROP_ERR_NOCLEAR) {
                (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: "
-                   "failed to clear unreceived properties on %s"),
-                   zc.zc_name);
+                   "failed to clear unreceived properties on %s"), name);
                (void) fprintf(stderr, "\n");
        }
        if (prop_errflags & ZPROP_ERR_NORESTORE) {
                (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: "
-                   "failed to restore original properties on %s"),
-                   zc.zc_name);
+                   "failed to restore original properties on %s"), name);
                (void) fprintf(stderr, "\n");
        }
 
-       if (err || ioctl_err)
-               return (-1);
-
-       *action_handlep = zc.zc_action_handle;
+       if (err || ioctl_err) {
+               err = -1;
+               goto out;
+       }
 
        if (flags->verbose) {
                char buf1[64];
                char buf2[64];
-               uint64_t bytes = zc.zc_cookie;
+               uint64_t bytes = read_bytes;
                time_t delta = time(NULL) - begin_time;
                if (delta == 0)
                        delta = 1;
@@ -3518,7 +3510,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                    buf1, delta, buf2);
        }
 
-       return (0);
+       err = 0;
+out:
+       if (prop_errors != NULL)
+               nvlist_free(prop_errors);
+
+       if (newprops)
+               nvlist_free(props);
+
+       return (err);
 }
 
 static int
index ada46969d5ff66d94f659aefb06bf0671133e69a..e20e58dbae3fac051ed7788b284ea302c84496fb 100644 (file)
@@ -547,82 +547,168 @@ recv_read(int fd, void *buf, int ilen)
        return (0);
 }
 
+/*
+ * Linux adds ZFS_IOC_RECV_NEW for resumable streams and preserves the legacy
+ * ZFS_IOC_RECV user/kernel interface.  The new interface supports all stream
+ * options but is currently only used for resumable streams.  This way updated
+ * user space utilities will interoperate with older kernel modules.
+ *
+ * Non-Linux OpenZFS platforms have opted to modify the legacy interface.
+ */
 static int
 recv_impl(const char *snapname, nvlist_t *props, const char *origin,
-    boolean_t force, boolean_t resumable, int fd,
-    const dmu_replay_record_t *begin_record)
+    boolean_t force, boolean_t resumable, int input_fd,
+    const dmu_replay_record_t *begin_record, int cleanup_fd,
+    uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+    nvlist_t **errors)
 {
-       /*
-        * The receive ioctl is still legacy, so we need to construct our own
-        * zfs_cmd_t rather than using zfsc_ioctl().
-        */
-       zfs_cmd_t zc = {"\0"};
+       dmu_replay_record_t drr;
+       char fsname[MAXPATHLEN];
        char *atp;
-       char *packed = NULL;
-       size_t size;
        int error;
 
-       ASSERT3S(g_refcount, >, 0);
-
-       /* zc_name is name of containing filesystem */
-       (void) strlcpy(zc.zc_name, snapname, sizeof (zc.zc_name));
-       atp = strchr(zc.zc_name, '@');
+       /* Set 'fsname' to the name of containing filesystem */
+       (void) strlcpy(fsname, snapname, sizeof (fsname));
+       atp = strchr(fsname, '@');
        if (atp == NULL)
                return (EINVAL);
        *atp = '\0';
 
-       /* if the fs does not exist, try its parent. */
-       if (!lzc_exists(zc.zc_name)) {
-               char *slashp = strrchr(zc.zc_name, '/');
+       /* If the fs does not exist, try its parent. */
+       if (!lzc_exists(fsname)) {
+               char *slashp = strrchr(fsname, '/');
                if (slashp == NULL)
                        return (ENOENT);
                *slashp = '\0';
+       }
 
+       /*
+        * The begin_record is normally a non-byteswapped BEGIN record.
+        * For resumable streams it may be set to any non-byteswapped
+        * dmu_replay_record_t.
+        */
+       if (begin_record == NULL) {
+               error = recv_read(input_fd, &drr, sizeof (drr));
+               if (error != 0)
+                       return (error);
+       } else {
+               drr = *begin_record;
        }
 
-       /* zc_value is full name of the snapshot to create */
-       (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+       if (resumable) {
+               nvlist_t *outnvl = NULL;
+               nvlist_t *innvl = fnvlist_alloc();
 
-       if (props != NULL) {
-               /* zc_nvlist_src is props to set */
-               packed = fnvlist_pack(props, &size);
-               zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
-               zc.zc_nvlist_src_size = size;
-       }
+               fnvlist_add_string(innvl, "snapname", snapname);
 
-       /* zc_string is name of clone origin (if DRR_FLAG_CLONE) */
-       if (origin != NULL)
-               (void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string));
+               if (props != NULL)
+                       fnvlist_add_nvlist(innvl, "props", props);
 
-       /* zc_begin_record is non-byteswapped BEGIN record */
-       if (begin_record == NULL) {
-               error = recv_read(fd, &zc.zc_begin_record,
-                   sizeof (zc.zc_begin_record));
-               if (error != 0)
-                       goto out;
+               if (origin != NULL && strlen(origin))
+                       fnvlist_add_string(innvl, "origin", origin);
+
+               fnvlist_add_byte_array(innvl, "begin_record",
+                   (uchar_t *) &drr, sizeof (drr));
+
+               fnvlist_add_int32(innvl, "input_fd", input_fd);
+
+               if (force)
+                       fnvlist_add_boolean(innvl, "force");
+
+               if (resumable)
+                       fnvlist_add_boolean(innvl, "resumable");
+
+               if (cleanup_fd >= 0)
+                       fnvlist_add_int32(innvl, "cleanup_fd", cleanup_fd);
+
+               if (action_handle != NULL)
+                       fnvlist_add_uint64(innvl, "action_handle",
+                           *action_handle);
+
+               error = lzc_ioctl(ZFS_IOC_RECV_NEW, fsname, innvl, &outnvl);
+
+               if (error == 0 && read_bytes != NULL)
+                       error = nvlist_lookup_uint64(outnvl, "read_bytes",
+                           read_bytes);
+
+               if (error == 0 && errflags != NULL)
+                       error = nvlist_lookup_uint64(outnvl, "error_flags",
+                           errflags);
+
+               if (error == 0 && action_handle != NULL)
+                       error = nvlist_lookup_uint64(outnvl, "action_handle",
+                           action_handle);
+
+               if (error == 0 && errors != NULL) {
+                       nvlist_t *nvl;
+                       error = nvlist_lookup_nvlist(outnvl, "errors", &nvl);
+                       if (error == 0)
+                               *errors = fnvlist_dup(nvl);
+               }
+
+               fnvlist_free(innvl);
+               fnvlist_free(outnvl);
        } else {
-               zc.zc_begin_record = *begin_record;
-       }
+               zfs_cmd_t zc = {"\0"};
+               char *packed = NULL;
+               size_t size;
 
-       /* zc_cookie is fd to read from */
-       zc.zc_cookie = fd;
+               ASSERT3S(g_refcount, >, 0);
 
-       /* zc guid is force flag */
-       zc.zc_guid = force;
+               (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_value));
+               (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
 
-       zc.zc_resumable = resumable;
+               if (props != NULL) {
+                       packed = fnvlist_pack(props, &size);
+                       zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
+                       zc.zc_nvlist_src_size = size;
+               }
 
-       /* zc_cleanup_fd is unused */
-       zc.zc_cleanup_fd = -1;
+               if (origin != NULL)
+                       (void) strlcpy(zc.zc_string, origin,
+                           sizeof (zc.zc_string));
 
-       error = ioctl(g_fd, ZFS_IOC_RECV, &zc);
-       if (error != 0)
-               error = errno;
+               ASSERT3S(drr.drr_type, ==, DRR_BEGIN);
+               zc.zc_begin_record = drr.drr_u.drr_begin;
+               zc.zc_guid = force;
+               zc.zc_cookie = input_fd;
+               zc.zc_cleanup_fd = -1;
+               zc.zc_action_handle = 0;
+
+               if (cleanup_fd >= 0)
+                       zc.zc_cleanup_fd = cleanup_fd;
+
+               if (action_handle != NULL)
+                       zc.zc_action_handle = *action_handle;
+
+               zc.zc_nvlist_dst_size = 128 * 1024;
+               zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
+                   malloc(zc.zc_nvlist_dst_size);
+
+               error = ioctl(g_fd, ZFS_IOC_RECV, &zc);
+               if (error != 0) {
+                       error = errno;
+               } else {
+                       if (read_bytes != NULL)
+                               *read_bytes = zc.zc_cookie;
+
+                       if (errflags != NULL)
+                               *errflags = zc.zc_obj;
+
+                       if (action_handle != NULL)
+                               *action_handle = zc.zc_action_handle;
+
+                       if (errors != NULL)
+                               VERIFY0(nvlist_unpack(
+                                   (void *)(uintptr_t)zc.zc_nvlist_dst,
+                                   zc.zc_nvlist_dst_size, errors, KM_SLEEP));
+               }
+
+               if (packed != NULL)
+                       fnvlist_pack_free(packed, size);
+               free((void *)(uintptr_t)zc.zc_nvlist_dst);
+       }
 
-out:
-       if (packed != NULL)
-               fnvlist_pack_free(packed, size);
-       free((void*)(uintptr_t)zc.zc_nvlist_dst);
        return (error);
 }
 
@@ -643,7 +729,8 @@ int
 lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
     boolean_t force, int fd)
 {
-       return (recv_impl(snapname, props, origin, force, B_FALSE, fd, NULL));
+       return (recv_impl(snapname, props, origin, force, B_FALSE, fd,
+           NULL, -1, NULL, NULL, NULL, NULL));
 }
 
 /*
@@ -656,7 +743,8 @@ int
 lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
     boolean_t force, int fd)
 {
-       return (recv_impl(snapname, props, origin, force, B_TRUE, fd, NULL));
+       return (recv_impl(snapname, props, origin, force, B_TRUE, fd,
+           NULL, -1, NULL, NULL, NULL, NULL));
 }
 
 /*
@@ -678,7 +766,38 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props,
        if (begin_record == NULL)
                return (EINVAL);
        return (recv_impl(snapname, props, origin, force, resumable, fd,
-           begin_record));
+           begin_record, -1, NULL, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but allows the caller to pass all supported arguments
+ * and retrieve all values returned.  The only additional input parameter
+ * is 'cleanup_fd' which is used to set a cleanup-on-exit file descriptor.
+ *
+ * The following parameters all provide return values.  Several may be set
+ * in the failure case and will contain additional information.
+ *
+ * The 'read_bytes' value will be set to the total number of bytes read.
+ *
+ * The 'errflags' value will contain zprop_errflags_t flags which are
+ * used to describe any failures.
+ *
+ * The 'action_handle' is used to pass the handle for this guid/ds mapping.
+ * It should be set to zero on first call and will contain an updated handle
+ * on success, it should be passed in subsequent calls.
+ *
+ * The 'errors' nvlist contains an entry for each unapplied received
+ * property.  Callers are responsible for freeing this nvlist.
+ */
+int lzc_receive_one(const char *snapname, nvlist_t *props,
+    const char *origin, boolean_t force, boolean_t resumable, int input_fd,
+    const dmu_replay_record_t *begin_record, int cleanup_fd,
+    uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+    nvlist_t **errors)
+{
+       return (recv_impl(snapname, props, origin, force, resumable,
+           input_fd, begin_record, cleanup_fd, read_bytes, errflags,
+           action_handle, errors));
 }
 
 /*
index dd3ce09e344266b0b1a26c35f27728b603db175c..96f28220105e342091c9dc80e734cf10fea1c993 100644 (file)
@@ -945,6 +945,13 @@ zfs_secpolicy_recv(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
            ZFS_DELEG_PERM_CREATE, cr));
 }
 
+/* ARGSUSED */
+static int
+zfs_secpolicy_recv_new(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
+{
+       return (zfs_secpolicy_recv(zc, innvl, cr));
+}
+
 int
 zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr)
 {
@@ -4046,74 +4053,38 @@ static boolean_t zfs_ioc_recv_inject_err;
 #endif
 
 /*
- * inputs:
- * zc_name             name of containing filesystem
- * zc_nvlist_src{_size}        nvlist of properties to apply
- * zc_value            name of snapshot to create
- * zc_string           name of clone origin (if DRR_FLAG_CLONE)
- * zc_cookie           file descriptor to recv from
- * zc_begin_record     the BEGIN record of the stream (not byteswapped)
- * zc_guid             force flag
- * zc_cleanup_fd       cleanup-on-exit file descriptor
- * zc_action_handle    handle for this guid/ds mapping (or zero on first call)
- * zc_resumable                if data is incomplete assume sender will resume
- *
- * outputs:
- * zc_cookie           number of bytes read
- * zc_nvlist_dst{_size} error for each unapplied received property
- * zc_obj              zprop_errflags_t
- * zc_action_handle    handle for this guid/ds mapping
+ * On failure the 'errors' nvlist may be allocated and will contain a
+ * descriptions of the failures.  It's the callers responsibilty to free.
  */
 static int
-zfs_ioc_recv(zfs_cmd_t *zc)
+zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin,
+    nvlist_t *props, boolean_t force, boolean_t resumable, int input_fd,
+    dmu_replay_record_t *begin_record, int cleanup_fd, uint64_t *read_bytes,
+    uint64_t *errflags, uint64_t *action_handle, nvlist_t **errors)
 {
-       file_t *fp;
        dmu_recv_cookie_t drc;
-       boolean_t force = (boolean_t)zc->zc_guid;
-       int fd;
        int error = 0;
        int props_error = 0;
-       nvlist_t *errors;
        offset_t off;
-       nvlist_t *props = NULL; /* sent properties */
-       nvlist_t *origprops = NULL; /* existing properties */
        nvlist_t *delayprops = NULL; /* sent properties applied post-receive */
-       char *origin = NULL;
-       char *tosnap;
-       char tofs[ZFS_MAXNAMELEN];
+       nvlist_t *origprops = NULL; /* existing properties */
        boolean_t first_recvd_props = B_FALSE;
+       file_t *input_fp;
 
-       if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0 ||
-           strchr(zc->zc_value, '@') == NULL ||
-           strchr(zc->zc_value, '%'))
-               return (SET_ERROR(EINVAL));
-
-       (void) strcpy(tofs, zc->zc_value);
-       tosnap = strchr(tofs, '@');
-       *tosnap++ = '\0';
-
-       if (zc->zc_nvlist_src != 0 &&
-           (error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
-           zc->zc_iflags, &props)) != 0)
-               return (error);
-
-       fd = zc->zc_cookie;
-       fp = getf(fd);
-       if (fp == NULL) {
-               nvlist_free(props);
+       *errors = NULL;
+       input_fp = getf(input_fd);
+       if (input_fp == NULL)
                return (SET_ERROR(EBADF));
-       }
-
-       errors = fnvlist_alloc();
-
-       if (zc->zc_string[0])
-               origin = zc->zc_string;
 
        error = dmu_recv_begin(tofs, tosnap,
-           &zc->zc_begin_record, force, zc->zc_resumable, origin, &drc);
+           begin_record, force, resumable, origin, &drc);
        if (error != 0)
                goto out;
 
+       *read_bytes = 0;
+       *errflags = 0;
+       *errors = fnvlist_alloc();
+
        /*
         * Set properties before we receive the stream so that they are applied
         * to the new data. Note that we must call dmu_recv_stream() if
@@ -4143,14 +4114,14 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                        if (!first_recvd_props)
                                props_reduce(props, origprops);
                        if (zfs_check_clearable(tofs, origprops, &errlist) != 0)
-                               (void) nvlist_merge(errors, errlist, 0);
+                               (void) nvlist_merge(*errors, errlist, 0);
                        nvlist_free(errlist);
 
                        if (clear_received_props(tofs, origprops,
                            first_recvd_props ? NULL : props) != 0)
-                               zc->zc_obj |= ZPROP_ERR_NOCLEAR;
+                               *errflags |= ZPROP_ERR_NOCLEAR;
                } else {
-                       zc->zc_obj |= ZPROP_ERR_NOCLEAR;
+                       *errflags |= ZPROP_ERR_NOCLEAR;
                }
        }
 
@@ -4160,13 +4131,13 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                if (props_error == 0) {
                        delayprops = extract_delay_props(props);
                        (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
-                           props, errors);
+                           props, *errors);
                }
        }
 
-       off = fp->f_offset;
-       error = dmu_recv_stream(&drc, fp->f_vnode, &off, zc->zc_cleanup_fd,
-           &zc->zc_action_handle);
+       off = input_fp->f_offset;
+       error = dmu_recv_stream(&drc, input_fp->f_vnode, &off, cleanup_fd,
+           action_handle);
 
        if (error == 0) {
                zfs_sb_t *zsb = NULL;
@@ -4192,7 +4163,7 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                /* Set delayed properties now, after we're done receiving. */
                if (delayprops != NULL && error == 0) {
                        (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
-                           delayprops, errors);
+                           delayprops, *errors);
                }
        }
 
@@ -4210,23 +4181,10 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                nvlist_free(delayprops);
        }
 
-       /*
-        * Now that all props, initial and delayed, are set, report the prop
-        * errors to the caller.
-        */
-       if (zc->zc_nvlist_dst_size != 0 &&
-           (nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 ||
-           put_nvlist(zc, errors) != 0)) {
-               /*
-                * Caller made zc->zc_nvlist_dst less than the minimum expected
-                * size or supplied an invalid address.
-                */
-               props_error = SET_ERROR(EINVAL);
-       }
 
-       zc->zc_cookie = off - fp->f_offset;
-       if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
-               fp->f_offset = off;
+       *read_bytes = off - input_fp->f_offset;
+       if (VOP_SEEK(input_fp->f_vnode, input_fp->f_offset, &off, NULL) == 0)
+           input_fp->f_offset = off;
 
 #ifdef DEBUG
        if (zfs_ioc_recv_inject_err) {
@@ -4245,14 +4203,14 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                         * Since we may have left a $recvd value on the
                         * system, we can't clear the $hasrecvd flag.
                         */
-                       zc->zc_obj |= ZPROP_ERR_NORESTORE;
+                       *errflags |= ZPROP_ERR_NORESTORE;
                } else if (first_recvd_props) {
                        dsl_prop_unset_hasrecvd(tofs);
                }
 
                if (origprops == NULL && !drc.drc_newfs) {
                        /* We failed to stash the original properties. */
-                       zc->zc_obj |= ZPROP_ERR_NORESTORE;
+                       *errflags |= ZPROP_ERR_NORESTORE;
                }
 
                /*
@@ -4269,14 +4227,12 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                         * We stashed the original properties but failed to
                         * restore them.
                         */
-                       zc->zc_obj |= ZPROP_ERR_NORESTORE;
+                       *errflags |= ZPROP_ERR_NORESTORE;
                }
        }
 out:
-       nvlist_free(props);
+       releasef(input_fd);
        nvlist_free(origprops);
-       nvlist_free(errors);
-       releasef(fd);
 
        if (error == 0)
                error = props_error;
@@ -4284,6 +4240,176 @@ out:
        return (error);
 }
 
+/*
+ * inputs:
+ * zc_name             name of containing filesystem (unused)
+ * zc_nvlist_src{_size}        nvlist of properties to apply
+ * zc_value            name of snapshot to create
+ * zc_string           name of clone origin (if DRR_FLAG_CLONE)
+ * zc_cookie           file descriptor to recv from
+ * zc_begin_record     the BEGIN record of the stream (not byteswapped)
+ * zc_guid             force flag
+ * zc_cleanup_fd       cleanup-on-exit file descriptor
+ * zc_action_handle    handle for this guid/ds mapping (or zero on first call)
+ *
+ * outputs:
+ * zc_cookie           number of bytes read
+ * zc_obj              zprop_errflags_t
+ * zc_action_handle    handle for this guid/ds mapping
+ * zc_nvlist_dst{_size} error for each unapplied received property
+ */
+static int
+zfs_ioc_recv(zfs_cmd_t *zc)
+{
+       dmu_replay_record_t begin_record;
+       nvlist_t *errors = NULL;
+       nvlist_t *props = NULL;
+       char *origin = NULL;
+       char *tosnap;
+       char tofs[ZFS_MAXNAMELEN];
+       int error = 0;
+
+       if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0 ||
+           strchr(zc->zc_value, '@') == NULL ||
+           strchr(zc->zc_value, '%'))
+               return (SET_ERROR(EINVAL));
+
+       (void) strcpy(tofs, zc->zc_value);
+       tosnap = strchr(tofs, '@');
+       *tosnap++ = '\0';
+
+       if (zc->zc_nvlist_src != 0 &&
+           (error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
+           zc->zc_iflags, &props)) != 0)
+               return (error);
+
+       if (zc->zc_string[0])
+               origin = zc->zc_string;
+
+       begin_record.drr_type = DRR_BEGIN;
+       begin_record.drr_payloadlen = 0;
+       begin_record.drr_u.drr_begin = zc->zc_begin_record;
+
+       error = zfs_ioc_recv_impl(tofs, tosnap, origin, props, zc->zc_guid,
+           B_FALSE, zc->zc_cookie, &begin_record, zc->zc_cleanup_fd,
+           &zc->zc_cookie, &zc->zc_obj, &zc->zc_action_handle, &errors);
+       nvlist_free(props);
+
+       /*
+        * Now that all props, initial and delayed, are set, report the prop
+        * errors to the caller.
+        */
+       if (zc->zc_nvlist_dst_size != 0 && errors != NULL &&
+           (nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 ||
+           put_nvlist(zc, errors) != 0)) {
+               /*
+                * Caller made zc->zc_nvlist_dst less than the minimum expected
+                * size or supplied an invalid address.
+                */
+               error = SET_ERROR(EINVAL);
+       }
+
+       nvlist_free(errors);
+
+       return (error);
+}
+
+/*
+ * innvl: {
+ *     "snapname" -> full name of the snapshot to create
+ *     (optional) "props" -> properties to set (nvlist)
+ *     (optional) "origin" -> name of clone origin (DRR_FLAG_CLONE)
+ *     "begin_record" -> non-byteswapped dmu_replay_record_t
+ *     "input_fd" -> file descriptor to read stream from (int32)
+ *     (optional) "force" -> force flag (value ignored)
+ *     (optional) "resumable" -> resumable flag (value ignored)
+ *     (optional) "cleanup_fd" -> cleanup-on-exit file descriptor
+ *     (optional) "action_handle" -> handle for this guid/ds mapping
+ * }
+ *
+ * outnvl: {
+ *     "read_bytes" -> number of bytes read
+ *     "error_flags" -> zprop_errflags_t
+ *     "action_handle" -> handle for this guid/ds mapping
+ *     "errors" -> error for each unapplied received property (nvlist)
+ * }
+ */
+static int
+zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl)
+{
+       dmu_replay_record_t *begin_record;
+       uint_t begin_record_size;
+       nvlist_t *errors = NULL;
+       nvlist_t *props = NULL;
+       char *snapname = NULL;
+       char *origin = NULL;
+       char *tosnap;
+       char tofs[ZFS_MAXNAMELEN];
+       boolean_t force;
+       boolean_t resumable;
+       uint64_t action_handle = 0;
+       uint64_t read_bytes = 0;
+       uint64_t errflags = 0;
+       int input_fd = -1;
+       int cleanup_fd = -1;
+       int error;
+
+       error = nvlist_lookup_string(innvl, "snapname", &snapname);
+       if (error != 0)
+               return (SET_ERROR(EINVAL));
+
+       if (dataset_namecheck(snapname, NULL, NULL) != 0 ||
+           strchr(snapname, '@') == NULL ||
+           strchr(snapname, '%'))
+               return (SET_ERROR(EINVAL));
+
+       (void) strcpy(tofs, snapname);
+       tosnap = strchr(tofs, '@');
+       *tosnap++ = '\0';
+
+       error = nvlist_lookup_string(innvl, "origin", &origin);
+       if (error && error != ENOENT)
+               return (error);
+
+       error = nvlist_lookup_byte_array(innvl, "begin_record",
+           (uchar_t **) &begin_record, &begin_record_size);
+       if (error != 0 || begin_record_size != sizeof (*begin_record))
+               return (SET_ERROR(EINVAL));
+
+       error = nvlist_lookup_int32(innvl, "input_fd", &input_fd);
+       if (error != 0)
+               return (SET_ERROR(EINVAL));
+
+       force = nvlist_exists(innvl, "force");
+       resumable = nvlist_exists(innvl, "resumable");
+
+       error = nvlist_lookup_int32(innvl, "cleanup_fd", &cleanup_fd);
+       if (error && error != ENOENT)
+               return (error);
+
+       error = nvlist_lookup_uint64(innvl, "action_handle", &action_handle);
+       if (error && error != ENOENT)
+               return (error);
+
+       error = nvlist_lookup_nvlist(innvl, "props", &props);
+       if (error && error != ENOENT)
+               return (error);
+
+       error = zfs_ioc_recv_impl(tofs, tosnap, origin, props, force,
+           resumable, input_fd, begin_record, cleanup_fd, &read_bytes,
+           &errflags, &action_handle, &errors);
+
+       fnvlist_add_uint64(outnvl, "read_bytes", read_bytes);
+       fnvlist_add_uint64(outnvl, "error_flags", errflags);
+       fnvlist_add_uint64(outnvl, "action_handle", action_handle);
+       fnvlist_add_nvlist(outnvl, "errors", errors);
+
+       nvlist_free(errors);
+       nvlist_free(props);
+
+       return (error);
+}
+
 /*
  * inputs:
  * zc_name     name of snapshot to send
@@ -5555,6 +5681,10 @@ zfs_ioctl_init(void)
            POOL_NAME,
            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE);
 
+       zfs_ioctl_register("receive", ZFS_IOC_RECV_NEW,
+           zfs_ioc_recv_new, zfs_secpolicy_recv_new, DATASET_NAME,
+           POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE);
+
        /* IOCTLS that use the legacy function signature */
 
        zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,