aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLOLi <[email protected]>2017-05-10 01:21:09 +0200
committerBrian Behlendorf <[email protected]>2017-05-09 16:21:09 -0700
commita3eeab2de68670a4481eab3d086982aff23b6906 (patch)
tree5e3da58bca04309596df84dd84b591e8631eae0f
parent305bc4b370b20de81eaf10a1cf724374258b74d1 (diff)
Add property overriding (-o|-x) to 'zfs receive'
This allows users to specify "-o property=value" to override and "-x property" to exclude properties when receiving a zfs send stream. Both native and user properties can be specified. This is useful when using zfs send/receive for periodic backup/replication because it lets users change properties such as canmount, mountpoint, or compression without modifying the source. References: https://www.illumos.org/issues/2745 https://www.illumos.org/issues/3753 Reviewed by: Matthew Ahrens <[email protected]> Reviewed-by: Alek Pinchuk <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: loli10K <[email protected]> Closes #1350 Closes #5349
-rw-r--r--cmd/zfs/zfs_main.c75
-rw-r--r--include/libzfs_core.h3
-rw-r--r--lib/libzfs/libzfs_sendrecv.c241
-rw-r--r--lib/libzfs_core/libzfs_core.c48
-rw-r--r--man/man8/zfs.846
-rw-r--r--module/zfs/zfs_ioctl.c196
-rw-r--r--tests/runfiles/linux.run3
-rw-r--r--tests/zfs-tests/tests/functional/cli_root/zfs_receive/Makefile.am3
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh375
9 files changed, 901 insertions, 89 deletions
diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c
index 6ac26fdaf..e860e94b1 100644
--- a/cmd/zfs/zfs_main.c
+++ b/cmd/zfs/zfs_main.c
@@ -250,10 +250,12 @@ get_usage(zfs_help_t idx)
case HELP_PROMOTE:
return (gettext("\tpromote <clone-filesystem>\n"));
case HELP_RECEIVE:
- return (gettext("\treceive [-vnsFu] <filesystem|volume|"
- "snapshot>\n"
- "\treceive [-vnsFu] [-o origin=<snapshot>] [-d | -e] "
- "<filesystem>\n"
+ return (gettext("\treceive [-vnsFu] "
+ "[-o <property>=<value>] ... [-x <property>] ...\n"
+ "\t <filesystem|volume|snapshot>\n"
+ "\treceive [-vnsFu] [-o <property>=<value>] ... "
+ "[-x <property>] ... \n"
+ "\t [-d | -e] <filesystem>\n"
"\treceive -A <filesystem|volume>\n"));
case HELP_RENAME:
return (gettext("\trename [-f] <filesystem|volume|snapshot> "
@@ -488,26 +490,48 @@ usage(boolean_t requested)
* Take a property=value argument string and add it to the given nvlist.
* Modifies the argument inplace.
*/
-static int
+static boolean_t
parseprop(nvlist_t *props, char *propname)
{
- char *propval, *strval;
+ char *propval;
if ((propval = strchr(propname, '=')) == NULL) {
(void) fprintf(stderr, gettext("missing "
"'=' for property=value argument\n"));
- return (-1);
+ return (B_FALSE);
}
*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);
+ return (B_FALSE);
}
if (nvlist_add_string(props, propname, propval) != 0)
nomem();
- return (0);
+ return (B_TRUE);
+}
+
+/*
+ * Take a property name argument and add it to the given nvlist.
+ * Modifies the argument inplace.
+ */
+static boolean_t
+parsepropname(nvlist_t *props, char *propname)
+{
+ if (strchr(propname, '=') != NULL) {
+ (void) fprintf(stderr, gettext("invalid character "
+ "'=' in property argument\n"));
+ return (B_FALSE);
+ }
+ if (nvlist_exists(props, propname)) {
+ (void) fprintf(stderr, gettext("property '%s' "
+ "specified multiple times\n"), propname);
+ return (B_FALSE);
+ }
+ if (nvlist_add_boolean(props, propname) != 0)
+ nomem();
+ return (B_TRUE);
}
static int
@@ -661,7 +685,7 @@ zfs_do_clone(int argc, char **argv)
while ((c = getopt(argc, argv, "o:p")) != -1) {
switch (c) {
case 'o':
- if (parseprop(props, optarg) != 0) {
+ if (!parseprop(props, optarg)) {
nvlist_free(props);
return (1);
}
@@ -813,7 +837,7 @@ zfs_do_create(int argc, char **argv)
nomem();
break;
case 'o':
- if (parseprop(props, optarg) != 0)
+ if (!parseprop(props, optarg))
goto error;
break;
case 's':
@@ -3649,8 +3673,10 @@ zfs_do_set(int argc, char **argv)
if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
nomem();
for (i = 1; i < ds_start; i++) {
- if ((ret = parseprop(props, argv[i])) != 0)
+ if (!parseprop(props, argv[i])) {
+ ret = -1;
goto error;
+ }
}
ret = zfs_for_each(argc - ds_start, argv + ds_start, 0,
@@ -3717,7 +3743,7 @@ zfs_do_snapshot(int argc, char **argv)
while ((c = getopt(argc, argv, "ro:")) != -1) {
switch (c) {
case 'o':
- if (parseprop(props, optarg) != 0) {
+ if (!parseprop(props, optarg)) {
nvlist_free(sd.sd_nvl);
nvlist_free(props);
return (1);
@@ -4050,20 +4076,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) {
+ if (!parseprop(props, optarg)) {
nvlist_free(props);
- return (1);
+ usage(B_FALSE);
+ }
+ break;
+ case 'x':
+ if (!parsepropname(props, optarg)) {
+ nvlist_free(props);
+ usage(B_FALSE);
}
break;
case 'd':
@@ -4116,13 +4146,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 e67e4a130..0dfbca72b 100644
--- a/include/libzfs_core.h
+++ b/include/libzfs_core.h
@@ -81,6 +81,9 @@ int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t,
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_with_cmdprops(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 5e1896a3f..71ee8faae 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 <[email protected]>
+ * Copyright (c) 2017, loli10K <[email protected]>. All rights reserved.
*/
#include <assert.h>
@@ -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 *);
@@ -2745,7 +2746,8 @@ doagain:
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;
@@ -2921,7 +2923,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;
@@ -3086,6 +3088,131 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap,
}
/*
+ * Prepare a new nvlist of properties that are to override (-o) or be excluded
+ * (-x) from the received dataset
+ * recvprops: received properties from the send stream
+ * cmdprops: raw input properties from command line
+ * origprops: properties, both locally-set and received, currently set on the
+ * target dataset if it exists, NULL otherwise.
+ * oxprops: valid output override (-o) and excluded (-x) properties
+ */
+static int
+zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
+ boolean_t recursive, boolean_t toplevel, nvlist_t *recvprops,
+ nvlist_t *cmdprops, nvlist_t *origprops, nvlist_t **oxprops,
+ const char *errbuf)
+{
+ nvpair_t *nvp;
+ nvlist_t *oprops, *voprops;
+ zfs_handle_t *zhp = NULL;
+ zpool_handle_t *zpool_hdl = NULL;
+ int ret = 0;
+
+ if (nvlist_empty(cmdprops))
+ return (0); /* No properties to override or exclude */
+
+ *oxprops = fnvlist_alloc();
+ oprops = fnvlist_alloc();
+
+ /*
+ * first iteration: process excluded (-x) properties now and gather
+ * added (-o) properties to be later processed by zfs_valid_proplist()
+ */
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(cmdprops, nvp)) != NULL) {
+ const char *name = nvpair_name(nvp);
+ zfs_prop_t prop = zfs_name_to_prop(name);
+
+ /* "origin" is processed separately, don't handle it here */
+ if (prop == ZFS_PROP_ORIGIN)
+ continue;
+
+ /*
+ * we're trying to override or exclude a property that does not
+ * make sense for this type of dataset, but we don't want to
+ * fail if the receive is recursive: this comes in handy when
+ * the send stream contains, for instance, a child ZVOL and
+ * we're trying to receive it with "-o atime=on"
+ */
+ if (!zfs_prop_valid_for_type(prop, type, B_FALSE) &&
+ !zfs_prop_user(name)) {
+ if (recursive)
+ continue;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' does not apply to datasets of this "
+ "type"), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ switch (nvpair_type(nvp)) {
+ case DATA_TYPE_BOOLEAN: /* -x property */
+ /*
+ * DATA_TYPE_BOOLEAN is the way we're asked to "exclude"
+ * a property: this is done by forcing an explicit
+ * inherit on the destination so the effective value is
+ * not the one we received from the send stream.
+ * We do this only if the property is not already
+ * locally-set, in which case its value will take
+ * priority over the received anyway.
+ */
+ if (nvlist_exists(origprops, name)) {
+ nvlist_t *attrs;
+
+ attrs = fnvlist_lookup_nvlist(origprops, name);
+ if (strcmp(fnvlist_lookup_string(attrs,
+ ZPROP_SOURCE), ZPROP_SOURCE_VAL_RECVD) != 0)
+ continue;
+ }
+ /*
+ * We can't force an explicit inherit on non-inheritable
+ * properties: if we're asked to exclude this kind of
+ * values we remove them from "recvprops" input nvlist.
+ */
+ if (!zfs_prop_inheritable(prop) &&
+ !zfs_prop_user(name) && /* can be inherited too */
+ nvlist_exists(recvprops, name))
+ fnvlist_remove(recvprops, name);
+ else
+ fnvlist_add_nvpair(*oxprops, nvp);
+ break;
+ case DATA_TYPE_STRING: /* -o property=value */
+ fnvlist_add_nvpair(oprops, nvp);
+ break;
+ default:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' must be a string or boolean"), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ }
+
+ if (toplevel) {
+ /* convert override strings properties to native */
+ if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET,
+ oprops, zoned, zhp, zpool_hdl, errbuf)) == NULL) {
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ /* second pass: process "-o" properties */
+ fnvlist_merge(*oxprops, voprops);
+ fnvlist_free(voprops);
+ } else {
+ /* override props on child dataset are inherited */
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(oprops, nvp)) != NULL) {
+ const char *name = nvpair_name(nvp);
+ fnvlist_add_boolean(*oxprops, name);
+ }
+ }
+
+error:
+ fnvlist_free(oprops);
+ return (ret);
+}
+
+/*
* Restores a backup of tosnap from the file descriptor specified by infd.
*/
static int
@@ -3093,7 +3220,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;
@@ -3116,7 +3243,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);
@@ -3134,14 +3266,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)) {
@@ -3395,6 +3527,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 */
+ origprops = fnvlist_alloc();
+ fnvlist_merge(origprops, zhp->zfs_props);
+ fnvlist_merge(origprops, zhp->zfs_user_props);
+
zfs_close(zhp);
} else {
/*
@@ -3441,9 +3581,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
goto out;
}
- err = ioctl_err = lzc_receive_one(destsnap, props, origin,
- flags->force, flags->resumable, infd, drr_noswap, cleanup_fd,
- &read_bytes, &errflags, action_handlep, &prop_errors);
+ toplevel = chopprefix[0] != '/';
+ if (drrb->drr_type == DMU_OST_ZVOL) {
+ type = ZFS_TYPE_VOLUME;
+ } else if (drrb->drr_type == DMU_OST_ZFS) {
+ type = ZFS_TYPE_FILESYSTEM;
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid record type: 0x%d"), drrb->drr_type);
+ err = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+ if ((err = zfs_setup_cmdline_props(hdl, type, zoned, recursive,
+ toplevel, rcvprops, cmdprops, origprops, &oxprops, errbuf)) != 0)
+ goto out;
+
+ err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, oxprops,
+ origin, flags->force, flags->resumable, infd, drr_noswap,
+ cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors);
ioctl_errno = ioctl_err;
prop_errflags = errflags;
@@ -3658,16 +3813,65 @@ out:
nvlist_free(prop_errors);
if (newprops)
- nvlist_free(props);
+ nvlist_free(rcvprops);
+
+ nvlist_free(oxprops);
+ nvlist_free(origprops);
return (err);
}
+/*
+ * Check properties we were asked to override (both -o|-x)
+ */
+static boolean_t
+zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props,
+ const char *errbuf)
+{
+ nvpair_t *nvp;
+ zfs_prop_t prop;
+ const char *name;
+
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+ prop = zfs_name_to_prop(name);
+
+ if (prop == ZPROP_INVAL) {
+ if (!zfs_prop_user(name)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property '%s'"), name);
+ return (B_FALSE);
+ }
+ continue;
+ }
+ /*
+ * "origin" is readonly but is used to receive datasets as
+ * clones so we don't raise an error here
+ */
+ if (prop == ZFS_PROP_ORIGIN)
+ continue;
+
+ /*
+ * cannot override readonly, set-once and other specific
+ * settable properties
+ */
+ if (zfs_prop_readonly(prop) || prop == ZFS_PROP_VERSION ||
+ prop == ZFS_PROP_VOLSIZE) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property '%s'"), name);
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
static int
zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
const char *originsnap, recvflags_t *flags, int infd, const char *sendfs,
nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
- uint64_t *action_handlep, const char *finalsnap)
+ uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops)
{
int err;
dmu_replay_record_t drr, drr_noswap;
@@ -3680,6 +3884,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive"));
+ /* check cmdline props, raise an error if they cannot be received */
+ if (!zfs_receive_checkprops(hdl, cmdprops, errbuf)) {
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+
if (flags->isprefix &&
!zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs "
@@ -3768,12 +3977,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));
}
}
@@ -3846,7 +4055,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 591e769dd..17f9931a0 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));
}
@@ -816,7 +825,26 @@ int lzc_receive_one(const char *snapname, nvlist_t *props,
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, NULL, origin, force, resumable,
+ input_fd, begin_record, cleanup_fd, read_bytes, errflags,
+ action_handle, errors));
+}
+
+/*
+ * Like lzc_receive_one, but allows the caller to pass an additional 'cmdprops'
+ * argument.
+ *
+ * The 'cmdprops' nvlist contains both override ('zfs receive -o') and
+ * exclude ('zfs receive -x') properties. Callers are responsible for freeing
+ * this nvlist
+ */
+int lzc_receive_with_cmdprops(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, 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 5d0b79b99..c591ca788 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] ...
+ \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] \fIfilesystem\fR
.fi
.LP
@@ -2984,11 +2986,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] ... \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] \fIfilesystem\fR\fR
.ad
.sp .6
.RS 4n
@@ -2998,6 +3000,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. Specifying \fB-o origin\fR=\fIsnapshot\fR is a special case because, even if \fBorigin\fR is a read-only property and cannot be set, it allows to receive the send stream as a clone of the given snapshot.
+.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.
@@ -3060,6 +3064,40 @@ Print verbose information about the stream and the time required to perform the
.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 immediately before the receive. 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 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. Additionally both settable properties \fBversion\fR and \fBvolsize\fR cannot be set at receive time.
+.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 will be set or inherited, depending on whether the property is inheritable or not.
+.sp
+In the case of an incremental update, \fB-x\fR leaves any existing local setting or explicit inheritance unchanged.
+.sp
+All \fB-o\fR restrictions on set-once and special properties apply equally to \fB-x\fR.
+.RE
+
+.sp
+.ne 2
.na
\fB\fB-d\fR\fR
.ad
diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c
index 109762795..f94bf3b1c 100644
--- a/module/zfs/zfs_ioctl.c
+++ b/module/zfs/zfs_ioctl.c
@@ -33,6 +33,7 @@
* Copyright (c) 2014 Integros [integros.com]
* Copyright 2016 Toomas Soome <[email protected]>
* Copyright (c) 2016 Actifio, Inc. All rights reserved.
+ * Copyright (c) 2017, loli10K <[email protected]>. All rights reserved.
*/
/*
@@ -2497,7 +2498,11 @@ retry:
}
/* 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);
@@ -2542,7 +2547,11 @@ retry:
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
@@ -2594,6 +2603,9 @@ retry:
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,
@@ -4159,8 +4171,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)
{
@@ -4170,6 +4182,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;
@@ -4191,7 +4204,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))
@@ -4202,7 +4215,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
@@ -4213,29 +4226,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);
@@ -4284,7 +4344,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);
}
@@ -4303,8 +4363,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
@@ -4315,7 +4375,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;
}
@@ -4326,10 +4386,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.
@@ -4337,8 +4397,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)
@@ -4351,6 +4467,8 @@ out:
* 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 exclude
+ * (DATA_TYPE_BOOLEAN) and override (everything else)
* zc_value name of snapshot to create
* zc_string name of clone origin (if DRR_FLAG_CLONE)
* zc_cookie file descriptor to recv from
@@ -4370,7 +4488,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];
@@ -4387,7 +4506,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])
@@ -4397,10 +4521,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
@@ -4424,7 +4550,8 @@ zfs_ioc_recv(zfs_cmd_t *zc)
/*
* innvl: {
* "snapname" -> full name of the snapshot to create
- * (optional) "props" -> properties to set (nvlist)
+ * (optional) "props" -> 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)
@@ -4447,7 +4574,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;
@@ -4498,12 +4626,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);
@@ -4512,7 +4645,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 d8c70ab02..5476c06a5 100644
--- a/tests/runfiles/linux.run
+++ b/tests/runfiles/linux.run
@@ -145,7 +145,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 3c87a9927..87e543b00 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 000000000..9574bd43b
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh
@@ -0,0 +1,375 @@
+#!/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 2017, loli10K <[email protected]>. 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 (-o) and exclude (-x) options work when
+# receiving a send stream
+#
+# STRATEGY:
+# 1. Create a filesystem with children.
+# 2. Snapshot the filesystems.
+# 3. Create various send streams (full, incremental, replication) and verify
+# we can both override and exclude native and user properties.
+#
+
+verify_runnable "both"
+
+function cleanup
+{
+ log_must rm -f $streamfile_full
+ log_must rm -f $streamfile_incr
+ log_must rm -f $streamfile_repl
+ log_must rm -f $streamfile_trun
+ log_must zfs destroy -r -f $orig
+ log_must zfs destroy -r -f $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 property $2 received value on dataset $1 has value $3
+#
+# $1 checked dataset
+# $2 property name
+# $3 checked value
+#
+function check_prop_received
+{
+ typeset dataset="$1"
+ typeset prop="$2"
+ typeset value="$3"
+
+ received=$(zfs get -H -o received "$prop" "$dataset")
+ if (($? != 0)); then
+ log_fail "Unable to get $prop received value for dataset " \
+ "$dataset"
+ fi
+ if [[ "$received" == "$value" ]]
+ then
+ return 0
+ else
+ return 1
+ fi
+}
+
+#
+# Verify user property $2 is not set on dataset $1
+#
+# $1 checked dataset
+# $2 property name
+#
+function check_prop_missing
+{
+ typeset dataset="$1"
+ typeset prop="$2"
+
+ value=$(zfs get -H -o value "$prop" "$dataset")
+ if (($? != 0)); then
+ log_fail "Unable to get $prop value for dataset $dataset"
+ fi
+ if [[ "-" == "$value" ]]
+ then
+ return 0
+ else
+ return 1
+ fi
+}
+
+log_assert "ZFS receive property override and exclude options work as expected."
+log_onexit cleanup
+
+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=$TESTDIR/streamfile_full.$$
+typeset streamfile_incr=$TESTDIR/streamfile_incr.$$
+typeset streamfile_repl=$TESTDIR/streamfile_repl.$$
+typeset streamfile_trun=$TESTDIR/streamfile_trun.$$
+
+#
+# 3.1 Verify we can't specify the same property in multiple -o or -x options
+# or an invalid value was specified.
+#
+# Create a full send stream
+log_must zfs create $orig
+log_must zfs snapshot $orig@snap1
+log_must eval "zfs send $orig@snap1 > $streamfile_full"
+# Verify we reject invalid options
+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 version < $streamfile_full"
+log_mustnot eval "zfs recv $dest -x normalization < $streamfile_full"
+# Verify we also reject invalid ZVOL options
+log_must zfs create -V 32K -s $orig/zvol
+log_must eval "zfs send $orig@snap1 > $streamfile_full"
+log_mustnot eval "zfs recv $dest -x volsize < $streamfile_full"
+log_mustnot eval "zfs recv $dest -o volsize=32K < $streamfile_full"
+# Cleanup
+log_must zfs destroy -r -f $orig
+
+#
+# 3.2 Verify -o property=value works on streams without properties.
+#
+# Create a full send stream
+log_must zfs create $orig
+log_must zfs snapshot $orig@snap1
+log_must eval "zfs send $orig@snap1 > $streamfile_full"
+# Receive the full stream, override some properties
+log_must eval "zfs recv -o compression=on -o '$userprop:dest'='$userval' "\
+ "$dest < $streamfile_full"
+log_must eval "check_prop_source $dest compression on local"
+log_must eval "check_prop_source $dest '$userprop:dest' '$userval' local"
+# Cleanup
+log_must zfs destroy -r -f $orig
+log_must zfs destroy -r -f $dest
+
+#
+# 3.3 Verify -o property=value and -x work on both native and user properties
+# for an incremental replication send stream.
+#
+# Create a dataset tree and receive it
+log_must zfs create $orig
+log_must zfs create $origsub
+log_must zfs snapshot -r $orig@snap1
+log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
+log_must eval "zfs recv $dest < $streamfile_repl"
+# Fill the datasets with properties and create an incremental replication stream
+log_must zfs snapshot -r $orig@snap2
+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_incr"
+# Sets various combination of override and exclude options
+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_incr"
+# Verify we can correctly override and exclude properties
+log_must eval "check_prop_source $dest copies 2 received"
+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_source " \
+ "$dest@snap1 '$userprop:snap' '$userval' received"
+log_must eval "check_prop_source " \
+ "$destsub@snap2 '$userprop:snap' '$userval' received"
+log_must eval "check_prop_missing $dest@snap2 '$userprop:snap2'"
+log_must eval "check_prop_missing $destsub@snap2 '$userprop:snap2'"
+# Cleanup
+log_must zfs destroy -r -f $orig
+log_must zfs destroy -r -f $dest
+
+#
+# 3.4 Verify '-x property' does not remove existing local properties and a
+# modified sent property is received and updated to the new value but can
+# still be excluded.
+#
+# Create a dataset tree
+log_must zfs create $orig
+log_must zfs create $origsub
+log_must zfs snapshot -r $orig@snap1
+log_must eval "zfs set copies=2 $orig"
+log_must eval "zfs set '$userprop:orig'='oldval' $orig"
+log_must eval "zfs set '$userprop:orig'='oldsubval' $origsub"
+log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
+log_must eval "zfs receive $dest < $streamfile_repl"
+log_must eval "check_prop_source $dest copies 2 received"
+log_must eval "check_prop_inherit $destsub copies $dest"
+log_must eval "check_prop_source $dest '$userprop:orig' 'oldval' received"
+log_must eval "check_prop_source $destsub '$userprop:orig' 'oldsubval' received"
+# Set new custom properties on both source and destination
+log_must eval "zfs set copies=3 $orig"
+log_must eval "zfs set '$userprop:orig'='newval' $orig"
+log_must eval "zfs set '$userprop:orig'='newsubval' $origsub"
+log_must eval "zfs set compression=gzip $dest"
+log_must eval "zfs set '$userprop:dest'='localval' $dest"
+# Receive the new stream, verify we preserve locally set properties
+log_must zfs snapshot -r $orig@snap2
+log_must eval "zfs send -R -I $orig@snap1 $orig@snap2 > $streamfile_incr"
+log_must eval "zfs recv -F -x copies -x compression -x '$userprop:orig' " \
+ "-x '$userprop:dest' $dest < $streamfile_incr"
+log_must eval "check_prop_source $dest '$userprop:dest' 'localval' local"
+log_must eval "check_prop_received $dest '$userprop:orig' 'newval'"
+log_must eval "check_prop_received $destsub '$userprop:orig' 'newsubval'"
+log_must eval "check_prop_missing $dest '$userprop:orig'"
+log_must eval "check_prop_missing $destsub '$userprop:orig'"
+log_must eval "check_prop_source $dest copies 1 default"
+log_must eval "check_prop_received $dest copies 3"
+log_must eval "check_prop_source $destsub copies 1 default"
+log_must eval "check_prop_received $destsub copies '-'"
+log_must eval "check_prop_source $dest compression gzip local"
+log_must eval "check_prop_inherit $destsub compression $dest"
+# Cleanup
+log_must zfs destroy -r -f $orig
+log_must zfs destroy -r -f $dest
+
+#
+# 3.5 Verify we can exclude non-inheritable properties from a send stream
+#
+# Create a dataset tree and replication stream
+log_must zfs create $orig
+log_must zfs create $origsub
+log_must zfs snapshot -r $orig@snap1
+log_must eval "zfs set quota=123456789 $orig"
+log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
+# Receive the stream excluding non-inheritable properties
+log_must eval "zfs recv -F -x quota $dest < $streamfile_repl"
+log_must eval "check_prop_source $dest quota 0 default"
+log_must eval "check_prop_source $destsub quota 0 default"
+# Set some non-inheritable properties on the destination, verify we keep them
+log_must eval "zfs set quota=123456789 $dest"
+log_must eval "zfs set canmount=off $destsub"
+log_must zfs snapshot -r $orig@snap2
+log_must eval "zfs send -R -I $orig@snap1 $orig@snap2 > $streamfile_incr"
+log_must eval "zfs recv -F -x quota -x canmount $dest < $streamfile_incr"
+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_source $destsub canmount off local"
+# Cleanup
+log_must zfs destroy -r -f $orig
+log_must zfs destroy -r -f $dest
+
+#
+# 3.6 Verify we correctly restore existing properties on a failed receive
+#
+# Receive a "clean" dataset tree
+log_must zfs create $orig
+log_must zfs create $origsub
+log_must zfs snapshot -r $orig@snap1
+log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
+log_must eval "zfs receive $dest < $streamfile_repl"
+# Set custom properties on the destination
+log_must eval "zfs set atime=off $dest"
+log_must eval "zfs set quota=123456789 $dest"
+log_must eval "zfs set '$userprop:orig'='$userval' $dest"
+log_must eval "zfs set '$userprop:origsub'='$userval' $destsub"
+# Create a truncated incremental replication stream
+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@snap2
+log_must eval "zfs send -R -I $orig@snap1 $orig@snap2 > $streamfile_incr"
+log_must eval "dd if=$streamfile_incr of=$streamfile_trun bs=1024k count=9"
+# Receive the truncated stream, verify original properties are kept
+log_mustnot eval "zfs recv -F -o copies=3 -o quota=987654321 "\
+ "-o '$userprop:new'='badval' $dest < $streamfile_trun"
+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 atime off local"
+log_must eval "check_prop_inherit $destsub atime $dest"
+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_source $dest '$userprop:orig' '$userval' local"
+log_must eval "check_prop_inherit $destsub '$userprop:orig' $dest"
+log_must eval "check_prop_source $destsub '$userprop:origsub' '$userval' local"
+log_must eval "check_prop_missing $dest '$userprop:new'"
+# Cleanup
+log_must zfs destroy -r -f $orig
+log_must zfs destroy -r -f $dest
+
+#
+# 3.7 Verify we can't receive a send stream overriding or excluding properties
+# invalid for the dataset type unless the stream it's recursive, in which
+# case only the appropriate properties are set on the destination.
+#
+log_must zfs create -V 128K -s $orig
+log_must zfs snapshot $orig@snap1
+log_must eval "zfs send $orig@snap1 > $streamfile_full"
+log_mustnot eval "zfs receive -x atime $dest < $streamfile_full"
+log_mustnot eval "zfs receive -o atime=off $dest < $streamfile_full"
+log_must zfs destroy -r -f $orig
+log_must zfs create $orig
+log_must zfs create -V 128K -s $origsub
+log_must zfs snapshot -r $orig@snap1
+log_must eval "zfs send -R $orig@snap1 > $streamfile_repl"
+log_must eval "zfs receive -o atime=off $dest < $streamfile_repl"
+log_must eval "check_prop_source $dest type filesystem -"
+log_must eval "check_prop_source $dest atime off local"
+log_must eval "check_prop_source $destsub type volume -"
+log_must eval "check_prop_source $destsub atime - -"
+# We don't need to cleanup here
+
+log_pass "ZFS receive property override and exclude options passed."