2008-11-20 23:01:55 +03: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
|
2022-07-12 00:16:13 +03:00
|
|
|
* or https://opensource.org/licenses/CDDL-1.0.
|
2008-11-20 23:01:55 +03:00
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
/*
|
2010-05-29 00:45:14 +04:00
|
|
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
2016-06-16 00:28:36 +03:00
|
|
|
* Copyright (c) 2012, 2015 by Delphix. All rights reserved.
|
2013-05-23 21:07:25 +04:00
|
|
|
* Copyright (c) 2013 Martin Matuska. All rights reserved.
|
2020-01-23 04:03:17 +03:00
|
|
|
* Copyright 2019 Joyent, Inc.
|
2022-10-20 03:07:51 +03:00
|
|
|
* Copyright (c) 2022 Hewlett Packard Enterprise Development LP.
|
2008-11-20 23:01:55 +03:00
|
|
|
*/
|
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
#include <sys/zfs_context.h>
|
2008-11-20 23:01:55 +03: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"
|
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
#define ZPROP_INHERIT_SUFFIX "$inherit"
|
|
|
|
#define ZPROP_RECVD_SUFFIX "$recvd"
|
2022-10-20 03:07:51 +03:00
|
|
|
#define ZPROP_IUV_SUFFIX "$iuv"
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
static int
|
2016-03-12 02:25:32 +03:00
|
|
|
dodefault(zfs_prop_t prop, int intsz, int numints, void *buf)
|
2008-11-20 23:01:55 +03:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* The setonce properties are read-only, BUT they still
|
|
|
|
* have a default value that can be used as the initial
|
|
|
|
* value.
|
|
|
|
*/
|
2016-03-12 02:25:32 +03:00
|
|
|
if (prop == ZPROP_INVAL ||
|
2008-11-20 23:01:55 +03:00
|
|
|
(zfs_prop_readonly(prop) && !zfs_prop_setonce(prop)))
|
2013-03-08 22:41:28 +04:00
|
|
|
return (SET_ERROR(ENOENT));
|
2008-11-20 23:01:55 +03:00
|
|
|
|
|
|
|
if (zfs_prop_get_type(prop) == PROP_TYPE_STRING) {
|
|
|
|
if (intsz != 1)
|
2013-03-08 22:41:28 +04:00
|
|
|
return (SET_ERROR(EOVERFLOW));
|
Cleanup: Switch to strlcpy from strncpy
Coverity found a bug in `zfs_secpolicy_create_clone()` where it is
possible for us to pass an unterminated string when `zfs_get_parent()`
returns an error. Upon inspection, it is clear that using `strlcpy()`
would have avoided this issue.
Looking at the codebase, there are a number of other uses of `strncpy()`
that are unsafe and even when it is used safely, switching to
`strlcpy()` would make the code more readable. Therefore, we switch all
instances where we use `strncpy()` to use `strlcpy()`.
Unfortunately, we do not portably have access to `strlcpy()` in
tests/zfs-tests/cmd/zfs_diff-socket.c because it does not link to
libspl. Modifying the appropriate Makefile.am to try to link to it
resulted in an error from the naming choice used in the file. Trying to
disable the check on the file did not work on FreeBSD because Clang
ignores `#undef` when a definition is provided by `-Dstrncpy(...)=...`.
We workaround that by explictly including the C file from libspl into
the test. This makes things build correctly everywhere.
We add a deprecation warning to `config/Rules.am` and suppress it on the
remaining `strncpy()` usage. `strlcpy()` is not portably avaliable in
tests/zfs-tests/cmd/zfs_diff-socket.c, so we use `snprintf()` there as a
substitute.
This patch does not tackle the related problem of `strcpy()`, which is
even less safe. Thankfully, a quick inspection found that it is used far
more correctly than strncpy() was used. A quick inspection did not find
any problems with `strcpy()` usage outside of zhack, but it should be
said that I only checked around 90% of them.
Lastly, some of the fields in kstat_t varied in size by 1 depending on
whether they were in userspace or in the kernel. The origin of this
discrepancy appears to be 04a479f7066ccdaa23a6546955303b172f4a6909 where
it was made for no apparent reason. It conflicts with the comment on
KSTAT_STRLEN, so we shrink the kernel field sizes to match the userspace
field sizes.
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ryan Moeller <ryan@iXsystems.com>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #13876
2022-09-28 02:35:29 +03:00
|
|
|
(void) strlcpy(buf, zfs_prop_default_string(prop),
|
2010-05-29 00:45:14 +04:00
|
|
|
numints);
|
2008-11-20 23:01:55 +03:00
|
|
|
} else {
|
2010-05-29 00:45:14 +04:00
|
|
|
if (intsz != 8 || numints < 1)
|
2013-03-08 22:41:28 +04:00
|
|
|
return (SET_ERROR(EOVERFLOW));
|
2008-11-20 23:01:55 +03:00
|
|
|
|
|
|
|
*(uint64_t *)buf = zfs_prop_default_numeric(prop);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2022-10-20 03:07:51 +03:00
|
|
|
static int
|
|
|
|
dsl_prop_known_index(zfs_prop_t prop, uint64_t value)
|
|
|
|
{
|
|
|
|
const char *str = NULL;
|
|
|
|
if (zfs_prop_get_type(prop) == PROP_TYPE_INDEX)
|
|
|
|
return (!zfs_prop_index_to_string(prop, value, &str));
|
|
|
|
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
2008-12-03 23:09:06 +03:00
|
|
|
int
|
|
|
|
dsl_prop_get_dd(dsl_dir_t *dd, const char *propname,
|
2010-05-29 00:45:14 +04:00
|
|
|
int intsz, int numints, void *buf, char *setpoint, boolean_t snapshot)
|
2008-11-20 23:01:55 +03:00
|
|
|
{
|
2020-02-27 03:09:17 +03:00
|
|
|
int err;
|
2010-05-29 00:45:14 +04:00
|
|
|
dsl_dir_t *target = dd;
|
2008-12-03 23:09:06 +03:00
|
|
|
objset_t *mos = dd->dd_pool->dp_meta_objset;
|
2008-11-20 23:01:55 +03:00
|
|
|
zfs_prop_t prop;
|
2010-05-29 00:45:14 +04:00
|
|
|
boolean_t inheritable;
|
|
|
|
boolean_t inheriting = B_FALSE;
|
|
|
|
char *inheritstr;
|
|
|
|
char *recvdstr;
|
2022-10-20 03:07:51 +03:00
|
|
|
char *iuvstr;
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
ASSERT(dsl_pool_config_held(dd->dd_pool));
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
if (setpoint)
|
|
|
|
setpoint[0] = '\0';
|
|
|
|
|
|
|
|
prop = zfs_name_to_prop(propname);
|
2022-06-14 21:27:53 +03:00
|
|
|
inheritable = (prop == ZPROP_USERPROP || zfs_prop_inheritable(prop));
|
2010-05-29 00:45:14 +04:00
|
|
|
inheritstr = kmem_asprintf("%s%s", propname, ZPROP_INHERIT_SUFFIX);
|
|
|
|
recvdstr = kmem_asprintf("%s%s", propname, ZPROP_RECVD_SUFFIX);
|
2022-10-20 03:07:51 +03:00
|
|
|
iuvstr = kmem_asprintf("%s%s", propname, ZPROP_IUV_SUFFIX);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
|
|
|
/*
|
2010-05-29 00:45:14 +04:00
|
|
|
* Note: dd may become NULL, therefore we shouldn't dereference it
|
|
|
|
* after this loop.
|
2008-11-20 23:01:55 +03:00
|
|
|
*/
|
|
|
|
for (; dd != NULL; dd = dd->dd_parent) {
|
2010-05-29 00:45:14 +04:00
|
|
|
if (dd != target || snapshot) {
|
2020-02-27 03:09:17 +03:00
|
|
|
if (!inheritable) {
|
|
|
|
err = SET_ERROR(ENOENT);
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
2020-02-27 03:09:17 +03:00
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
inheriting = B_TRUE;
|
|
|
|
}
|
|
|
|
|
2022-10-20 03:07:51 +03:00
|
|
|
/* Check for a iuv value. */
|
|
|
|
err = zap_lookup(mos, dsl_dir_phys(dd)->dd_props_zapobj,
|
|
|
|
iuvstr, intsz, numints, buf);
|
|
|
|
if (dsl_prop_known_index(zfs_name_to_prop(propname),
|
|
|
|
*(uint64_t *)buf) != 1)
|
|
|
|
err = ENOENT;
|
|
|
|
if (err != ENOENT) {
|
|
|
|
if (setpoint != NULL && err == 0)
|
|
|
|
dsl_dir_name(dd, setpoint);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
/* Check for a local value. */
|
2015-04-01 18:14:34 +03:00
|
|
|
err = zap_lookup(mos, dsl_dir_phys(dd)->dd_props_zapobj,
|
|
|
|
propname, intsz, numints, buf);
|
2008-11-20 23:01:55 +03:00
|
|
|
if (err != ENOENT) {
|
2010-05-29 00:45:14 +04:00
|
|
|
if (setpoint != NULL && err == 0)
|
2008-11-20 23:01:55 +03:00
|
|
|
dsl_dir_name(dd, setpoint);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2010-05-29 00:45:14 +04:00
|
|
|
* Skip the check for a received value if there is an explicit
|
|
|
|
* inheritance entry.
|
2008-11-20 23:01:55 +03:00
|
|
|
*/
|
2015-04-01 18:14:34 +03:00
|
|
|
err = zap_contains(mos, dsl_dir_phys(dd)->dd_props_zapobj,
|
2010-05-29 00:45:14 +04:00
|
|
|
inheritstr);
|
|
|
|
if (err != 0 && err != ENOENT)
|
2008-11-20 23:01:55 +03:00
|
|
|
break;
|
2010-05-29 00:45:14 +04:00
|
|
|
|
|
|
|
if (err == ENOENT) {
|
|
|
|
/* Check for a received value. */
|
2015-04-01 18:14:34 +03:00
|
|
|
err = zap_lookup(mos, dsl_dir_phys(dd)->dd_props_zapobj,
|
2010-05-29 00:45:14 +04:00
|
|
|
recvdstr, intsz, numints, buf);
|
|
|
|
if (err != ENOENT) {
|
|
|
|
if (setpoint != NULL && err == 0) {
|
|
|
|
if (inheriting) {
|
|
|
|
dsl_dir_name(dd, setpoint);
|
|
|
|
} else {
|
2020-06-07 21:42:12 +03:00
|
|
|
(void) strlcpy(setpoint,
|
|
|
|
ZPROP_SOURCE_VAL_RECVD,
|
|
|
|
MAXNAMELEN);
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
*/
|
2013-03-08 22:41:28 +04:00
|
|
|
err = SET_ERROR(ENOENT);
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
if (err == ENOENT)
|
2016-03-12 02:25:32 +03:00
|
|
|
err = dodefault(prop, intsz, numints, buf);
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2019-10-10 19:47:06 +03:00
|
|
|
kmem_strfree(inheritstr);
|
|
|
|
kmem_strfree(recvdstr);
|
2022-10-20 03:07:51 +03:00
|
|
|
kmem_strfree(iuvstr);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
|
|
|
return (err);
|
|
|
|
}
|
|
|
|
|
2008-12-03 23:09:06 +03:00
|
|
|
int
|
|
|
|
dsl_prop_get_ds(dsl_dataset_t *ds, const char *propname,
|
2010-05-29 00:45:14 +04:00
|
|
|
int intsz, int numints, void *buf, char *setpoint)
|
2008-12-03 23:09:06 +03:00
|
|
|
{
|
2010-05-29 00:45:14 +04:00
|
|
|
zfs_prop_t prop = zfs_name_to_prop(propname);
|
|
|
|
boolean_t inheritable;
|
|
|
|
uint64_t zapobj;
|
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
ASSERT(dsl_pool_config_held(ds->ds_dir->dd_pool));
|
2022-06-14 21:27:53 +03:00
|
|
|
inheritable = (prop == ZPROP_USERPROP || zfs_prop_inheritable(prop));
|
2015-04-01 18:14:34 +03:00
|
|
|
zapobj = dsl_dataset_phys(ds)->ds_props_obj;
|
2010-05-29 00:45:14 +04:00
|
|
|
|
|
|
|
if (zapobj != 0) {
|
|
|
|
objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
|
|
|
|
int err;
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2015-04-02 06:44:32 +03:00
|
|
|
ASSERT(ds->ds_is_snapshot);
|
2010-05-29 00:45:14 +04:00
|
|
|
|
|
|
|
/* Check for a local value. */
|
|
|
|
err = zap_lookup(mos, zapobj, propname, intsz, numints, buf);
|
2008-12-03 23:09:06 +03:00
|
|
|
if (err != ENOENT) {
|
2010-05-29 00:45:14 +04:00
|
|
|
if (setpoint != NULL && err == 0)
|
2008-12-03 23:09:06 +03:00
|
|
|
dsl_dataset_name(ds, setpoint);
|
|
|
|
return (err);
|
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
2019-10-10 19:47:06 +03:00
|
|
|
kmem_strfree(inheritstr);
|
2010-05-29 00:45:14 +04:00
|
|
|
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);
|
2019-10-10 19:47:06 +03:00
|
|
|
kmem_strfree(recvdstr);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (err != ENOENT) {
|
|
|
|
if (setpoint != NULL && err == 0)
|
2020-06-07 21:42:12 +03:00
|
|
|
(void) strlcpy(setpoint,
|
|
|
|
ZPROP_SOURCE_VAL_RECVD,
|
|
|
|
MAXNAMELEN);
|
2010-05-29 00:45:14 +04:00
|
|
|
return (err);
|
|
|
|
}
|
|
|
|
}
|
2008-12-03 23:09:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return (dsl_prop_get_dd(ds->ds_dir, propname,
|
2015-04-02 06:44:32 +03:00
|
|
|
intsz, numints, buf, setpoint, ds->ds_is_snapshot));
|
2008-12-03 23:09:06 +03:00
|
|
|
}
|
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
static dsl_prop_record_t *
|
|
|
|
dsl_prop_record_find(dsl_dir_t *dd, const char *propname)
|
|
|
|
{
|
|
|
|
dsl_prop_record_t *pr = NULL;
|
|
|
|
|
|
|
|
ASSERT(MUTEX_HELD(&dd->dd_lock));
|
|
|
|
|
|
|
|
for (pr = list_head(&dd->dd_props);
|
|
|
|
pr != NULL; pr = list_next(&dd->dd_props, pr)) {
|
|
|
|
if (strcmp(pr->pr_propname, propname) == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (pr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static dsl_prop_record_t *
|
|
|
|
dsl_prop_record_create(dsl_dir_t *dd, const char *propname)
|
|
|
|
{
|
|
|
|
dsl_prop_record_t *pr;
|
|
|
|
|
|
|
|
ASSERT(MUTEX_HELD(&dd->dd_lock));
|
|
|
|
|
|
|
|
pr = kmem_alloc(sizeof (dsl_prop_record_t), KM_SLEEP);
|
|
|
|
pr->pr_propname = spa_strdup(propname);
|
|
|
|
list_create(&pr->pr_cbs, sizeof (dsl_prop_cb_record_t),
|
|
|
|
offsetof(dsl_prop_cb_record_t, cbr_pr_node));
|
|
|
|
list_insert_head(&dd->dd_props, pr);
|
|
|
|
|
|
|
|
return (pr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dsl_prop_init(dsl_dir_t *dd)
|
|
|
|
{
|
|
|
|
list_create(&dd->dd_props, sizeof (dsl_prop_record_t),
|
|
|
|
offsetof(dsl_prop_record_t, pr_node));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dsl_prop_fini(dsl_dir_t *dd)
|
|
|
|
{
|
|
|
|
dsl_prop_record_t *pr;
|
|
|
|
|
|
|
|
while ((pr = list_remove_head(&dd->dd_props)) != NULL) {
|
|
|
|
list_destroy(&pr->pr_cbs);
|
2016-09-10 18:16:13 +03:00
|
|
|
spa_strfree((char *)pr->pr_propname);
|
2015-11-05 02:00:58 +03:00
|
|
|
kmem_free(pr, sizeof (dsl_prop_record_t));
|
|
|
|
}
|
|
|
|
list_destroy(&dd->dd_props);
|
|
|
|
}
|
|
|
|
|
2008-11-20 23:01:55 +03: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;
|
2015-11-05 02:00:58 +03:00
|
|
|
dsl_prop_record_t *pr;
|
2008-11-20 23:01:55 +03:00
|
|
|
dsl_prop_cb_record_t *cbr;
|
|
|
|
int err;
|
2019-12-05 23:37:00 +03:00
|
|
|
dsl_pool_t *dp __maybe_unused = dd->dd_pool;
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
ASSERT(dsl_pool_config_held(dp));
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
err = dsl_prop_get_int_ds(ds, propname, &value);
|
|
|
|
if (err != 0)
|
2008-11-20 23:01:55 +03:00
|
|
|
return (err);
|
|
|
|
|
2014-11-21 03:09:39 +03:00
|
|
|
cbr = kmem_alloc(sizeof (dsl_prop_cb_record_t), KM_SLEEP);
|
2008-11-20 23:01:55 +03:00
|
|
|
cbr->cbr_ds = ds;
|
|
|
|
cbr->cbr_func = callback;
|
|
|
|
cbr->cbr_arg = cbarg;
|
2015-11-05 02:00:58 +03:00
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
mutex_enter(&dd->dd_lock);
|
2015-11-05 02:00:58 +03:00
|
|
|
pr = dsl_prop_record_find(dd, propname);
|
|
|
|
if (pr == NULL)
|
|
|
|
pr = dsl_prop_record_create(dd, propname);
|
|
|
|
cbr->cbr_pr = pr;
|
|
|
|
list_insert_head(&pr->pr_cbs, cbr);
|
|
|
|
list_insert_head(&ds->ds_prop_cbs, cbr);
|
2008-11-20 23:01:55 +03:00
|
|
|
mutex_exit(&dd->dd_lock);
|
|
|
|
|
|
|
|
cbr->cbr_func(cbr->cbr_arg, value);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2008-12-03 23:09:06 +03:00
|
|
|
dsl_prop_get(const char *dsname, const char *propname,
|
2008-11-20 23:01:55 +03:00
|
|
|
int intsz, int numints, void *buf, char *setpoint)
|
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
objset_t *os;
|
|
|
|
int error;
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
error = dmu_objset_hold(dsname, FTAG, &os);
|
|
|
|
if (error != 0)
|
|
|
|
return (error);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
error = dsl_prop_get_ds(dmu_objset_ds(os), propname,
|
|
|
|
intsz, numints, buf, setpoint);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
dmu_objset_rele(os, FTAG);
|
|
|
|
return (error);
|
2008-11-20 23:01:55 +03: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));
|
|
|
|
}
|
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
int
|
|
|
|
dsl_prop_get_int_ds(dsl_dataset_t *ds, const char *propname,
|
|
|
|
uint64_t *valuep)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
return (dsl_prop_get_ds(ds, propname, 8, 1, valuep, NULL));
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_predict(dsl_dir_t *dd, const char *propname,
|
|
|
|
zprop_source_t source, uint64_t value, uint64_t *newvalp)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
|
|
|
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;
|
2015-04-01 18:14:34 +03:00
|
|
|
zapobj = dsl_dir_phys(dd)->dd_props_zapobj;
|
2010-05-29 00:45:14 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-02-23 23:50:05 +03:00
|
|
|
switch ((int)source) {
|
2010-05-29 00:45:14 +04:00
|
|
|
case ZPROP_SRC_NONE:
|
|
|
|
/* Revert to the received value, if any. */
|
2013-09-04 16:00:57 +04:00
|
|
|
err = zap_lookup(mos, zapobj, recvdstr, 8, 1, newvalp);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (err == ENOENT)
|
2013-09-04 16:00:57 +04:00
|
|
|
*newvalp = 0;
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
|
|
|
case ZPROP_SRC_LOCAL:
|
2013-09-04 16:00:57 +04:00
|
|
|
*newvalp = value;
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
|
|
|
case ZPROP_SRC_RECEIVED:
|
|
|
|
/*
|
|
|
|
* If there's no local setting, then the new received value will
|
|
|
|
* be the effective value.
|
|
|
|
*/
|
2013-09-04 16:00:57 +04:00
|
|
|
err = zap_lookup(mos, zapobj, propname, 8, 1, newvalp);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (err == ENOENT)
|
2013-09-04 16:00:57 +04:00
|
|
|
*newvalp = value;
|
2010-05-29 00:45:14 +04:00
|
|
|
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.
|
|
|
|
*/
|
2013-09-04 16:00:57 +04:00
|
|
|
err = zap_lookup(mos, zapobj, propname, 8, 1, newvalp);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (err == ENOENT)
|
2013-09-04 16:00:57 +04:00
|
|
|
*newvalp = 0;
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
|
|
|
default:
|
2013-09-04 16:00:57 +04:00
|
|
|
panic("unexpected property source: %d", source);
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
|
|
|
|
2019-10-10 19:47:06 +03:00
|
|
|
kmem_strfree(recvdstr);
|
2010-05-29 00:45:14 +04:00
|
|
|
|
|
|
|
if (err == ENOENT)
|
|
|
|
return (0);
|
|
|
|
|
|
|
|
return (err);
|
|
|
|
}
|
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
/*
|
|
|
|
* Unregister this callback. Return 0 on success, ENOENT if ddname is
|
2013-06-11 21:12:34 +04:00
|
|
|
* invalid, or ENOMSG if no matching callback registered.
|
2015-11-05 02:00:58 +03:00
|
|
|
*
|
|
|
|
* NOTE: This function is no longer used internally but has been preserved
|
|
|
|
* to prevent breaking external consumers (Lustre, etc).
|
2008-11-20 23:01:55 +03: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);
|
2015-11-05 02:00:58 +03:00
|
|
|
for (cbr = list_head(&ds->ds_prop_cbs);
|
|
|
|
cbr; cbr = list_next(&ds->ds_prop_cbs, cbr)) {
|
2008-11-20 23:01:55 +03:00
|
|
|
if (cbr->cbr_ds == ds &&
|
|
|
|
cbr->cbr_func == callback &&
|
|
|
|
cbr->cbr_arg == cbarg &&
|
2015-11-05 02:00:58 +03:00
|
|
|
strcmp(cbr->cbr_pr->pr_propname, propname) == 0)
|
2008-11-20 23:01:55 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cbr == NULL) {
|
|
|
|
mutex_exit(&dd->dd_lock);
|
2013-03-08 22:41:28 +04:00
|
|
|
return (SET_ERROR(ENOMSG));
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
list_remove(&ds->ds_prop_cbs, cbr);
|
|
|
|
list_remove(&cbr->cbr_pr->pr_cbs, cbr);
|
2008-11-20 23:01:55 +03:00
|
|
|
mutex_exit(&dd->dd_lock);
|
|
|
|
kmem_free(cbr, sizeof (dsl_prop_cb_record_t));
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
/*
|
|
|
|
* Unregister all callbacks that are registered with the
|
|
|
|
* given callback argument.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
dsl_prop_unregister_all(dsl_dataset_t *ds, void *cbarg)
|
2008-11-20 23:01:55 +03:00
|
|
|
{
|
2015-11-05 02:00:58 +03:00
|
|
|
dsl_prop_cb_record_t *cbr, *next_cbr;
|
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
dsl_dir_t *dd = ds->ds_dir;
|
|
|
|
|
|
|
|
mutex_enter(&dd->dd_lock);
|
2015-11-05 02:00:58 +03:00
|
|
|
next_cbr = list_head(&ds->ds_prop_cbs);
|
|
|
|
while (next_cbr != NULL) {
|
|
|
|
cbr = next_cbr;
|
|
|
|
next_cbr = list_next(&ds->ds_prop_cbs, cbr);
|
|
|
|
if (cbr->cbr_arg == cbarg) {
|
|
|
|
list_remove(&ds->ds_prop_cbs, cbr);
|
|
|
|
list_remove(&cbr->cbr_pr->pr_cbs, cbr);
|
|
|
|
kmem_free(cbr, sizeof (dsl_prop_cb_record_t));
|
2013-09-04 16:00:57 +04:00
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
|
|
|
mutex_exit(&dd->dd_lock);
|
2015-11-05 02:00:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
boolean_t
|
|
|
|
dsl_prop_hascb(dsl_dataset_t *ds)
|
|
|
|
{
|
|
|
|
return (!list_is_empty(&ds->ds_prop_cbs));
|
2013-09-04 16:00:57 +04:00
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
static int
|
|
|
|
dsl_prop_notify_all_cb(dsl_pool_t *dp, dsl_dataset_t *ds, void *arg)
|
|
|
|
{
|
2021-12-12 18:06:44 +03:00
|
|
|
(void) arg;
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_dir_t *dd = ds->ds_dir;
|
2015-11-05 02:00:58 +03:00
|
|
|
dsl_prop_record_t *pr;
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_cb_record_t *cbr;
|
|
|
|
|
|
|
|
mutex_enter(&dd->dd_lock);
|
2015-11-05 02:00:58 +03:00
|
|
|
for (pr = list_head(&dd->dd_props);
|
|
|
|
pr; pr = list_next(&dd->dd_props, pr)) {
|
|
|
|
for (cbr = list_head(&pr->pr_cbs); cbr;
|
|
|
|
cbr = list_next(&pr->pr_cbs, cbr)) {
|
|
|
|
uint64_t value;
|
2013-09-04 16:00:57 +04:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
/*
|
|
|
|
* Callback entries do not have holds on their
|
|
|
|
* datasets so that datasets with registered
|
|
|
|
* callbacks are still eligible for eviction.
|
|
|
|
* Unlike operations to update properties on a
|
|
|
|
* single dataset, we are performing a recursive
|
|
|
|
* descent of related head datasets. The caller
|
|
|
|
* of this function only has a dataset hold on
|
|
|
|
* the passed in head dataset, not the snapshots
|
|
|
|
* associated with this dataset. Without a hold,
|
|
|
|
* the dataset pointer within callback records
|
|
|
|
* for snapshots can be invalidated by eviction
|
|
|
|
* at any time.
|
|
|
|
*
|
|
|
|
* Use dsl_dataset_try_add_ref() to verify
|
|
|
|
* that the dataset for a snapshot has not
|
|
|
|
* begun eviction processing and to prevent
|
|
|
|
* eviction from occurring for the duration of
|
|
|
|
* the callback. If the hold attempt fails,
|
|
|
|
* this object is already being evicted and the
|
|
|
|
* callback can be safely ignored.
|
|
|
|
*/
|
|
|
|
if (ds != cbr->cbr_ds &&
|
|
|
|
!dsl_dataset_try_add_ref(dp, cbr->cbr_ds, FTAG))
|
|
|
|
continue;
|
2015-04-02 14:59:15 +03:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
if (dsl_prop_get_ds(cbr->cbr_ds,
|
|
|
|
cbr->cbr_pr->pr_propname, sizeof (value), 1,
|
|
|
|
&value, NULL) == 0)
|
|
|
|
cbr->cbr_func(cbr->cbr_arg, value);
|
2015-04-02 14:59:15 +03:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
if (ds != cbr->cbr_ds)
|
|
|
|
dsl_dataset_rele(cbr->cbr_ds, FTAG);
|
|
|
|
}
|
2013-09-04 16:00:57 +04:00
|
|
|
}
|
|
|
|
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 23:01:55 +03: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;
|
2015-11-05 02:00:58 +03:00
|
|
|
dsl_prop_record_t *pr;
|
2008-11-20 23:01:55 +03:00
|
|
|
dsl_prop_cb_record_t *cbr;
|
|
|
|
objset_t *mos = dp->dp_meta_objset;
|
|
|
|
zap_cursor_t zc;
|
|
|
|
zap_attribute_t *za;
|
|
|
|
int err;
|
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
|
|
|
|
err = dsl_dir_hold_obj(dp, ddobj, NULL, FTAG, &dd);
|
2008-11-20 23:01:55 +03: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.
|
|
|
|
*/
|
2015-04-01 18:14:34 +03:00
|
|
|
err = zap_contains(mos, dsl_dir_phys(dd)->dd_props_zapobj,
|
|
|
|
propname);
|
2008-11-20 23:01:55 +03:00
|
|
|
if (err == 0) {
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_dir_rele(dd, FTAG);
|
2008-11-20 23:01:55 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
ASSERT3U(err, ==, ENOENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_enter(&dd->dd_lock);
|
2015-11-05 02:00:58 +03:00
|
|
|
pr = dsl_prop_record_find(dd, propname);
|
|
|
|
if (pr != NULL) {
|
|
|
|
for (cbr = list_head(&pr->pr_cbs); cbr;
|
|
|
|
cbr = list_next(&pr->pr_cbs, cbr)) {
|
|
|
|
uint64_t propobj;
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
/*
|
|
|
|
* cbr->cbr_ds may be invalidated due to eviction,
|
|
|
|
* requiring the use of dsl_dataset_try_add_ref().
|
|
|
|
* See comment block in dsl_prop_notify_all_cb()
|
|
|
|
* for details.
|
|
|
|
*/
|
|
|
|
if (!dsl_dataset_try_add_ref(dp, cbr->cbr_ds, FTAG))
|
|
|
|
continue;
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
propobj = dsl_dataset_phys(cbr->cbr_ds)->ds_props_obj;
|
2015-04-02 14:59:15 +03:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
/*
|
|
|
|
* If the property is not set on this ds, then it is
|
|
|
|
* inherited here; call the callback.
|
|
|
|
*/
|
|
|
|
if (propobj == 0 ||
|
|
|
|
zap_contains(mos, propobj, propname) != 0)
|
|
|
|
cbr->cbr_func(cbr->cbr_arg, value);
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2015-11-05 02:00:58 +03:00
|
|
|
dsl_dataset_rele(cbr->cbr_ds, FTAG);
|
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
|
|
|
mutex_exit(&dd->dd_lock);
|
|
|
|
|
2014-11-21 03:09:39 +03:00
|
|
|
za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
|
2008-11-20 23:01:55 +03:00
|
|
|
for (zap_cursor_init(&zc, mos,
|
2015-04-01 18:14:34 +03:00
|
|
|
dsl_dir_phys(dd)->dd_child_dir_zapobj);
|
2008-11-20 23:01:55 +03: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);
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_dir_rele(dd, FTAG);
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
2022-10-20 03:07:51 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* For newer values in zfs index type properties, we add a new key
|
|
|
|
* propname$iuv (iuv = Ignore Unknown Values) to the properties zap object
|
|
|
|
* to store the new property value and store the default value in the
|
|
|
|
* existing prop key. So that the propname$iuv key is ignored by the older zfs
|
|
|
|
* versions and the default property value from the existing prop key is
|
|
|
|
* used.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
dsl_prop_set_iuv(objset_t *mos, uint64_t zapobj, const char *propname,
|
|
|
|
int intsz, int numints, const void *value, dmu_tx_t *tx)
|
|
|
|
{
|
|
|
|
char *iuvstr = kmem_asprintf("%s%s", propname, ZPROP_IUV_SUFFIX);
|
|
|
|
boolean_t iuv = B_FALSE;
|
|
|
|
zfs_prop_t prop = zfs_name_to_prop(propname);
|
|
|
|
|
|
|
|
switch (prop) {
|
|
|
|
case ZFS_PROP_REDUNDANT_METADATA:
|
|
|
|
if (*(uint64_t *)value == ZFS_REDUNDANT_METADATA_SOME ||
|
|
|
|
*(uint64_t *)value == ZFS_REDUNDANT_METADATA_NONE)
|
|
|
|
iuv = B_TRUE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iuv) {
|
|
|
|
VERIFY0(zap_update(mos, zapobj, iuvstr, intsz, numints,
|
|
|
|
value, tx));
|
|
|
|
uint64_t val = zfs_prop_default_numeric(prop);
|
|
|
|
VERIFY0(zap_update(mos, zapobj, propname, intsz, numints,
|
|
|
|
&val, tx));
|
|
|
|
} else {
|
|
|
|
zap_remove(mos, zapobj, iuvstr, tx);
|
|
|
|
}
|
|
|
|
kmem_strfree(iuvstr);
|
|
|
|
}
|
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
void
|
2013-09-04 16:00:57 +04:00
|
|
|
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 23:01:55 +03:00
|
|
|
{
|
2008-12-03 23:09:06 +03:00
|
|
|
objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
|
2019-12-13 22:51:39 +03:00
|
|
|
uint64_t zapobj, intval, dummy, count;
|
2008-11-20 23:01:55 +03:00
|
|
|
int isint;
|
|
|
|
char valbuf[32];
|
2013-09-04 16:00:57 +04:00
|
|
|
const char *valstr = NULL;
|
2010-05-29 00:45:14 +04:00
|
|
|
char *inheritstr;
|
|
|
|
char *recvdstr;
|
2022-10-20 03:07:51 +03:00
|
|
|
char *iuvstr;
|
2010-05-29 00:45:14 +04:00
|
|
|
char *tbuf = NULL;
|
|
|
|
int err;
|
|
|
|
uint64_t version = spa_version(ds->ds_dir->dd_pool->dp_spa);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2016-03-12 02:25:32 +03:00
|
|
|
isint = (dodefault(zfs_name_to_prop(propname), 8, 1, &intval) == 0);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2015-04-02 06:44:32 +03:00
|
|
|
if (ds->ds_is_snapshot) {
|
2010-05-29 00:45:14 +04:00
|
|
|
ASSERT(version >= SPA_VERSION_SNAP_PROPS);
|
2019-12-13 22:51:39 +03:00
|
|
|
if (dsl_dataset_phys(ds)->ds_props_obj == 0 &&
|
|
|
|
(source & ZPROP_SRC_NONE) == 0) {
|
2008-12-03 23:09:06 +03:00
|
|
|
dmu_buf_will_dirty(ds->ds_dbuf, tx);
|
2015-04-01 18:14:34 +03:00
|
|
|
dsl_dataset_phys(ds)->ds_props_obj =
|
2008-12-03 23:09:06 +03:00
|
|
|
zap_create(mos,
|
|
|
|
DMU_OT_DSL_PROPS, DMU_OT_NONE, 0, tx);
|
|
|
|
}
|
2015-04-01 18:14:34 +03:00
|
|
|
zapobj = dsl_dataset_phys(ds)->ds_props_obj;
|
2008-12-03 23:09:06 +03:00
|
|
|
} else {
|
2015-04-01 18:14:34 +03:00
|
|
|
zapobj = dsl_dir_phys(ds->ds_dir)->dd_props_zapobj;
|
2008-12-03 23:09:06 +03:00
|
|
|
}
|
|
|
|
|
2019-12-13 22:51:39 +03:00
|
|
|
/* If we are removing objects from a non-existent ZAP just return */
|
|
|
|
if (zapobj == 0)
|
|
|
|
return;
|
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
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);
|
2022-10-20 03:07:51 +03:00
|
|
|
iuvstr = kmem_asprintf("%s%s", propname, ZPROP_IUV_SUFFIX);
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2011-02-23 23:50:05 +03:00
|
|
|
switch ((int)source) {
|
2010-05-29 00:45:14 +04:00
|
|
|
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 23:01:55 +03:00
|
|
|
ASSERT(err == 0 || err == ENOENT);
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
|
|
|
case ZPROP_SRC_LOCAL:
|
|
|
|
/*
|
|
|
|
* remove propname$inherit
|
|
|
|
* set propname -> value
|
2022-10-20 03:07:51 +03:00
|
|
|
* set propname$iuv -> new property value
|
2010-05-29 00:45:14 +04:00
|
|
|
*/
|
|
|
|
err = zap_remove(mos, zapobj, inheritstr, tx);
|
|
|
|
ASSERT(err == 0 || err == ENOENT);
|
2013-09-04 16:00:57 +04:00
|
|
|
VERIFY0(zap_update(mos, zapobj, propname,
|
|
|
|
intsz, numints, value, tx));
|
2022-10-20 03:07:51 +03:00
|
|
|
(void) dsl_prop_set_iuv(mos, zapobj, propname, intsz,
|
|
|
|
numints, value, tx);
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
|
|
|
case ZPROP_SRC_INHERITED:
|
|
|
|
/*
|
|
|
|
* explicitly inherit
|
|
|
|
* - remove propname
|
|
|
|
* - set propname$inherit
|
|
|
|
*/
|
|
|
|
err = zap_remove(mos, zapobj, propname, tx);
|
|
|
|
ASSERT(err == 0 || err == ENOENT);
|
2022-10-20 03:07:51 +03:00
|
|
|
err = zap_remove(mos, zapobj, iuvstr, tx);
|
|
|
|
ASSERT(err == 0 || err == ENOENT);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (version >= SPA_VERSION_RECVD_PROPS &&
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_get_int_ds(ds, ZPROP_HAS_RECVD, &dummy) == 0) {
|
2010-05-29 00:45:14 +04:00
|
|
|
dummy = 0;
|
2013-09-04 16:00:57 +04:00
|
|
|
VERIFY0(zap_update(mos, zapobj, inheritstr,
|
|
|
|
8, 1, &dummy, tx));
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
break;
|
|
|
|
case ZPROP_SRC_RECEIVED:
|
|
|
|
/*
|
|
|
|
* set propname$recvd -> value
|
|
|
|
*/
|
|
|
|
err = zap_update(mos, zapobj, recvdstr,
|
2013-09-04 16:00:57 +04:00
|
|
|
intsz, numints, value, tx);
|
2010-05-29 00:45:14 +04:00
|
|
|
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);
|
2022-02-15 19:58:59 +03:00
|
|
|
zfs_fallthrough;
|
2010-05-29 00:45:14 +04:00
|
|
|
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 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
2019-10-10 19:47:06 +03:00
|
|
|
kmem_strfree(inheritstr);
|
|
|
|
kmem_strfree(recvdstr);
|
2022-10-20 03:07:51 +03:00
|
|
|
kmem_strfree(iuvstr);
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2019-12-13 22:51:39 +03:00
|
|
|
/*
|
|
|
|
* If we are left with an empty snap zap we can destroy it.
|
|
|
|
* This will prevent unnecessary calls to zap_lookup() in
|
|
|
|
* the "zfs list" and "zfs get" code paths.
|
|
|
|
*/
|
|
|
|
if (ds->ds_is_snapshot &&
|
|
|
|
zap_count(mos, zapobj, &count) == 0 && count == 0) {
|
|
|
|
dmu_buf_will_dirty(ds->ds_dbuf, tx);
|
|
|
|
dsl_dataset_phys(ds)->ds_props_obj = 0;
|
|
|
|
zap_destroy(mos, zapobj, tx);
|
|
|
|
}
|
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
if (isint) {
|
2013-09-04 16:00:57 +04:00
|
|
|
VERIFY0(dsl_prop_get_int_ds(ds, propname, &intval));
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2015-04-02 06:44:32 +03:00
|
|
|
if (ds->ds_is_snapshot) {
|
2008-12-03 23:09:06 +03:00
|
|
|
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);
|
2015-11-05 02:00:58 +03:00
|
|
|
for (cbr = list_head(&ds->ds_prop_cbs); cbr;
|
|
|
|
cbr = list_next(&ds->ds_prop_cbs, cbr)) {
|
|
|
|
if (strcmp(cbr->cbr_pr->pr_propname,
|
|
|
|
propname) == 0)
|
2008-12-03 23:09:06 +03:00
|
|
|
cbr->cbr_func(cbr->cbr_arg, intval);
|
|
|
|
}
|
|
|
|
mutex_exit(&ds->ds_dir->dd_lock);
|
|
|
|
} else {
|
|
|
|
dsl_prop_changed_notify(ds->ds_dir->dd_pool,
|
2010-05-29 00:45:14 +04:00
|
|
|
ds->ds_dir->dd_object, propname, intval, TRUE);
|
2008-12-03 23:09:06 +03:00
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
(void) snprintf(valbuf, sizeof (valbuf),
|
|
|
|
"%lld", (longlong_t)intval);
|
|
|
|
valstr = valbuf;
|
|
|
|
} else {
|
2010-05-29 00:45:14 +04:00
|
|
|
if (source == ZPROP_SRC_LOCAL) {
|
2013-09-04 16:00:57 +04:00
|
|
|
valstr = value;
|
2010-05-29 00:45:14 +04:00
|
|
|
} else {
|
2014-11-21 03:09:39 +03:00
|
|
|
tbuf = kmem_alloc(ZAP_MAXVALUELEN, KM_SLEEP);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (dsl_prop_get_ds(ds, propname, 1,
|
|
|
|
ZAP_MAXVALUELEN, tbuf, NULL) == 0)
|
|
|
|
valstr = tbuf;
|
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2013-08-28 15:45:09 +04:00
|
|
|
spa_history_log_internal_ds(ds, (source == ZPROP_SRC_NONE ||
|
|
|
|
source == ZPROP_SRC_INHERITED) ? "inherit" : "set", tx,
|
|
|
|
"%s=%s", propname, (valstr == NULL ? "" : valstr));
|
2010-05-29 00:45:14 +04:00
|
|
|
|
|
|
|
if (tbuf != NULL)
|
|
|
|
kmem_free(tbuf, ZAP_MAXVALUELEN);
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
int
|
|
|
|
dsl_prop_set_int(const char *dsname, const char *propname,
|
|
|
|
zprop_source_t source, uint64_t value)
|
2009-07-03 02:44:48 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
nvlist_t *nvl = fnvlist_alloc();
|
|
|
|
int error;
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
fnvlist_add_uint64(nvl, propname, value);
|
|
|
|
error = dsl_props_set(dsname, source, nvl);
|
|
|
|
fnvlist_free(nvl);
|
|
|
|
return (error);
|
2009-07-03 02:44:48 +04:00
|
|
|
}
|
|
|
|
|
2008-11-20 23:01:55 +03:00
|
|
|
int
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_set_string(const char *dsname, const char *propname,
|
|
|
|
zprop_source_t source, const char *value)
|
2008-11-20 23:01:55 +03:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
nvlist_t *nvl = fnvlist_alloc();
|
|
|
|
int error;
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
fnvlist_add_string(nvl, propname, value);
|
|
|
|
error = dsl_props_set(dsname, source, nvl);
|
|
|
|
fnvlist_free(nvl);
|
|
|
|
return (error);
|
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
int
|
|
|
|
dsl_prop_inherit(const char *dsname, const char *propname,
|
|
|
|
zprop_source_t source)
|
|
|
|
{
|
|
|
|
nvlist_t *nvl = fnvlist_alloc();
|
|
|
|
int error;
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
fnvlist_add_boolean(nvl, propname);
|
|
|
|
error = dsl_props_set(dsname, source, nvl);
|
|
|
|
fnvlist_free(nvl);
|
|
|
|
return (error);
|
2008-11-20 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
2020-01-23 04:03:17 +03:00
|
|
|
int
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_props_set_check(void *arg, dmu_tx_t *tx)
|
2009-07-03 02:44:48 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_props_set_arg_t *dpsa = arg;
|
|
|
|
dsl_pool_t *dp = dmu_tx_pool(tx);
|
2009-07-03 02:44:48 +04:00
|
|
|
dsl_dataset_t *ds;
|
|
|
|
uint64_t version;
|
|
|
|
nvpair_t *elem = NULL;
|
|
|
|
int err;
|
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
err = dsl_dataset_hold(dp, dpsa->dpsa_dsname, FTAG, &ds);
|
|
|
|
if (err != 0)
|
2009-07-03 02:44:48 +04:00
|
|
|
return (err);
|
2013-09-04 16:00:57 +04:00
|
|
|
|
2009-07-03 02:44:48 +04:00
|
|
|
version = spa_version(ds->ds_dir->dd_pool->dp_spa);
|
2013-09-04 16:00:57 +04:00
|
|
|
while ((elem = nvlist_next_nvpair(dpsa->dpsa_props, elem)) != NULL) {
|
2009-07-03 02:44:48 +04:00
|
|
|
if (strlen(nvpair_name(elem)) >= ZAP_MAXNAMELEN) {
|
|
|
|
dsl_dataset_rele(ds, FTAG);
|
2013-03-08 22:41:28 +04:00
|
|
|
return (SET_ERROR(ENAMETOOLONG));
|
2009-07-03 02:44:48 +04:00
|
|
|
}
|
|
|
|
if (nvpair_type(elem) == DATA_TYPE_STRING) {
|
2013-09-04 16:00:57 +04:00
|
|
|
char *valstr = fnvpair_value_string(elem);
|
2009-07-03 02:44:48 +04:00
|
|
|
if (strlen(valstr) >= (version <
|
|
|
|
SPA_VERSION_STMF_PROP ?
|
|
|
|
ZAP_OLDMAXVALUELEN : ZAP_MAXVALUELEN)) {
|
|
|
|
dsl_dataset_rele(ds, FTAG);
|
2017-08-03 07:16:12 +03:00
|
|
|
return (SET_ERROR(E2BIG));
|
2009-07-03 02:44:48 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-02 06:44:32 +03:00
|
|
|
if (ds->ds_is_snapshot && version < SPA_VERSION_SNAP_PROPS) {
|
2009-07-03 02:44:48 +04:00
|
|
|
dsl_dataset_rele(ds, FTAG);
|
2013-03-08 22:41:28 +04:00
|
|
|
return (SET_ERROR(ENOTSUP));
|
2009-07-03 02:44:48 +04:00
|
|
|
}
|
2013-09-04 16:00:57 +04: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;
|
2016-12-21 05:46:59 +03:00
|
|
|
const char *name = nvpair_name(pair);
|
2009-07-03 02:44:48 +04:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
|
|
|
|
/*
|
2016-12-21 05:46:59 +03:00
|
|
|
* This usually happens when we reuse the nvlist_t data
|
|
|
|
* returned by the counterpart dsl_prop_get_all_impl().
|
|
|
|
* For instance we do this to restore the original
|
|
|
|
* received properties when an error occurs in the
|
|
|
|
* zfs_ioc_recv() codepath.
|
2013-09-04 16:00:57 +04:00
|
|
|
*/
|
|
|
|
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);
|
2016-12-21 05:46:59 +03:00
|
|
|
dsl_prop_set_sync_impl(ds, name,
|
2013-09-04 16:00:57 +04:00
|
|
|
source, 1, strlen(value) + 1, value, tx);
|
|
|
|
} else if (nvpair_type(pair) == DATA_TYPE_UINT64) {
|
|
|
|
uint64_t intval = fnvpair_value_uint64(pair);
|
2016-12-21 05:46:59 +03:00
|
|
|
dsl_prop_set_sync_impl(ds, name,
|
2013-09-04 16:00:57 +04:00
|
|
|
source, sizeof (intval), 1, &intval, tx);
|
|
|
|
} else if (nvpair_type(pair) == DATA_TYPE_BOOLEAN) {
|
2016-12-21 05:46:59 +03:00
|
|
|
dsl_prop_set_sync_impl(ds, name,
|
2013-09-04 16:00:57 +04:00
|
|
|
source, 0, 0, NULL, tx);
|
|
|
|
} else {
|
|
|
|
panic("invalid nvpair type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2020-01-23 04:03:17 +03:00
|
|
|
void
|
2013-09-04 16:00:57 +04:00
|
|
|
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-03 02:44:48 +04:00
|
|
|
|
2013-09-04 16:00:57 +04: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-03 02:44:48 +04:00
|
|
|
dsl_dataset_rele(ds, FTAG);
|
2013-09-04 16:00:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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,
|
2014-11-03 23:28:43 +03:00
|
|
|
&dpsa, nblks, ZFS_SPACE_CHECK_RESERVED));
|
2009-07-03 02:44:48 +04:00
|
|
|
}
|
|
|
|
|
2010-05-29 00:45:14 +04: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 */
|
Native Encryption for ZFS on Linux
This change incorporates three major pieces:
The first change is a keystore that manages wrapping
and encryption keys for encrypted datasets. These
commands mostly involve manipulating the new
DSL Crypto Key ZAP Objects that live in the MOS. Each
encrypted dataset has its own DSL Crypto Key that is
protected with a user's key. This level of indirection
allows users to change their keys without re-encrypting
their entire datasets. The change implements the new
subcommands "zfs load-key", "zfs unload-key" and
"zfs change-key" which allow the user to manage their
encryption keys and settings. In addition, several new
flags and properties have been added to allow dataset
creation and to make mounting and unmounting more
convenient.
The second piece of this patch provides the ability to
encrypt, decyrpt, and authenticate protected datasets.
Each object set maintains a Merkel tree of Message
Authentication Codes that protect the lower layers,
similarly to how checksums are maintained. This part
impacts the zio layer, which handles the actual
encryption and generation of MACs, as well as the ARC
and DMU, which need to be able to handle encrypted
buffers and protected data.
The last addition is the ability to do raw, encrypted
sends and receives. The idea here is to send raw
encrypted and compressed data and receive it exactly
as is on a backup system. This means that the dataset
on the receiving system is protected using the same
user key that is in use on the sending side. By doing
so, datasets can be efficiently backed up to an
untrusted system without fear of data being
compromised.
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Jorgen Lundman <lundman@lundman.net>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #494
Closes #5769
2017-08-14 20:36:48 +03:00
|
|
|
DSL_PROP_GET_RECEIVED = 0x8, /* received properties */
|
2010-05-29 00:45:14 +04:00
|
|
|
} 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;
|
2022-10-20 03:07:51 +03:00
|
|
|
|
|
|
|
/* Skip if iuv entries are preset. */
|
|
|
|
valstr = kmem_asprintf("%s%s", propname,
|
|
|
|
ZPROP_IUV_SUFFIX);
|
|
|
|
err = zap_contains(mos, propobj, valstr);
|
|
|
|
kmem_strfree(valstr);
|
|
|
|
if (err == 0)
|
|
|
|
continue;
|
2010-05-29 00:45:14 +04:00
|
|
|
} 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;
|
|
|
|
|
Cleanup: Switch to strlcpy from strncpy
Coverity found a bug in `zfs_secpolicy_create_clone()` where it is
possible for us to pass an unterminated string when `zfs_get_parent()`
returns an error. Upon inspection, it is clear that using `strlcpy()`
would have avoided this issue.
Looking at the codebase, there are a number of other uses of `strncpy()`
that are unsafe and even when it is used safely, switching to
`strlcpy()` would make the code more readable. Therefore, we switch all
instances where we use `strncpy()` to use `strlcpy()`.
Unfortunately, we do not portably have access to `strlcpy()` in
tests/zfs-tests/cmd/zfs_diff-socket.c because it does not link to
libspl. Modifying the appropriate Makefile.am to try to link to it
resulted in an error from the naming choice used in the file. Trying to
disable the check on the file did not work on FreeBSD because Clang
ignores `#undef` when a definition is provided by `-Dstrncpy(...)=...`.
We workaround that by explictly including the C file from libspl into
the test. This makes things build correctly everywhere.
We add a deprecation warning to `config/Rules.am` and suppress it on the
remaining `strncpy()` usage. `strlcpy()` is not portably avaliable in
tests/zfs-tests/cmd/zfs_diff-socket.c, so we use `snprintf()` there as a
substitute.
This patch does not tackle the related problem of `strcpy()`, which is
even less safe. Thankfully, a quick inspection found that it is used far
more correctly than strncpy() was used. A quick inspection did not find
any problems with `strcpy()` usage outside of zhack, but it should be
said that I only checked around 90% of them.
Lastly, some of the fields in kstat_t varied in size by 1 depending on
whether they were in userspace or in the kernel. The origin of this
discrepancy appears to be 04a479f7066ccdaa23a6546955303b172f4a6909 where
it was made for no apparent reason. It conflicts with the comment on
KSTAT_STRLEN, so we shrink the kernel field sizes to match the userspace
field sizes.
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ryan Moeller <ryan@iXsystems.com>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #13876
2022-09-28 02:35:29 +03:00
|
|
|
(void) strlcpy(buf, za.za_name,
|
|
|
|
MIN(sizeof (buf), suffix - za.za_name + 1));
|
2010-05-29 00:45:14 +04:00
|
|
|
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);
|
2019-10-10 19:47:06 +03:00
|
|
|
kmem_strfree(valstr);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (err == 0)
|
|
|
|
continue;
|
|
|
|
if (err != ENOENT)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
source = ((flags & DSL_PROP_GET_INHERITING) ?
|
|
|
|
setpoint : ZPROP_SOURCE_VAL_RECVD);
|
2022-10-20 03:07:51 +03:00
|
|
|
} else if (strcmp(suffix, ZPROP_IUV_SUFFIX) == 0) {
|
|
|
|
(void) strlcpy(buf, za.za_name,
|
|
|
|
MIN(sizeof (buf), suffix - za.za_name + 1));
|
|
|
|
propname = buf;
|
|
|
|
source = setpoint;
|
|
|
|
prop = zfs_name_to_prop(propname);
|
|
|
|
|
|
|
|
if (dsl_prop_known_index(prop,
|
|
|
|
za.za_first_integer) != 1)
|
|
|
|
continue;
|
2010-05-29 00:45:14 +04:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* For backward compatibility, skip suffixes we don't
|
|
|
|
* recognize.
|
|
|
|
*/
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
prop = zfs_name_to_prop(propname);
|
|
|
|
|
|
|
|
/* Skip non-inheritable properties. */
|
2022-06-14 21:27:53 +03:00
|
|
|
if ((flags & DSL_PROP_GET_INHERITING) &&
|
|
|
|
prop != ZPROP_USERPROP && !zfs_prop_inheritable(prop))
|
2010-05-29 00:45:14 +04:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Skip properties not valid for this type. */
|
2022-06-14 21:27:53 +03:00
|
|
|
if ((flags & DSL_PROP_GET_SNAPSHOT) && prop != ZPROP_USERPROP &&
|
2014-04-21 22:22:08 +04:00
|
|
|
!zfs_prop_valid_for_type(prop, ZFS_TYPE_SNAPSHOT, B_FALSE))
|
2010-05-29 00:45:14 +04:00
|
|
|
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 23:01:55 +03:00
|
|
|
/*
|
|
|
|
* Iterate over all properties for this dataset and return them in an nvlist.
|
|
|
|
*/
|
2010-05-29 00:45:14 +04:00
|
|
|
static int
|
|
|
|
dsl_prop_get_all_ds(dsl_dataset_t *ds, nvlist_t **nvp,
|
|
|
|
dsl_prop_getflags_t flags)
|
2008-11-20 23:01:55 +03:00
|
|
|
{
|
|
|
|
dsl_dir_t *dd = ds->ds_dir;
|
2008-12-03 23:09:06 +03:00
|
|
|
dsl_pool_t *dp = dd->dd_pool;
|
|
|
|
objset_t *mos = dp->dp_meta_objset;
|
2010-05-29 00:45:14 +04:00
|
|
|
int err = 0;
|
2016-06-16 00:28:36 +03:00
|
|
|
char setpoint[ZFS_MAX_DATASET_NAME_LEN];
|
2008-11-20 23:01:55 +03:00
|
|
|
|
|
|
|
VERIFY(nvlist_alloc(nvp, NV_UNIQUE_NAME, KM_SLEEP) == 0);
|
|
|
|
|
2015-04-02 06:44:32 +03:00
|
|
|
if (ds->ds_is_snapshot)
|
2010-05-29 00:45:14 +04:00
|
|
|
flags |= DSL_PROP_GET_SNAPSHOT;
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
ASSERT(dsl_pool_config_held(dp));
|
2010-05-29 00:45:14 +04:00
|
|
|
|
2015-04-01 18:14:34 +03:00
|
|
|
if (dsl_dataset_phys(ds)->ds_props_obj != 0) {
|
2010-05-29 00:45:14 +04:00
|
|
|
ASSERT(flags & DSL_PROP_GET_SNAPSHOT);
|
|
|
|
dsl_dataset_name(ds, setpoint);
|
2015-04-01 18:14:34 +03:00
|
|
|
err = dsl_prop_get_all_impl(mos,
|
|
|
|
dsl_dataset_phys(ds)->ds_props_obj, setpoint, flags, *nvp);
|
2010-05-29 00:45:14 +04:00
|
|
|
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;
|
2008-12-03 23:09:06 +03:00
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
dsl_dir_name(dd, setpoint);
|
2015-04-01 18:14:34 +03:00
|
|
|
err = dsl_prop_get_all_impl(mos,
|
|
|
|
dsl_dir_phys(dd)->dd_props_zapobj, setpoint, flags, *nvp);
|
2010-05-29 00:45:14 +04:00
|
|
|
if (err)
|
|
|
|
break;
|
|
|
|
}
|
Native Encryption for ZFS on Linux
This change incorporates three major pieces:
The first change is a keystore that manages wrapping
and encryption keys for encrypted datasets. These
commands mostly involve manipulating the new
DSL Crypto Key ZAP Objects that live in the MOS. Each
encrypted dataset has its own DSL Crypto Key that is
protected with a user's key. This level of indirection
allows users to change their keys without re-encrypting
their entire datasets. The change implements the new
subcommands "zfs load-key", "zfs unload-key" and
"zfs change-key" which allow the user to manage their
encryption keys and settings. In addition, several new
flags and properties have been added to allow dataset
creation and to make mounting and unmounting more
convenient.
The second piece of this patch provides the ability to
encrypt, decyrpt, and authenticate protected datasets.
Each object set maintains a Merkel tree of Message
Authentication Codes that protect the lower layers,
similarly to how checksums are maintained. This part
impacts the zio layer, which handles the actual
encryption and generation of MACs, as well as the ARC
and DMU, which need to be able to handle encrypted
buffers and protected data.
The last addition is the ability to do raw, encrypted
sends and receives. The idea here is to send raw
encrypted and compressed data and receive it exactly
as is on a backup system. This means that the dataset
on the receiving system is protected using the same
user key that is in use on the sending side. By doing
so, datasets can be efficiently backed up to an
untrusted system without fear of data being
compromised.
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Jorgen Lundman <lundman@lundman.net>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #494
Closes #5769
2017-08-14 20:36:48 +03:00
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
out:
|
2016-11-02 22:34:10 +03:00
|
|
|
if (err) {
|
|
|
|
nvlist_free(*nvp);
|
|
|
|
*nvp = NULL;
|
|
|
|
}
|
2010-05-29 00:45:14 +04:00
|
|
|
return (err);
|
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
boolean_t
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_get_hasrecvd(const char *dsname)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
|
|
|
uint64_t dummy;
|
2008-12-03 23:09:06 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
return (0 ==
|
|
|
|
dsl_prop_get_integer(dsname, ZPROP_HAS_RECVD, &dummy, NULL));
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
static int
|
|
|
|
dsl_prop_set_hasrecvd_impl(const char *dsname, zprop_source_t source)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
uint64_t version;
|
|
|
|
spa_t *spa;
|
|
|
|
int error = 0;
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
VERIFY0(spa_open(dsname, &spa, FTAG));
|
|
|
|
version = spa_version(spa);
|
|
|
|
spa_close(spa, FTAG);
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2013-09-04 16:00:57 +04:00
|
|
|
if (version >= SPA_VERSION_RECVD_PROPS)
|
|
|
|
error = dsl_prop_set_int(dsname, ZPROP_HAS_RECVD, source, 0);
|
|
|
|
return (error);
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
/*
|
|
|
|
* Call after successfully receiving properties to ensure that only the first
|
|
|
|
* receive on or after SPA_VERSION_RECVD_PROPS blows away local properties.
|
|
|
|
*/
|
2013-09-04 16:00:57 +04:00
|
|
|
int
|
|
|
|
dsl_prop_set_hasrecvd(const char *dsname)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
int error = 0;
|
|
|
|
if (!dsl_prop_get_hasrecvd(dsname))
|
|
|
|
error = dsl_prop_set_hasrecvd_impl(dsname, ZPROP_SRC_LOCAL);
|
|
|
|
return (error);
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
2008-11-20 23:01:55 +03:00
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
void
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_unset_hasrecvd(const char *dsname)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
VERIFY0(dsl_prop_set_hasrecvd_impl(dsname, ZPROP_SRC_NONE));
|
2010-05-29 00:45:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
dsl_prop_get_all(objset_t *os, nvlist_t **nvp)
|
|
|
|
{
|
|
|
|
return (dsl_prop_get_all_ds(os->os_dsl_dataset, nvp, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_get_received(const char *dsname, nvlist_t **nvp)
|
2010-05-29 00:45:14 +04:00
|
|
|
{
|
2013-09-04 16:00:57 +04:00
|
|
|
objset_t *os;
|
|
|
|
int error;
|
|
|
|
|
2010-05-29 00:45:14 +04:00
|
|
|
/*
|
|
|
|
* Received properties are not distinguishable from local properties
|
|
|
|
* until the dataset has received properties on or after
|
|
|
|
* SPA_VERSION_RECVD_PROPS.
|
|
|
|
*/
|
2013-09-04 16:00:57 +04:00
|
|
|
dsl_prop_getflags_t flags = (dsl_prop_get_hasrecvd(dsname) ?
|
2010-05-29 00:45:14 +04:00
|
|
|
DSL_PROP_GET_RECEIVED : DSL_PROP_GET_LOCAL);
|
2013-09-04 16:00:57 +04:00
|
|
|
|
|
|
|
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 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dsl_prop_nvlist_add_uint64(nvlist_t *nv, zfs_prop_t prop, uint64_t value)
|
|
|
|
{
|
|
|
|
nvlist_t *propval;
|
2010-05-29 00:45:14 +04:00
|
|
|
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 23:01:55 +03:00
|
|
|
|
|
|
|
VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0);
|
|
|
|
VERIFY(nvlist_add_uint64(propval, ZPROP_VALUE, value) == 0);
|
2010-05-29 00:45:14 +04:00
|
|
|
/* Indicate the default source if we can. */
|
2016-03-12 02:25:32 +03:00
|
|
|
if (dodefault(prop, 8, 1, &default_value) == 0 &&
|
2010-05-29 00:45:14 +04:00
|
|
|
value == default_value) {
|
|
|
|
VERIFY(nvlist_add_string(propval, ZPROP_SOURCE, "") == 0);
|
|
|
|
}
|
|
|
|
VERIFY(nvlist_add_nvlist(nv, propname, propval) == 0);
|
2008-11-20 23:01:55 +03:00
|
|
|
nvlist_free(propval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dsl_prop_nvlist_add_string(nvlist_t *nv, zfs_prop_t prop, const char *value)
|
|
|
|
{
|
|
|
|
nvlist_t *propval;
|
2010-05-29 00:45:14 +04:00
|
|
|
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 23:01:55 +03:00
|
|
|
|
|
|
|
VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0);
|
|
|
|
VERIFY(nvlist_add_string(propval, ZPROP_VALUE, value) == 0);
|
2010-05-29 00:45:14 +04:00
|
|
|
VERIFY(nvlist_add_nvlist(nv, propname, propval) == 0);
|
2008-11-20 23:01:55 +03:00
|
|
|
nvlist_free(propval);
|
|
|
|
}
|
2010-08-26 22:49:16 +04:00
|
|
|
|
2018-02-16 04:53:18 +03:00
|
|
|
#if defined(_KERNEL)
|
2012-04-05 00:46:55 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_register);
|
|
|
|
EXPORT_SYMBOL(dsl_prop_unregister);
|
2015-11-05 02:00:58 +03:00
|
|
|
EXPORT_SYMBOL(dsl_prop_unregister_all);
|
2012-04-05 00:46:55 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_get);
|
|
|
|
EXPORT_SYMBOL(dsl_prop_get_integer);
|
2010-08-26 22:49:16 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_get_all);
|
2012-04-05 00:46:55 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_get_received);
|
|
|
|
EXPORT_SYMBOL(dsl_prop_get_ds);
|
2013-09-25 20:33:00 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_get_int_ds);
|
2012-04-05 00:46:55 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_get_dd);
|
2013-09-25 20:33:00 +04:00
|
|
|
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);
|
2010-08-26 22:49:16 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_nvlist_add_uint64);
|
2012-04-05 00:46:55 +04:00
|
|
|
EXPORT_SYMBOL(dsl_prop_nvlist_add_string);
|
2010-08-26 22:49:16 +04:00
|
|
|
#endif
|