freebsd-dev/module/zfs/dsl_prop.c

1147 lines
29 KiB
C
Raw Normal View History

2008-11-20 20:01:55 +00:00
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013 by Delphix. All rights reserved.
* Copyright (c) 2013 Martin Matuska. All rights reserved.
2008-11-20 20:01:55 +00:00
*/
#include <sys/zfs_context.h>
2008-11-20 20:01:55 +00:00
#include <sys/dmu.h>
#include <sys/dmu_objset.h>
#include <sys/dmu_tx.h>
#include <sys/dsl_dataset.h>
#include <sys/dsl_dir.h>
#include <sys/dsl_prop.h>
#include <sys/dsl_synctask.h>
#include <sys/spa.h>
#include <sys/zap.h>
#include <sys/fs/zfs.h>
#include "zfs_prop.h"
#define ZPROP_INHERIT_SUFFIX "$inherit"
#define ZPROP_RECVD_SUFFIX "$recvd"
2008-11-20 20:01:55 +00:00
static int
dodefault(const char *propname, int intsz, int numints, void *buf)
2008-11-20 20:01:55 +00:00
{
zfs_prop_t prop;
/*
* The setonce properties are read-only, BUT they still
* have a default value that can be used as the initial
* value.
*/
if ((prop = zfs_name_to_prop(propname)) == ZPROP_INVAL ||
(zfs_prop_readonly(prop) && !zfs_prop_setonce(prop)))
return (SET_ERROR(ENOENT));
2008-11-20 20:01:55 +00:00
if (zfs_prop_get_type(prop) == PROP_TYPE_STRING) {
if (intsz != 1)
return (SET_ERROR(EOVERFLOW));
2008-11-20 20:01:55 +00:00
(void) strncpy(buf, zfs_prop_default_string(prop),
numints);
2008-11-20 20:01:55 +00:00
} else {
if (intsz != 8 || numints < 1)
return (SET_ERROR(EOVERFLOW));
2008-11-20 20:01:55 +00:00
*(uint64_t *)buf = zfs_prop_default_numeric(prop);
}
return (0);
}
int
dsl_prop_get_dd(dsl_dir_t *dd, const char *propname,
int intsz, int numints, void *buf, char *setpoint, boolean_t snapshot)
2008-11-20 20:01:55 +00:00
{
int err = ENOENT;
dsl_dir_t *target = dd;
objset_t *mos = dd->dd_pool->dp_meta_objset;
2008-11-20 20:01:55 +00:00
zfs_prop_t prop;
boolean_t inheritable;
boolean_t inheriting = B_FALSE;
char *inheritstr;
char *recvdstr;
2008-11-20 20:01:55 +00:00
ASSERT(dsl_pool_config_held(dd->dd_pool));
2008-11-20 20:01:55 +00:00
if (setpoint)
setpoint[0] = '\0';
prop = zfs_name_to_prop(propname);
inheritable = (prop == ZPROP_INVAL || zfs_prop_inheritable(prop));
inheritstr = kmem_asprintf("%s%s", propname, ZPROP_INHERIT_SUFFIX);
recvdstr = kmem_asprintf("%s%s", propname, ZPROP_RECVD_SUFFIX);
2008-11-20 20:01:55 +00:00
/*
* Note: dd may become NULL, therefore we shouldn't dereference it
* after this loop.
2008-11-20 20:01:55 +00:00
*/
for (; dd != NULL; dd = dd->dd_parent) {
if (dd != target || snapshot) {
if (!inheritable)
break;
inheriting = B_TRUE;
}
/* Check for a local value. */
err = zap_lookup(mos, dsl_dir_phys(dd)->dd_props_zapobj,
propname, intsz, numints, buf);
2008-11-20 20:01:55 +00:00
if (err != ENOENT) {
if (setpoint != NULL && err == 0)
2008-11-20 20:01:55 +00:00
dsl_dir_name(dd, setpoint);
break;
}
/*
* Skip the check for a received value if there is an explicit
* inheritance entry.
2008-11-20 20:01:55 +00:00
*/
err = zap_contains(mos, dsl_dir_phys(dd)->dd_props_zapobj,
inheritstr);
if (err != 0 && err != ENOENT)
2008-11-20 20:01:55 +00:00
break;
if (err == ENOENT) {
/* Check for a received value. */
err = zap_lookup(mos, dsl_dir_phys(dd)->dd_props_zapobj,
recvdstr, intsz, numints, buf);
if (err != ENOENT) {
if (setpoint != NULL && err == 0) {
if (inheriting) {
dsl_dir_name(dd, setpoint);
} else {
(void) strcpy(setpoint,
ZPROP_SOURCE_VAL_RECVD);
}
}
break;
}
}
/*
* If we found an explicit inheritance entry, err is zero even
* though we haven't yet found the value, so reinitializing err
* at the end of the loop (instead of at the beginning) ensures
* that err has a valid post-loop value.
*/
err = SET_ERROR(ENOENT);
2008-11-20 20:01:55 +00:00
}
2008-11-20 20:01:55 +00:00
if (err == ENOENT)
err = dodefault(propname, intsz, numints, buf);
strfree(inheritstr);
strfree(recvdstr);
2008-11-20 20:01:55 +00:00
return (err);
}
int
dsl_prop_get_ds(dsl_dataset_t *ds, const char *propname,
int intsz, int numints, void *buf, char *setpoint)
{
zfs_prop_t prop = zfs_name_to_prop(propname);
boolean_t inheritable;
boolean_t snapshot;
uint64_t zapobj;
ASSERT(dsl_pool_config_held(ds->ds_dir->dd_pool));
inheritable = (prop == ZPROP_INVAL || zfs_prop_inheritable(prop));
snapshot = dsl_dataset_is_snapshot(ds);
zapobj = dsl_dataset_phys(ds)->ds_props_obj;
if (zapobj != 0) {
objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
int err;
ASSERT(snapshot);
/* Check for a local value. */
err = zap_lookup(mos, zapobj, propname, intsz, numints, buf);
if (err != ENOENT) {
if (setpoint != NULL && err == 0)
dsl_dataset_name(ds, setpoint);
return (err);
}
/*
* Skip the check for a received value if there is an explicit
* inheritance entry.
*/
if (inheritable) {
char *inheritstr = kmem_asprintf("%s%s", propname,
ZPROP_INHERIT_SUFFIX);
err = zap_contains(mos, zapobj, inheritstr);
strfree(inheritstr);
if (err != 0 && err != ENOENT)
return (err);
}
if (err == ENOENT) {
/* Check for a received value. */
char *recvdstr = kmem_asprintf("%s%s", propname,
ZPROP_RECVD_SUFFIX);
err = zap_lookup(mos, zapobj, recvdstr,
intsz, numints, buf);
strfree(recvdstr);
if (err != ENOENT) {
if (setpoint != NULL && err == 0)
(void) strcpy(setpoint,
ZPROP_SOURCE_VAL_RECVD);
return (err);
}
}
}
return (dsl_prop_get_dd(ds->ds_dir, propname,
intsz, numints, buf, setpoint, snapshot));
}
2008-11-20 20:01:55 +00:00
/*
* Register interest in the named property. We'll call the callback
* once to notify it of the current property value, and again each time
* the property changes, until this callback is unregistered.
*
* Return 0 on success, errno if the prop is not an integer value.
*/
int
dsl_prop_register(dsl_dataset_t *ds, const char *propname,
dsl_prop_changed_cb_t *callback, void *cbarg)
{
dsl_dir_t *dd = ds->ds_dir;
uint64_t value;
dsl_prop_cb_record_t *cbr;
int err;
ASSERTV(dsl_pool_t *dp = dd->dd_pool);
2008-11-20 20:01:55 +00:00
ASSERT(dsl_pool_config_held(dp));
2008-11-20 20:01:55 +00:00
err = dsl_prop_get_int_ds(ds, propname, &value);
if (err != 0)
2008-11-20 20:01:55 +00:00
return (err);
cbr = kmem_alloc(sizeof (dsl_prop_cb_record_t), KM_SLEEP);
2008-11-20 20:01:55 +00:00
cbr->cbr_ds = ds;
cbr->cbr_propname = kmem_alloc(strlen(propname)+1, KM_SLEEP);
2008-11-20 20:01:55 +00:00
(void) strcpy((char *)cbr->cbr_propname, propname);
cbr->cbr_func = callback;
cbr->cbr_arg = cbarg;
mutex_enter(&dd->dd_lock);
list_insert_head(&dd->dd_prop_cbs, cbr);
mutex_exit(&dd->dd_lock);
cbr->cbr_func(cbr->cbr_arg, value);
return (0);
}
int
dsl_prop_get(const char *dsname, const char *propname,
2008-11-20 20:01:55 +00:00
int intsz, int numints, void *buf, char *setpoint)
{
objset_t *os;
int error;
2008-11-20 20:01:55 +00:00
error = dmu_objset_hold(dsname, FTAG, &os);
if (error != 0)
return (error);
2008-11-20 20:01:55 +00:00
error = dsl_prop_get_ds(dmu_objset_ds(os), propname,
intsz, numints, buf, setpoint);
2008-11-20 20:01:55 +00:00
dmu_objset_rele(os, FTAG);
return (error);
2008-11-20 20:01:55 +00:00
}
/*
* Get the current property value. It may have changed by the time this
* function returns, so it is NOT safe to follow up with
* dsl_prop_register() and assume that the value has not changed in
* between.
*
* Return 0 on success, ENOENT if ddname is invalid.
*/
int
dsl_prop_get_integer(const char *ddname, const char *propname,
uint64_t *valuep, char *setpoint)
{
return (dsl_prop_get(ddname, propname, 8, 1, valuep, setpoint));
}
int
dsl_prop_get_int_ds(dsl_dataset_t *ds, const char *propname,
uint64_t *valuep)
{
return (dsl_prop_get_ds(ds, propname, 8, 1, valuep, NULL));
}
/*
* Predict the effective value of the given special property if it were set with
* the given value and source. This is not a general purpose function. It exists
* only to handle the special requirements of the quota and reservation
* properties. The fact that these properties are non-inheritable greatly
* simplifies the prediction logic.
*
* Returns 0 on success, a positive error code on failure, or -1 if called with
* a property not handled by this function.
*/
int
dsl_prop_predict(dsl_dir_t *dd, const char *propname,
zprop_source_t source, uint64_t value, uint64_t *newvalp)
{
zfs_prop_t prop = zfs_name_to_prop(propname);
objset_t *mos;
uint64_t zapobj;
uint64_t version;
char *recvdstr;
int err = 0;
switch (prop) {
case ZFS_PROP_QUOTA:
case ZFS_PROP_RESERVATION:
case ZFS_PROP_REFQUOTA:
case ZFS_PROP_REFRESERVATION:
break;
default:
return (-1);
}
mos = dd->dd_pool->dp_meta_objset;
zapobj = dsl_dir_phys(dd)->dd_props_zapobj;
recvdstr = kmem_asprintf("%s%s", propname, ZPROP_RECVD_SUFFIX);
version = spa_version(dd->dd_pool->dp_spa);
if (version < SPA_VERSION_RECVD_PROPS) {
if (source & ZPROP_SRC_NONE)
source = ZPROP_SRC_NONE;
else if (source & ZPROP_SRC_RECEIVED)
source = ZPROP_SRC_LOCAL;
}
switch ((int)source) {
case ZPROP_SRC_NONE:
/* Revert to the received value, if any. */
err = zap_lookup(mos, zapobj, recvdstr, 8, 1, newvalp);
if (err == ENOENT)
*newvalp = 0;
break;
case ZPROP_SRC_LOCAL:
*newvalp = value;
break;
case ZPROP_SRC_RECEIVED:
/*
* If there's no local setting, then the new received value will
* be the effective value.
*/
err = zap_lookup(mos, zapobj, propname, 8, 1, newvalp);
if (err == ENOENT)
*newvalp = value;
break;
case (ZPROP_SRC_NONE | ZPROP_SRC_RECEIVED):
/*
* We're clearing the received value, so the local setting (if
* it exists) remains the effective value.
*/
err = zap_lookup(mos, zapobj, propname, 8, 1, newvalp);
if (err == ENOENT)
*newvalp = 0;
break;
default:
panic("unexpected property source: %d", source);
}
strfree(recvdstr);
if (err == ENOENT)
return (0);
return (err);
}
2008-11-20 20:01:55 +00:00
/*
* Unregister this callback. Return 0 on success, ENOENT if ddname is
* invalid, or ENOMSG if no matching callback registered.
2008-11-20 20:01:55 +00:00
*/
int
dsl_prop_unregister(dsl_dataset_t *ds, const char *propname,
dsl_prop_changed_cb_t *callback, void *cbarg)
{
dsl_dir_t *dd = ds->ds_dir;
dsl_prop_cb_record_t *cbr;
mutex_enter(&dd->dd_lock);
for (cbr = list_head(&dd->dd_prop_cbs);
cbr; cbr = list_next(&dd->dd_prop_cbs, cbr)) {
if (cbr->cbr_ds == ds &&
cbr->cbr_func == callback &&
cbr->cbr_arg == cbarg &&
strcmp(cbr->cbr_propname, propname) == 0)
break;
}
if (cbr == NULL) {
mutex_exit(&dd->dd_lock);
return (SET_ERROR(ENOMSG));
2008-11-20 20:01:55 +00:00
}
list_remove(&dd->dd_prop_cbs, cbr);
mutex_exit(&dd->dd_lock);
kmem_free((void*)cbr->cbr_propname, strlen(cbr->cbr_propname)+1);
kmem_free(cbr, sizeof (dsl_prop_cb_record_t));
return (0);
}
boolean_t
dsl_prop_hascb(dsl_dataset_t *ds)
2008-11-20 20:01:55 +00:00
{
dsl_dir_t *dd = ds->ds_dir;
boolean_t rv = B_FALSE;
2008-11-20 20:01:55 +00:00
dsl_prop_cb_record_t *cbr;
mutex_enter(&dd->dd_lock);
for (cbr = list_head(&dd->dd_prop_cbs); cbr;
cbr = list_next(&dd->dd_prop_cbs, cbr)) {
if (cbr->cbr_ds == ds) {
rv = B_TRUE;
break;
}
2008-11-20 20:01:55 +00:00
}
mutex_exit(&dd->dd_lock);
return (rv);
}
2008-11-20 20:01:55 +00:00
/* ARGSUSED */
static int
dsl_prop_notify_all_cb(dsl_pool_t *dp, dsl_dataset_t *ds, void *arg)
{
dsl_dir_t *dd = ds->ds_dir;
dsl_prop_cb_record_t *cbr;
mutex_enter(&dd->dd_lock);
for (cbr = list_head(&dd->dd_prop_cbs); cbr;
cbr = list_next(&dd->dd_prop_cbs, cbr)) {
uint64_t value;
if (dsl_prop_get_ds(cbr->cbr_ds, cbr->cbr_propname,
sizeof (value), 1, &value, NULL) == 0)
cbr->cbr_func(cbr->cbr_arg, value);
}
mutex_exit(&dd->dd_lock);
return (0);
}
/*
* Update all property values for ddobj & its descendants. This is used
* when renaming the dir.
*/
void
dsl_prop_notify_all(dsl_dir_t *dd)
{
dsl_pool_t *dp = dd->dd_pool;
ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
(void) dmu_objset_find_dp(dp, dd->dd_object, dsl_prop_notify_all_cb,
NULL, DS_FIND_CHILDREN);
2008-11-20 20:01:55 +00:00
}
static void
dsl_prop_changed_notify(dsl_pool_t *dp, uint64_t ddobj,
const char *propname, uint64_t value, int first)
{
dsl_dir_t *dd;
dsl_prop_cb_record_t *cbr;
objset_t *mos = dp->dp_meta_objset;
zap_cursor_t zc;
zap_attribute_t *za;
int err;
ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
err = dsl_dir_hold_obj(dp, ddobj, NULL, FTAG, &dd);
2008-11-20 20:01:55 +00:00
if (err)
return;
if (!first) {
/*
* If the prop is set here, then this change is not
* being inherited here or below; stop the recursion.
*/
err = zap_contains(mos, dsl_dir_phys(dd)->dd_props_zapobj,
propname);
2008-11-20 20:01:55 +00:00
if (err == 0) {
dsl_dir_rele(dd, FTAG);
2008-11-20 20:01:55 +00:00
return;
}
ASSERT3U(err, ==, ENOENT);
}
mutex_enter(&dd->dd_lock);
for (cbr = list_head(&dd->dd_prop_cbs); cbr;
cbr = list_next(&dd->dd_prop_cbs, cbr)) {
uint64_t propobj = dsl_dataset_phys(cbr->cbr_ds)->ds_props_obj;
if (strcmp(cbr->cbr_propname, propname) != 0)
continue;
/*
* If the property is set on this ds, then it is not
* inherited here; don't call the callback.
*/
if (propobj && 0 == zap_contains(mos, propobj, propname))
continue;
cbr->cbr_func(cbr->cbr_arg, value);
2008-11-20 20:01:55 +00:00
}
mutex_exit(&dd->dd_lock);
za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
2008-11-20 20:01:55 +00:00
for (zap_cursor_init(&zc, mos,
dsl_dir_phys(dd)->dd_child_dir_zapobj);
2008-11-20 20:01:55 +00:00
zap_cursor_retrieve(&zc, za) == 0;
zap_cursor_advance(&zc)) {
dsl_prop_changed_notify(dp, za->za_first_integer,
propname, value, FALSE);
}
kmem_free(za, sizeof (zap_attribute_t));
zap_cursor_fini(&zc);
dsl_dir_rele(dd, FTAG);
2008-11-20 20:01:55 +00:00
}
void
dsl_prop_set_sync_impl(dsl_dataset_t *ds, const char *propname,
zprop_source_t source, int intsz, int numints, const void *value,
dmu_tx_t *tx)
2008-11-20 20:01:55 +00:00
{
objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
uint64_t zapobj, intval, dummy;
2008-11-20 20:01:55 +00:00
int isint;
char valbuf[32];
const char *valstr = NULL;
char *inheritstr;
char *recvdstr;
char *tbuf = NULL;
int err;
uint64_t version = spa_version(ds->ds_dir->dd_pool->dp_spa);
2008-11-20 20:01:55 +00:00
isint = (dodefault(propname, 8, 1, &intval) == 0);
2008-11-20 20:01:55 +00:00
if (dsl_dataset_is_snapshot(ds)) {
ASSERT(version >= SPA_VERSION_SNAP_PROPS);
if (dsl_dataset_phys(ds)->ds_props_obj == 0) {
dmu_buf_will_dirty(ds->ds_dbuf, tx);
dsl_dataset_phys(ds)->ds_props_obj =
zap_create(mos,
DMU_OT_DSL_PROPS, DMU_OT_NONE, 0, tx);
}
zapobj = dsl_dataset_phys(ds)->ds_props_obj;
} else {
zapobj = dsl_dir_phys(ds->ds_dir)->dd_props_zapobj;
}
if (version < SPA_VERSION_RECVD_PROPS) {
if (source & ZPROP_SRC_NONE)
source = ZPROP_SRC_NONE;
else if (source & ZPROP_SRC_RECEIVED)
source = ZPROP_SRC_LOCAL;
}
inheritstr = kmem_asprintf("%s%s", propname, ZPROP_INHERIT_SUFFIX);
recvdstr = kmem_asprintf("%s%s", propname, ZPROP_RECVD_SUFFIX);
switch ((int)source) {
case ZPROP_SRC_NONE:
/*
* revert to received value, if any (inherit -S)
* - remove propname
* - remove propname$inherit
*/
err = zap_remove(mos, zapobj, propname, tx);
ASSERT(err == 0 || err == ENOENT);
err = zap_remove(mos, zapobj, inheritstr, tx);
2008-11-20 20:01:55 +00:00
ASSERT(err == 0 || err == ENOENT);
break;
case ZPROP_SRC_LOCAL:
/*
* remove propname$inherit
* set propname -> value
*/
err = zap_remove(mos, zapobj, inheritstr, tx);
ASSERT(err == 0 || err == ENOENT);
VERIFY0(zap_update(mos, zapobj, propname,
intsz, numints, value, tx));
break;
case ZPROP_SRC_INHERITED:
/*
* explicitly inherit
* - remove propname
* - set propname$inherit
*/
err = zap_remove(mos, zapobj, propname, tx);
ASSERT(err == 0 || err == ENOENT);
if (version >= SPA_VERSION_RECVD_PROPS &&
dsl_prop_get_int_ds(ds, ZPROP_HAS_RECVD, &dummy) == 0) {
dummy = 0;
VERIFY0(zap_update(mos, zapobj, inheritstr,
8, 1, &dummy, tx));
2008-11-20 20:01:55 +00:00
}
break;
case ZPROP_SRC_RECEIVED:
/*
* set propname$recvd -> value
*/
err = zap_update(mos, zapobj, recvdstr,
intsz, numints, value, tx);
ASSERT(err == 0);
break;
case (ZPROP_SRC_NONE | ZPROP_SRC_LOCAL | ZPROP_SRC_RECEIVED):
/*
* clear local and received settings
* - remove propname
* - remove propname$inherit
* - remove propname$recvd
*/
err = zap_remove(mos, zapobj, propname, tx);
ASSERT(err == 0 || err == ENOENT);
err = zap_remove(mos, zapobj, inheritstr, tx);
ASSERT(err == 0 || err == ENOENT);
/* FALLTHRU */
case (ZPROP_SRC_NONE | ZPROP_SRC_RECEIVED):
/*
* remove propname$recvd
*/
err = zap_remove(mos, zapobj, recvdstr, tx);
ASSERT(err == 0 || err == ENOENT);
break;
default:
cmn_err(CE_PANIC, "unexpected property source: %d", source);
2008-11-20 20:01:55 +00:00
}
strfree(inheritstr);
strfree(recvdstr);
2008-11-20 20:01:55 +00:00
if (isint) {
VERIFY0(dsl_prop_get_int_ds(ds, propname, &intval));
if (dsl_dataset_is_snapshot(ds)) {
dsl_prop_cb_record_t *cbr;
/*
* It's a snapshot; nothing can inherit this
* property, so just look for callbacks on this
* ds here.
*/
mutex_enter(&ds->ds_dir->dd_lock);
for (cbr = list_head(&ds->ds_dir->dd_prop_cbs); cbr;
cbr = list_next(&ds->ds_dir->dd_prop_cbs, cbr)) {
if (cbr->cbr_ds == ds &&
strcmp(cbr->cbr_propname, propname) == 0)
cbr->cbr_func(cbr->cbr_arg, intval);
}
mutex_exit(&ds->ds_dir->dd_lock);
} else {
dsl_prop_changed_notify(ds->ds_dir->dd_pool,
ds->ds_dir->dd_object, propname, intval, TRUE);
}
2008-11-20 20:01:55 +00:00
(void) snprintf(valbuf, sizeof (valbuf),
"%lld", (longlong_t)intval);
valstr = valbuf;
} else {
if (source == ZPROP_SRC_LOCAL) {
valstr = value;
} else {
tbuf = kmem_alloc(ZAP_MAXVALUELEN, KM_SLEEP);
if (dsl_prop_get_ds(ds, propname, 1,
ZAP_MAXVALUELEN, tbuf, NULL) == 0)
valstr = tbuf;
}
2008-11-20 20:01:55 +00:00
}
Illumos #2882, #2883, #2900 2882 implement libzfs_core 2883 changing "canmount" property to "on" should not always remount dataset 2900 "zfs snapshot" should be able to create multiple, arbitrary snapshots at once Reviewed by: George Wilson <george.wilson@delphix.com> Reviewed by: Chris Siden <christopher.siden@delphix.com> Reviewed by: Garrett D'Amore <garrett@damore.org> Reviewed by: Bill Pijewski <wdp@joyent.com> Reviewed by: Dan Kruchinin <dan.kruchinin@gmail.com> Approved by: Eric Schrock <Eric.Schrock@delphix.com> References: https://www.illumos.org/issues/2882 https://www.illumos.org/issues/2883 https://www.illumos.org/issues/2900 illumos/illumos-gate@4445fffbbb1ea25fd0e9ea68b9380dd7a6709025 Ported-by: Tim Chase <tim@chase2k.com> Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Closes #1293 Porting notes: WARNING: This patch changes the user/kernel ABI. That means that the zfs/zpool utilities built from master are NOT compatible with the 0.6.2 kernel modules. Ensure you load the matching kernel modules from master after updating the utilities. Otherwise the zfs/zpool commands will be unable to interact with your pool and you will see errors similar to the following: $ zpool list failed to read pool configuration: bad address no pools available $ zfs list no datasets available Add zvol minor device creation to the new zfs_snapshot_nvl function. Remove the logging of the "release" operation in dsl_dataset_user_release_sync(). The logging caused a null dereference because ds->ds_dir is zeroed in dsl_dataset_destroy_sync() and the logging functions try to get the ds name via the dsl_dataset_name() function. I've got no idea why this particular code would have worked in Illumos. This code has subsequently been completely reworked in Illumos commit 3b2aab1 (3464 zfs synctask code needs restructuring). Squash some "may be used uninitialized" warning/erorrs. Fix some printf format warnings for %lld and %llu. Apply a few spa_writeable() changes that were made to Illumos in illumos/illumos-gate.git@cd1c8b8 as part of the 3112, 3113, 3114 and 3115 fixes. Add a missing call to fnvlist_free(nvl) in log_internal() that was added in Illumos to fix issue 3085 but couldn't be ported to ZoL at the time (zfsonlinux/zfs@9e11c73) because it depended on future work.
2013-08-28 11:45:09 +00:00
spa_history_log_internal_ds(ds, (source == ZPROP_SRC_NONE ||
source == ZPROP_SRC_INHERITED) ? "inherit" : "set", tx,
"%s=%s", propname, (valstr == NULL ? "" : valstr));
if (tbuf != NULL)
kmem_free(tbuf, ZAP_MAXVALUELEN);
2008-11-20 20:01:55 +00:00
}
int
dsl_prop_set_int(const char *dsname, const char *propname,
zprop_source_t source, uint64_t value)
2009-07-02 22:44:48 +00:00
{
nvlist_t *nvl = fnvlist_alloc();
int error;
fnvlist_add_uint64(nvl, propname, value);
error = dsl_props_set(dsname, source, nvl);
fnvlist_free(nvl);
return (error);
2009-07-02 22:44:48 +00:00
}
2008-11-20 20:01:55 +00:00
int
dsl_prop_set_string(const char *dsname, const char *propname,
zprop_source_t source, const char *value)
2008-11-20 20:01:55 +00:00
{
nvlist_t *nvl = fnvlist_alloc();
int error;
fnvlist_add_string(nvl, propname, value);
error = dsl_props_set(dsname, source, nvl);
fnvlist_free(nvl);
return (error);
}
int
dsl_prop_inherit(const char *dsname, const char *propname,
zprop_source_t source)
{
nvlist_t *nvl = fnvlist_alloc();
int error;
fnvlist_add_boolean(nvl, propname);
error = dsl_props_set(dsname, source, nvl);
fnvlist_free(nvl);
return (error);
2008-11-20 20:01:55 +00:00
}
typedef struct dsl_props_set_arg {
const char *dpsa_dsname;
zprop_source_t dpsa_source;
nvlist_t *dpsa_props;
} dsl_props_set_arg_t;
static int
dsl_props_set_check(void *arg, dmu_tx_t *tx)
2009-07-02 22:44:48 +00:00
{
dsl_props_set_arg_t *dpsa = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
2009-07-02 22:44:48 +00:00
dsl_dataset_t *ds;
uint64_t version;
nvpair_t *elem = NULL;
int err;
err = dsl_dataset_hold(dp, dpsa->dpsa_dsname, FTAG, &ds);
if (err != 0)
2009-07-02 22:44:48 +00:00
return (err);
2009-07-02 22:44:48 +00:00
version = spa_version(ds->ds_dir->dd_pool->dp_spa);
while ((elem = nvlist_next_nvpair(dpsa->dpsa_props, elem)) != NULL) {
2009-07-02 22:44:48 +00:00
if (strlen(nvpair_name(elem)) >= ZAP_MAXNAMELEN) {
dsl_dataset_rele(ds, FTAG);
return (SET_ERROR(ENAMETOOLONG));
2009-07-02 22:44:48 +00:00
}
if (nvpair_type(elem) == DATA_TYPE_STRING) {
char *valstr = fnvpair_value_string(elem);
2009-07-02 22:44:48 +00:00
if (strlen(valstr) >= (version <
SPA_VERSION_STMF_PROP ?
ZAP_OLDMAXVALUELEN : ZAP_MAXVALUELEN)) {
dsl_dataset_rele(ds, FTAG);
return (E2BIG);
}
}
}
if (dsl_dataset_is_snapshot(ds) && version < SPA_VERSION_SNAP_PROPS) {
2009-07-02 22:44:48 +00:00
dsl_dataset_rele(ds, FTAG);
return (SET_ERROR(ENOTSUP));
2009-07-02 22:44:48 +00:00
}
dsl_dataset_rele(ds, FTAG);
return (0);
}
void
dsl_props_set_sync_impl(dsl_dataset_t *ds, zprop_source_t source,
nvlist_t *props, dmu_tx_t *tx)
{
nvpair_t *elem = NULL;
while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
nvpair_t *pair = elem;
2009-07-02 22:44:48 +00:00
if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
/*
* dsl_prop_get_all_impl() returns properties in this
* format.
*/
nvlist_t *attrs = fnvpair_value_nvlist(pair);
pair = fnvlist_lookup_nvpair(attrs, ZPROP_VALUE);
}
if (nvpair_type(pair) == DATA_TYPE_STRING) {
const char *value = fnvpair_value_string(pair);
dsl_prop_set_sync_impl(ds, nvpair_name(pair),
source, 1, strlen(value) + 1, value, tx);
} else if (nvpair_type(pair) == DATA_TYPE_UINT64) {
uint64_t intval = fnvpair_value_uint64(pair);
dsl_prop_set_sync_impl(ds, nvpair_name(pair),
source, sizeof (intval), 1, &intval, tx);
} else if (nvpair_type(pair) == DATA_TYPE_BOOLEAN) {
dsl_prop_set_sync_impl(ds, nvpair_name(pair),
source, 0, 0, NULL, tx);
} else {
panic("invalid nvpair type");
}
}
}
static void
dsl_props_set_sync(void *arg, dmu_tx_t *tx)
{
dsl_props_set_arg_t *dpsa = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
dsl_dataset_t *ds;
2009-07-02 22:44:48 +00:00
VERIFY0(dsl_dataset_hold(dp, dpsa->dpsa_dsname, FTAG, &ds));
dsl_props_set_sync_impl(ds, dpsa->dpsa_source, dpsa->dpsa_props, tx);
2009-07-02 22:44:48 +00:00
dsl_dataset_rele(ds, FTAG);
}
/*
* All-or-nothing; if any prop can't be set, nothing will be modified.
*/
int
dsl_props_set(const char *dsname, zprop_source_t source, nvlist_t *props)
{
dsl_props_set_arg_t dpsa;
int nblks = 0;
dpsa.dpsa_dsname = dsname;
dpsa.dpsa_source = source;
dpsa.dpsa_props = props;
/*
* If the source includes NONE, then we will only be removing entries
* from the ZAP object. In that case don't check for ENOSPC.
*/
if ((source & ZPROP_SRC_NONE) == 0)
nblks = 2 * fnvlist_num_pairs(props);
return (dsl_sync_task(dsname, dsl_props_set_check, dsl_props_set_sync,
&dpsa, nblks));
2009-07-02 22:44:48 +00:00
}
typedef enum dsl_prop_getflags {
DSL_PROP_GET_INHERITING = 0x1, /* searching parent of target ds */
DSL_PROP_GET_SNAPSHOT = 0x2, /* snapshot dataset */
DSL_PROP_GET_LOCAL = 0x4, /* local properties */
DSL_PROP_GET_RECEIVED = 0x8 /* received properties */
} dsl_prop_getflags_t;
static int
dsl_prop_get_all_impl(objset_t *mos, uint64_t propobj,
const char *setpoint, dsl_prop_getflags_t flags, nvlist_t *nv)
{
zap_cursor_t zc;
zap_attribute_t za;
int err = 0;
for (zap_cursor_init(&zc, mos, propobj);
(err = zap_cursor_retrieve(&zc, &za)) == 0;
zap_cursor_advance(&zc)) {
nvlist_t *propval;
zfs_prop_t prop;
char buf[ZAP_MAXNAMELEN];
char *valstr;
const char *suffix;
const char *propname;
const char *source;
suffix = strchr(za.za_name, '$');
if (suffix == NULL) {
/*
* Skip local properties if we only want received
* properties.
*/
if (flags & DSL_PROP_GET_RECEIVED)
continue;
propname = za.za_name;
source = setpoint;
} else if (strcmp(suffix, ZPROP_INHERIT_SUFFIX) == 0) {
/* Skip explicitly inherited entries. */
continue;
} else if (strcmp(suffix, ZPROP_RECVD_SUFFIX) == 0) {
if (flags & DSL_PROP_GET_LOCAL)
continue;
(void) strncpy(buf, za.za_name, (suffix - za.za_name));
buf[suffix - za.za_name] = '\0';
propname = buf;
if (!(flags & DSL_PROP_GET_RECEIVED)) {
/* Skip if locally overridden. */
err = zap_contains(mos, propobj, propname);
if (err == 0)
continue;
if (err != ENOENT)
break;
/* Skip if explicitly inherited. */
valstr = kmem_asprintf("%s%s", propname,
ZPROP_INHERIT_SUFFIX);
err = zap_contains(mos, propobj, valstr);
strfree(valstr);
if (err == 0)
continue;
if (err != ENOENT)
break;
}
source = ((flags & DSL_PROP_GET_INHERITING) ?
setpoint : ZPROP_SOURCE_VAL_RECVD);
} else {
/*
* For backward compatibility, skip suffixes we don't
* recognize.
*/
continue;
}
prop = zfs_name_to_prop(propname);
/* Skip non-inheritable properties. */
if ((flags & DSL_PROP_GET_INHERITING) && prop != ZPROP_INVAL &&
!zfs_prop_inheritable(prop))
continue;
/* Skip properties not valid for this type. */
if ((flags & DSL_PROP_GET_SNAPSHOT) && prop != ZPROP_INVAL &&
!zfs_prop_valid_for_type(prop, ZFS_TYPE_SNAPSHOT, B_FALSE))
continue;
/* Skip properties already defined. */
if (nvlist_exists(nv, propname))
continue;
VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0);
if (za.za_integer_length == 1) {
/*
* String property
*/
char *tmp = kmem_alloc(za.za_num_integers,
KM_SLEEP);
err = zap_lookup(mos, propobj,
za.za_name, 1, za.za_num_integers, tmp);
if (err != 0) {
kmem_free(tmp, za.za_num_integers);
break;
}
VERIFY(nvlist_add_string(propval, ZPROP_VALUE,
tmp) == 0);
kmem_free(tmp, za.za_num_integers);
} else {
/*
* Integer property
*/
ASSERT(za.za_integer_length == 8);
(void) nvlist_add_uint64(propval, ZPROP_VALUE,
za.za_first_integer);
}
VERIFY(nvlist_add_string(propval, ZPROP_SOURCE, source) == 0);
VERIFY(nvlist_add_nvlist(nv, propname, propval) == 0);
nvlist_free(propval);
}
zap_cursor_fini(&zc);
if (err == ENOENT)
err = 0;
return (err);
}
2008-11-20 20:01:55 +00:00
/*
* Iterate over all properties for this dataset and return them in an nvlist.
*/
static int
dsl_prop_get_all_ds(dsl_dataset_t *ds, nvlist_t **nvp,
dsl_prop_getflags_t flags)
2008-11-20 20:01:55 +00:00
{
dsl_dir_t *dd = ds->ds_dir;
dsl_pool_t *dp = dd->dd_pool;
objset_t *mos = dp->dp_meta_objset;
int err = 0;
char setpoint[MAXNAMELEN];
2008-11-20 20:01:55 +00:00
VERIFY(nvlist_alloc(nvp, NV_UNIQUE_NAME, KM_SLEEP) == 0);
if (dsl_dataset_is_snapshot(ds))
flags |= DSL_PROP_GET_SNAPSHOT;
2008-11-20 20:01:55 +00:00
ASSERT(dsl_pool_config_held(dp));
if (dsl_dataset_phys(ds)->ds_props_obj != 0) {
ASSERT(flags & DSL_PROP_GET_SNAPSHOT);
dsl_dataset_name(ds, setpoint);
err = dsl_prop_get_all_impl(mos,
dsl_dataset_phys(ds)->ds_props_obj, setpoint, flags, *nvp);
if (err)
goto out;
}
for (; dd != NULL; dd = dd->dd_parent) {
if (dd != ds->ds_dir || (flags & DSL_PROP_GET_SNAPSHOT)) {
if (flags & (DSL_PROP_GET_LOCAL |
DSL_PROP_GET_RECEIVED))
break;
flags |= DSL_PROP_GET_INHERITING;
}
dsl_dir_name(dd, setpoint);
err = dsl_prop_get_all_impl(mos,
dsl_dir_phys(dd)->dd_props_zapobj, setpoint, flags, *nvp);
if (err)
break;
}
out:
return (err);
}
2008-11-20 20:01:55 +00:00
boolean_t
dsl_prop_get_hasrecvd(const char *dsname)
{
uint64_t dummy;
return (0 ==
dsl_prop_get_integer(dsname, ZPROP_HAS_RECVD, &dummy, NULL));
}
2008-11-20 20:01:55 +00:00
static int
dsl_prop_set_hasrecvd_impl(const char *dsname, zprop_source_t source)
{
uint64_t version;
spa_t *spa;
int error = 0;
2008-11-20 20:01:55 +00:00
VERIFY0(spa_open(dsname, &spa, FTAG));
version = spa_version(spa);
spa_close(spa, FTAG);
2008-11-20 20:01:55 +00:00
if (version >= SPA_VERSION_RECVD_PROPS)
error = dsl_prop_set_int(dsname, ZPROP_HAS_RECVD, source, 0);
return (error);
}
2008-11-20 20:01:55 +00:00
/*
* Call after successfully receiving properties to ensure that only the first
* receive on or after SPA_VERSION_RECVD_PROPS blows away local properties.
*/
int
dsl_prop_set_hasrecvd(const char *dsname)
{
int error = 0;
if (!dsl_prop_get_hasrecvd(dsname))
error = dsl_prop_set_hasrecvd_impl(dsname, ZPROP_SRC_LOCAL);
return (error);
}
2008-11-20 20:01:55 +00:00
void
dsl_prop_unset_hasrecvd(const char *dsname)
{
VERIFY0(dsl_prop_set_hasrecvd_impl(dsname, ZPROP_SRC_NONE));
}
int
dsl_prop_get_all(objset_t *os, nvlist_t **nvp)
{
return (dsl_prop_get_all_ds(os->os_dsl_dataset, nvp, 0));
}
int
dsl_prop_get_received(const char *dsname, nvlist_t **nvp)
{
objset_t *os;
int error;
/*
* Received properties are not distinguishable from local properties
* until the dataset has received properties on or after
* SPA_VERSION_RECVD_PROPS.
*/
dsl_prop_getflags_t flags = (dsl_prop_get_hasrecvd(dsname) ?
DSL_PROP_GET_RECEIVED : DSL_PROP_GET_LOCAL);
error = dmu_objset_hold(dsname, FTAG, &os);
if (error != 0)
return (error);
error = dsl_prop_get_all_ds(os->os_dsl_dataset, nvp, flags);
dmu_objset_rele(os, FTAG);
return (error);
2008-11-20 20:01:55 +00:00
}
void
dsl_prop_nvlist_add_uint64(nvlist_t *nv, zfs_prop_t prop, uint64_t value)
{
nvlist_t *propval;
const char *propname = zfs_prop_to_name(prop);
uint64_t default_value;
if (nvlist_lookup_nvlist(nv, propname, &propval) == 0) {
VERIFY(nvlist_add_uint64(propval, ZPROP_VALUE, value) == 0);
return;
}
2008-11-20 20:01:55 +00:00
VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0);
VERIFY(nvlist_add_uint64(propval, ZPROP_VALUE, value) == 0);
/* Indicate the default source if we can. */
if (dodefault(propname, 8, 1, &default_value) == 0 &&
value == default_value) {
VERIFY(nvlist_add_string(propval, ZPROP_SOURCE, "") == 0);
}
VERIFY(nvlist_add_nvlist(nv, propname, propval) == 0);
2008-11-20 20:01:55 +00:00
nvlist_free(propval);
}
void
dsl_prop_nvlist_add_string(nvlist_t *nv, zfs_prop_t prop, const char *value)
{
nvlist_t *propval;
const char *propname = zfs_prop_to_name(prop);
if (nvlist_lookup_nvlist(nv, propname, &propval) == 0) {
VERIFY(nvlist_add_string(propval, ZPROP_VALUE, value) == 0);
return;
}
2008-11-20 20:01:55 +00:00
VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0);
VERIFY(nvlist_add_string(propval, ZPROP_VALUE, value) == 0);
VERIFY(nvlist_add_nvlist(nv, propname, propval) == 0);
2008-11-20 20:01:55 +00:00
nvlist_free(propval);
}
#if defined(_KERNEL) && defined(HAVE_SPL)
EXPORT_SYMBOL(dsl_prop_register);
EXPORT_SYMBOL(dsl_prop_unregister);
EXPORT_SYMBOL(dsl_prop_get);
EXPORT_SYMBOL(dsl_prop_get_integer);
EXPORT_SYMBOL(dsl_prop_get_all);
EXPORT_SYMBOL(dsl_prop_get_received);
EXPORT_SYMBOL(dsl_prop_get_ds);
EXPORT_SYMBOL(dsl_prop_get_int_ds);
EXPORT_SYMBOL(dsl_prop_get_dd);
EXPORT_SYMBOL(dsl_props_set);
EXPORT_SYMBOL(dsl_prop_set_int);
EXPORT_SYMBOL(dsl_prop_set_string);
EXPORT_SYMBOL(dsl_prop_inherit);
EXPORT_SYMBOL(dsl_prop_predict);
EXPORT_SYMBOL(dsl_prop_nvlist_add_uint64);
EXPORT_SYMBOL(dsl_prop_nvlist_add_string);
#endif