diff options
-rw-r--r-- | cmd/zpool/zpool_main.c | 18 | ||||
-rw-r--r-- | include/libzfs.h | 2 | ||||
-rw-r--r-- | lib/libzfs/libzfs.abi | 131 | ||||
-rw-r--r-- | lib/libzfs/libzfs_pool.c | 101 | ||||
-rw-r--r-- | lib/libzfs/libzfs_util.c | 1 | ||||
-rw-r--r-- | man/man7/zpoolprops.7 | 55 | ||||
-rw-r--r-- | module/zfs/spa.c | 128 | ||||
-rw-r--r-- | tests/runfiles/common.run | 3 | ||||
-rw-r--r-- | tests/zfs-tests/tests/Makefile.am | 3 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_001_pos.ksh | 89 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_002_neg.ksh | 88 | ||||
-rw-r--r-- | tests/zfs-tests/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib | 178 |
12 files changed, 688 insertions, 109 deletions
diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 4965cba52..301c5f4bf 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -6071,11 +6071,14 @@ print_pool(zpool_handle_t *zhp, list_cbdata_t *cb) zpool_prop_get_feature(zhp, pl->pl_user_prop, property, sizeof (property)) == 0) { propstr = property; + } else if (zfs_prop_user(pl->pl_user_prop) && + zpool_get_userprop(zhp, pl->pl_user_prop, property, + sizeof (property), NULL) == 0) { + propstr = property; } else { propstr = "-"; } - /* * If this is being called in scripted mode, or if this is the * last column and it is left-justified, don't include a width @@ -10035,7 +10038,7 @@ static int get_callback(zpool_handle_t *zhp, void *data) { zprop_get_cbdata_t *cbp = (zprop_get_cbdata_t *)data; - char value[MAXNAMELEN]; + char value[ZFS_MAXPROPLEN]; zprop_source_t srctype; zprop_list_t *pl; int vid; @@ -10070,6 +10073,17 @@ get_callback(zpool_handle_t *zhp, void *data) continue; if (pl->pl_prop == ZPROP_INVAL && + zfs_prop_user(pl->pl_user_prop)) { + srctype = ZPROP_SRC_LOCAL; + + if (zpool_get_userprop(zhp, pl->pl_user_prop, + value, sizeof (value), &srctype) != 0) + continue; + + zprop_print_one_property(zpool_get_name(zhp), + cbp, pl->pl_user_prop, value, srctype, + NULL, NULL); + } else if (pl->pl_prop == ZPROP_INVAL && (zpool_prop_feature(pl->pl_user_prop) || zpool_prop_unsupported(pl->pl_user_prop))) { srctype = ZPROP_SRC_LOCAL; diff --git a/include/libzfs.h b/include/libzfs.h index 7ec9768d8..87d1ed738 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -333,6 +333,8 @@ _LIBZFS_H const char *zpool_get_state_str(zpool_handle_t *); _LIBZFS_H int zpool_set_prop(zpool_handle_t *, const char *, const char *); _LIBZFS_H int zpool_get_prop(zpool_handle_t *, zpool_prop_t, char *, size_t proplen, zprop_source_t *, boolean_t literal); +_LIBZFS_H int zpool_get_userprop(zpool_handle_t *, const char *, char *, + size_t proplen, zprop_source_t *); _LIBZFS_H uint64_t zpool_get_prop_int(zpool_handle_t *, zpool_prop_t, zprop_source_t *); _LIBZFS_H int zpool_props_refresh(zpool_handle_t *); diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index f9aed4e0d..732863dcf 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -259,8 +259,8 @@ <elf-symbol name='tpool_suspend' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='tpool_suspended' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='tpool_wait' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> - <elf-symbol name='use_color' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='update_vdev_config_dev_strs' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> + <elf-symbol name='use_color' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='vdev_expand_proplist' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='vdev_name_to_prop' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='vdev_prop_align_right' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> @@ -492,6 +492,7 @@ <elf-symbol name='zpool_get_state' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='zpool_get_state_str' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='zpool_get_status' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> + <elf-symbol name='zpool_get_userprop' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='zpool_get_vdev_prop' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='zpool_get_vdev_prop_value' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> <elf-symbol name='zpool_history_unpack' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/> @@ -2267,32 +2268,19 @@ <parameter type-id='58603c44'/> <return type-id='9c313c2d'/> </function-decl> - <function-decl name='zfs_iter_children' mangled-name='zfs_iter_children' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_children'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> - <return type-id='95e97e5e'/> - </function-decl> <function-decl name='zfs_iter_children_v2' mangled-name='zfs_iter_children_v2' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_children_v2'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='95e97e5e' name='flags'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> - <return type-id='95e97e5e'/> - </function-decl> - <function-decl name='zfs_iter_dependents' mangled-name='zfs_iter_dependents' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_dependents'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='c19b74c3' name='allowrecursion'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> + <parameter type-id='9200a744'/> + <parameter type-id='95e97e5e'/> + <parameter type-id='d8e49ab9'/> + <parameter type-id='eaa32e2f'/> <return type-id='95e97e5e'/> </function-decl> <function-decl name='zfs_iter_dependents_v2' mangled-name='zfs_iter_dependents_v2' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_dependents_v2'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='95e97e5e' name='flags'/> - <parameter type-id='c19b74c3' name='allowrecursion'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> + <parameter type-id='9200a744'/> + <parameter type-id='95e97e5e'/> + <parameter type-id='c19b74c3'/> + <parameter type-id='d8e49ab9'/> + <parameter type-id='eaa32e2f'/> <return type-id='95e97e5e'/> </function-decl> <function-decl name='zfs_iter_mounted' mangled-name='zfs_iter_mounted' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_mounted'> @@ -3324,17 +3312,11 @@ <parameter type-id='58603c44'/> <return type-id='80f4b756'/> </function-decl> - <function-decl name='zfs_iter_filesystems' mangled-name='zfs_iter_filesystems' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_filesystems'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> - <return type-id='95e97e5e'/> - </function-decl> <function-decl name='zfs_iter_filesystems_v2' mangled-name='zfs_iter_filesystems_v2' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_filesystems_v2'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='95e97e5e' name='flags'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> + <parameter type-id='9200a744'/> + <parameter type-id='95e97e5e'/> + <parameter type-id='d8e49ab9'/> + <parameter type-id='eaa32e2f'/> <return type-id='95e97e5e'/> </function-decl> <function-decl name='zfs_parent_name' mangled-name='zfs_parent_name' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_parent_name'> @@ -3907,35 +3889,20 @@ <parameter type-id='b59d7dce'/> <return type-id='95e97e5e'/> </function-decl> - <function-decl name='zfs_iter_snapshots' mangled-name='zfs_iter_snapshots' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_snapshots'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='c19b74c3' name='simple'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> - <parameter type-id='9c313c2d' name='min_txg'/> - <parameter type-id='9c313c2d' name='max_txg'/> - <return type-id='95e97e5e'/> - </function-decl> <function-decl name='zfs_iter_snapshots_v2' mangled-name='zfs_iter_snapshots_v2' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_snapshots_v2'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='95e97e5e' name='flags'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> - <parameter type-id='9c313c2d' name='min_txg'/> - <parameter type-id='9c313c2d' name='max_txg'/> - <return type-id='95e97e5e'/> - </function-decl> - <function-decl name='zfs_iter_bookmarks' mangled-name='zfs_iter_bookmarks' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_bookmarks'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> + <parameter type-id='9200a744'/> + <parameter type-id='95e97e5e'/> + <parameter type-id='d8e49ab9'/> + <parameter type-id='eaa32e2f'/> + <parameter type-id='9c313c2d'/> + <parameter type-id='9c313c2d'/> <return type-id='95e97e5e'/> </function-decl> <function-decl name='zfs_iter_bookmarks_v2' mangled-name='zfs_iter_bookmarks_v2' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_bookmarks_v2'> - <parameter type-id='9200a744' name='zhp'/> - <parameter type-id='95e97e5e' name='flags'/> - <parameter type-id='d8e49ab9' name='func'/> - <parameter type-id='eaa32e2f' name='data'/> + <parameter type-id='9200a744'/> + <parameter type-id='95e97e5e'/> + <parameter type-id='d8e49ab9'/> + <parameter type-id='eaa32e2f'/> <return type-id='95e97e5e'/> </function-decl> <function-decl name='zfs_destroy_snaps_nvl_os' mangled-name='zfs_destroy_snaps_nvl_os' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_destroy_snaps_nvl_os'> @@ -5131,6 +5098,27 @@ <parameter type-id='5ce45b60'/> <return type-id='9200a744'/> </function-decl> + <function-decl name='zfs_iter_filesystems' mangled-name='zfs_iter_filesystems' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_filesystems'> + <parameter type-id='9200a744' name='zhp'/> + <parameter type-id='d8e49ab9' name='func'/> + <parameter type-id='eaa32e2f' name='data'/> + <return type-id='95e97e5e'/> + </function-decl> + <function-decl name='zfs_iter_snapshots' mangled-name='zfs_iter_snapshots' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_snapshots'> + <parameter type-id='9200a744' name='zhp'/> + <parameter type-id='c19b74c3' name='simple'/> + <parameter type-id='d8e49ab9' name='func'/> + <parameter type-id='eaa32e2f' name='data'/> + <parameter type-id='9c313c2d' name='min_txg'/> + <parameter type-id='9c313c2d' name='max_txg'/> + <return type-id='95e97e5e'/> + </function-decl> + <function-decl name='zfs_iter_bookmarks' mangled-name='zfs_iter_bookmarks' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_bookmarks'> + <parameter type-id='9200a744' name='zhp'/> + <parameter type-id='d8e49ab9' name='func'/> + <parameter type-id='eaa32e2f' name='data'/> + <return type-id='95e97e5e'/> + </function-decl> <function-decl name='zfs_iter_snapshots_sorted' mangled-name='zfs_iter_snapshots_sorted' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_snapshots_sorted'> <parameter type-id='9200a744' name='zhp'/> <parameter type-id='d8e49ab9' name='callback'/> @@ -5163,6 +5151,19 @@ <parameter type-id='eaa32e2f' name='arg'/> <return type-id='95e97e5e'/> </function-decl> + <function-decl name='zfs_iter_children' mangled-name='zfs_iter_children' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_children'> + <parameter type-id='9200a744' name='zhp'/> + <parameter type-id='d8e49ab9' name='func'/> + <parameter type-id='eaa32e2f' name='data'/> + <return type-id='95e97e5e'/> + </function-decl> + <function-decl name='zfs_iter_dependents' mangled-name='zfs_iter_dependents' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_dependents'> + <parameter type-id='9200a744' name='zhp'/> + <parameter type-id='c19b74c3' name='allowrecursion'/> + <parameter type-id='d8e49ab9' name='func'/> + <parameter type-id='eaa32e2f' name='data'/> + <return type-id='95e97e5e'/> + </function-decl> </abi-instr> <abi-instr address-size='64' path='lib/libzfs/libzfs_mount.c' language='LANG_C99'> <array-type-def dimensions='1' type-id='6028cbfe' size-in-bits='256' id='b39b9aa7'> @@ -5395,9 +5396,6 @@ <parameter type-id='9cf59a50'/> <return type-id='48b5725f'/> </function-decl> - <function-decl name='use_color' mangled-name='use_color' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='use_color'> - <return type-id='95e97e5e'/> - </function-decl> <function-decl name='mkdirp' mangled-name='mkdirp' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='mkdirp'> <parameter type-id='80f4b756'/> <parameter type-id='d50d396c'/> @@ -6169,6 +6167,14 @@ <parameter type-id='4c81de99' name='zhp'/> <return type-id='80f4b756'/> </function-decl> + <function-decl name='zpool_get_userprop' mangled-name='zpool_get_userprop' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zpool_get_userprop'> + <parameter type-id='4c81de99' name='zhp'/> + <parameter type-id='80f4b756' name='propname'/> + <parameter type-id='26a90f95' name='buf'/> + <parameter type-id='b59d7dce' name='len'/> + <parameter type-id='debc6aa3' name='srctype'/> + <return type-id='95e97e5e'/> + </function-decl> <function-decl name='zpool_set_prop' mangled-name='zpool_set_prop' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zpool_set_prop'> <parameter type-id='4c81de99' name='zhp'/> <parameter type-id='80f4b756' name='propname'/> @@ -7852,6 +7858,9 @@ <function-decl name='zfs_version_print' mangled-name='zfs_version_print' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_version_print'> <return type-id='95e97e5e'/> </function-decl> + <function-decl name='use_color' mangled-name='use_color' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='use_color'> + <return type-id='95e97e5e'/> + </function-decl> <function-decl name='printf_color' mangled-name='printf_color' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='printf_color'> <parameter type-id='80f4b756' name='color'/> <parameter type-id='80f4b756' name='format'/> diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index ae4c86159..4fb71b4e0 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -427,6 +427,37 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, } /* + * Get a zpool property value for 'propname' and return the value in + * a pre-allocated buffer. + */ +int +zpool_get_userprop(zpool_handle_t *zhp, const char *propname, char *buf, + size_t len, zprop_source_t *srctype) +{ + nvlist_t *nv, *nvl; + uint64_t ival; + const char *value; + zprop_source_t source = ZPROP_SRC_LOCAL; + + nvl = zhp->zpool_props; + if (nvlist_lookup_nvlist(nvl, propname, &nv) == 0) { + if (nvlist_lookup_uint64(nv, ZPROP_SOURCE, &ival) == 0) + source = ival; + verify(nvlist_lookup_string(nv, ZPROP_VALUE, &value) == 0); + } else { + source = ZPROP_SRC_DEFAULT; + value = "-"; + } + + if (srctype) + *srctype = source; + + (void) strlcpy(buf, value, len); + + return (0); +} + +/* * Check if the bootfs name has the same pool name as it is set to. * Assuming bootfs is a valid dataset name. */ @@ -550,6 +581,44 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname, goto error; } continue; + } else if (prop == ZPOOL_PROP_INVAL && + zfs_prop_user(propname)) { + /* + * This is a user property: make sure it's a + * string, and that it's less than ZAP_MAXNAMELEN. + */ + if (nvpair_type(elem) != DATA_TYPE_STRING) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' must be a string"), propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + if (strlen(nvpair_name(elem)) >= ZAP_MAXNAMELEN) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property name '%s' is too long"), + propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + (void) nvpair_value_string(elem, &strval); + + if (strlen(strval) >= ZFS_MAXPROPLEN) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property value '%s' is too long"), + strval); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + if (nvlist_add_string(retprops, propname, + strval) != 0) { + (void) no_memory(hdl); + goto error; + } + + continue; } /* @@ -855,9 +924,30 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp, features = zpool_get_features(zhp); if ((*plp)->pl_all && firstexpand) { + /* Handle userprops in the all properties case */ + if (zhp->zpool_props == NULL && zpool_props_refresh(zhp)) + return (-1); + + nvp = NULL; + while ((nvp = nvlist_next_nvpair(zhp->zpool_props, nvp)) != + NULL) { + const char *propname = nvpair_name(nvp); + + if (!zfs_prop_user(propname)) + continue; + + entry = zfs_alloc(hdl, sizeof (zprop_list_t)); + entry->pl_prop = ZPROP_USERPROP; + entry->pl_user_prop = zfs_strdup(hdl, propname); + entry->pl_width = strlen(entry->pl_user_prop); + entry->pl_all = B_TRUE; + + *last = entry; + last = &entry->pl_next; + } + for (i = 0; i < SPA_FEATURES; i++) { - zprop_list_t *entry = zfs_alloc(hdl, - sizeof (zprop_list_t)); + entry = zfs_alloc(hdl, sizeof (zprop_list_t)); entry->pl_prop = ZPROP_USERPROP; entry->pl_user_prop = zfs_asprintf(hdl, "feature@%s", spa_feature_table[i].fi_uname); @@ -874,7 +964,6 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp, nvp != NULL; nvp = nvlist_next_nvpair(features, nvp)) { char *propname; boolean_t found; - zprop_list_t *entry; if (zfeature_is_supported(nvpair_name(nvp))) continue; @@ -920,6 +1009,12 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp, NULL, literal) == 0) { if (strlen(buf) > entry->pl_width) entry->pl_width = strlen(buf); + } else if (entry->pl_prop == ZPROP_INVAL && + zfs_prop_user(entry->pl_user_prop) && + zpool_get_userprop(zhp, entry->pl_user_prop, buf, + sizeof (buf), NULL) == 0) { + if (strlen(buf) > entry->pl_width) + entry->pl_width = strlen(buf); } } diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c index 393971ddf..4b8a20160 100644 --- a/lib/libzfs/libzfs_util.c +++ b/lib/libzfs/libzfs_util.c @@ -1774,6 +1774,7 @@ addlist(libzfs_handle_t *hdl, const char *propname, zprop_list_t **listp, * a user-defined property. */ if (prop == ZPROP_USERPROP && ((type == ZFS_TYPE_POOL && + !zfs_prop_user(propname) && !zpool_prop_feature(propname) && !zpool_prop_unsupported(propname)) || ((type == ZFS_TYPE_DATASET) && !zfs_prop_user(propname) && diff --git a/man/man7/zpoolprops.7 b/man/man7/zpoolprops.7 index 12b9b1190..7709d8522 100644 --- a/man/man7/zpoolprops.7 +++ b/man/man7/zpoolprops.7 @@ -26,8 +26,9 @@ .\" Copyright 2017 Nexenta Systems, Inc. .\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved. .\" Copyright (c) 2021, Colm Buckley <[email protected]> +.\" Copyright (c) 2023, Klara Inc. .\" -.Dd May 27, 2021 +.Dd April 18, 2023 .Dt ZPOOLPROPS 7 .Os . @@ -40,6 +41,12 @@ Each pool has several properties associated with it. Some properties are read-only statistics while others are configurable and change the behavior of the pool. .Pp +User properties have no effect on ZFS behavior. +Use them to annotate pools in a way that is meaningful in your environment. +For more information about user properties, see the +.Sx User Properties +section. +.Pp The following are read-only properties: .Bl -tag -width "unsupported@guid" .It Sy allocated @@ -431,3 +438,49 @@ backwards compatibility. Once feature flags are enabled on a pool this property will no longer have a value. .El +. +.Ss User Properties +In addition to the standard native properties, ZFS supports arbitrary user +properties. +User properties have no effect on ZFS behavior, but applications or +administrators can use them to annotate pools. +.Pp +User property names must contain a colon +.Pq Qq Sy \&: +character to distinguish them from native properties. +They may contain lowercase letters, numbers, and the following punctuation +characters: colon +.Pq Qq Sy \&: , +dash +.Pq Qq Sy - , +period +.Pq Qq Sy \&. , +and underscore +.Pq Qq Sy _ . +The expected convention is that the property name is divided into two portions +such as +.Ar module : Ns Ar property , +but this namespace is not enforced by ZFS. +User property names can be at most 256 characters, and cannot begin with a dash +.Pq Qq Sy - . +.Pp +When making programmatic use of user properties, it is strongly suggested to use +a reversed DNS domain name for the +.Ar module +component of property names to reduce the chance that two +independently-developed packages use the same property name for different +purposes. +.Pp +The values of user properties are arbitrary strings and +are never validated. +All of the commands that operate on properties +.Po Nm zpool Cm list , +.Nm zpool Cm get , +.Nm zpool Cm set , +and so forth +.Pc +can be used to manipulate both native properties and user properties. +Use +.Nm zpool Cm set Ar name Ns = +to clear a user property. +Property values are limited to 8192 bytes. diff --git a/module/zfs/spa.c b/module/zfs/spa.c index 67601211d..dd4a442d9 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -297,6 +297,22 @@ spa_prop_add_list(nvlist_t *nvl, zpool_prop_t prop, const char *strval, } /* + * Add a user property (source=src, propname=propval) to an nvlist. + */ +static void +spa_prop_add_user(nvlist_t *nvl, const char *propname, char *strval, + zprop_source_t src) +{ + nvlist_t *propval; + + VERIFY(nvlist_alloc(&propval, NV_UNIQUE_NAME, KM_SLEEP) == 0); + VERIFY(nvlist_add_uint64(propval, ZPROP_SOURCE, src) == 0); + VERIFY(nvlist_add_string(propval, ZPROP_VALUE, strval) == 0); + VERIFY(nvlist_add_nvlist(nvl, propname, propval) == 0); + nvlist_free(propval); +} + +/* * Get property values from the spa configuration. */ static void @@ -471,7 +487,8 @@ spa_prop_get(spa_t *spa, nvlist_t **nvp) zprop_source_t src = ZPROP_SRC_DEFAULT; zpool_prop_t prop; - if ((prop = zpool_name_to_prop(za.za_name)) == ZPOOL_PROP_INVAL) + if ((prop = zpool_name_to_prop(za.za_name)) == + ZPOOL_PROP_INVAL && !zfs_prop_user(za.za_name)) continue; switch (za.za_integer_length) { @@ -514,7 +531,13 @@ spa_prop_get(spa_t *spa, nvlist_t **nvp) kmem_free(strval, za.za_num_integers); break; } - spa_prop_add_list(*nvp, prop, strval, 0, src); + if (prop != ZPOOL_PROP_INVAL) { + spa_prop_add_list(*nvp, prop, strval, 0, src); + } else { + src = ZPROP_SRC_LOCAL; + spa_prop_add_user(*nvp, za.za_name, strval, + src); + } kmem_free(strval, za.za_num_integers); break; @@ -556,36 +579,47 @@ spa_prop_validate(spa_t *spa, nvlist_t *props) switch (prop) { case ZPOOL_PROP_INVAL: - if (!zpool_prop_feature(propname)) { - error = SET_ERROR(EINVAL); - break; - } - /* * Sanitize the input. */ - if (nvpair_type(elem) != DATA_TYPE_UINT64) { - error = SET_ERROR(EINVAL); - break; - } + if (zfs_prop_user(propname)) { + if (strlen(propname) >= ZAP_MAXNAMELEN) { + error = SET_ERROR(ENAMETOOLONG); + break; + } - if (nvpair_value_uint64(elem, &intval) != 0) { - error = SET_ERROR(EINVAL); - break; - } + if (strlen(fnvpair_value_string(elem)) >= + ZAP_MAXVALUELEN) { + error = SET_ERROR(E2BIG); + break; + } + } else if (zpool_prop_feature(propname)) { + if (nvpair_type(elem) != DATA_TYPE_UINT64) { + error = SET_ERROR(EINVAL); + break; + } - if (intval != 0) { - error = SET_ERROR(EINVAL); - break; - } + if (nvpair_value_uint64(elem, &intval) != 0) { + error = SET_ERROR(EINVAL); + break; + } + + if (intval != 0) { + error = SET_ERROR(EINVAL); + break; + } + + fname = strchr(propname, '@') + 1; + if (zfeature_lookup_name(fname, NULL) != 0) { + error = SET_ERROR(EINVAL); + break; + } - fname = strchr(propname, '@') + 1; - if (zfeature_lookup_name(fname, NULL) != 0) { + has_feature = B_TRUE; + } else { error = SET_ERROR(EINVAL); break; } - - has_feature = B_TRUE; break; case ZPOOL_PROP_VERSION: @@ -792,6 +826,12 @@ spa_prop_set(spa_t *spa, nvlist_t *nvp) prop == ZPOOL_PROP_READONLY) continue; + if (prop == ZPOOL_PROP_INVAL && + zfs_prop_user(nvpair_name(elem))) { + need_sync = B_TRUE; + break; + } + if (prop == ZPOOL_PROP_VERSION || prop == ZPOOL_PROP_INVAL) { uint64_t ver = 0; @@ -8800,24 +8840,11 @@ spa_sync_props(void *arg, dmu_tx_t *tx) const char *strval, *fname; zpool_prop_t prop; const char *propname; + const char *elemname = nvpair_name(elem); zprop_type_t proptype; spa_feature_t fid; - switch (prop = zpool_name_to_prop(nvpair_name(elem))) { - case ZPOOL_PROP_INVAL: - /* - * We checked this earlier in spa_prop_validate(). - */ - ASSERT(zpool_prop_feature(nvpair_name(elem))); - - fname = strchr(nvpair_name(elem), '@') + 1; - VERIFY0(zfeature_lookup_name(fname, &fid)); - - spa_feature_enable(spa, fid, tx); - spa_history_log_internal(spa, "set", tx, - "%s=enabled", nvpair_name(elem)); - break; - + switch (prop = zpool_name_to_prop(elemname)) { case ZPOOL_PROP_VERSION: intval = fnvpair_value_uint64(elem); /* @@ -8860,7 +8887,7 @@ spa_sync_props(void *arg, dmu_tx_t *tx) spa_async_request(spa, SPA_ASYNC_CONFIG_UPDATE); } spa_history_log_internal(spa, "set", tx, - "%s=%s", nvpair_name(elem), strval); + "%s=%s", elemname, strval); break; case ZPOOL_PROP_COMPATIBILITY: strval = fnvpair_value_string(elem); @@ -8879,6 +8906,20 @@ spa_sync_props(void *arg, dmu_tx_t *tx) "%s=%s", nvpair_name(elem), strval); break; + case ZPOOL_PROP_INVAL: + if (zpool_prop_feature(elemname)) { + fname = strchr(elemname, '@') + 1; + VERIFY0(zfeature_lookup_name(fname, &fid)); + + spa_feature_enable(spa, fid, tx); + spa_history_log_internal(spa, "set", tx, + "%s=enabled", elemname); + break; + } else if (!zfs_prop_user(elemname)) { + ASSERT(zpool_prop_feature(elemname)); + break; + } + zfs_fallthrough; default: /* * Set pool property values in the poolprops mos object. @@ -8893,6 +8934,11 @@ spa_sync_props(void *arg, dmu_tx_t *tx) /* normalize the property name */ propname = zpool_prop_to_name(prop); proptype = zpool_prop_get_type(prop); + if (prop == ZPOOL_PROP_INVAL && + zfs_prop_user(elemname)) { + propname = elemname; + proptype = PROP_TYPE_STRING; + } if (nvpair_type(elem) == DATA_TYPE_STRING) { ASSERT(proptype == PROP_TYPE_STRING); @@ -8901,7 +8947,7 @@ spa_sync_props(void *arg, dmu_tx_t *tx) spa->spa_pool_props_object, propname, 1, strlen(strval) + 1, strval, tx)); spa_history_log_internal(spa, "set", tx, - "%s=%s", nvpair_name(elem), strval); + "%s=%s", elemname, strval); } else if (nvpair_type(elem) == DATA_TYPE_UINT64) { intval = fnvpair_value_uint64(elem); @@ -8914,7 +8960,7 @@ spa_sync_props(void *arg, dmu_tx_t *tx) spa->spa_pool_props_object, propname, 8, 1, &intval, tx)); spa_history_log_internal(spa, "set", tx, - "%s=%lld", nvpair_name(elem), + "%s=%lld", elemname, (longlong_t)intval); switch (prop) { diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index cc4ce0367..55991cfea 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -482,7 +482,8 @@ tags = ['functional', 'cli_root', 'zpool_scrub'] [tests/functional/cli_root/zpool_set] tests = ['zpool_set_001_pos', 'zpool_set_002_neg', 'zpool_set_003_neg', - 'zpool_set_ashift', 'zpool_set_features', 'vdev_set_001_pos'] + 'zpool_set_ashift', 'zpool_set_features', 'vdev_set_001_pos', + 'user_property_001_pos', 'user_property_002_neg'] tags = ['functional', 'cli_root', 'zpool_set'] [tests/functional/cli_root/zpool_split] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index e671a3f6b..74295b86d 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1149,10 +1149,13 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zpool_set/setup.ksh \ functional/cli_root/zpool/setup.ksh \ functional/cli_root/zpool_set/vdev_set_001_pos.ksh \ + functional/cli_root/zpool_set/zpool_set_common.kshlib \ functional/cli_root/zpool_set/zpool_set_001_pos.ksh \ functional/cli_root/zpool_set/zpool_set_002_neg.ksh \ functional/cli_root/zpool_set/zpool_set_003_neg.ksh \ functional/cli_root/zpool_set/zpool_set_ashift.ksh \ + functional/cli_root/zpool_set/user_property_001_pos.ksh \ + functional/cli_root/zpool_set/user_property_002_neg.ksh \ functional/cli_root/zpool_set/zpool_set_features.ksh \ functional/cli_root/zpool_split/cleanup.ksh \ functional/cli_root/zpool_split/setup.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_001_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_001_pos.ksh new file mode 100755 index 000000000..4b9097933 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_001_pos.ksh @@ -0,0 +1,89 @@ +#!/bin/ksh -p +# +# 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 https://opensource.org/licenses/CDDL-1.0. +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2016 by Delphix. All rights reserved. +# Copyright (c) 2023 by Klara Inc. +# + +. $STF_SUITE/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib + +# +# DESCRIPTION: +# ZFS can set any valid user-defined pool property. +# +# STRATEGY: +# 1. Combine all kind of valid characters into a valid user-defined +# property name. +# 2. Random get a string as the value. +# 3. Verify all the valid user-defined pool properties can be set to a +# pool. +# + +verify_runnable "both" + +log_assert "ZFS can set any valid user-defined pool property." +log_onexit cleanup_user_prop $TESTPOOL + +typeset -a names=() +typeset -a values=() + +# Longest property name (255 bytes, which is the 256-byte limit minus 1 byte +# for the null byte) +names+=("$(awk 'BEGIN { printf "x:"; while (c++ < (256 - 2 - 1)) printf "a" }')") +values+=("long-property-name") +# Longest property value (the limits are 1024 on FreeBSD and 4096 on Linux, so +# pick the right one; the longest value can use limit minus 1 bytes for the +# null byte) +if is_linux; then + typeset ZFS_MAXPROPLEN=4096 +else + typeset ZFS_MAXPROPLEN=1024 +fi +names+=("long:property:value") +values+=("$(awk -v max="$ZFS_MAXPROPLEN" 'BEGIN { while (c++ < (max - 1)) printf "A" }')") +# Valid property names +for i in {1..10}; do + typeset -i len + ((len = RANDOM % 32)) + names+=("$(valid_user_property $len)") + ((len = RANDOM % 512)) + values+=("$(user_property_value $len)") +done + +typeset -i i=0 +while ((i < ${#names[@]})); do + typeset name="${names[$i]}" + typeset value="${values[$i]}" + + log_must eval "zpool set $name='$value' $TESTPOOL" + log_must eval "check_user_prop $TESTPOOL $name '$value'" + + ((i += 1)) +done + +log_pass "ZFS can set any valid user-defined pool property passed." diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_002_neg.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_002_neg.ksh new file mode 100755 index 000000000..7c8fcba6e --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_002_neg.ksh @@ -0,0 +1,88 @@ +#!/bin/ksh -p +# +# 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 https://opensource.org/licenses/CDDL-1.0. +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2016 by Delphix. All rights reserved. +# Copyright (c) 2023 by Klara Inc. +# + +. $STF_SUITE/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib + +# +# DESCRIPTION: +# ZFS can handle any invalid user-defined pool property. +# +# STRATEGY: +# 1. Combine all kind of invalid user pool property names. +# 2. Random get a string as the value. +# 3. Verify all the invalid user-defined pool properties can not be set +# to the pool. +# + +verify_runnable "both" + +log_assert "ZFS can handle any invalid user pool property." +log_onexit cleanup_user_prop $TESTPOOL + +typeset -a names=() +typeset -a values=() + +# Too long property name (256 bytes, which is the 256-byte limit minus 1 byte +# for the null byte plus 1 byte to reach back over the limit) +names+=("$(awk 'BEGIN { printf "x:"; while (c++ < (256 - 2 - 1 + 1)) printf "a" }')") +values+=("too-long-property-name") +# Too long property value (the limits are 1024 on FreeBSD and 4096 on Linux, so +# pick the right one; the too long value is, e.g., the limit minus 1 bytes for the +# null byte plus 1 byte to reach back over the limit) +if is_linux; then + typeset ZFS_MAXPROPLEN=4096 +else + typeset ZFS_MAXPROPLEN=1024 +fi +names+=("too:long:property:value") +values+=("$(awk -v max="$ZFS_MAXPROPLEN" 'BEGIN { while (c++ < (max - 1 + 1)) printf "A" }')") +# Invalid property names +for i in {1..10}; do + typeset -i len + ((len = RANDOM % 32)) + names+=("$(invalid_user_property $len)") + ((len = RANDOM % 512)) + values+=("$(user_property_value $len)") +done + +typeset -i i=0 +while ((i < ${#names[@]})); do + typeset name="${names[$i]}" + typeset value="${values[$i]}" + + log_mustnot zpool set $name=$value $TESTPOOL + log_mustnot check_user_prop $TESTPOOL \"$name\" \"$value\" + + ((i += 1)) +done + +log_pass "ZFS can handle invalid user pool property passed." diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib b/tests/zfs-tests/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib new file mode 100644 index 000000000..346e4a16b --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib @@ -0,0 +1,178 @@ +# +# 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 https://opensource.org/licenses/CDDL-1.0. +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2014, 2016 by Delphix. All rights reserved. +# Copyright (c) 2023 by Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib + +set -A VALID_NAME_CHAR a b c d e f g h i j k l m n o p q r s t u v w x y z \ + 0 1 2 3 4 5 6 7 8 9 ':' '-' '.' '_' +set -A INVALID_NAME_CHAR A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ + '`' '~' '!' '@' '#' '$' '%' '^' '&' '(' ')' '+' '=' '|' "\\" '{' '[' ']' \ + '}' ';' '"' '<' ',' '>' '?' '/' ' ' +set -A ALL_CHAR ${VALID_NAME_CHAR[*]} ${INVALID_NAME_CHAR[*]} + +# +# Cleanup all the user properties of the pool. +# +# $1 pool name +# +function cleanup_user_prop +{ + typeset pool=$1 + + typeset user_prop + user_prop=$(zpool get -H -o property all $pool | grep ":") + + typeset prop + for prop in $user_prop; do + zpool set $prop="" $pool || + log_must zpool set $prop="" $pool + done +} + +# +# Random select character from the specified character set and combine into a +# random string +# +# $1 character set name +# $2 String length +# +function random_string +{ + typeset char_set=${1:-VALID_NAME_CHAR} + typeset -i len=${2:-5} + + eval typeset -i count=\${#$char_set[@]} + + # No consumers want an empty string. + ((len == 0)) && len=3 + + typeset str + typeset -i i=0 + while ((i < len)); do + typeset -i ind + ((ind = RANDOM % count)) + eval str=\${str}\${$char_set[\$ind]} + + ((i += 1)) + done + + echo "$str" +} + +# +# Get valid user-defined property name +# +# $1 user-defined property name length +# +function valid_user_property +{ + typeset -i sumlen=${1:-10} + ((sumlen < 2 )) && sumlen=2 + typeset -i len + ((len = RANDOM % sumlen)) + typeset part1 part2 + + while true; do + part1="$(random_string VALID_NAME_CHAR $len)" + if [[ "$part1" == "-"* ]]; then + continue + fi + break + done + ((len = sumlen - (len + 1))) + + while true; do + part2="$(random_string VALID_NAME_CHAR $len)" + if [[ -z $part1 && -z $part2 ]]; then + continue + fi + break + done + + echo "${part1}:${part2}" +} + +# +# Get invalid user-defined property name +# +# $1 user-defined property name length +# +function invalid_user_property +{ + typeset -i sumlen=${1:-10} + ((sumlen == 0)) && sumlen=1 + typeset -i len + ((len = RANDOM % sumlen)) + + typeset part1 part2 + while true; do + part1="$(random_string VALID_NAME_CHAR $len)" + ((len = sumlen - len)) + part2="$(random_string INVALID_NAME_CHAR $len)" + + # Avoid $part1 is *:* and $part2 is "=*" + if [[ "$part1" == *":"* && "$part2" == "="* ]]; then + continue + fi + break + done + + echo "${part1}${part2}" +} + +# +# Get user-defined property value +# +# $1 user-defined property name length +# +function user_property_value +{ + typeset -i len=${1:-100} + + random_string ALL_CHAR $len +} + +# +# Check if the user-defined property is identical to the expected value. +# +# $1 pool +# $2 user property +# $3 expected value +# +function check_user_prop +{ + typeset pool=$1 + typeset user_prop="$2" + typeset expect_value="$3" + typeset value=$(zpool get -p -H -o value "$user_prop" $pool 2>&1) + + [ "$expect_value" = "$value" ] +} |