]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Add support for zpool user properties
authorAllan Jude <allan@klarasystems.com>
Fri, 21 Apr 2023 17:20:36 +0000 (13:20 -0400)
committerGitHub <noreply@github.com>
Fri, 21 Apr 2023 17:20:36 +0000 (10:20 -0700)
Usage:

    zpool set org.freebsd:comment="this is my pool" poolname

Tests are based on zfs_set's user property tests.

Also stop truncating property values at MAXNAMELEN, use ZFS_MAXPROPLEN.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Allan Jude <allan@klarasystems.com>
Signed-off-by: Mateusz Piotrowski <mateusz.piotrowski@klarasystems.com>
Sponsored-by: Beckhoff Automation GmbH & Co. KG.
Sponsored-by: Klara Inc.
Closes #11680

12 files changed:
cmd/zpool/zpool_main.c
include/libzfs.h
lib/libzfs/libzfs.abi
lib/libzfs/libzfs_pool.c
lib/libzfs/libzfs_util.c
man/man7/zpoolprops.7
module/zfs/spa.c
tests/runfiles/common.run
tests/zfs-tests/tests/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_001_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zpool_set/user_property_002_neg.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zpool_set/zpool_set_common.kshlib [new file with mode: 0644]

index 4965cba5269262f4b945ebb55cb09bf17e3969f3..301c5f4bfc6fe4ab9bb8ee4c127e3260611a6d2f 100644 (file)
@@ -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;
index 7ec9768d8e93c5bb0b4b1c003459bcd0a0f6e347..87d1ed738f2b724001e81d1124822ace4ff960a4 100644 (file)
@@ -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 *);
index f9aed4e0d57e7bfddf87fe472e7354a9f9636845..732863dcffc73e987b0dea759130bfd313e9c6c3 100644 (file)
     <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'/>
     <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'/>
       <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'>
       <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'>
       <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'>
       <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'/>
       <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'>
       <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'/>
       <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'/>
     <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'/>
index ae4c861590fdc98aa0d2206f8ea5b647dbb82ebe..4fb71b4e0dc8595dcaa9896aabe6cd3cf7c668ff 100644 (file)
@@ -426,6 +426,37 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
        return (0);
 }
 
+/*
+ * 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.
@@ -549,6 +580,44 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
                                (void) no_memory(hdl);
                                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);
                }
        }
 
index 393971ddf13c9327e4f2da4a923a54d7e933aa02..4b8a20160e02cfb011a0cf59ac0837e2d683be4e 100644 (file)
@@ -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) &&
index 12b9b11903dfafa6441e928332d73c230e2d9d3e..7709d85226dcec0e135d092060d0d1be7bbce498 100644 (file)
@@ -26,8 +26,9 @@
 .\" Copyright 2017 Nexenta Systems, Inc.
 .\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
 .\" Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
+.\" 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.
index 67601211d6c280c428cbaf072807d86870f012be..dd4a442d97a1f96dda65920bed8300a41a55f7c4 100644 (file)
@@ -296,6 +296,22 @@ spa_prop_add_list(nvlist_t *nvl, zpool_prop_t prop, const char *strval,
        nvlist_free(propval);
 }
 
+/*
+ * 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.
  */
@@ -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) {
index cc4ce03677cb6e374368f995f3e44dbd0dd4ed1f..55991cfeaf7889d0f5fd8e9887095a79f86c37f0 100644 (file)
@@ -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]
index e671a3f6b02bbd504e823b0e824981220b529bec..74295b86ddc2d1a05c731a01843a753c997e24bd 100644 (file)
@@ -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 (executable)
index 0000000..4b90979
--- /dev/null
@@ -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 (executable)
index 0000000..7c8fcba
--- /dev/null
@@ -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 (file)
index 0000000..346e4a1
--- /dev/null
@@ -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" ]
+}