summaryrefslogtreecommitdiffstats
path: root/module/zfs/zfs_sysfs.c
diff options
context:
space:
mode:
authorDon Brady <[email protected]>2018-09-02 15:09:53 -0400
committerBrian Behlendorf <[email protected]>2018-09-02 12:09:53 -0700
commite8bcb693d6a19962291e4429f2dc4ce60693bef7 (patch)
treea4afbb8d0562aae23e9fd8fd561fd2182b14db8e /module/zfs/zfs_sysfs.c
parentbb91178e60df553071ce2e18b0067ef703f7b583 (diff)
Add zfs module feature and property info to sysfs
This extends our sysfs '/sys/module/zfs' entry to include feature and property attributes. The primary consumer of this information is user processes, like the zfs CLI, that need to know what the current loaded ZFS module supports. The libzfs binary will consult this information when instantiating the zfs and zpool property tables and the pool features table. This introduces 4 kernel objects (dirs) into '/sys/module/zfs' with corresponding attributes (files): features.runtime features.pool properties.dataset properties.pool Reviewed-by: Matthew Ahrens <[email protected]> Reviewed-by: Tony Hutter <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Don Brady <[email protected]> Closes #7706
Diffstat (limited to 'module/zfs/zfs_sysfs.c')
-rw-r--r--module/zfs/zfs_sysfs.c617
1 files changed, 617 insertions, 0 deletions
diff --git a/module/zfs/zfs_sysfs.c b/module/zfs/zfs_sysfs.c
new file mode 100644
index 000000000..6bf2e23aa
--- /dev/null
+++ b/module/zfs/zfs_sysfs.c
@@ -0,0 +1,617 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2018 by Delphix. All rights reserved.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/zfeature.h>
+#include <sys/zfs_ioctl.h>
+#include <sys/zfs_sysfs.h>
+#include <sys/kmem.h>
+#include <sys/fs/zfs.h>
+#include <linux/kobject.h>
+
+#include "zfs_prop.h"
+
+#if !defined(_KERNEL)
+#error kernel builds only
+#endif
+
+/*
+ * ZFS Module sysfs support
+ *
+ * This extends our sysfs '/sys/module/zfs' entry to include feature
+ * and property attributes. The primary consumer of this information
+ * is user processes, like the zfs CLI, that need to know what the
+ * current loaded ZFS module supports. The libzfs binary will consult
+ * this information when instantiating the zfs|zpool property tables
+ * and the pool features table.
+ *
+ * The added top-level directories are:
+ * /sys/module/zfs
+ * ├── features.kernel
+ * ├── features.pool
+ * ├── properties.dataset
+ * └── properties.pool
+ *
+ * The local interface for the zfs kobjects includes:
+ * zfs_kobj_init()
+ * zfs_kobj_add()
+ * zfs_kobj_release()
+ * zfs_kobj_add_attr()
+ * zfs_kobj_fini()
+ */
+
+/*
+ * A zfs_mod_kobj_t represents a zfs kobject under '/sys/module/zfs'
+ */
+struct zfs_mod_kobj;
+typedef struct zfs_mod_kobj zfs_mod_kobj_t;
+
+struct zfs_mod_kobj {
+ struct kobject zko_kobj;
+ struct kobj_type zko_kobj_type;
+ struct sysfs_ops zko_sysfs_ops;
+ size_t zko_attr_count;
+ struct attribute *zko_attr_list; /* allocated */
+ struct attribute **zko_default_attrs; /* allocated */
+ size_t zko_child_count;
+ zfs_mod_kobj_t *zko_children; /* allocated */
+};
+
+#define ATTR_TABLE_SIZE(cnt) (sizeof (struct attribute) * (cnt))
+/* Note +1 for NULL terminator slot */
+#define DEFAULT_ATTR_SIZE(cnt) (sizeof (struct attribute *) * (cnt + 1))
+#define CHILD_TABLE_SIZE(cnt) (sizeof (zfs_mod_kobj_t) * (cnt))
+
+/*
+ * These are the top-level kobjects under '/sys/module/zfs/'
+ */
+static zfs_mod_kobj_t kernel_features_kobj;
+static zfs_mod_kobj_t pool_features_kobj;
+static zfs_mod_kobj_t dataset_props_kobj;
+static zfs_mod_kobj_t pool_props_kobj;
+
+/*
+ * The show function is used to provide the content
+ * of an attribute into a PAGE_SIZE buffer.
+ */
+typedef ssize_t (*sysfs_show_func)(struct kobject *, struct attribute *,
+ char *);
+
+static void
+zfs_kobj_fini(zfs_mod_kobj_t *zkobj)
+{
+ /* finialize any child kobjects */
+ if (zkobj->zko_child_count != 0) {
+ ASSERT(zkobj->zko_children);
+ for (int i = 0; i < zkobj->zko_child_count; i++)
+ zfs_kobj_fini(&zkobj->zko_children[i]);
+ }
+
+ /* kobject_put() will call zfs_kobj_release() to release memory */
+ kobject_del(&zkobj->zko_kobj);
+ kobject_put(&zkobj->zko_kobj);
+}
+
+static void
+zfs_kobj_release(struct kobject *kobj)
+{
+ zfs_mod_kobj_t *zkobj = container_of(kobj, zfs_mod_kobj_t, zko_kobj);
+
+ if (zkobj->zko_attr_count != 0) {
+ ASSERT(zkobj->zko_attr_list);
+ ASSERT(zkobj->zko_default_attrs);
+
+ kmem_free(zkobj->zko_attr_list,
+ ATTR_TABLE_SIZE(zkobj->zko_attr_count));
+ kmem_free(zkobj->zko_default_attrs,
+ DEFAULT_ATTR_SIZE(zkobj->zko_attr_count));
+ zkobj->zko_attr_count = 0;
+ zkobj->zko_attr_list = NULL;
+ zkobj->zko_default_attrs = NULL;
+ }
+ if (zkobj->zko_child_count != 0) {
+ ASSERT(zkobj->zko_children);
+
+ kmem_free(zkobj->zko_children,
+ CHILD_TABLE_SIZE(zkobj->zko_child_count));
+ zkobj->zko_child_count = 0;
+ zkobj->zko_children = NULL;
+ }
+}
+
+static void
+zfs_kobj_add_attr(zfs_mod_kobj_t *zkobj, int attr_num, const char *attr_name)
+{
+ VERIFY3U(attr_num, <, zkobj->zko_attr_count);
+ ASSERT(zkobj->zko_attr_list);
+ ASSERT(zkobj->zko_default_attrs);
+
+ zkobj->zko_attr_list[attr_num].name = attr_name;
+ zkobj->zko_attr_list[attr_num].mode = 0444;
+ zkobj->zko_default_attrs[attr_num] = &zkobj->zko_attr_list[attr_num];
+}
+
+static int
+zfs_kobj_init(zfs_mod_kobj_t *zkobj, int attr_cnt, int child_cnt,
+ sysfs_show_func show_func)
+{
+ /*
+ * Initialize object's attributes. Count can be zero.
+ */
+ if (attr_cnt > 0) {
+ zkobj->zko_attr_list = kmem_zalloc(ATTR_TABLE_SIZE(attr_cnt),
+ KM_SLEEP);
+ if (zkobj->zko_attr_list == NULL)
+ return (ENOMEM);
+ }
+ /* this will always have at least one slot for NULL termination */
+ zkobj->zko_default_attrs = kmem_zalloc(DEFAULT_ATTR_SIZE(attr_cnt),
+ KM_SLEEP);
+ if (zkobj->zko_default_attrs == NULL) {
+ if (zkobj->zko_attr_list) {
+ kmem_free(zkobj->zko_attr_list,
+ ATTR_TABLE_SIZE(attr_cnt));
+ }
+ return (ENOMEM);
+ }
+ zkobj->zko_attr_count = attr_cnt;
+ zkobj->zko_kobj_type.default_attrs = zkobj->zko_default_attrs;
+
+ if (child_cnt > 0) {
+ zkobj->zko_children = kmem_zalloc(CHILD_TABLE_SIZE(child_cnt),
+ KM_SLEEP);
+ if (zkobj->zko_children == NULL) {
+ if (attr_cnt > 0) {
+ kmem_free(zkobj->zko_attr_list,
+ DEFAULT_ATTR_SIZE(attr_cnt));
+ kmem_free(zkobj->zko_attr_list,
+ ATTR_TABLE_SIZE(attr_cnt));
+ }
+ return (ENOMEM);
+ }
+ zkobj->zko_child_count = child_cnt;
+ }
+
+ zkobj->zko_sysfs_ops.show = show_func;
+ zkobj->zko_kobj_type.sysfs_ops = &zkobj->zko_sysfs_ops;
+ zkobj->zko_kobj_type.release = zfs_kobj_release;
+
+ return (0);
+}
+
+static int
+zfs_kobj_add(zfs_mod_kobj_t *zkobj, struct kobject *parent, const char *name)
+{
+ /* zko_default_attrs must be NULL terminated */
+ ASSERT(zkobj->zko_default_attrs != NULL);
+ ASSERT(zkobj->zko_default_attrs[zkobj->zko_attr_count] == NULL);
+
+ kobject_init(&zkobj->zko_kobj, &zkobj->zko_kobj_type);
+ return (kobject_add(&zkobj->zko_kobj, parent, name));
+}
+
+/*
+ * Each zfs property has these common attributes
+ */
+static const char *zprop_attrs[] = {
+ "type",
+ "readonly",
+ "setonce",
+ "visible",
+ "values",
+ "default",
+ "datasets" /* zfs properties only */
+};
+
+#define ZFS_PROP_ATTR_COUNT ARRAY_SIZE(zprop_attrs)
+#define ZPOOL_PROP_ATTR_COUNT (ZFS_PROP_ATTR_COUNT - 1)
+
+static const char *zprop_types[] = {
+ "number",
+ "string",
+ "index",
+};
+
+typedef struct zfs_type_map {
+ zfs_type_t ztm_type;
+ const char *ztm_name;
+} zfs_type_map_t;
+
+static zfs_type_map_t type_map[] = {
+ {ZFS_TYPE_FILESYSTEM, "filesystem"},
+ {ZFS_TYPE_SNAPSHOT, "snapshot"},
+ {ZFS_TYPE_VOLUME, "volume"},
+ {ZFS_TYPE_BOOKMARK, "bookmark"}
+};
+
+/*
+ * Show the content for a zfs property attribute
+ */
+static ssize_t
+zprop_sysfs_show(const char *attr_name, const zprop_desc_t *property,
+ char *buf, size_t buflen)
+{
+ const char *show_str;
+
+ /* For dataset properties list the dataset types that apply */
+ if (strcmp(attr_name, "datasets") == 0 &&
+ property->pd_types != ZFS_TYPE_POOL) {
+ int len = 0;
+
+ for (int i = 0; i < ARRAY_SIZE(type_map); i++) {
+ if (type_map[i].ztm_type & property->pd_types) {
+ len += snprintf(buf + len, buflen - len, "%s ",
+ type_map[i].ztm_name);
+ }
+ }
+ len += snprintf(buf + len, buflen - len, "\n");
+ return (len);
+ }
+
+ if (strcmp(attr_name, "type") == 0) {
+ show_str = zprop_types[property->pd_proptype];
+ } else if (strcmp(attr_name, "readonly") == 0) {
+ show_str = property->pd_attr == PROP_READONLY ? "1" : "0";
+ } else if (strcmp(attr_name, "setonce") == 0) {
+ show_str = property->pd_attr == PROP_ONETIME ? "1" : "0";
+ } else if (strcmp(attr_name, "visible") == 0) {
+ show_str = property->pd_visible ? "1" : "0";
+ } else if (strcmp(attr_name, "values") == 0) {
+ show_str = property->pd_values ? property->pd_values : "";
+ } else if (strcmp(attr_name, "default") == 0) {
+ char number[32];
+
+ switch (property->pd_proptype) {
+ case PROP_TYPE_NUMBER:
+ (void) snprintf(number, sizeof (number), "%llu",
+ (u_longlong_t)property->pd_numdefault);
+ show_str = number;
+ break;
+ case PROP_TYPE_STRING:
+ show_str = property->pd_strdefault ?
+ property->pd_strdefault : "";
+ break;
+ case PROP_TYPE_INDEX:
+ (void) zprop_index_to_string(property->pd_propnum,
+ property->pd_numdefault, &show_str,
+ property->pd_types);
+ break;
+ default:
+ return (0);
+ }
+ } else {
+ return (0);
+ }
+
+ return (snprintf(buf, buflen, "%s\n", show_str));
+}
+
+static ssize_t
+dataset_property_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ zfs_prop_t prop = zfs_name_to_prop(kobject_name(kobj));
+ zprop_desc_t *prop_tbl = zfs_prop_get_table();
+ ssize_t len;
+
+ ASSERT3U(prop, <, ZFS_NUM_PROPS);
+
+ len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);
+
+ return (len);
+}
+
+static ssize_t
+pool_property_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ zpool_prop_t prop = zpool_name_to_prop(kobject_name(kobj));
+ zprop_desc_t *prop_tbl = zpool_prop_get_table();
+ ssize_t len;
+
+ ASSERT3U(prop, <, ZPOOL_NUM_PROPS);
+
+ len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);
+
+ return (len);
+}
+
+/*
+ * ZFS kernel feature attributes for '/sys/module/zfs/features.kernel'
+ *
+ * This list is intended for kernel features that don't have a pool feature
+ * association or that extend existing user kernel interfaces.
+ *
+ * A user processes can easily check if the running zfs kernel module
+ * supports the new feature.
+ *
+ * For example, the initial channel_program feature was extended to support
+ * async calls (i.e. a sync flag). If this mechanism were in place at that
+ * time, we could have added a 'channel_program_async' to this list.
+ */
+static const char *zfs_features[] = {
+ /* --> Add new kernel features here (post ZoL 0.8.0) */
+};
+
+#define ZFS_FEATURE_COUNT ARRAY_SIZE(zfs_features)
+
+static ssize_t
+kernel_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ return (snprintf(buf, PAGE_SIZE, "supported\n"));
+}
+
+static int
+zfs_kernel_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)
+{
+ int err;
+
+ err = zfs_kobj_init(zfs_kobj, ZFS_FEATURE_COUNT, 0,
+ kernel_feature_show);
+ if (err)
+ return (err);
+
+ for (int f = 0; f < ZFS_FEATURE_COUNT; f++)
+ zfs_kobj_add_attr(zfs_kobj, f, zfs_features[f]);
+
+ err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_KERNEL_FEATURES);
+ if (err)
+ zfs_kobj_release(&zfs_kobj->zko_kobj);
+
+ return (err);
+}
+
+/*
+ * Each pool feature has these common attributes
+ */
+static const char *pool_feature_attrs[] = {
+ "description",
+ "guid",
+ "uname",
+ "readonly_compatible",
+ "required_for_mos",
+ "activate_on_enable",
+ "per_dataset"
+};
+
+#define ZPOOL_FEATURE_ATTR_COUNT ARRAY_SIZE(pool_feature_attrs)
+
+/*
+ * Show the content for the given zfs pool feature attribute
+ */
+static ssize_t
+pool_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ spa_feature_t fid;
+
+ if (zfeature_lookup_guid(kobject_name(kobj), &fid) != 0)
+ return (0);
+
+ ASSERT3U(fid, <, SPA_FEATURES);
+
+ zfeature_flags_t flags = spa_feature_table[fid].fi_flags;
+ const char *show_str = NULL;
+
+ if (strcmp(attr->name, "description") == 0) {
+ show_str = spa_feature_table[fid].fi_desc;
+ } else if (strcmp(attr->name, "guid") == 0) {
+ show_str = spa_feature_table[fid].fi_guid;
+ } else if (strcmp(attr->name, "uname") == 0) {
+ show_str = spa_feature_table[fid].fi_uname;
+ } else if (strcmp(attr->name, "readonly_compatible") == 0) {
+ show_str = flags & ZFEATURE_FLAG_READONLY_COMPAT ? "1" : "0";
+ } else if (strcmp(attr->name, "required_for_mos") == 0) {
+ show_str = flags & ZFEATURE_FLAG_MOS ? "1" : "0";
+ } else if (strcmp(attr->name, "activate_on_enable") == 0) {
+ show_str = flags & ZFEATURE_FLAG_ACTIVATE_ON_ENABLE ? "1" : "0";
+ } else if (strcmp(attr->name, "per_dataset") == 0) {
+ show_str = flags & ZFEATURE_FLAG_PER_DATASET ? "1" : "0";
+ }
+ if (show_str == NULL)
+ return (0);
+
+ return (snprintf(buf, PAGE_SIZE, "%s\n", show_str));
+}
+
+static void
+pool_feature_to_kobj(zfs_mod_kobj_t *parent, spa_feature_t fid,
+ const char *name)
+{
+ zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[fid];
+
+ ASSERT3U(fid, <, SPA_FEATURES);
+ ASSERT(name);
+
+ int err = zfs_kobj_init(zfs_kobj, ZPOOL_FEATURE_ATTR_COUNT, 0,
+ pool_feature_show);
+ if (err)
+ return;
+
+ for (int i = 0; i < ZPOOL_FEATURE_ATTR_COUNT; i++)
+ zfs_kobj_add_attr(zfs_kobj, i, pool_feature_attrs[i]);
+
+ err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
+ if (err)
+ zfs_kobj_release(&zfs_kobj->zko_kobj);
+}
+
+static int
+zfs_pool_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)
+{
+ /*
+ * Create a parent kobject to host pool features.
+ *
+ * '/sys/module/zfs/features.pool'
+ */
+ int err = zfs_kobj_init(zfs_kobj, 0, SPA_FEATURES, pool_feature_show);
+ if (err)
+ return (err);
+ err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_POOL_FEATURES);
+ if (err) {
+ zfs_kobj_release(&zfs_kobj->zko_kobj);
+ return (err);
+ }
+
+ /*
+ * Now create a kobject for each feature.
+ *
+ * '/sys/module/zfs/features.pool/<feature>'
+ */
+ for (spa_feature_t i = 0; i < SPA_FEATURES; i++)
+ pool_feature_to_kobj(zfs_kobj, i, spa_feature_table[i].fi_guid);
+
+ return (0);
+}
+
+typedef struct prop_to_kobj_arg {
+ zprop_desc_t *p2k_table;
+ zfs_mod_kobj_t *p2k_parent;
+ sysfs_show_func p2k_show_func;
+ int p2k_attr_count;
+} prop_to_kobj_arg_t;
+
+static int
+zprop_to_kobj(int prop, void *args)
+{
+ prop_to_kobj_arg_t *data = args;
+ zfs_mod_kobj_t *parent = data->p2k_parent;
+ zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[prop];
+ const char *name = data->p2k_table[prop].pd_name;
+ int err;
+
+ ASSERT(name);
+
+ err = zfs_kobj_init(zfs_kobj, data->p2k_attr_count, 0,
+ data->p2k_show_func);
+ if (err)
+ return (ZPROP_CONT);
+
+ for (int i = 0; i < data->p2k_attr_count; i++)
+ zfs_kobj_add_attr(zfs_kobj, i, zprop_attrs[i]);
+
+ err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
+ if (err)
+ zfs_kobj_release(&zfs_kobj->zko_kobj);
+
+ return (ZPROP_CONT);
+}
+
+static int
+zfs_sysfs_properties_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent,
+ zfs_type_t type)
+{
+ prop_to_kobj_arg_t context;
+ const char *name;
+ int err;
+
+ /*
+ * Create a parent kobject to host properties.
+ *
+ * '/sys/module/zfs/properties.<type>'
+ */
+ if (type == ZFS_TYPE_POOL) {
+ name = ZFS_SYSFS_POOL_PROPERTIES;
+ context.p2k_table = zpool_prop_get_table();
+ context.p2k_attr_count = ZPOOL_PROP_ATTR_COUNT;
+ context.p2k_parent = zfs_kobj;
+ context.p2k_show_func = pool_property_show;
+ err = zfs_kobj_init(zfs_kobj, 0, ZPOOL_NUM_PROPS,
+ pool_property_show);
+ } else {
+ name = ZFS_SYSFS_DATASET_PROPERTIES;
+ context.p2k_table = zfs_prop_get_table();
+ context.p2k_attr_count = ZFS_PROP_ATTR_COUNT;
+ context.p2k_parent = zfs_kobj;
+ context.p2k_show_func = dataset_property_show;
+ err = zfs_kobj_init(zfs_kobj, 0, ZFS_NUM_PROPS,
+ dataset_property_show);
+ }
+
+ if (err)
+ return (err);
+
+ err = zfs_kobj_add(zfs_kobj, parent, name);
+ if (err) {
+ zfs_kobj_release(&zfs_kobj->zko_kobj);
+ return (err);
+ }
+
+ /*
+ * Create a kobject for each property.
+ *
+ * '/sys/module/zfs/properties.<type>/<property>'
+ */
+ (void) zprop_iter_common(zprop_to_kobj, &context, B_TRUE,
+ B_FALSE, type);
+
+ return (err);
+}
+
+void
+zfs_sysfs_init(void)
+{
+ struct kobject *parent =
+ &(((struct module *)(THIS_MODULE))->mkobj).kobj;
+ int err;
+
+ ASSERT(parent != NULL);
+
+ err = zfs_kernel_features_init(&kernel_features_kobj, parent);
+ if (err)
+ return;
+
+ err = zfs_pool_features_init(&pool_features_kobj, parent);
+ if (err) {
+ zfs_kobj_fini(&kernel_features_kobj);
+ return;
+ }
+
+ err = zfs_sysfs_properties_init(&pool_props_kobj, parent,
+ ZFS_TYPE_POOL);
+ if (err) {
+ zfs_kobj_fini(&kernel_features_kobj);
+ zfs_kobj_fini(&pool_features_kobj);
+ return;
+ }
+
+ err = zfs_sysfs_properties_init(&dataset_props_kobj, parent,
+ ZFS_TYPE_FILESYSTEM);
+ if (err) {
+ zfs_kobj_fini(&kernel_features_kobj);
+ zfs_kobj_fini(&pool_features_kobj);
+ zfs_kobj_fini(&pool_props_kobj);
+ return;
+ }
+}
+
+void
+zfs_sysfs_fini(void)
+{
+ /*
+ * Remove top-level kobjects; each will remove any children kobjects
+ */
+ zfs_kobj_fini(&kernel_features_kobj);
+ zfs_kobj_fini(&pool_features_kobj);
+ zfs_kobj_fini(&dataset_props_kobj);
+ zfs_kobj_fini(&pool_props_kobj);
+}