diff options
Diffstat (limited to 'usr/src/uts/common/fs/zfs/zfs_ioctl.c')
-rw-r--r-- | usr/src/uts/common/fs/zfs/zfs_ioctl.c | 1162 |
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 |