diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 5074913a5ecf..dbec4338be08 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -249,10 +249,13 @@ get_usage(zfs_help_t idx) case HELP_PROMOTE: return (gettext("\tpromote \n")); case HELP_RECEIVE: - return (gettext("\treceive [-vnsFu] \n" - "\treceive [-vnsFu] [-o origin=] [-d | -e] " - "\n" + return (gettext("\treceive [-vnsFu] [[-o property=] | " + "[-x property]] ...\n" + "\t [-o origin=] " + "\n" + "\treceive [-vnsFu] [[-o property=] | " + "[-x property]] ... \n" + "\t [-d | -e] [-o origin=] \n" "\treceive -A \n")); case HELP_RENAME: return (gettext("\trename [-f] " @@ -490,7 +493,7 @@ usage(boolean_t requested) static int parseprop(nvlist_t *props, char *propname) { - char *propval, *strval; + char *propval; if ((propval = strchr(propname, '=')) == NULL) { (void) fprintf(stderr, gettext("missing " @@ -499,7 +502,7 @@ parseprop(nvlist_t *props, char *propname) } *propval = '\0'; propval++; - if (nvlist_lookup_string(props, propname, &strval) == 0) { + if (nvlist_exists(props, propname)) { (void) fprintf(stderr, gettext("property '%s' " "specified multiple times\n"), propname); return (-1); @@ -509,6 +512,28 @@ parseprop(nvlist_t *props, char *propname) return (0); } +/* + * Take a property name argument and add it to the given nvlist. + * Modifies the argument inplace. + */ +static int +parsepropname(nvlist_t *props, char *propname) +{ + if (strchr(propname, '=') != NULL) { + (void) fprintf(stderr, gettext("invalid character " + "'=' in property argument\n")); + return (-1); + } + if (nvlist_exists(props, propname)) { + (void) fprintf(stderr, gettext("property '%s' " + "specified multiple times\n"), propname); + return (-1); + } + if (nvlist_add_boolean(props, propname) != 0) + nomem(); + return (0); +} + static int parse_depth(char *opt, int *flags) { @@ -4014,20 +4039,24 @@ zfs_do_receive(int argc, char **argv) int c, err = 0; recvflags_t flags = { 0 }; boolean_t abort_resumable = B_FALSE; - nvlist_t *props; - nvpair_t *nvp = NULL; if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); /* check options */ - while ((c = getopt(argc, argv, ":o:denuvFsA")) != -1) { + while ((c = getopt(argc, argv, ":o:x:denuvFsA")) != -1) { switch (c) { case 'o': if (parseprop(props, optarg) != 0) { nvlist_free(props); - return (1); + usage(B_FALSE); + } + break; + case 'x': + if (parsepropname(props, optarg) != 0) { + nvlist_free(props); + usage(B_FALSE); } break; case 'd': @@ -4080,13 +4109,6 @@ zfs_do_receive(int argc, char **argv) usage(B_FALSE); } - while ((nvp = nvlist_next_nvpair(props, nvp))) { - if (strcmp(nvpair_name(nvp), "origin") != 0) { - (void) fprintf(stderr, gettext("invalid option")); - usage(B_FALSE); - } - } - if (abort_resumable) { if (flags.isprefix || flags.istail || flags.dryrun || flags.resumable || flags.nomount) { diff --git a/include/libzfs_core.h b/include/libzfs_core.h index e67e4a1302d7..716e1e9811cd 100644 --- a/include/libzfs_core.h +++ b/include/libzfs_core.h @@ -78,9 +78,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 **); +int lzc_receive_one(const char *, nvlist_t *, 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/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 709ca5997391..5c6cf9ac888c 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -28,6 +28,7 @@ * Copyright (c) 2013 Steven Hartland. All rights reserved. * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. * Copyright 2016 Igor Kozhukhov + * Copyright (c) 2017, loli10K . All rights reserved. */ #include @@ -69,7 +70,7 @@ extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *); static int zfs_receive_impl(libzfs_handle_t *, const char *, const char *, recvflags_t *, int, const char *, nvlist_t *, avl_tree_t *, char **, int, - uint64_t *, const char *); + uint64_t *, const char *, nvlist_t *); static int guid_to_name(libzfs_handle_t *, const char *, uint64_t, boolean_t, char *); @@ -2743,7 +2744,8 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, static int zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, recvflags_t *flags, dmu_replay_record_t *drr, zio_cksum_t *zc, - char **top_zfs, int cleanup_fd, uint64_t *action_handlep) + char **top_zfs, int cleanup_fd, uint64_t *action_handlep, + nvlist_t *cmdprops) { nvlist_t *stream_nv = NULL; avl_tree_t *stream_avl = NULL; @@ -2919,7 +2921,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, */ error = zfs_receive_impl(hdl, destname, NULL, flags, fd, sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd, - action_handlep, sendsnap); + action_handlep, sendsnap, cmdprops); if (error == ENODATA) { error = 0; break; @@ -3083,6 +3085,103 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap, zfs_close(zhp); } +/* + * Prepare a new nvlist of properties that are to override (-o) or be excluded + * (-x) from the received dataset + * cmdprops: input properties from command line + * origprops: original properties if the destination exists, NULL otherwise + * oxprops: output override (-o) and excluded (-x) properties + */ +static int +zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, + boolean_t recursive, boolean_t toplevel, nvlist_t *cmdprops, + nvlist_t *origprops, nvlist_t **oxprops, const char *errbuf) +{ + nvpair_t *nvp; + nvlist_t *oprops, *voprops; + zfs_handle_t *zhp = NULL; + zpool_handle_t *zpool_hdl = NULL; + int ret = 0; + + if (nvlist_empty(cmdprops)) + return (0); /* No properties to override */ + + *oxprops = fnvlist_alloc(); + oprops = fnvlist_alloc(); + + /* + * first iteration: process excluded (-x) properties now and gather + * added (-o) properties to be later processed by zfs_valid_proplist() + */ + nvp = NULL; + while ((nvp = nvlist_next_nvpair(cmdprops, nvp)) != NULL) { + const char *name = nvpair_name(nvp); + zfs_prop_t prop = zfs_name_to_prop(name); + + /* "origin" is processed separately, don't handle it here */ + if (prop == ZFS_PROP_ORIGIN) + continue; + + /* + * we're trying to override or exclude a property that does not + * make sense for this type of dataset, but we don't want to + * fail if the receive is recursive: this comes in handy when + * the send stream contains, for instance, a child ZVOL and + * we're trying to receive it with "-o atime=on" + */ + if (zfs_prop_valid_for_type(prop, type, B_FALSE) == B_FALSE && + !(recursive == B_TRUE || zfs_prop_user(name) == B_TRUE)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property '%s' does not apply to datasets of this " + "type"), name); + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + switch (nvpair_type(nvp)) { + case DATA_TYPE_BOOLEAN: /* -x property */ + if (!nvlist_exists(origprops, name)) + fnvlist_add_nvpair(*oxprops, nvp); + break; + case DATA_TYPE_STRING: /* -o property=value */ + fnvlist_add_nvpair(oprops, nvp); + break; + default: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property '%s' must be a string or boolean"), name); + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + } + + if (toplevel) { + /* convert override strings properties to native */ + if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET, + oprops, zoned, zhp, zpool_hdl, errbuf)) == NULL) { + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + /* second pass: process "-o" properties */ + nvp = NULL; + while ((nvp = nvlist_next_nvpair(voprops, nvp)) != NULL) { + fnvlist_add_nvpair(*oxprops, nvp); + } + fnvlist_free(voprops); + } else { + /* override props on child dataset are inherited */ + nvp = NULL; + while ((nvp = nvlist_next_nvpair(oprops, nvp)) != NULL) { + const char *name = nvpair_name(nvp); + fnvlist_add_boolean(*oxprops, name); + } + } + +error: + fnvlist_free(oprops); + return (ret); +} + /* * Restores a backup of tosnap from the file descriptor specified by infd. */ @@ -3091,7 +3190,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, const char *originsnap, recvflags_t *flags, dmu_replay_record_t *drr, dmu_replay_record_t *drr_noswap, const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd, - uint64_t *action_handlep, const char *finalsnap) + uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops) { time_t begin_time; int ioctl_err, ioctl_errno, err; @@ -3114,7 +3213,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, char destsnap[MAXPATHLEN * 2]; char origin[MAXNAMELEN]; char name[MAXPATHLEN]; - nvlist_t *props = NULL; + nvlist_t *rcvprops = NULL; /* props received from the send stream */ + nvlist_t *oxprops = NULL; /* override (-o) and exclude (-x) props */ + nvlist_t *origprops = NULL; /* original props (if destination exists) */ + zfs_type_t type; + boolean_t toplevel; + boolean_t zoned = B_FALSE; begin_time = time(NULL); bzero(origin, MAXNAMELEN); @@ -3132,14 +3236,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) nvlist_lookup_uint64(fs, "parentfromsnap", &parent_snapguid); - err = nvlist_lookup_nvlist(fs, "props", &props); + err = nvlist_lookup_nvlist(fs, "props", &rcvprops); if (err) { - VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0)); + VERIFY(0 == nvlist_alloc(&rcvprops, NV_UNIQUE_NAME, 0)); newprops = B_TRUE; } if (flags->canmountoff) { - VERIFY(0 == nvlist_add_uint64(props, + VERIFY(0 == nvlist_add_uint64(rcvprops, zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0)); } if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) { @@ -3393,6 +3497,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (resuming && zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT)) newfs = B_TRUE; + /* we want to know if we're zoned when validating -o|-x props */ + zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); + + /* gather existing properties on destination dataset */ + origprops = fnvlist_alloc(); + fnvlist_merge(origprops, zhp->zfs_props); + fnvlist_merge(origprops, zhp->zfs_user_props); + zfs_close(zhp); } else { /* @@ -3439,7 +3551,16 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, goto out; } - err = ioctl_err = lzc_receive_one(destsnap, props, origin, + toplevel = chopprefix[0] != '/'; + if (drrb->drr_type == DMU_OST_ZVOL) + type = ZFS_TYPE_VOLUME; + else + type = ZFS_TYPE_FILESYSTEM; + if ((err = zfs_setup_cmdline_props(hdl, type, zoned, recursive, + toplevel, cmdprops, origprops, &oxprops, errbuf)) != 0) + goto out; + + err = ioctl_err = lzc_receive_one(destsnap, rcvprops, oxprops, origin, flags->force, flags->resumable, infd, drr_noswap, cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors); ioctl_errno = ioctl_err; @@ -3656,7 +3777,57 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, nvlist_free(prop_errors); if (newprops) - nvlist_free(props); + nvlist_free(rcvprops); + + if (origprops != NULL) + nvlist_free(origprops); + + return (err); +} + +/* + * Check properties we were asked to override (both -o|-x) + */ +static int +zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props, + const char *errbuf) +{ + nvpair_t *nvp; + int err = 0; + const char *name; + + nvp = NULL; + while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL && err == 0) { + zfs_prop_t prop; + name = nvpair_name(nvp); + prop = zfs_name_to_prop(name); + + if (prop == ZPROP_INVAL) { + if (zfs_prop_user(name) != B_TRUE) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid property '%s'"), name); + err = 1; + } + continue; + } + /* + * "origin" is readonly but is used to receive datasets as + * clones so we don't raise an error here + */ + if (prop == ZFS_PROP_ORIGIN) + continue; + + /* + * cannot override readonly, set-once and other specific + * settable properties + */ + if (zfs_prop_readonly(prop) == B_TRUE || + prop == ZFS_PROP_VERSION || prop == ZFS_PROP_VOLSIZE) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid property '%s'"), name); + err = 1; + } + } return (err); } @@ -3665,7 +3836,7 @@ static int zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, const char *originsnap, recvflags_t *flags, int infd, const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd, - uint64_t *action_handlep, const char *finalsnap) + uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops) { int err; dmu_replay_record_t drr, drr_noswap; @@ -3678,6 +3849,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot receive")); + /* check cmdline props, raise an error if they cannot be received */ + if (zfs_receive_checkprops(hdl, cmdprops, errbuf) != 0) { + return (zfs_error(hdl, EZFS_BADPROP, errbuf)); + } + if (flags->isprefix && !zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs " @@ -3766,12 +3942,12 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, } return (zfs_receive_one(hdl, infd, tosnap, originsnap, flags, &drr, &drr_noswap, sendfs, stream_nv, stream_avl, top_zfs, - cleanup_fd, action_handlep, finalsnap)); + cleanup_fd, action_handlep, finalsnap, cmdprops)); } else { assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) == DMU_COMPOUNDSTREAM); return (zfs_receive_package(hdl, infd, tosnap, flags, &drr, - &zcksum, top_zfs, cleanup_fd, action_handlep)); + &zcksum, top_zfs, cleanup_fd, action_handlep, cmdprops)); } } @@ -3844,7 +4020,7 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props, VERIFY(cleanup_fd >= 0); err = zfs_receive_impl(hdl, tosnap, originsnap, flags, infd, NULL, NULL, - stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL); + stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL, props); VERIFY(0 == close(cleanup_fd)); diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c index bd2db2211f20..da658a4f555f 100644 --- a/lib/libzfs_core/libzfs_core.c +++ b/lib/libzfs_core/libzfs_core.c @@ -574,8 +574,8 @@ recv_read(int fd, void *buf, int ilen) * 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 input_fd, +recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops, + 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) @@ -622,8 +622,11 @@ recv_impl(const char *snapname, nvlist_t *props, const char *origin, fnvlist_add_string(innvl, "snapname", snapname); - if (props != NULL) - fnvlist_add_nvlist(innvl, "props", props); + if (recvdprops != NULL) + fnvlist_add_nvlist(innvl, "props", recvdprops); + + if (localprops != NULL) + fnvlist_add_nvlist(innvl, "localprops", localprops); if (origin != NULL && strlen(origin)) fnvlist_add_string(innvl, "origin", origin); @@ -679,12 +682,18 @@ recv_impl(const char *snapname, nvlist_t *props, const char *origin, (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); + if (recvdprops != NULL) { + packed = fnvlist_pack(recvdprops, &size); zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed; zc.zc_nvlist_src_size = size; } + if (localprops != NULL) { + packed = fnvlist_pack(localprops, &size); + zc.zc_nvlist_conf = (uint64_t)(uintptr_t)packed; + zc.zc_nvlist_conf_size = size; + } + if (origin != NULL) (void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string)); @@ -750,7 +759,7 @@ 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, + return (recv_impl(snapname, props, NULL, origin, force, B_FALSE, fd, NULL, -1, NULL, NULL, NULL, NULL)); } @@ -764,7 +773,7 @@ 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, + return (recv_impl(snapname, props, NULL, origin, force, B_TRUE, fd, NULL, -1, NULL, NULL, NULL, NULL)); } @@ -786,7 +795,7 @@ 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, + return (recv_impl(snapname, props, NULL, origin, force, resumable, fd, begin_record, -1, NULL, NULL, NULL, NULL)); } @@ -810,13 +819,13 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props, * 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, +int lzc_receive_one(const char *snapname, nvlist_t *props, nvlist_t *cmdprops, 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, + return (recv_impl(snapname, props, cmdprops, origin, force, resumable, input_fd, begin_record, cleanup_fd, read_bytes, errflags, action_handle, errors)); } diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index fe865ee3db71..6908c4d224ea 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -190,12 +190,14 @@ zfs \- configures ZFS file systems .LP .nf -\fBzfs\fR \fBreceive\fR [\fB-Fnsuv\fR] [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR +\fBzfs\fR \fBreceive\fR [\fB-Fnsuv\fR] [[\fB-o\fR \fIproperty\fR=\fIvalue\fR] | [\fB-x\fR \fIproperty\fR]] ... + [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR .fi .LP .nf -\fBzfs\fR \fBreceive\fR [\fB-Fnsuv\fR] [\fB-d\fR|\fB-e\fR] [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR +\fBzfs\fR \fBreceive\fR [\fB-Fnsuv\fR] [[\fB-o\fR \fIproperty\fR=\fIvalue\fR] | [\fB-x\fR \fIproperty\fR]] ... + [\fB-d\fR|\fB-e\fR] [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR .fi .LP @@ -2960,11 +2962,11 @@ Creates a send stream which resumes an interrupted receive. The \fIreceive_resum .sp .ne 2 .na -\fB\fBzfs receive\fR [\fB-Fnsuv\fR] [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR\fR +\fB\fBzfs receive\fR [\fB-Fnsuv\fR] [[\fB-o\fR \fIproperty\fR=\fIvalue\fR] | [\fB-x\fR \fIproperty\fR]] ... [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR\fR .ad .br .na -\fB\fBzfs receive\fR [\fB-Fnsuv\fR] [\fB-d\fR|\fB-e\fR] [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR\fR +\fB\fBzfs receive\fR [\fB-Fnsuv\fR] [[\fB-o\fR \fIproperty\fR=\fIvalue\fR] | [\fB-x\fR \fIproperty\fR]] ... [\fB-d\fR|\fB-e\fR] [\fB-o origin\fR=\fIsnapshot\fR] \fIfilesystem\fR\fR .ad .sp .6 .RS 4n @@ -2974,6 +2976,8 @@ If an incremental stream is received, then the destination file system must alre .sp When a snapshot replication package stream that is generated by using the \fBzfs send\fR \fB-R\fR command is received, any snapshots that do not exist on the sending location are destroyed by using the \fBzfs destroy\fR \fB-d\fR command. .sp +If \fB-o\fR \fIproperty\fR=\fIvalue\fR or \fB-x\fR \fIproperty\fR is specified, it applies to the effective value of the property throughout the entire subtree of replicated datasets. Effective property values will be set (-o) or inherited (-x) on the topmost in the replicated subtree. In descendant datasets, if the property is set by the send stream, it will be overridden by forcing the property to be inherited from the topmost filesystem. Received properties are retained in spite of being overridden and may be restored with \fBzfs inherit\fR \fB-S\fR. +.sp The name of the snapshot (and file system, if a full stream is received) that this subcommand creates depends on the argument type and the use of the \fB-d\fR or \fB-e\fR options. .sp If the argument is a snapshot name, the specified \fIsnapshot\fR is created. If the argument is a file system or volume name, a snapshot with the same name as the sent snapshot is created within the specified \fIfilesystem\fR or \fIvolume\fR. If neither of the \fB-d\fR or \fB-e\fR options are specified, the provided target snapshot name is used exactly as provided. @@ -3034,6 +3038,40 @@ Do not mount the file system that is associated with the received stream. Print verbose information about the stream and the time required to perform the receive operation. .RE +.sp +.ne 2 +.mk +.na +\fB-o\fR \fIproperty\fR=\fIvalue\fR +.ad +.sp .6 +.RS 4n +Sets the specified property as if the command \fBzfs set\fR \fIproperty\fR=\fIvalue\fR was invoked at the same time the received dataset was created from the non-incremental send stream or updated from the incremental send stream. When receiving a stream from \fBzfs send -R\fR, causes the property to be inherited by all descendant datasets, as through \fBzfs inherit\fR \fIproperty\fR was run on any descendant datasets that have this property set on the sending system. +.sp +Any editable ZFS property can also be set at receive time. Set-once properties bound to the received data, such as \fBnormalization\fR and \fBcasesensitivity\fR, cannot be set at receive time even when the datasets are newly created by zfs receive. +.sp +The \fB-o\fR option may be specified multiple times, for different properties. An error results if the same property is specified in multiple \fB-o\fR or \fB-x\fR options. +.RE + +.sp +.ne 2 +.mk +.na +\fB-x\fR \fIproperty\fR +.ad +.sp .6 +.RS 4n +Ensures that the effective value of the specified property after the receive is unaffected by the value of that property in the send stream (if any), as if the property had been excluded from the send stream. +.sp +If the specified property is not present in the send stream, this option does nothing. +.sp +If a received property needs to be overridden, the effective value can be set or inherited, depending on the property. +.sp +In the case of an incremental update, \fB-x\fR leaves any existing local setting or explicit inheritance unchanged (since the received property is already overridden). +.sp +All \fB-o\fR restrictions apply equally to \fB-x\fR. +.RE + .sp .ne 2 .na diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 6df61fecb831..70559f17792d 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -31,6 +31,7 @@ * Copyright (c) 2013 by Saso Kiselkov. All rights reserved. * Copyright (c) 2013 Steven Hartland. All rights reserved. * Copyright (c) 2016 Actifio, Inc. All rights reserved. + * Copyright (c) 2017, loli10K . All rights reserved. */ /* @@ -2495,7 +2496,11 @@ zfs_set_prop_nvlist(const char *dsname, zprop_source_t source, nvlist_t *nvl, } /* Validate value type */ - if (err == 0 && prop == ZPROP_INVAL) { + if (err == 0 && source == ZPROP_SRC_INHERITED) { + /* inherited properties are expected to be booleans */ + if (nvpair_type(propval) != DATA_TYPE_BOOLEAN) + err = SET_ERROR(EINVAL); + } else if (err == 0 && prop == ZPROP_INVAL) { if (zfs_prop_user(propname)) { if (nvpair_type(propval) != DATA_TYPE_STRING) err = SET_ERROR(EINVAL); @@ -2540,7 +2545,11 @@ zfs_set_prop_nvlist(const char *dsname, zprop_source_t source, nvlist_t *nvl, err = zfs_check_settable(dsname, pair, CRED()); if (err == 0) { - err = zfs_prop_set_special(dsname, source, pair); + if (source == ZPROP_SRC_INHERITED) + err = -1; /* does not need special handling */ + else + err = zfs_prop_set_special(dsname, source, + pair); if (err == -1) { /* * For better performance we build up a list of @@ -2592,6 +2601,9 @@ zfs_set_prop_nvlist(const char *dsname, zprop_source_t source, nvlist_t *nvl, strval = fnvpair_value_string(propval); err = dsl_prop_set_string(dsname, propname, source, strval); + } else if (nvpair_type(propval) == DATA_TYPE_BOOLEAN) { + err = dsl_prop_inherit(dsname, propname, + source); } else { intval = fnvpair_value_uint64(propval); err = dsl_prop_set_int(dsname, propname, source, @@ -4164,8 +4176,8 @@ static boolean_t zfs_ioc_recv_inject_err; * encountered errors, if any. It's the callers responsibility to free. */ static int -zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, - nvlist_t *props, boolean_t force, boolean_t resumable, int input_fd, +zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, + nvlist_t *localprops, 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) { @@ -4175,6 +4187,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, offset_t off; nvlist_t *delayprops = NULL; /* sent properties applied post-receive */ nvlist_t *origprops = NULL; /* existing properties */ + nvlist_t *origrecvd = NULL; /* existing received properties */ boolean_t first_recvd_props = B_FALSE; file_t *input_fp; @@ -4196,7 +4209,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, * to the new data. Note that we must call dmu_recv_stream() if * dmu_recv_begin() succeeds. */ - if (props != NULL && !drc.drc_newfs) { + if (recvprops != NULL && !drc.drc_newfs) { if (spa_version(dsl_dataset_get_spa(drc.drc_ds)) >= SPA_VERSION_RECVD_PROPS && !dsl_prop_get_hasrecvd(tofs)) @@ -4207,7 +4220,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, * completely replace the existing received properties, so stash * away the existing ones. */ - if (dsl_prop_get_received(tofs, &origprops) == 0) { + if (dsl_prop_get_received(tofs, &origrecvd) == 0) { nvlist_t *errlist = NULL; /* * Don't bother writing a property if its value won't @@ -4218,29 +4231,76 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, * regardless. */ if (!first_recvd_props) - props_reduce(props, origprops); - if (zfs_check_clearable(tofs, origprops, &errlist) != 0) + props_reduce(recvprops, origrecvd); + if (zfs_check_clearable(tofs, origrecvd, &errlist) != 0) (void) nvlist_merge(*errors, errlist, 0); nvlist_free(errlist); - if (clear_received_props(tofs, origprops, - first_recvd_props ? NULL : props) != 0) + if (clear_received_props(tofs, origrecvd, + first_recvd_props ? NULL : recvprops) != 0) + *errflags |= ZPROP_ERR_NOCLEAR; + } else { + *errflags |= ZPROP_ERR_NOCLEAR; + } + } + + /* + * Stash away existing properties so we can restore them on error unless + * we're doing the first receive after SPA_VERSION_RECVD_PROPS, in which + * case "origrecvd" will take care of that. + */ + if (localprops != NULL && !drc.drc_newfs && !first_recvd_props) { + objset_t *os; + if (dmu_objset_hold(tofs, FTAG, &os) == 0) { + if (dsl_prop_get_all(os, &origprops) != 0) { *errflags |= ZPROP_ERR_NOCLEAR; + } + dmu_objset_rele(os, FTAG); } else { *errflags |= ZPROP_ERR_NOCLEAR; } } - if (props != NULL) { + if (recvprops != NULL) { props_error = dsl_prop_set_hasrecvd(tofs); if (props_error == 0) { - delayprops = extract_delay_props(props); + delayprops = extract_delay_props(recvprops); (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, - props, *errors); + recvprops, *errors); } } + if (localprops != NULL) { + nvlist_t *oprops = fnvlist_alloc(); + nvlist_t *xprops = fnvlist_alloc(); + nvpair_t *nvp = NULL; + + while ((nvp = nvlist_next_nvpair(localprops, nvp)) != NULL) { + if (nvpair_type(nvp) == DATA_TYPE_BOOLEAN) { + /* -x property */ + const char *name = nvpair_name(nvp); + zfs_prop_t prop = zfs_name_to_prop(name); + if (prop != ZPROP_INVAL) { + if (!zfs_prop_inheritable(prop)) + continue; + } else if (!zfs_prop_user(name)) + continue; + fnvlist_add_boolean(xprops, name); + } else { + /* -o property=value */ + fnvlist_add_nvpair(oprops, nvp); + } + } + (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, + oprops, *errors); + (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED, + xprops, *errors); + + nvlist_free(oprops); + nvlist_free(xprops); + } + off = input_fp->f_offset; error = dmu_recv_stream(&drc, input_fp->f_vnode, &off, cleanup_fd, action_handle); @@ -4289,7 +4349,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, * Since zfs_ioc_recv_inject_err is only in DEBUG kernels, * using ASSERT() will be just like a VERIFY. */ - ASSERT(nvlist_merge(props, delayprops, 0) == 0); + ASSERT(nvlist_merge(recvprops, delayprops, 0) == 0); nvlist_free(delayprops); } @@ -4308,8 +4368,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, /* * On error, restore the original props. */ - if (error != 0 && props != NULL && !drc.drc_newfs) { - if (clear_received_props(tofs, props, NULL) != 0) { + if (error != 0 && recvprops != NULL && !drc.drc_newfs) { + if (clear_received_props(tofs, recvprops, NULL) != 0) { /* * We failed to clear the received properties. * Since we may have left a $recvd value on the @@ -4320,7 +4380,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, dsl_prop_unset_hasrecvd(tofs); } - if (origprops == NULL && !drc.drc_newfs) { + if (origrecvd == NULL && !drc.drc_newfs) { /* We failed to stash the original properties. */ *errflags |= ZPROP_ERR_NORESTORE; } @@ -4331,10 +4391,10 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, * explicitly if we're restoring local properties cleared in the * first new-style receive. */ - if (origprops != NULL && + if (origrecvd != NULL && zfs_set_prop_nvlist(tofs, (first_recvd_props ? ZPROP_SRC_LOCAL : ZPROP_SRC_RECEIVED), - origprops, NULL) != 0) { + origrecvd, NULL) != 0) { /* * We stashed the original properties but failed to * restore them. @@ -4342,8 +4402,64 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, *errflags |= ZPROP_ERR_NORESTORE; } } + if (error != 0 && localprops != NULL && !drc.drc_newfs && + !first_recvd_props) { + nvlist_t *setprops; + nvlist_t *inheritprops; + nvpair_t *nvp; + + if (origprops == NULL) { + /* We failed to stash the original properties. */ + *errflags |= ZPROP_ERR_NORESTORE; + goto out; + } + + /* Restore original props */ + setprops = fnvlist_alloc(); + inheritprops = fnvlist_alloc(); + nvp = NULL; + while ((nvp = nvlist_next_nvpair(localprops, nvp)) != NULL) { + const char *name = nvpair_name(nvp); + const char *source; + nvlist_t *attrs; + + if (!nvlist_exists(origprops, name)) { + /* + * Property was not present or was explicitly + * inherited before the receive, restore this. + */ + fnvlist_add_boolean(inheritprops, name); + continue; + } + attrs = fnvlist_lookup_nvlist(origprops, name); + source = fnvlist_lookup_string(attrs, ZPROP_SOURCE); + + /* Skip received properties */ + if (strcmp(source, ZPROP_SOURCE_VAL_RECVD) == 0) + continue; + + if (strcmp(source, tofs) == 0) { + /* Property was locally set */ + fnvlist_add_nvlist(setprops, name, attrs); + } else { + /* Property was implicitly inherited */ + fnvlist_add_boolean(inheritprops, name); + } + } + + if (zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, setprops, + NULL) != 0) + *errflags |= ZPROP_ERR_NORESTORE; + if (zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED, inheritprops, + NULL) != 0) + *errflags |= ZPROP_ERR_NORESTORE; + + nvlist_free(setprops); + nvlist_free(inheritprops); + } out: releasef(input_fd); + nvlist_free(origrecvd); nvlist_free(origprops); if (error == 0) @@ -4356,6 +4472,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, * inputs: * zc_name name of containing filesystem (unused) * zc_nvlist_src{_size} nvlist of properties to apply + * zc_nvlist_conf{_size}nvlist of properties to override or exclude * zc_value name of snapshot to create * zc_string name of clone origin (if DRR_FLAG_CLONE) * zc_cookie file descriptor to recv from @@ -4375,7 +4492,8 @@ zfs_ioc_recv(zfs_cmd_t *zc) { dmu_replay_record_t begin_record; nvlist_t *errors = NULL; - nvlist_t *props = NULL; + nvlist_t *recvdprops = NULL; + nvlist_t *localprops = NULL; char *origin = NULL; char *tosnap; char tofs[ZFS_MAX_DATASET_NAME_LEN]; @@ -4392,7 +4510,12 @@ zfs_ioc_recv(zfs_cmd_t *zc) if (zc->zc_nvlist_src != 0 && (error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size, - zc->zc_iflags, &props)) != 0) + zc->zc_iflags, &recvdprops)) != 0) + return (error); + + if (zc->zc_nvlist_conf != 0 && + (error = get_nvlist(zc->zc_nvlist_conf, zc->zc_nvlist_conf_size, + zc->zc_iflags, &localprops)) != 0) return (error); if (zc->zc_string[0]) @@ -4402,10 +4525,12 @@ zfs_ioc_recv(zfs_cmd_t *zc) 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); + error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvdprops, localprops, + 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(recvdprops); + nvlist_free(localprops); /* * Now that all props, initial and delayed, are set, report the prop @@ -4429,7 +4554,8 @@ zfs_ioc_recv(zfs_cmd_t *zc) /* * innvl: { * "snapname" -> full name of the snapshot to create - * (optional) "props" -> properties to set (nvlist) + * (optional) "recvprops" -> received properties to set (nvlist) + * (optional) "localprops" -> override and exclude properties (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) @@ -4452,7 +4578,8 @@ 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; + nvlist_t *recvprops = NULL; + nvlist_t *localprops = NULL; char *snapname = NULL; char *origin = NULL; char *tosnap; @@ -4503,12 +4630,17 @@ zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl) if (error && error != ENOENT) return (error); - error = nvlist_lookup_nvlist(innvl, "props", &props); + /* we still use "props" here for backwards compatibility */ + error = nvlist_lookup_nvlist(innvl, "props", &recvprops); + if (error && error != ENOENT) + return (error); + + error = nvlist_lookup_nvlist(innvl, "localprops", &localprops); 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, + error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvprops, localprops, + force, resumable, input_fd, begin_record, cleanup_fd, &read_bytes, &errflags, &action_handle, &errors); fnvlist_add_uint64(outnvl, "read_bytes", read_bytes); @@ -4517,7 +4649,8 @@ zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl) fnvlist_add_nvlist(outnvl, "errors", errors); nvlist_free(errors); - nvlist_free(props); + nvlist_free(recvprops); + nvlist_free(localprops); return (error); } diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index cba8e06e1a44..53e7a3f74856 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -149,7 +149,8 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos', 'zfs_receive_005_neg', 'zfs_receive_006_pos', 'zfs_receive_007_neg', 'zfs_receive_008_pos', 'zfs_receive_009_neg', 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', - 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos'] + 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', + 'receive-o-x_props_override'] # DISABLED: # zfs_rename_006_pos - https://github.com/zfsonlinux/zfs/issues/5647 diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/Makefile.am index 3c87a992706d..87e543b00704 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/Makefile.am @@ -16,4 +16,5 @@ dist_pkgdata_SCRIPTS = \ zfs_receive_012_pos.ksh \ zfs_receive_013_pos.ksh \ zfs_receive_014_pos.ksh \ - zfs_receive_015_pos.ksh + zfs_receive_015_pos.ksh \ + receive-o-x_props_override.ksh diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh new file mode 100755 index 000000000000..0425590e8359 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh @@ -0,0 +1,227 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright 2016, loli10K. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib + +# +# DESCRIPTION: +# Verify ZFS property override options works when receiving a send stream +# +# STRATEGY: +# 1. Create a filesystem with children. +# 2. Snapshot the filesystems. +# 3. Create a full stream without properties. +# 4. Create also some incremental replication streams with properties. +# 5. Receive the send streams and verify we can override properties. +# + +verify_runnable "both" + +orig=$TESTPOOL/$TESTFS1 +origsub=$orig/sub +dest=$TESTPOOL/$TESTFS2 +destsub=$dest/sub +typeset userprop=$(valid_user_property 8) +typeset userval=$(user_property_value 8) +typeset streamfile_full=/var/tmp/streamfile_full.$$ +typeset streamfile_repl1=/var/tmp/streamfile_repl1.$$ +typeset streamfile_repl2=/var/tmp/streamfile_repl2.$$ +typeset streamfile_repl3=/var/tmp/streamfile_repl3.$$ + +function cleanup +{ + log_must $RM $streamfile_full + log_must $RM $streamfile_repl1 + log_must $RM $streamfile_repl2 + log_must $RM $streamfile_repl3 + log_must $RM $streamfile_repl3.x + log_must $ZFS destroy -rf $orig + log_must $ZFS destroy -rf $dest +} + +# +# Verify property $2 is set from source $4 on dataset $1 and has value $3. +# +# $1 checked dataset +# $2 user property +# $3 property value +# $4 source +# +function check_prop_source +{ + typeset dataset=$1 + typeset prop=$2 + typeset value=$3 + typeset source=$4 + typeset chk_value=$(get_prop "$prop" "$dataset") + typeset chk_source=$(get_source "$prop" "$dataset") + if [[ "$chk_value" != "$value" || \ + "$chk_source" != "$4" ]] + then + return 1 + else + return 0 + fi +} + +# +# Verify target dataset $1 inherit property $2 from dataset $3. +# +# $1 checked dataset +# $2 property +# $3 inherited dataset +# +function check_prop_inherit +{ + typeset checked_dtst=$1 + typeset prop=$2 + typeset inherited_dtst=$3 + typeset inherited_value=$(get_prop "$prop" "$inherited_dtst") + typeset value=$(get_prop "$prop" "$checked_dtst") + typeset source=$(get_source "$prop" "$checked_dtst") + if [[ "$value" != "$inherited_value" || \ + "$source" != "inherited from $inherited_dtst" ]] + then + return 1 + else + return 0 + fi +} + +# +# Verify user property $2 was received on dataset $1 +# +# $1 checked dataset +# $2 user property +# +function check_prop_received +{ + typeset dataset=$1 + typeset prop=$2 + if [[ "received" == "$(get_source "$prop" "$dataset")" ]] + then + return 0 + else + return 1 + fi +} + +# +# Verify user property $2 is not set on dataset $1 +# +# $1 checked dataset +# $2 user propert +# +function check_prop_missing +{ + typeset dataset=$1 + typeset prop=$2 + if [[ "-" == "$(get_source "$prop" "$dataset")" ]] + then + return 0 + else + return 1 + fi +} + +log_assert "ZFS receive property override options work as expected." +log_onexit cleanup + +# 1. Create a filesystem with children. +log_must $ZFS create $orig +log_must $ZFS create $origsub + +# 2. Snapshot the filesystems. +log_must $ZFS snapshot $orig@snap1 +log_must $ZFS snapshot -r $orig@snap2 +log_must $ZFS snapshot -r $orig@snap3 +mntpnt=$(get_prop mountpoint $orig) +log_must eval "$DD if=/dev/urandom of=$mntpnt/file bs=1024k count=10" +log_must $ZFS snapshot -r $orig@snap4 + +# 3. Create a full stream without properties. +log_must eval "$ZFS send $orig@snap1 > $streamfile_full" + +# 4. Create also some incremental replication streams with properties. +log_must eval "$ZFS set copies=2 $orig" +log_must eval "$ZFS set '$userprop:orig'='$userval' $orig" +log_must eval "$ZFS set '$userprop:orig'='$userval' $origsub" +log_must eval "$ZFS set '$userprop:snap'='$userval' $orig@snap1" +log_must eval "$ZFS set '$userprop:snap'='$userval' $origsub@snap2" +log_must eval "$ZFS send -R -I $orig@snap1 $orig@snap2 > $streamfile_repl1" +log_must eval "$ZFS send -R -I $orig@snap2 $orig@snap3 > $streamfile_repl2" +log_must eval "$ZFS send -R -I $orig@snap3 $orig@snap4 > $streamfile_repl3.x" +log_must eval "$DD if=$streamfile_repl3.x of=$streamfile_repl3 bs=1024k count=9" + +# 5. Receive the send streams and verify we can override properties. +# 5.1 Error results if the same property is specified in multiple -o or +# -x options or an invalid value was specified. +log_mustnot eval "$ZFS recv $dest -o atime < $streamfile_full" +log_mustnot eval "$ZFS recv $dest -x atime=off < $streamfile_full" +log_mustnot eval "$ZFS recv $dest -o atime=off -x atime < $streamfile_full" +log_mustnot eval "$ZFS recv $dest -o atime=off -o atime=on < $streamfile_full" +log_mustnot eval "$ZFS recv $dest -x atime -x atime < $streamfile_full" +log_mustnot eval "$ZFS recv $dest -o version=1 < $streamfile_full" +log_mustnot eval "$ZFS recv $dest -x normalization < $streamfile_full" + +# 5.2 Verify -o property=value works on streams without properties. +log_must eval "$ZFS recv -o compression=on -o '$userprop:dest1'='$userval' "\ + "$dest < $streamfile_full" +log_must eval "check_prop_source $dest compression on local" +log_must eval "check_prop_source $dest '$userprop:dest1' '$userval' local" + +# 5.3 Verify -o property=value and -x work on both native and user +# properties for an incremental replication send stream. +log_must eval "$ZFS recv -F -o atime=off -o '$userprop:dest2'='$userval' "\ + "-o quota=123456789 -x compression -x '$userprop:orig' -x '$userprop:snap2' "\ + "$dest < $streamfile_repl1" +log_must eval "check_prop_received $dest copies" +log_must eval "check_prop_source $dest atime off local" +log_must eval "check_prop_source $dest '$userprop:dest2' '$userval' local" +log_must eval "check_prop_source $dest quota 123456789 local" +log_must eval "check_prop_inherit $destsub copies $dest" +log_must eval "check_prop_inherit $destsub atime $dest" +log_must eval "check_prop_inherit $destsub '$userprop:dest2' $dest" +log_must eval "check_prop_source $destsub quota 0 default" +log_must eval "check_prop_source $destsub compression off default" +log_must eval "check_prop_missing $dest '$userprop:orig'" +log_must eval "check_prop_missing $destsub '$userprop:orig'" +log_must eval "check_prop_received $dest@snap1 '$userprop:snap'" +log_must eval "check_prop_received $destsub@snap2 '$userprop:snap'" +log_must eval "check_prop_missing $dest@snap2 '$userprop:snap2'" +log_must eval "check_prop_missing $destsub@snap2 '$userprop:snap2'" + +# 5.4 Verify -x property does not remove existing local properties +log_must eval "$ZFS set '$userprop:orig'='newval' $destsub" +log_must eval "$ZFS recv -F -x '$userprop:orig' $dest < $streamfile_repl2" +log_must eval "check_prop_source $destsub '$userprop:orig' 'newval' local" + +# 5.5 Verify we correctly restore properties on a failed receive +log_must eval "$ZFS inherit copies $dest" +log_mustnot eval "$ZFS recv -F -o copies=3 -o quota=987654321 "\ + "-o '$userprop:new'='badval' $dest < $streamfile_repl3" +log_must eval "check_prop_source $dest copies 1 default" +log_must eval "check_prop_source $destsub copies 1 default" +log_must eval "check_prop_source $dest quota 123456789 local" +log_must eval "check_prop_source $destsub quota 0 default" +log_must eval "check_prop_missing $dest '$userprop:new'" + +log_pass "ZFS receive property override options passed."