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 <behlendorf1@llnl.gov>
This commit is contained in:
Brian Behlendorf 2016-06-09 17:04:12 -07:00
parent 8c62a0d0f3
commit 43e52eddb1
6 changed files with 519 additions and 266 deletions

View File

@ -69,6 +69,9 @@ int lzc_receive_resumable(const char *, nvlist_t *, const char *,
boolean_t, int); boolean_t, int);
int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t, int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t,
boolean_t, int, const struct dmu_replay_record *); 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 *); boolean_t lzc_exists(const char *);

View File

@ -232,6 +232,7 @@ typedef enum {
#define ZPROP_SOURCE_VAL_RECVD "$recvd" #define ZPROP_SOURCE_VAL_RECVD "$recvd"
#define ZPROP_N_MORE_ERRORS "N_MORE_ERRORS" #define ZPROP_N_MORE_ERRORS "N_MORE_ERRORS"
/* /*
* Dataset flag implemented as a special entry in the props zap object * Dataset flag implemented as a special entry in the props zap object
* indicating that the dataset has received properties on or after * indicating that the dataset has received properties on or after
@ -923,7 +924,7 @@ typedef struct ddt_histogram {
*/ */
typedef enum zfs_ioc { typedef enum zfs_ioc {
/* /*
* Illumos - 70/128 numbers reserved. * Illumos - 71/128 numbers reserved.
*/ */
ZFS_IOC_FIRST = ('Z' << 8), ZFS_IOC_FIRST = ('Z' << 8),
ZFS_IOC = ZFS_IOC_FIRST, ZFS_IOC = ZFS_IOC_FIRST,
@ -997,6 +998,7 @@ typedef enum zfs_ioc {
ZFS_IOC_BOOKMARK, ZFS_IOC_BOOKMARK,
ZFS_IOC_GET_BOOKMARKS, ZFS_IOC_GET_BOOKMARKS,
ZFS_IOC_DESTROY_BOOKMARKS, ZFS_IOC_DESTROY_BOOKMARKS,
ZFS_IOC_RECV_NEW,
/* /*
* Linux - 3/64 numbers reserved. * Linux - 3/64 numbers reserved.

View File

@ -389,15 +389,14 @@ typedef struct zfs_cmd {
uint64_t zc_iflags; /* internal to zfs(7fs) */ uint64_t zc_iflags; /* internal to zfs(7fs) */
zfs_share_t zc_share; zfs_share_t zc_share;
dmu_objset_stats_t zc_objset_stats; 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; zinject_record_t zc_inject_record;
uint32_t zc_defer_destroy; uint32_t zc_defer_destroy;
uint32_t zc_flags; uint32_t zc_flags;
uint64_t zc_action_handle; uint64_t zc_action_handle;
int zc_cleanup_fd; int zc_cleanup_fd;
uint8_t zc_simple; uint8_t zc_simple;
boolean_t zc_resumable; uint8_t zc_pad[3]; /* alignment */
uint8_t zc_pad[2]; /* alignment */
uint64_t zc_sendobj; uint64_t zc_sendobj;
uint64_t zc_fromobj; uint64_t zc_fromobj;
uint64_t zc_createtxg; uint64_t zc_createtxg;

View 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, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
uint64_t *action_handlep, const char *finalsnap) uint64_t *action_handlep, const char *finalsnap)
{ {
zfs_cmd_t zc = {"\0"};
time_t begin_time; time_t begin_time;
int ioctl_err, ioctl_errno, err; int ioctl_err, ioctl_errno, err;
char *cp; char *cp;
struct drr_begin *drrb = &drr->drr_u.drr_begin; struct drr_begin *drrb = &drr->drr_u.drr_begin;
char errbuf[1024]; char errbuf[1024];
char prop_errbuf[1024];
const char *chopprefix; const char *chopprefix;
boolean_t newfs = B_FALSE; boolean_t newfs = B_FALSE;
boolean_t stream_wantsnewfs; boolean_t stream_wantsnewfs;
boolean_t newprops = B_FALSE;
uint64_t read_bytes = 0;
uint64_t errflags = 0;
uint64_t parent_snapguid = 0; uint64_t parent_snapguid = 0;
prop_changelist_t *clp = NULL; prop_changelist_t *clp = NULL;
nvlist_t *snapprops_nvlist = NULL; nvlist_t *snapprops_nvlist = NULL;
zprop_errflags_t prop_errflags; zprop_errflags_t prop_errflags;
nvlist_t *prop_errors = NULL;
boolean_t recursive; boolean_t recursive;
char *snapname = NULL; char *snapname = NULL;
char destsnap[MAXPATHLEN * 2];
char origin[MAXNAMELEN];
char name[MAXPATHLEN];
nvlist_t *props = NULL;
begin_time = time(NULL); begin_time = time(NULL);
bzero(origin, MAXNAMELEN);
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive")); "cannot receive"));
@ -2988,24 +2995,19 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (stream_avl != NULL) { if (stream_avl != NULL) {
nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid, nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid,
&snapname); &snapname);
nvlist_t *props;
int ret;
(void) nvlist_lookup_uint64(fs, "parentfromsnap", (void) nvlist_lookup_uint64(fs, "parentfromsnap",
&parent_snapguid); &parent_snapguid);
err = nvlist_lookup_nvlist(fs, "props", &props); err = nvlist_lookup_nvlist(fs, "props", &props);
if (err) if (err) {
VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0)); VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0));
newprops = B_TRUE;
}
if (flags->canmountoff) { if (flags->canmountoff) {
VERIFY(0 == nvlist_add_uint64(props, VERIFY(0 == nvlist_add_uint64(props,
zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0)); 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; cp = NULL;
@ -3026,7 +3028,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (strchr(tosnap, '@')) { if (strchr(tosnap, '@')) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
"argument - snapshot not allowed with -e")); "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, '/'); chopprefix = strrchr(sendfs, '/');
@ -3053,7 +3056,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (strchr(tosnap, '@')) { if (strchr(tosnap, '@')) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
"argument - snapshot not allowed with -d")); "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, '/'); 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, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"cannot specify snapshot name for multi-snapshot " "cannot specify snapshot name for multi-snapshot "
"stream")); "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); 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'); chopprefix[0] == '\0');
/* /*
* Determine name of destination snapshot, store in zc_value. * Determine name of destination snapshot.
*/ */
(void) strcpy(zc.zc_value, tosnap); (void) strcpy(destsnap, tosnap);
(void) strlcat(zc.zc_value, chopprefix, sizeof (zc.zc_value)); (void) strlcat(destsnap, chopprefix, sizeof (destsnap));
free(cp); free(cp);
if (!zfs_name_valid(zc.zc_value, ZFS_TYPE_SNAPSHOT)) { if (!zfs_name_valid(destsnap, ZFS_TYPE_SNAPSHOT)) {
zcmd_free_nvlists(&zc); err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
return (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 (drrb->drr_flags & DRR_FLAG_CLONE) {
if (guid_to_name(hdl, zc.zc_value, if (guid_to_name(hdl, destsnap,
drrb->drr_fromguid, B_FALSE, zc.zc_string) != 0) { drrb->drr_fromguid, B_FALSE, origin) != 0) {
zcmd_free_nvlists(&zc);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"local origin for clone %s does not exist"), "local origin for clone %s does not exist"),
zc.zc_value); destsnap);
return (zfs_error(hdl, EZFS_NOENT, errbuf)); err = zfs_error(hdl, EZFS_NOENT, errbuf);
goto out;
} }
if (flags->verbose) if (flags->verbose)
(void) printf("found clone origin %s\n", zc.zc_string); (void) printf("found clone origin %s\n", origin);
} else if (originsnap) { } else if (originsnap) {
(void) strncpy(zc.zc_string, originsnap, ZFS_MAXNAMELEN); (void) strncpy(origin, originsnap, ZFS_MAXNAMELEN);
if (flags->verbose) if (flags->verbose)
(void) printf("using provided clone origin %s\n", (void) printf("using provided clone origin %s\n",
zc.zc_string); origin);
} }
boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & 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, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive new filesystem stream")); "cannot receive new filesystem stream"));
(void) strcpy(zc.zc_name, zc.zc_value); (void) strcpy(name, destsnap);
cp = strrchr(zc.zc_name, '/'); cp = strrchr(name, '/');
if (cp) if (cp)
*cp = '\0'; *cp = '\0';
if (cp && if (cp &&
!zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
char suffix[ZFS_MAXNAMELEN]; char suffix[ZFS_MAXNAMELEN];
(void) strcpy(suffix, strrchr(zc.zc_value, '/')); (void) strcpy(suffix, strrchr(destsnap, '/'));
if (guid_to_name(hdl, zc.zc_name, parent_snapguid, if (guid_to_name(hdl, name, parent_snapguid,
B_FALSE, zc.zc_value) == 0) { B_FALSE, destsnap) == 0) {
*strchr(zc.zc_value, '@') = '\0'; *strchr(destsnap, '@') = '\0';
(void) strcat(zc.zc_value, suffix); (void) strcat(destsnap, suffix);
} }
} }
} else { } 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, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive incremental stream")); "cannot receive incremental stream"));
(void) strcpy(zc.zc_name, zc.zc_value); (void) strcpy(name, destsnap);
*strchr(zc.zc_name, '@') = '\0'; *strchr(name, '@') = '\0';
/* /*
* If the exact receive path was specified and this is the * 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 + if ((flags->isprefix || (*(chopprefix = drrb->drr_toname +
strlen(sendfs)) != '\0' && *chopprefix != '@')) && 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]; char snap[ZFS_MAXNAMELEN];
(void) strcpy(snap, strchr(zc.zc_value, '@')); (void) strcpy(snap, strchr(destsnap, '@'));
if (guid_to_name(hdl, zc.zc_name, drrb->drr_fromguid, if (guid_to_name(hdl, name, drrb->drr_fromguid,
B_FALSE, zc.zc_value) == 0) { B_FALSE, destsnap) == 0) {
*strchr(zc.zc_value, '@') = '\0'; *strchr(destsnap, '@') = '\0';
(void) strcat(zc.zc_value, snap); (void) strcat(destsnap, snap);
} }
} }
} }
(void) strcpy(zc.zc_name, zc.zc_value); (void) strcpy(name, destsnap);
*strchr(zc.zc_name, '@') = '\0'; *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; zfs_handle_t *zhp;
(void) strcpy(zc.zc_name, name);
/* /*
* Destination fs exists. It must be one of these cases: * Destination fs exists. It must be one of these cases:
* - an incremental send stream * - an incremental send stream
@ -3186,39 +3194,37 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
*/ */
if (stream_wantsnewfs) { if (stream_wantsnewfs) {
if (!flags->force) { if (!flags->force) {
zcmd_free_nvlists(&zc);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"destination '%s' exists\n" "destination '%s' exists\n"
"must specify -F to overwrite it"), "must specify -F to overwrite it"), name);
zc.zc_name); err = zfs_error(hdl, EZFS_EXISTS, errbuf);
return (zfs_error(hdl, EZFS_EXISTS, errbuf)); goto out;
} }
if (ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT, if (ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT,
&zc) == 0) { &zc) == 0) {
zcmd_free_nvlists(&zc);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"destination has snapshots (eg. %s)\n" "destination has snapshots (eg. %s)\n"
"must destroy them to overwrite it"), "must destroy them to overwrite it"),
zc.zc_name); name);
return (zfs_error(hdl, EZFS_EXISTS, errbuf)); err = zfs_error(hdl, EZFS_EXISTS, errbuf);
goto out;
} }
} }
if ((zhp = zfs_open(hdl, zc.zc_name, if ((zhp = zfs_open(hdl, name,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) { ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) {
zcmd_free_nvlists(&zc); err = -1;
return (-1); goto out;
} }
if (stream_wantsnewfs && if (stream_wantsnewfs &&
zhp->zfs_dmustats.dds_origin[0]) { zhp->zfs_dmustats.dds_origin[0]) {
zcmd_free_nvlists(&zc);
zfs_close(zhp); zfs_close(zhp);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"destination '%s' is a clone\n" "destination '%s' is a clone\n"
"must destroy it to overwrite it"), "must destroy it to overwrite it"), name);
zc.zc_name); err = zfs_error(hdl, EZFS_EXISTS, errbuf);
return (zfs_error(hdl, EZFS_EXISTS, errbuf)); goto out;
} }
if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM && 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); clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0);
if (clp == NULL) { if (clp == NULL) {
zfs_close(zhp); zfs_close(zhp);
zcmd_free_nvlists(&zc); err = -1;
return (-1); goto out;
} }
if (changelist_prefix(clp) != 0) { if (changelist_prefix(clp) != 0) {
changelist_free(clp); changelist_free(clp);
zfs_close(zhp); zfs_close(zhp);
zcmd_free_nvlists(&zc); err = -1;
return (-1); goto out;
} }
} }
@ -3259,11 +3265,11 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
* contained no slash character). * contained no slash character).
*/ */
if (!stream_wantsnewfs || if (!stream_wantsnewfs ||
(cp = strrchr(zc.zc_name, '/')) == NULL) { (cp = strrchr(name, '/')) == NULL) {
zcmd_free_nvlists(&zc);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"destination '%s' does not exist"), zc.zc_name); "destination '%s' does not exist"), name);
return (zfs_error(hdl, EZFS_NOENT, errbuf)); 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'; *cp = '\0';
if (flags->isprefix && !flags->istail && !flags->dryrun && if (flags->isprefix && !flags->istail && !flags->dryrun &&
create_parents(hdl, zc.zc_value, strlen(tosnap)) != 0) { create_parents(hdl, destsnap, strlen(tosnap)) != 0) {
zcmd_free_nvlists(&zc); err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
return (zfs_error(hdl, EZFS_BADRESTORE, errbuf)); goto out;
} }
newfs = B_TRUE; 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) { if (flags->verbose) {
(void) printf("%s %s stream of %s into %s\n", (void) printf("%s %s stream of %s into %s\n",
flags->dryrun ? "would receive" : "receiving", flags->dryrun ? "would receive" : "receiving",
drrb->drr_fromguid ? "incremental" : "full", drrb->drr_fromguid ? "incremental" : "full",
drrb->drr_toname, zc.zc_value); drrb->drr_toname, destsnap);
(void) fflush(stdout); (void) fflush(stdout);
} }
if (flags->dryrun) { if (flags->dryrun) {
zcmd_free_nvlists(&zc); err = recv_skip(hdl, infd, flags->byteswap);
return (recv_skip(hdl, infd, flags->byteswap)); goto out;
} }
zc.zc_nvlist_dst = (uint64_t)(uintptr_t)prop_errbuf; err = ioctl_err = lzc_receive_one(destsnap, props, origin,
zc.zc_nvlist_dst_size = sizeof (prop_errbuf); flags->force, flags->resumable, infd, drr_noswap, cleanup_fd,
zc.zc_cleanup_fd = cleanup_fd; &read_bytes, &errflags, action_handlep, &prop_errors);
zc.zc_action_handle = *action_handlep; ioctl_errno = ioctl_err;
prop_errflags = errflags;
err = ioctl_err = zfs_ioctl(hdl, ZFS_IOC_RECV, &zc);
ioctl_errno = errno;
prop_errflags = (zprop_errflags_t)zc.zc_obj;
if (err == 0) { 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; nvpair_t *prop_err = NULL;
while ((prop_err = nvlist_next_nvpair(prop_errors, 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), (void) snprintf(tbuf, sizeof (tbuf),
dgettext(TEXT_DOMAIN, dgettext(TEXT_DOMAIN,
"cannot receive %s property on %s"), "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); 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) { if (err == 0 && snapprops_nvlist) {
zfs_cmd_t zc2 = {"\0"}; zfs_cmd_t zc = {"\0"};
(void) strcpy(zc2.zc_name, zc.zc_value); (void) strcpy(zc.zc_name, destsnap);
zc2.zc_cookie = B_TRUE; /* received */ zc.zc_cookie = B_TRUE; /* received */
if (zcmd_write_src_nvlist(hdl, &zc2, snapprops_nvlist) == 0) { if (zcmd_write_src_nvlist(hdl, &zc, snapprops_nvlist) == 0) {
(void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc2); (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
zcmd_free_nvlists(&zc2); 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; avl_tree_t *local_avl;
nvlist_t *local_nv, *fs; nvlist_t *local_nv, *fs;
cp = strchr(zc.zc_value, '@'); cp = strchr(destsnap, '@');
/* /*
* XXX Do this faster by just iterating over snaps in * 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. * get a strange "does not exist" error message.
*/ */
*cp = '\0'; *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) { &local_nv, &local_avl) == 0) {
*cp = '@'; *cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL); 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 (fs != NULL) {
if (flags->verbose) { if (flags->verbose) {
(void) printf("snap %s already exists; " (void) printf("snap %s already exists; "
"ignoring\n", zc.zc_value); "ignoring\n", destsnap);
} }
err = ioctl_err = recv_skip(hdl, infd, err = ioctl_err = recv_skip(hdl, infd,
flags->byteswap); flags->byteswap);
@ -3404,22 +3394,22 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (ioctl_err != 0) { if (ioctl_err != 0) {
switch (ioctl_errno) { switch (ioctl_errno) {
case ENODEV: case ENODEV:
cp = strchr(zc.zc_value, '@'); cp = strchr(destsnap, '@');
*cp = '\0'; *cp = '\0';
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"most recent snapshot of %s does not\n" "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); (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
*cp = '@'; *cp = '@';
break; break;
case ETXTBSY: case ETXTBSY:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"destination %s has been modified\n" "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); (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
break; break;
case EEXIST: case EEXIST:
cp = strchr(zc.zc_value, '@'); cp = strchr(destsnap, '@');
if (newfs) { if (newfs) {
/* it's the containing fs that exists */ /* it's the containing fs that exists */
*cp = '\0'; *cp = '\0';
@ -3428,14 +3418,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"destination already exists")); "destination already exists"));
(void) zfs_error_fmt(hdl, EZFS_EXISTS, (void) zfs_error_fmt(hdl, EZFS_EXISTS,
dgettext(TEXT_DOMAIN, "cannot restore to %s"), dgettext(TEXT_DOMAIN, "cannot restore to %s"),
zc.zc_value); destsnap);
*cp = '@'; *cp = '@';
break; break;
case EINVAL: 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); (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
break; break;
case ECKSUM: 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); (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
break; break;
case ENOTSUP: case ENOTSUP:
@ -3445,7 +3439,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
break; break;
case EDQUOT: case EDQUOT:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, 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); (void) zfs_error(hdl, EZFS_NOSPC, errbuf);
break; break;
default: 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 * children of the target filesystem if we did a replication
* receive (indicated by stream_avl being non-NULL). * receive (indicated by stream_avl being non-NULL).
*/ */
cp = strchr(zc.zc_value, '@'); cp = strchr(destsnap, '@');
if (cp && (ioctl_err == 0 || !newfs)) { if (cp && (ioctl_err == 0 || !newfs)) {
zfs_handle_t *h; zfs_handle_t *h;
*cp = '\0'; *cp = '\0';
h = zfs_open(hdl, zc.zc_value, h = zfs_open(hdl, destsnap,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
if (h != NULL) { if (h != NULL) {
if (h->zfs_type == ZFS_TYPE_VOLUME) { 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. * for mounting and sharing later.
*/ */
if (top_zfs && *top_zfs == NULL) if (top_zfs && *top_zfs == NULL)
*top_zfs = zfs_strdup(hdl, zc.zc_value); *top_zfs = zfs_strdup(hdl, destsnap);
} }
zfs_close(h); 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) { if (prop_errflags & ZPROP_ERR_NOCLEAR) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: " (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: "
"failed to clear unreceived properties on %s"), "failed to clear unreceived properties on %s"), name);
zc.zc_name);
(void) fprintf(stderr, "\n"); (void) fprintf(stderr, "\n");
} }
if (prop_errflags & ZPROP_ERR_NORESTORE) { if (prop_errflags & ZPROP_ERR_NORESTORE) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: " (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: "
"failed to restore original properties on %s"), "failed to restore original properties on %s"), name);
zc.zc_name);
(void) fprintf(stderr, "\n"); (void) fprintf(stderr, "\n");
} }
if (err || ioctl_err) if (err || ioctl_err) {
return (-1); err = -1;
goto out;
*action_handlep = zc.zc_action_handle; }
if (flags->verbose) { if (flags->verbose) {
char buf1[64]; char buf1[64];
char buf2[64]; char buf2[64];
uint64_t bytes = zc.zc_cookie; uint64_t bytes = read_bytes;
time_t delta = time(NULL) - begin_time; time_t delta = time(NULL) - begin_time;
if (delta == 0) if (delta == 0)
delta = 1; delta = 1;
@ -3518,7 +3510,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
buf1, delta, buf2); 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 static int

View File

@ -547,82 +547,168 @@ recv_read(int fd, void *buf, int ilen)
return (0); 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 static int
recv_impl(const char *snapname, nvlist_t *props, const char *origin, recv_impl(const char *snapname, nvlist_t *props, const char *origin,
boolean_t force, boolean_t resumable, int fd, boolean_t force, boolean_t resumable, int input_fd,
const dmu_replay_record_t *begin_record) const dmu_replay_record_t *begin_record, int cleanup_fd,
uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
nvlist_t **errors)
{ {
/* dmu_replay_record_t drr;
* The receive ioctl is still legacy, so we need to construct our own char fsname[MAXPATHLEN];
* zfs_cmd_t rather than using zfsc_ioctl().
*/
zfs_cmd_t zc = {"\0"};
char *atp; char *atp;
char *packed = NULL;
size_t size;
int error; int error;
ASSERT3S(g_refcount, >, 0); /* Set 'fsname' to the name of containing filesystem */
(void) strlcpy(fsname, snapname, sizeof (fsname));
/* zc_name is name of containing filesystem */ atp = strchr(fsname, '@');
(void) strlcpy(zc.zc_name, snapname, sizeof (zc.zc_name));
atp = strchr(zc.zc_name, '@');
if (atp == NULL) if (atp == NULL)
return (EINVAL); return (EINVAL);
*atp = '\0'; *atp = '\0';
/* if the fs does not exist, try its parent. */ /* If the fs does not exist, try its parent. */
if (!lzc_exists(zc.zc_name)) { if (!lzc_exists(fsname)) {
char *slashp = strrchr(zc.zc_name, '/'); char *slashp = strrchr(fsname, '/');
if (slashp == NULL) if (slashp == NULL)
return (ENOENT); return (ENOENT);
*slashp = '\0'; *slashp = '\0';
} }
/* zc_value is full name of the snapshot to create */ /*
(void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); * The begin_record is normally a non-byteswapped BEGIN record.
* For resumable streams it may be set to any non-byteswapped
if (props != NULL) { * dmu_replay_record_t.
/* 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;
}
/* zc_string is name of clone origin (if DRR_FLAG_CLONE) */
if (origin != NULL)
(void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string));
/* zc_begin_record is non-byteswapped BEGIN record */
if (begin_record == NULL) { if (begin_record == NULL) {
error = recv_read(fd, &zc.zc_begin_record, error = recv_read(input_fd, &drr, sizeof (drr));
sizeof (zc.zc_begin_record));
if (error != 0) if (error != 0)
goto out; return (error);
} else { } else {
zc.zc_begin_record = *begin_record; drr = *begin_record;
} }
/* zc_cookie is fd to read from */ if (resumable) {
zc.zc_cookie = fd; nvlist_t *outnvl = NULL;
nvlist_t *innvl = fnvlist_alloc();
/* zc guid is force flag */ fnvlist_add_string(innvl, "snapname", snapname);
zc.zc_guid = force;
zc.zc_resumable = resumable; if (props != NULL)
fnvlist_add_nvlist(innvl, "props", props);
/* zc_cleanup_fd is unused */ if (origin != NULL && strlen(origin))
zc.zc_cleanup_fd = -1; fnvlist_add_string(innvl, "origin", origin);
error = ioctl(g_fd, ZFS_IOC_RECV, &zc); fnvlist_add_byte_array(innvl, "begin_record",
if (error != 0) (uchar_t *) &drr, sizeof (drr));
error = errno;
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 {
zfs_cmd_t zc = {"\0"};
char *packed = NULL;
size_t size;
ASSERT3S(g_refcount, >, 0);
(void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_value));
(void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
if (props != NULL) {
packed = fnvlist_pack(props, &size);
zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
zc.zc_nvlist_src_size = size;
}
if (origin != NULL)
(void) strlcpy(zc.zc_string, origin,
sizeof (zc.zc_string));
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); return (error);
} }
@ -643,7 +729,8 @@ int
lzc_receive(const char *snapname, nvlist_t *props, const char *origin, lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
boolean_t force, int fd) 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, lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
boolean_t force, int fd) 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) if (begin_record == NULL)
return (EINVAL); return (EINVAL);
return (recv_impl(snapname, props, origin, force, resumable, fd, 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));
} }
/* /*

View File

@ -945,6 +945,13 @@ zfs_secpolicy_recv(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
ZFS_DELEG_PERM_CREATE, 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 int
zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr) zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr)
{ {
@ -4046,74 +4053,38 @@ static boolean_t zfs_ioc_recv_inject_err;
#endif #endif
/* /*
* inputs: * On failure the 'errors' nvlist may be allocated and will contain a
* zc_name name of containing filesystem * descriptions of the failures. It's the callers responsibilty to free.
* 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
*/ */
static int 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; dmu_recv_cookie_t drc;
boolean_t force = (boolean_t)zc->zc_guid;
int fd;
int error = 0; int error = 0;
int props_error = 0; int props_error = 0;
nvlist_t *errors;
offset_t off; offset_t off;
nvlist_t *props = NULL; /* sent properties */
nvlist_t *origprops = NULL; /* existing properties */
nvlist_t *delayprops = NULL; /* sent properties applied post-receive */ nvlist_t *delayprops = NULL; /* sent properties applied post-receive */
char *origin = NULL; nvlist_t *origprops = NULL; /* existing properties */
char *tosnap;
char tofs[ZFS_MAXNAMELEN];
boolean_t first_recvd_props = B_FALSE; boolean_t first_recvd_props = B_FALSE;
file_t *input_fp;
if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0 || *errors = NULL;
strchr(zc->zc_value, '@') == NULL || input_fp = getf(input_fd);
strchr(zc->zc_value, '%')) if (input_fp == NULL)
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);
return (SET_ERROR(EBADF)); return (SET_ERROR(EBADF));
}
errors = fnvlist_alloc();
if (zc->zc_string[0])
origin = zc->zc_string;
error = dmu_recv_begin(tofs, tosnap, 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) if (error != 0)
goto out; goto out;
*read_bytes = 0;
*errflags = 0;
*errors = fnvlist_alloc();
/* /*
* Set properties before we receive the stream so that they are applied * 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 * 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) if (!first_recvd_props)
props_reduce(props, origprops); props_reduce(props, origprops);
if (zfs_check_clearable(tofs, origprops, &errlist) != 0) if (zfs_check_clearable(tofs, origprops, &errlist) != 0)
(void) nvlist_merge(errors, errlist, 0); (void) nvlist_merge(*errors, errlist, 0);
nvlist_free(errlist); nvlist_free(errlist);
if (clear_received_props(tofs, origprops, if (clear_received_props(tofs, origprops,
first_recvd_props ? NULL : props) != 0) first_recvd_props ? NULL : props) != 0)
zc->zc_obj |= ZPROP_ERR_NOCLEAR; *errflags |= ZPROP_ERR_NOCLEAR;
} else { } 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) { if (props_error == 0) {
delayprops = extract_delay_props(props); delayprops = extract_delay_props(props);
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
props, errors); props, *errors);
} }
} }
off = fp->f_offset; off = input_fp->f_offset;
error = dmu_recv_stream(&drc, fp->f_vnode, &off, zc->zc_cleanup_fd, error = dmu_recv_stream(&drc, input_fp->f_vnode, &off, cleanup_fd,
&zc->zc_action_handle); action_handle);
if (error == 0) { if (error == 0) {
zfs_sb_t *zsb = NULL; 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. */ /* Set delayed properties now, after we're done receiving. */
if (delayprops != NULL && error == 0) { if (delayprops != NULL && error == 0) {
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, (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); 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; *read_bytes = off - input_fp->f_offset;
if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0) if (VOP_SEEK(input_fp->f_vnode, input_fp->f_offset, &off, NULL) == 0)
fp->f_offset = off; input_fp->f_offset = off;
#ifdef DEBUG #ifdef DEBUG
if (zfs_ioc_recv_inject_err) { 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 * Since we may have left a $recvd value on the
* system, we can't clear the $hasrecvd flag. * system, we can't clear the $hasrecvd flag.
*/ */
zc->zc_obj |= ZPROP_ERR_NORESTORE; *errflags |= ZPROP_ERR_NORESTORE;
} else if (first_recvd_props) { } else if (first_recvd_props) {
dsl_prop_unset_hasrecvd(tofs); dsl_prop_unset_hasrecvd(tofs);
} }
if (origprops == NULL && !drc.drc_newfs) { if (origprops == NULL && !drc.drc_newfs) {
/* We failed to stash the original properties. */ /* 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 * We stashed the original properties but failed to
* restore them. * restore them.
*/ */
zc->zc_obj |= ZPROP_ERR_NORESTORE; *errflags |= ZPROP_ERR_NORESTORE;
} }
} }
out: out:
nvlist_free(props); releasef(input_fd);
nvlist_free(origprops); nvlist_free(origprops);
nvlist_free(errors);
releasef(fd);
if (error == 0) if (error == 0)
error = props_error; error = props_error;
@ -4284,6 +4240,176 @@ out:
return (error); 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: * inputs:
* zc_name name of snapshot to send * zc_name name of snapshot to send
@ -5555,6 +5681,10 @@ zfs_ioctl_init(void)
POOL_NAME, POOL_NAME,
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE); 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 */ /* IOCTLS that use the legacy function signature */
zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze, zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,