diff options
author | Brian Behlendorf <[email protected]> | 2016-06-09 17:04:12 -0700 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2016-06-28 13:47:03 -0700 |
commit | 43e52eddb13d8accbd052fac9a242ce979531aa4 (patch) | |
tree | 35a4f1d573f6255e13ad1e36ed1c72827c376849 | |
parent | 8c62a0d0f39c450d1fdb001b8073193f89611f56 (diff) |
Implement zfs_ioc_recv_new() for OpenZFS 2605
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 <[email protected]>
-rw-r--r-- | include/libzfs_core.h | 3 | ||||
-rw-r--r-- | include/sys/fs/zfs.h | 4 | ||||
-rw-r--r-- | include/sys/zfs_ioctl.h | 5 | ||||
-rw-r--r-- | lib/libzfs/libzfs_sendrecv.c | 252 | ||||
-rw-r--r-- | lib/libzfs_core/libzfs_core.c | 225 | ||||
-rw-r--r-- | module/zfs/zfs_ioctl.c | 294 |
6 files changed, 518 insertions, 265 deletions
diff --git a/include/libzfs_core.h b/include/libzfs_core.h index d2d79175f..9e761004a 100644 --- a/include/libzfs_core.h +++ b/include/libzfs_core.h @@ -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 *); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 8a581eee5..e802454b9 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -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. diff --git a/include/sys/zfs_ioctl.h b/include/sys/zfs_ioctl.h index 7b751fea6..5157c6704 100644 --- a/include/sys/zfs_ioctl.h +++ b/include/sys/zfs_ioctl.h @@ -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; diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 6231cf0e4..fdf9a115f 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -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 diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c index ada46969d..e20e58dba 100644 --- a/lib/libzfs_core/libzfs_core.c +++ b/lib/libzfs_core/libzfs_core.c @@ -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)); } /* diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index dd3ce09e3..96f282201 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -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; @@ -4286,6 +4242,176 @@ out: /* * 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 * zc_cookie file descriptor to send stream to * zc_obj fromorigin flag (mutually exclusive with zc_fromobj) @@ -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, |