summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/zfs/zfs_ioctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/fs/zfs/zfs_ioctl.c')
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_ioctl.c1162
1 files changed, 847 insertions, 315 deletions
diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
index dfb4ca6c8f..687385f086 100644
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
@@ -102,13 +102,14 @@ static const char *userquota_perms[] = {
ZFS_DELEG_PERM_GROUPQUOTA,
};
-static char *setsl_tag = "setsl_tag";
-
static int zfs_ioc_userspace_upgrade(zfs_cmd_t *zc);
-static void clear_props(char *dataset, nvlist_t *props, nvlist_t *newprops);
+static int zfs_check_settable(const char *name, nvpair_t *property,
+ cred_t *cr);
+static int zfs_check_clearable(char *dataset, nvlist_t *props,
+ nvlist_t **errors);
static int zfs_fill_zplprops_root(uint64_t, nvlist_t *, nvlist_t *,
boolean_t *);
-int zfs_set_prop_nvlist(const char *, nvlist_t *);
+int zfs_set_prop_nvlist(const char *, zprop_source_t, nvlist_t *, nvlist_t **);
/* _NOTE(PRINTFLIKE(4)) - this is printf-like, but lint is too whiney */
void
@@ -334,14 +335,9 @@ zfs_secpolicy_write_perms(const char *name, const char *perm, cred_t *cr)
* Policy for setting the security label property.
*
* Returns 0 for success, non-zero for access and other errors.
- *
- * If the objset is non-NULL upon return, the caller is responsible
- * for dis-owning it, using the tag: setsl_tag.
- *
*/
static int
-zfs_set_slabel_policy(const char *name, char *strval, cred_t *cr,
- objset_t **osp)
+zfs_set_slabel_policy(const char *name, char *strval, cred_t *cr)
{
char ds_hexsl[MAXNAMELEN];
bslabel_t ds_sl, new_sl;
@@ -394,16 +390,20 @@ zfs_set_slabel_policy(const char *name, char *strval, cred_t *cr,
* mounted (or isn't a dataset, doesn't exist, ...).
*/
if (strcasecmp(ds_hexsl, ZFS_MLSLABEL_DEFAULT) != 0) {
- ASSERT(osp != NULL);
+ objset_t *os;
+ static char *setsl_tag = "setsl_tag";
+
/*
* Try to own the dataset; abort if there is any error,
* (e.g., already mounted, in use, or other error).
*/
error = dmu_objset_own(name, DMU_OST_ZFS, B_TRUE,
- setsl_tag, osp);
+ setsl_tag, &os);
if (error)
return (EPERM);
+ dmu_objset_disown(os, setsl_tag);
+
if (new_default) {
needed_priv = PRIV_FILE_DOWNGRADE_SL;
goto out_check;
@@ -429,8 +429,11 @@ out_check:
}
static int
-zfs_secpolicy_setprop(const char *name, zfs_prop_t prop, cred_t *cr)
+zfs_secpolicy_setprop(const char *dsname, zfs_prop_t prop, nvpair_t *propval,
+ cred_t *cr)
{
+ char *strval;
+
/*
* Check permissions for special properties.
*/
@@ -452,10 +455,10 @@ zfs_secpolicy_setprop(const char *name, zfs_prop_t prop, cred_t *cr)
* quota on things *under* (ie. contained by)
* the thing they own.
*/
- if (dsl_prop_get_integer(name, "zoned", &zoned,
+ if (dsl_prop_get_integer(dsname, "zoned", &zoned,
setpoint))
return (EPERM);
- if (!zoned || strlen(name) <= strlen(setpoint))
+ if (!zoned || strlen(dsname) <= strlen(setpoint))
return (EPERM);
}
break;
@@ -463,10 +466,18 @@ zfs_secpolicy_setprop(const char *name, zfs_prop_t prop, cred_t *cr)
case ZFS_PROP_MLSLABEL:
if (!is_system_labeled())
return (EPERM);
+
+ if (nvpair_value_string(propval, &strval) == 0) {
+ int err;
+
+ err = zfs_set_slabel_policy(dsname, strval, CRED());
+ if (err != 0)
+ return (err);
+ }
break;
}
- return (zfs_secpolicy_write_perms(name, zfs_prop_to_name(prop), cr));
+ return (zfs_secpolicy_write_perms(dsname, zfs_prop_to_name(prop), cr));
}
int
@@ -602,7 +613,7 @@ zfs_secpolicy_iscsi(zfs_cmd_t *zc, cred_t *cr)
int
zfs_secpolicy_rename_perms(const char *from, const char *to, cred_t *cr)
{
- char parentname[MAXNAMELEN];
+ char parentname[MAXNAMELEN];
int error;
if ((error = zfs_secpolicy_write_perms(from,
@@ -637,7 +648,7 @@ zfs_secpolicy_rename(zfs_cmd_t *zc, cred_t *cr)
static int
zfs_secpolicy_promote(zfs_cmd_t *zc, cred_t *cr)
{
- char parentname[MAXNAMELEN];
+ char parentname[MAXNAMELEN];
objset_t *clone;
int error;
@@ -709,8 +720,8 @@ zfs_secpolicy_snapshot(zfs_cmd_t *zc, cred_t *cr)
static int
zfs_secpolicy_create(zfs_cmd_t *zc, cred_t *cr)
{
- char parentname[MAXNAMELEN];
- int error;
+ char parentname[MAXNAMELEN];
+ int error;
if ((error = zfs_get_parent(zc->zc_name, parentname,
sizeof (parentname))) != 0)
@@ -779,9 +790,8 @@ zfs_secpolicy_inherit(zfs_cmd_t *zc, cred_t *cr)
return (zfs_secpolicy_write_perms(zc->zc_name,
ZFS_DELEG_PERM_USERPROP, cr));
} else {
- if (!zfs_prop_inheritable(prop))
- return (EINVAL);
- return (zfs_secpolicy_setprop(zc->zc_name, prop, cr));
+ return (zfs_secpolicy_setprop(zc->zc_name, prop,
+ NULL, cr));
}
}
@@ -831,7 +841,8 @@ zfs_secpolicy_userspace_many(zfs_cmd_t *zc, cred_t *cr)
static int
zfs_secpolicy_userspace_upgrade(zfs_cmd_t *zc, cred_t *cr)
{
- return (zfs_secpolicy_setprop(zc->zc_name, ZFS_PROP_VERSION, cr));
+ return (zfs_secpolicy_setprop(zc->zc_name, ZFS_PROP_VERSION,
+ NULL, cr));
}
static int
@@ -884,6 +895,41 @@ get_nvlist(uint64_t nvl, uint64_t size, int iflag, nvlist_t **nvp)
}
static int
+fit_error_list(zfs_cmd_t *zc, nvlist_t **errors)
+{
+ size_t size;
+
+ VERIFY(nvlist_size(*errors, &size, NV_ENCODE_NATIVE) == 0);
+
+ if (size > zc->zc_nvlist_dst_size) {
+ nvpair_t *more_errors;
+ int n = 0;
+
+ if (zc->zc_nvlist_dst_size < 1024)
+ return (ENOMEM);
+
+ VERIFY(nvlist_add_int32(*errors, ZPROP_N_MORE_ERRORS, 0) == 0);
+ more_errors = nvlist_prev_nvpair(*errors, NULL);
+
+ do {
+ nvpair_t *pair = nvlist_prev_nvpair(*errors,
+ more_errors);
+ VERIFY(nvlist_remove_nvpair(*errors, pair) == 0);
+ n++;
+ VERIFY(nvlist_size(*errors, &size,
+ NV_ENCODE_NATIVE) == 0);
+ } while (size > zc->zc_nvlist_dst_size);
+
+ VERIFY(nvlist_remove_nvpair(*errors, more_errors) == 0);
+ VERIFY(nvlist_add_int32(*errors, ZPROP_N_MORE_ERRORS, n) == 0);
+ ASSERT(nvlist_size(*errors, &size, NV_ENCODE_NATIVE) == 0);
+ ASSERT(size <= zc->zc_nvlist_dst_size);
+ }
+
+ return (0);
+}
+
+static int
put_nvlist(zfs_cmd_t *zc, nvlist_t *nvl)
{
char *packed = NULL;
@@ -1026,8 +1072,8 @@ zfs_ioc_pool_create(zfs_cmd_t *zc)
/*
* Set the remaining root properties
*/
- if (!error &&
- (error = zfs_set_prop_nvlist(zc->zc_name, rootprops)) != 0)
+ if (!error && (error = zfs_set_prop_nvlist(zc->zc_name,
+ ZPROP_SRC_LOCAL, rootprops, NULL)) != 0)
(void) spa_destroy(zc->zc_name);
if (buf != NULL)
@@ -1487,7 +1533,7 @@ zfs_ioc_objset_stats(zfs_cmd_t *zc)
dmu_objset_fast_stat(os, &zc->zc_objset_stats);
if (zc->zc_nvlist_dst != 0 &&
- (error = dsl_prop_get_all(os, &nv, FALSE)) == 0) {
+ (error = dsl_prop_get_all(os, &nv)) == 0) {
dmu_objset_stats(os, nv);
/*
* NB: zvol_get_stats() will read the objset contents,
@@ -1508,6 +1554,49 @@ zfs_ioc_objset_stats(zfs_cmd_t *zc)
return (error);
}
+/*
+ * inputs:
+ * zc_name name of filesystem
+ * zc_nvlist_dst_size size of buffer for property nvlist
+ *
+ * outputs:
+ * zc_nvlist_dst received property nvlist
+ * zc_nvlist_dst_size size of received property nvlist
+ *
+ * Gets received properties (distinct from local properties on or after
+ * SPA_VERSION_RECVD_PROPS) for callers who want to differentiate received from
+ * local property values.
+ */
+static int
+zfs_ioc_objset_recvd_props(zfs_cmd_t *zc)
+{
+ objset_t *os = NULL;
+ int error;
+ nvlist_t *nv;
+
+ if (error = dmu_objset_hold(zc->zc_name, FTAG, &os))
+ return (error);
+
+ /*
+ * Without this check, we would return local property values if the
+ * caller has not already received properties on or after
+ * SPA_VERSION_RECVD_PROPS.
+ */
+ if (!dsl_prop_get_hasrecvd(os)) {
+ dmu_objset_rele(os, FTAG);
+ return (ENOTSUP);
+ }
+
+ if (zc->zc_nvlist_dst != 0 &&
+ (error = dsl_prop_get_received(os, &nv)) == 0) {
+ error = put_nvlist(zc, nv);
+ nvlist_free(nv);
+ }
+
+ dmu_objset_rele(os, FTAG);
+ return (error);
+}
+
static int
nvl_add_zplprop(objset_t *os, nvlist_t *props, zfs_prop_t prop)
{
@@ -1698,293 +1787,289 @@ zfs_ioc_snapshot_list_next(zfs_cmd_t *zc)
return (error);
}
-int
-zfs_set_prop_nvlist(const char *name, nvlist_t *nvl)
+static int
+zfs_prop_set_userquota(const char *dsname, nvpair_t *pair)
{
- nvpair_t *elem;
- int error = 0;
- uint64_t intval;
- char *strval;
- nvlist_t *genericnvl;
- boolean_t issnap = (strchr(name, '@') != NULL);
+ const char *propname = nvpair_name(pair);
+ uint64_t *valary;
+ unsigned int vallen;
+ const char *domain;
+ zfs_userquota_prop_t type;
+ uint64_t rid;
+ uint64_t quota;
+ zfsvfs_t *zfsvfs;
+ int err;
+
+ if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(pair, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &pair) == 0);
+ }
+ VERIFY(nvpair_value_uint64_array(pair, &valary, &vallen) == 0);
+ VERIFY(vallen == 3);
+ type = valary[0];
+ rid = valary[1];
+ quota = valary[2];
/*
- * First validate permission to set all of the properties
+ * The propname is encoded as
+ * userquota@<rid>-<domain>.
*/
- elem = NULL;
- while ((elem = nvlist_next_nvpair(nvl, elem)) != NULL) {
- const char *propname = nvpair_name(elem);
- zfs_prop_t prop = zfs_name_to_prop(propname);
+ domain = strchr(propname, '-') + 1;
- if (prop == ZPROP_INVAL) {
- /*
- * If this is a user-defined property, it must be a
- * string, and there is no further validation to do.
- */
- if (zfs_prop_user(propname) &&
- nvpair_type(elem) == DATA_TYPE_STRING) {
- if (error = zfs_secpolicy_write_perms(name,
- ZFS_DELEG_PERM_USERPROP, CRED()))
- return (error);
- continue;
- }
+ err = zfsvfs_hold(dsname, FTAG, &zfsvfs);
+ if (err == 0) {
+ err = zfs_set_userquota(zfsvfs, type, domain, rid, quota);
+ zfsvfs_rele(zfsvfs, FTAG);
+ }
- if (!issnap && zfs_prop_userquota(propname) &&
- nvpair_type(elem) == DATA_TYPE_UINT64_ARRAY) {
- const char *perm;
- const char *up = zfs_userquota_prop_prefixes
- [ZFS_PROP_USERQUOTA];
- if (strncmp(propname, up, strlen(up)) == 0)
- perm = ZFS_DELEG_PERM_USERQUOTA;
- else
- perm = ZFS_DELEG_PERM_GROUPQUOTA;
- if (error = zfs_secpolicy_write_perms(name,
- perm, CRED()))
- return (error);
- continue;
- }
+ return (err);
+}
- return (EINVAL);
- }
+/*
+ * If the named property is one that has a special function to set its value,
+ * return 0 on success and a positive error code on failure; otherwise if it is
+ * not one of the special properties handled by this function, return -1.
+ *
+ * XXX: It would be better for callers of the properety interface if we handled
+ * these special cases in dsl_prop.c (in the dsl layer).
+ */
+static int
+zfs_prop_set_special(const char *dsname, zprop_source_t source,
+ nvpair_t *pair)
+{
+ const char *propname = nvpair_name(pair);
+ zfs_prop_t prop = zfs_name_to_prop(propname);
+ uint64_t intval;
+ int err;
- if (issnap)
- return (EINVAL);
+ if (prop == ZPROP_INVAL) {
+ if (zfs_prop_userquota(propname))
+ return (zfs_prop_set_userquota(dsname, pair));
+ return (-1);
+ }
- if ((error = zfs_secpolicy_setprop(name, prop, CRED())) != 0)
- return (error);
+ if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(pair, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &pair) == 0);
+ }
- /*
- * Check that this value is valid for this pool version
- */
- switch (prop) {
- case ZFS_PROP_COMPRESSION:
- /*
- * If the user specified gzip compression, make sure
- * the SPA supports it. We ignore any errors here since
- * we'll catch them later.
- */
- if (nvpair_type(elem) == DATA_TYPE_UINT64 &&
- nvpair_value_uint64(elem, &intval) == 0) {
- if (intval >= ZIO_COMPRESS_GZIP_1 &&
- intval <= ZIO_COMPRESS_GZIP_9 &&
- zfs_earlier_version(name,
- SPA_VERSION_GZIP_COMPRESSION))
- return (ENOTSUP);
-
- if (intval == ZIO_COMPRESS_ZLE &&
- zfs_earlier_version(name,
- SPA_VERSION_ZLE_COMPRESSION))
- return (ENOTSUP);
+ if (zfs_prop_get_type(prop) == PROP_TYPE_STRING)
+ return (-1);
- /*
- * If this is a bootable dataset then
- * verify that the compression algorithm
- * is supported for booting. We must return
- * something other than ENOTSUP since it
- * implies a downrev pool version.
- */
- if (zfs_is_bootfs(name) &&
- !BOOTFS_COMPRESS_VALID(intval))
- return (ERANGE);
- }
- break;
+ VERIFY(0 == nvpair_value_uint64(pair, &intval));
- case ZFS_PROP_COPIES:
- if (zfs_earlier_version(name, SPA_VERSION_DITTO_BLOCKS))
- return (ENOTSUP);
- break;
+ switch (prop) {
+ case ZFS_PROP_QUOTA:
+ err = dsl_dir_set_quota(dsname, source, intval);
+ break;
+ case ZFS_PROP_REFQUOTA:
+ err = dsl_dataset_set_quota(dsname, source, intval);
+ break;
+ case ZFS_PROP_RESERVATION:
+ err = dsl_dir_set_reservation(dsname, source, intval);
+ break;
+ case ZFS_PROP_REFRESERVATION:
+ err = dsl_dataset_set_reservation(dsname, source, intval);
+ break;
+ case ZFS_PROP_VOLSIZE:
+ err = zvol_set_volsize(dsname, ddi_driver_major(zfs_dip),
+ intval);
+ break;
+ case ZFS_PROP_VERSION:
+ {
+ zfsvfs_t *zfsvfs;
- case ZFS_PROP_DEDUP:
- if (zfs_earlier_version(name, SPA_VERSION_DEDUP))
- return (ENOTSUP);
+ if ((err = zfsvfs_hold(dsname, FTAG, &zfsvfs)) != 0)
break;
- case ZFS_PROP_SHARESMB:
- if (zpl_earlier_version(name, ZPL_VERSION_FUID))
- return (ENOTSUP);
- break;
+ err = zfs_set_version(zfsvfs, intval);
+ zfsvfs_rele(zfsvfs, FTAG);
- case ZFS_PROP_ACLINHERIT:
- if (nvpair_type(elem) == DATA_TYPE_UINT64 &&
- nvpair_value_uint64(elem, &intval) == 0)
- if (intval == ZFS_ACL_PASSTHROUGH_X &&
- zfs_earlier_version(name,
- SPA_VERSION_PASSTHROUGH_X))
- return (ENOTSUP);
+ if (err == 0 && intval >= ZPL_VERSION_USERSPACE) {
+ zfs_cmd_t zc = { 0 };
+ (void) strcpy(zc.zc_name, dsname);
+ (void) zfs_ioc_userspace_upgrade(&zc);
}
+ break;
}
- VERIFY(nvlist_alloc(&genericnvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
- elem = NULL;
- while ((elem = nvlist_next_nvpair(nvl, elem)) != NULL) {
- const char *propname = nvpair_name(elem);
- zfs_prop_t prop = zfs_name_to_prop(propname);
-
- if (prop == ZPROP_INVAL) {
- if (zfs_prop_userquota(propname)) {
- uint64_t *valary;
- unsigned int vallen;
- const char *domain;
- zfs_userquota_prop_t type;
- uint64_t rid;
- uint64_t quota;
- zfsvfs_t *zfsvfs;
-
- VERIFY(nvpair_value_uint64_array(elem,
- &valary, &vallen) == 0);
- VERIFY(vallen == 3);
- type = valary[0];
- rid = valary[1];
- quota = valary[2];
- /*
- * The propname is encoded as
- * userquota@<rid>-<domain>.
- */
- domain = strchr(propname, '-') + 1;
-
- error = zfsvfs_hold(name, FTAG, &zfsvfs);
- if (error == 0) {
- error = zfs_set_userquota(zfsvfs,
- type, domain, rid, quota);
- zfsvfs_rele(zfsvfs, FTAG);
- }
- if (error == 0)
- continue;
- else
- goto out;
- } else if (zfs_prop_user(propname)) {
- VERIFY(nvpair_value_string(elem, &strval) == 0);
- error = dsl_prop_set(name, propname, 1,
- strlen(strval) + 1, strval);
- if (error == 0)
- continue;
- else
- goto out;
- }
- }
-
- switch (prop) {
- case ZFS_PROP_QUOTA:
- if ((error = nvpair_value_uint64(elem, &intval)) != 0 ||
- (error = dsl_dir_set_quota(name, intval)) != 0)
- goto out;
- break;
+ default:
+ err = -1;
+ }
- case ZFS_PROP_REFQUOTA:
- if ((error = nvpair_value_uint64(elem, &intval)) != 0 ||
- (error = dsl_dataset_set_quota(name, intval)) != 0)
- goto out;
- break;
+ return (err);
+}
- case ZFS_PROP_RESERVATION:
- if ((error = nvpair_value_uint64(elem, &intval)) != 0 ||
- (error = dsl_dir_set_reservation(name,
- intval)) != 0)
- goto out;
- break;
+/*
+ * This function is best effort. If it fails to set any of the given properties,
+ * it continues to set as many as it can and returns the first error
+ * encountered. If the caller provides a non-NULL errlist, it also gives the
+ * complete list of names of all the properties it failed to set along with the
+ * corresponding error numbers. The caller is responsible for freeing the
+ * returned errlist.
+ *
+ * If every property is set successfully, zero is returned and the list pointed
+ * at by errlist is NULL.
+ */
+int
+zfs_set_prop_nvlist(const char *dsname, zprop_source_t source, nvlist_t *nvl,
+ nvlist_t **errlist)
+{
+ nvpair_t *pair;
+ nvpair_t *propval;
+ int err, rv = 0;
+ uint64_t intval;
+ char *strval;
+ nvlist_t *genericnvl;
+ nvlist_t *errors;
+ nvlist_t *retrynvl;
- case ZFS_PROP_REFRESERVATION:
- if ((error = nvpair_value_uint64(elem, &intval)) != 0 ||
- (error = dsl_dataset_set_reservation(name,
- intval)) != 0)
- goto out;
- break;
+ VERIFY(nvlist_alloc(&genericnvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+ VERIFY(nvlist_alloc(&errors, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+ VERIFY(nvlist_alloc(&retrynvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
- case ZFS_PROP_VOLSIZE:
- if ((error = nvpair_value_uint64(elem, &intval)) != 0 ||
- (error = zvol_set_volsize(name,
- ddi_driver_major(zfs_dip), intval)) != 0)
- goto out;
- break;
+retry:
+ pair = NULL;
+ while ((pair = nvlist_next_nvpair(nvl, pair)) != NULL) {
+ const char *propname = nvpair_name(pair);
+ zfs_prop_t prop = zfs_name_to_prop(propname);
- case ZFS_PROP_VERSION:
- {
- zfsvfs_t *zfsvfs;
-
- if ((error = nvpair_value_uint64(elem, &intval)) != 0)
- goto out;
- if ((error = zfsvfs_hold(name, FTAG, &zfsvfs)) != 0)
- goto out;
- error = zfs_set_version(zfsvfs, intval);
- zfsvfs_rele(zfsvfs, FTAG);
-
- if (error == 0 && intval >= ZPL_VERSION_USERSPACE) {
- zfs_cmd_t zc = { 0 };
- (void) strcpy(zc.zc_name, name);
- (void) zfs_ioc_userspace_upgrade(&zc);
- }
- if (error)
- goto out;
- break;
+ /* decode the property value */
+ propval = pair;
+ if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(pair, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &propval) == 0);
}
- case ZFS_PROP_MLSLABEL:
- {
- objset_t *os = NULL;
-
- if ((error = nvpair_value_string(elem, &strval)) != 0)
- goto out;
- if ((error = zfs_set_slabel_policy(name, strval,
- CRED(), &os)) != 0) {
- /* error; first release the dataset if needed */
- if (os)
- dmu_objset_disown(os, setsl_tag);
- goto out;
+ /* Validate value type */
+ if (prop == ZPROP_INVAL) {
+ if (zfs_prop_user(propname)) {
+ if (nvpair_type(propval) != DATA_TYPE_STRING)
+ err = EINVAL;
+ } else if (zfs_prop_userquota(propname)) {
+ if (nvpair_type(propval) !=
+ DATA_TYPE_UINT64_ARRAY)
+ err = EINVAL;
}
-
- error = nvlist_add_nvpair(genericnvl, elem);
- if (os)
- dmu_objset_disown(os, setsl_tag);
- if (error != 0)
- goto out;
- break;
- }
-
- default:
- if (nvpair_type(elem) == DATA_TYPE_STRING) {
- if (zfs_prop_get_type(prop) !=
- PROP_TYPE_STRING) {
- error = EINVAL;
- goto out;
- }
- } else if (nvpair_type(elem) == DATA_TYPE_UINT64) {
+ } else {
+ if (nvpair_type(propval) == DATA_TYPE_STRING) {
+ if (zfs_prop_get_type(prop) != PROP_TYPE_STRING)
+ err = EINVAL;
+ } else if (nvpair_type(propval) == DATA_TYPE_UINT64) {
const char *unused;
- VERIFY(nvpair_value_uint64(elem, &intval) == 0);
+ VERIFY(nvpair_value_uint64(propval,
+ &intval) == 0);
switch (zfs_prop_get_type(prop)) {
case PROP_TYPE_NUMBER:
break;
case PROP_TYPE_STRING:
- error = EINVAL;
- goto out;
+ err = EINVAL;
+ break;
case PROP_TYPE_INDEX:
if (zfs_prop_index_to_string(prop,
- intval, &unused) != 0) {
- error = EINVAL;
- goto out;
- }
+ intval, &unused) != 0)
+ err = EINVAL;
break;
default:
cmn_err(CE_PANIC,
"unknown property type");
- break;
}
} else {
- error = EINVAL;
- goto out;
+ err = EINVAL;
}
- if ((error = nvlist_add_nvpair(genericnvl, elem)) != 0)
- goto out;
}
+
+ /* Validate permissions */
+ if (err == 0)
+ err = zfs_check_settable(dsname, pair, CRED());
+
+ if (err == 0) {
+ err = zfs_prop_set_special(dsname, source, pair);
+ if (err == -1) {
+ /*
+ * For better performance we build up a list of
+ * properties to set in a single transaction.
+ */
+ err = nvlist_add_nvpair(genericnvl, pair);
+ } else if (err != 0 && nvl != retrynvl) {
+ /*
+ * This may be a spurious error caused by
+ * receiving quota and reservation out of order.
+ * Try again in a second pass.
+ */
+ err = nvlist_add_nvpair(retrynvl, pair);
+ }
+ }
+
+ if (err != 0)
+ VERIFY(nvlist_add_int32(errors, propname, err) == 0);
}
- if (nvlist_next_nvpair(genericnvl, NULL) != NULL) {
- error = dsl_props_set(name, genericnvl);
+ if (nvl != retrynvl && !nvlist_empty(retrynvl)) {
+ nvl = retrynvl;
+ goto retry;
+ }
+
+ if (!nvlist_empty(genericnvl) &&
+ dsl_props_set(dsname, source, genericnvl) != 0) {
+ /*
+ * If this fails, we still want to set as many properties as we
+ * can, so try setting them individually.
+ */
+ pair = NULL;
+ while ((pair = nvlist_next_nvpair(genericnvl, pair)) != NULL) {
+ const char *propname = nvpair_name(pair);
+
+ propval = pair;
+ if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(pair, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &propval) == 0);
+ }
+
+ if (nvpair_type(propval) == DATA_TYPE_STRING) {
+ VERIFY(nvpair_value_string(propval,
+ &strval) == 0);
+ err = dsl_prop_set(dsname, propname, source, 1,
+ strlen(strval) + 1, strval);
+ } else {
+ VERIFY(nvpair_value_uint64(propval,
+ &intval) == 0);
+ err = dsl_prop_set(dsname, propname, source, 8,
+ 1, &intval);
+ }
+
+ if (err != 0) {
+ VERIFY(nvlist_add_int32(errors, propname,
+ err) == 0);
+ }
+ }
}
-out:
nvlist_free(genericnvl);
- return (error);
+ nvlist_free(retrynvl);
+
+ if ((pair = nvlist_next_nvpair(errors, NULL)) == NULL) {
+ nvlist_free(errors);
+ errors = NULL;
+ } else {
+ VERIFY(nvpair_value_int32(pair, &rv) == 0);
+ }
+
+ if (errlist == NULL)
+ nvlist_free(errors);
+ else
+ *errlist = errors;
+
+ return (rv);
}
/*
@@ -1993,15 +2078,15 @@ out:
static int
zfs_check_userprops(char *fsname, nvlist_t *nvl)
{
- nvpair_t *elem = NULL;
+ nvpair_t *pair = NULL;
int error = 0;
- while ((elem = nvlist_next_nvpair(nvl, elem)) != NULL) {
- const char *propname = nvpair_name(elem);
+ while ((pair = nvlist_next_nvpair(nvl, pair)) != NULL) {
+ const char *propname = nvpair_name(pair);
char *valstr;
if (!zfs_prop_user(propname) ||
- nvpair_type(elem) != DATA_TYPE_STRING)
+ nvpair_type(pair) != DATA_TYPE_STRING)
return (EINVAL);
if (error = zfs_secpolicy_write_perms(fsname,
@@ -2011,48 +2096,96 @@ zfs_check_userprops(char *fsname, nvlist_t *nvl)
if (strlen(propname) >= ZAP_MAXNAMELEN)
return (ENAMETOOLONG);
- VERIFY(nvpair_value_string(elem, &valstr) == 0);
+ VERIFY(nvpair_value_string(pair, &valstr) == 0);
if (strlen(valstr) >= ZAP_MAXVALUELEN)
return (E2BIG);
}
return (0);
}
+static void
+props_skip(nvlist_t *props, nvlist_t *skipped, nvlist_t **newprops)
+{
+ nvpair_t *pair;
+
+ VERIFY(nvlist_alloc(newprops, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+ pair = NULL;
+ while ((pair = nvlist_next_nvpair(props, pair)) != NULL) {
+ if (nvlist_exists(skipped, nvpair_name(pair)))
+ continue;
+
+ VERIFY(nvlist_add_nvpair(*newprops, pair) == 0);
+ }
+}
+
+static int
+clear_received_props(objset_t *os, const char *fs, nvlist_t *props,
+ nvlist_t *skipped)
+{
+ int err = 0;
+ nvlist_t *cleared_props = NULL;
+ props_skip(props, skipped, &cleared_props);
+ if (!nvlist_empty(cleared_props)) {
+ /*
+ * Acts on local properties until the dataset has received
+ * properties at least once on or after SPA_VERSION_RECVD_PROPS.
+ */
+ zprop_source_t flags = (ZPROP_SRC_NONE |
+ (dsl_prop_get_hasrecvd(os) ? ZPROP_SRC_RECEIVED : 0));
+ err = zfs_set_prop_nvlist(fs, flags, cleared_props, NULL);
+ }
+ nvlist_free(cleared_props);
+ return (err);
+}
+
/*
* inputs:
* zc_name name of filesystem
* zc_value name of property to set
* zc_nvlist_src{_size} nvlist of properties to apply
- * zc_cookie clear existing local props?
+ * zc_cookie received properties flag
*
- * outputs: none
+ * outputs:
+ * zc_nvlist_dst{_size} error for each unapplied received property
*/
static int
zfs_ioc_set_prop(zfs_cmd_t *zc)
{
nvlist_t *nvl;
+ boolean_t received = zc->zc_cookie;
+ zprop_source_t source = (received ? ZPROP_SRC_RECEIVED :
+ ZPROP_SRC_LOCAL);
+ nvlist_t *errors = NULL;
int error;
if ((error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
zc->zc_iflags, &nvl)) != 0)
return (error);
- if (zc->zc_cookie) {
+ if (received) {
nvlist_t *origprops;
objset_t *os;
if (dmu_objset_hold(zc->zc_name, FTAG, &os) == 0) {
- if (dsl_prop_get_all(os, &origprops, TRUE) == 0) {
- clear_props(zc->zc_name, origprops, nvl);
+ if (dsl_prop_get_received(os, &origprops) == 0) {
+ (void) clear_received_props(os,
+ zc->zc_name, origprops, nvl);
nvlist_free(origprops);
}
+
+ dsl_prop_set_hasrecvd(os);
dmu_objset_rele(os, FTAG);
}
-
}
- error = zfs_set_prop_nvlist(zc->zc_name, nvl);
+ error = zfs_set_prop_nvlist(zc->zc_name, source, nvl, &errors);
+
+ if (zc->zc_nvlist_dst != NULL && errors != NULL) {
+ (void) put_nvlist(zc, errors);
+ }
+ nvlist_free(errors);
nvlist_free(nvl);
return (error);
}
@@ -2061,14 +2194,72 @@ zfs_ioc_set_prop(zfs_cmd_t *zc)
* inputs:
* zc_name name of filesystem
* zc_value name of property to inherit
+ * zc_cookie revert to received value if TRUE
*
* outputs: none
*/
static int
zfs_ioc_inherit_prop(zfs_cmd_t *zc)
{
+ const char *propname = zc->zc_value;
+ zfs_prop_t prop = zfs_name_to_prop(propname);
+ boolean_t received = zc->zc_cookie;
+ zprop_source_t source = (received
+ ? ZPROP_SRC_NONE /* revert to received value, if any */
+ : ZPROP_SRC_INHERITED); /* explicitly inherit */
+
+ if (received) {
+ nvlist_t *dummy;
+ nvpair_t *pair;
+ zprop_type_t type;
+ int err;
+
+ /*
+ * zfs_prop_set_special() expects properties in the form of an
+ * nvpair with type info.
+ */
+ if (prop == ZPROP_INVAL) {
+ if (!zfs_prop_user(propname))
+ return (EINVAL);
+
+ type = PROP_TYPE_STRING;
+ } else {
+ type = zfs_prop_get_type(prop);
+ }
+
+ VERIFY(nvlist_alloc(&dummy, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+ switch (type) {
+ case PROP_TYPE_STRING:
+ VERIFY(0 == nvlist_add_string(dummy, propname, ""));
+ break;
+ case PROP_TYPE_NUMBER:
+ case PROP_TYPE_INDEX:
+ VERIFY(0 == nvlist_add_uint64(dummy, propname, 0));
+ break;
+ default:
+ nvlist_free(dummy);
+ return (EINVAL);
+ }
+
+ pair = nvlist_next_nvpair(dummy, NULL);
+ err = zfs_prop_set_special(zc->zc_name, source, pair);
+ nvlist_free(dummy);
+ if (err != -1)
+ return (err); /* special property already handled */
+ } else {
+ /*
+ * Only check this in the non-received case. We want to allow
+ * 'inherit -S' to revert non-inheritable properties like quota
+ * and reservation to the received or default values even though
+ * they are not considered inheritable.
+ */
+ if (prop != ZPROP_INVAL && !zfs_prop_inheritable(prop))
+ return (EINVAL);
+ }
+
/* the property name has been validated by zfs_secpolicy_inherit() */
- return (dsl_prop_set(zc->zc_name, zc->zc_value, 0, 0, NULL));
+ return (dsl_prop_set(zc->zc_name, zc->zc_value, source, 0, 0, NULL));
}
static int
@@ -2077,20 +2268,20 @@ zfs_ioc_pool_set_props(zfs_cmd_t *zc)
nvlist_t *props;
spa_t *spa;
int error;
- nvpair_t *elem;
+ nvpair_t *pair;
- if ((error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
- zc->zc_iflags, &props)))
+ if (error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
+ zc->zc_iflags, &props))
return (error);
/*
* If the only property is the configfile, then just do a spa_lookup()
* to handle the faulted case.
*/
- elem = nvlist_next_nvpair(props, NULL);
- if (elem != NULL && strcmp(nvpair_name(elem),
+ pair = nvlist_next_nvpair(props, NULL);
+ if (pair != NULL && strcmp(nvpair_name(pair),
zpool_prop_to_name(ZPOOL_PROP_CACHEFILE)) == 0 &&
- nvlist_next_nvpair(props, elem) == NULL) {
+ nvlist_next_nvpair(props, pair) == NULL) {
mutex_enter(&spa_namespace_lock);
if ((spa = spa_lookup(zc->zc_name)) != NULL) {
spa_configfile_set(spa, props, B_FALSE);
@@ -2582,7 +2773,9 @@ zfs_ioc_create(zfs_cmd_t *zc)
* It would be nice to do this atomically.
*/
if (error == 0) {
- if ((error = zfs_set_prop_nvlist(zc->zc_name, nvprops)) != 0)
+ error = zfs_set_prop_nvlist(zc->zc_name, ZPROP_SRC_LOCAL,
+ nvprops, NULL);
+ if (error != 0)
(void) dmu_objset_destroy(zc->zc_name, B_FALSE);
}
nvlist_free(nvprops);
@@ -2618,7 +2811,7 @@ zfs_ioc_snapshot(zfs_cmd_t *zc)
if (error)
goto out;
- if (nvprops != NULL && nvlist_next_nvpair(nvprops, NULL) != NULL &&
+ if (!nvlist_empty(nvprops) &&
zfs_earlier_version(zc->zc_name, SPA_VERSION_SNAP_PROPS)) {
error = ENOTSUP;
goto out;
@@ -2834,29 +3027,263 @@ zfs_ioc_rename(zfs_cmd_t *zc)
return (dmu_objset_rename(zc->zc_name, zc->zc_value, recursive));
}
-static void
-clear_props(char *dataset, nvlist_t *props, nvlist_t *newprops)
+static int
+zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
+{
+ const char *propname = nvpair_name(pair);
+ boolean_t issnap = (strchr(dsname, '@') != NULL);
+ zfs_prop_t prop = zfs_name_to_prop(propname);
+ uint64_t intval;
+ int err;
+
+ if (prop == ZPROP_INVAL) {
+ if (zfs_prop_user(propname)) {
+ if (err = zfs_secpolicy_write_perms(dsname,
+ ZFS_DELEG_PERM_USERPROP, cr))
+ return (err);
+ return (0);
+ }
+
+ if (!issnap && zfs_prop_userquota(propname)) {
+ const char *perm = NULL;
+ const char *uq_prefix =
+ zfs_userquota_prop_prefixes[ZFS_PROP_USERQUOTA];
+ const char *gq_prefix =
+ zfs_userquota_prop_prefixes[ZFS_PROP_GROUPQUOTA];
+
+ if (strncmp(propname, uq_prefix,
+ strlen(uq_prefix)) == 0) {
+ perm = ZFS_DELEG_PERM_USERQUOTA;
+ } else if (strncmp(propname, gq_prefix,
+ strlen(gq_prefix)) == 0) {
+ perm = ZFS_DELEG_PERM_GROUPQUOTA;
+ } else {
+ /* USERUSED and GROUPUSED are read-only */
+ return (EINVAL);
+ }
+
+ if (err = zfs_secpolicy_write_perms(dsname, perm, cr))
+ return (err);
+ return (0);
+ }
+
+ return (EINVAL);
+ }
+
+ if (issnap)
+ return (EINVAL);
+
+ if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
+ /*
+ * dsl_prop_get_all_impl() returns properties in this
+ * format.
+ */
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(pair, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &pair) == 0);
+ }
+
+ /*
+ * Check that this value is valid for this pool version
+ */
+ switch (prop) {
+ case ZFS_PROP_COMPRESSION:
+ /*
+ * If the user specified gzip compression, make sure
+ * the SPA supports it. We ignore any errors here since
+ * we'll catch them later.
+ */
+ if (nvpair_type(pair) == DATA_TYPE_UINT64 &&
+ nvpair_value_uint64(pair, &intval) == 0) {
+ if (intval >= ZIO_COMPRESS_GZIP_1 &&
+ intval <= ZIO_COMPRESS_GZIP_9 &&
+ zfs_earlier_version(dsname,
+ SPA_VERSION_GZIP_COMPRESSION)) {
+ return (ENOTSUP);
+ }
+
+ if (intval == ZIO_COMPRESS_ZLE &&
+ zfs_earlier_version(dsname,
+ SPA_VERSION_ZLE_COMPRESSION))
+ return (ENOTSUP);
+
+ /*
+ * If this is a bootable dataset then
+ * verify that the compression algorithm
+ * is supported for booting. We must return
+ * something other than ENOTSUP since it
+ * implies a downrev pool version.
+ */
+ if (zfs_is_bootfs(dsname) &&
+ !BOOTFS_COMPRESS_VALID(intval)) {
+ return (ERANGE);
+ }
+ }
+ break;
+
+ case ZFS_PROP_COPIES:
+ if (zfs_earlier_version(dsname, SPA_VERSION_DITTO_BLOCKS))
+ return (ENOTSUP);
+ break;
+
+ case ZFS_PROP_DEDUP:
+ if (zfs_earlier_version(dsname, SPA_VERSION_DEDUP))
+ return (ENOTSUP);
+ break;
+
+ case ZFS_PROP_SHARESMB:
+ if (zpl_earlier_version(dsname, ZPL_VERSION_FUID))
+ return (ENOTSUP);
+ break;
+
+ case ZFS_PROP_ACLINHERIT:
+ if (nvpair_type(pair) == DATA_TYPE_UINT64 &&
+ nvpair_value_uint64(pair, &intval) == 0) {
+ if (intval == ZFS_ACL_PASSTHROUGH_X &&
+ zfs_earlier_version(dsname,
+ SPA_VERSION_PASSTHROUGH_X))
+ return (ENOTSUP);
+ }
+ break;
+ }
+
+ return (zfs_secpolicy_setprop(dsname, prop, pair, CRED()));
+}
+
+/*
+ * Removes properties from the given props list that fail permission checks
+ * needed to clear them and to restore them in case of a receive error. For each
+ * property, make sure we have both set and inherit permissions.
+ *
+ * Returns the first error encountered if any permission checks fail. If the
+ * caller provides a non-NULL errlist, it also gives the complete list of names
+ * of all the properties that failed a permission check along with the
+ * corresponding error numbers. The caller is responsible for freeing the
+ * returned errlist.
+ *
+ * If every property checks out successfully, zero is returned and the list
+ * pointed at by errlist is NULL.
+ */
+static int
+zfs_check_clearable(char *dataset, nvlist_t *props, nvlist_t **errlist)
{
zfs_cmd_t *zc;
- nvpair_t *prop;
+ nvpair_t *pair, *next_pair;
+ nvlist_t *errors;
+ int err, rv = 0;
if (props == NULL)
- return;
+ return (0);
+
+ VERIFY(nvlist_alloc(&errors, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
zc = kmem_alloc(sizeof (zfs_cmd_t), KM_SLEEP);
(void) strcpy(zc->zc_name, dataset);
- for (prop = nvlist_next_nvpair(props, NULL); prop;
- prop = nvlist_next_nvpair(props, prop)) {
- if (newprops != NULL &&
- nvlist_exists(newprops, nvpair_name(prop)))
- continue;
- (void) strcpy(zc->zc_value, nvpair_name(prop));
- if (zfs_secpolicy_inherit(zc, CRED()) == 0)
- (void) zfs_ioc_inherit_prop(zc);
+ pair = nvlist_next_nvpair(props, NULL);
+ while (pair != NULL) {
+ next_pair = nvlist_next_nvpair(props, pair);
+
+ (void) strcpy(zc->zc_value, nvpair_name(pair));
+ if ((err = zfs_check_settable(dataset, pair, CRED())) != 0 ||
+ (err = zfs_secpolicy_inherit(zc, CRED())) != 0) {
+ VERIFY(nvlist_remove_nvpair(props, pair) == 0);
+ VERIFY(nvlist_add_int32(errors,
+ zc->zc_value, err) == 0);
+ }
+ pair = next_pair;
}
kmem_free(zc, sizeof (zfs_cmd_t));
+
+ if ((pair = nvlist_next_nvpair(errors, NULL)) == NULL) {
+ nvlist_free(errors);
+ errors = NULL;
+ } else {
+ VERIFY(nvpair_value_int32(pair, &rv) == 0);
+ }
+
+ if (errlist == NULL)
+ nvlist_free(errors);
+ else
+ *errlist = errors;
+
+ return (rv);
+}
+
+static boolean_t
+propval_equals(nvpair_t *p1, nvpair_t *p2)
+{
+ if (nvpair_type(p1) == DATA_TYPE_NVLIST) {
+ /* dsl_prop_get_all_impl() format */
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(p1, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &p1) == 0);
+ }
+
+ if (nvpair_type(p2) == DATA_TYPE_NVLIST) {
+ nvlist_t *attrs;
+ VERIFY(nvpair_value_nvlist(p2, &attrs) == 0);
+ VERIFY(nvlist_lookup_nvpair(attrs, ZPROP_VALUE,
+ &p2) == 0);
+ }
+
+ if (nvpair_type(p1) != nvpair_type(p2))
+ return (B_FALSE);
+
+ if (nvpair_type(p1) == DATA_TYPE_STRING) {
+ char *valstr1, *valstr2;
+
+ VERIFY(nvpair_value_string(p1, (char **)&valstr1) == 0);
+ VERIFY(nvpair_value_string(p2, (char **)&valstr2) == 0);
+ return (strcmp(valstr1, valstr2) == 0);
+ } else {
+ uint64_t intval1, intval2;
+
+ VERIFY(nvpair_value_uint64(p1, &intval1) == 0);
+ VERIFY(nvpair_value_uint64(p2, &intval2) == 0);
+ return (intval1 == intval2);
+ }
}
/*
+ * Remove properties from props if they are not going to change (as determined
+ * by comparison with origprops). Remove them from origprops as well, since we
+ * do not need to clear or restore properties that won't change.
+ */
+static void
+props_reduce(nvlist_t *props, nvlist_t *origprops)
+{
+ nvpair_t *pair, *next_pair;
+
+ if (origprops == NULL)
+ return; /* all props need to be received */
+
+ pair = nvlist_next_nvpair(props, NULL);
+ while (pair != NULL) {
+ const char *propname = nvpair_name(pair);
+ nvpair_t *match;
+
+ next_pair = nvlist_next_nvpair(props, pair);
+
+ if ((nvlist_lookup_nvpair(origprops, propname,
+ &match) != 0) || !propval_equals(pair, match))
+ goto next; /* need to set received value */
+
+ /* don't clear the existing received value */
+ (void) nvlist_remove_nvpair(origprops, match);
+ /* don't bother receiving the property */
+ (void) nvlist_remove_nvpair(props, pair);
+next:
+ pair = next_pair;
+ }
+}
+
+#ifdef DEBUG
+static boolean_t zfs_ioc_recv_inject_err;
+#endif
+
+/*
* inputs:
* zc_name name of containing filesystem
* zc_nvlist_src{_size} nvlist of properties to apply
@@ -2868,6 +3295,8 @@ clear_props(char *dataset, nvlist_t *props, nvlist_t *newprops)
*
* outputs:
* zc_cookie number of bytes read
+ * zc_nvlist_dst{_size} error for each unapplied received property
+ * zc_obj zprop_errflags_t
*/
static int
zfs_ioc_recv(zfs_cmd_t *zc)
@@ -2876,13 +3305,17 @@ zfs_ioc_recv(zfs_cmd_t *zc)
objset_t *os;
dmu_recv_cookie_t drc;
boolean_t force = (boolean_t)zc->zc_guid;
- int error, fd;
+ int fd;
+ int error = 0;
+ int props_error = 0;
+ nvlist_t *errors;
offset_t off;
- nvlist_t *props = NULL;
- nvlist_t *origprops = NULL;
+ nvlist_t *props = NULL; /* sent properties */
+ nvlist_t *origprops = NULL; /* existing properties */
objset_t *origin = NULL;
char *tosnap;
char tofs[ZFS_MAXNAMELEN];
+ boolean_t first_recvd_props = B_FALSE;
if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0 ||
strchr(zc->zc_value, '@') == NULL ||
@@ -2891,8 +3324,7 @@ zfs_ioc_recv(zfs_cmd_t *zc)
(void) strcpy(tofs, zc->zc_value);
tosnap = strchr(tofs, '@');
- *tosnap = '\0';
- tosnap++;
+ *tosnap++ = '\0';
if (zc->zc_nvlist_src != NULL &&
(error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size,
@@ -2906,12 +3338,36 @@ zfs_ioc_recv(zfs_cmd_t *zc)
return (EBADF);
}
+ VERIFY(nvlist_alloc(&errors, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
if (props && dmu_objset_hold(tofs, FTAG, &os) == 0) {
+ if ((spa_version(os->os_spa) >= SPA_VERSION_RECVD_PROPS) &&
+ !dsl_prop_get_hasrecvd(os)) {
+ first_recvd_props = B_TRUE;
+ }
+
/*
- * If new properties are supplied, they are to completely
- * replace the existing ones, so stash away the existing ones.
+ * If new received properties are supplied, they are to
+ * completely replace the existing received properties, so stash
+ * away the existing ones.
*/
- (void) dsl_prop_get_all(os, &origprops, B_TRUE);
+ if (dsl_prop_get_received(os, &origprops) == 0) {
+ nvlist_t *errlist = NULL;
+ /*
+ * Don't bother writing a property if its value won't
+ * change (and avoid the unnecessary security checks).
+ *
+ * The first receive after SPA_VERSION_RECVD_PROPS is a
+ * special case where we blow away all local properties
+ * regardless.
+ */
+ if (!first_recvd_props)
+ props_reduce(props, origprops);
+ if (zfs_check_clearable(tofs, origprops,
+ &errlist) != 0)
+ (void) nvlist_merge(errors, errlist, 0);
+ nvlist_free(errlist);
+ }
dmu_objset_rele(os, FTAG);
}
@@ -2930,15 +3386,42 @@ zfs_ioc_recv(zfs_cmd_t *zc)
goto out;
/*
- * Reset properties. We do this before we receive the stream
- * so that the properties are applied to the new data.
+ * Set properties before we receive the stream so that they are applied
+ * to the new data. Note that we must call dmu_recv_stream() if
+ * dmu_recv_begin() succeeds.
*/
if (props) {
- clear_props(tofs, origprops, props);
+ nvlist_t *errlist;
+
+ if (dmu_objset_from_ds(drc.drc_logical_ds, &os) == 0) {
+ if (drc.drc_newfs) {
+ if (spa_version(os->os_spa) >=
+ SPA_VERSION_RECVD_PROPS)
+ first_recvd_props = B_TRUE;
+ } else if (origprops != NULL) {
+ if (clear_received_props(os, tofs, origprops,
+ first_recvd_props ? NULL : props) != 0)
+ zc->zc_obj |= ZPROP_ERR_NOCLEAR;
+ } else {
+ zc->zc_obj |= ZPROP_ERR_NOCLEAR;
+ }
+ dsl_prop_set_hasrecvd(os);
+ } else if (!drc.drc_newfs) {
+ zc->zc_obj |= ZPROP_ERR_NOCLEAR;
+ }
+
+ (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
+ props, &errlist);
+ (void) nvlist_merge(errors, errlist, 0);
+ nvlist_free(errlist);
+ }
+
+ if (fit_error_list(zc, &errors) != 0 || put_nvlist(zc, errors) != 0) {
/*
- * XXX - Note, this is all-or-nothing; should be best-effort.
+ * Caller made zc->zc_nvlist_dst less than the minimum expected
+ * size or supplied an invalid address.
*/
- (void) zfs_set_prop_nvlist(tofs, props);
+ props_error = EINVAL;
}
off = fp->f_offset;
@@ -2973,17 +3456,64 @@ zfs_ioc_recv(zfs_cmd_t *zc)
if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
fp->f_offset = off;
+#ifdef DEBUG
+ if (zfs_ioc_recv_inject_err) {
+ zfs_ioc_recv_inject_err = B_FALSE;
+ error = 1;
+ }
+#endif
/*
* On error, restore the original props.
*/
if (error && props) {
- clear_props(tofs, props, NULL);
- (void) zfs_set_prop_nvlist(tofs, origprops);
+ if (dmu_objset_hold(tofs, FTAG, &os) == 0) {
+ if (clear_received_props(os, tofs, props, NULL) != 0) {
+ /*
+ * We failed to clear the received properties.
+ * Since we may have left a $recvd value on the
+ * system, we can't clear the $hasrecvd flag.
+ */
+ zc->zc_obj |= ZPROP_ERR_NORESTORE;
+ } else if (first_recvd_props) {
+ dsl_prop_unset_hasrecvd(os);
+ }
+ dmu_objset_rele(os, FTAG);
+ } else if (!drc.drc_newfs) {
+ /* We failed to clear the received properties. */
+ zc->zc_obj |= ZPROP_ERR_NORESTORE;
+ }
+
+ if (origprops == NULL && !drc.drc_newfs) {
+ /* We failed to stash the original properties. */
+ zc->zc_obj |= ZPROP_ERR_NORESTORE;
+ }
+
+ /*
+ * dsl_props_set() will not convert RECEIVED to LOCAL on or
+ * after SPA_VERSION_RECVD_PROPS, so we need to specify LOCAL
+ * explictly if we're restoring local properties cleared in the
+ * first new-style receive.
+ */
+ if (origprops != NULL &&
+ zfs_set_prop_nvlist(tofs, (first_recvd_props ?
+ ZPROP_SRC_LOCAL : ZPROP_SRC_RECEIVED),
+ origprops, NULL) != 0) {
+ /*
+ * We stashed the original properties but failed to
+ * restore them.
+ */
+ zc->zc_obj |= ZPROP_ERR_NORESTORE;
+ }
}
out:
nvlist_free(props);
nvlist_free(origprops);
+ nvlist_free(errors);
releasef(fd);
+
+ if (error == 0)
+ error = props_error;
+
return (error);
}
@@ -3767,7 +4297,9 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {
{ zfs_ioc_release, zfs_secpolicy_release, DATASET_NAME, B_TRUE,
B_TRUE },
{ zfs_ioc_get_holds, zfs_secpolicy_read, DATASET_NAME, B_FALSE,
- B_TRUE }
+ B_TRUE },
+ { zfs_ioc_objset_recvd_props, zfs_secpolicy_read, DATASET_NAME, B_FALSE,
+ B_FALSE }
};
int