Path: blob/main/sys/contrib/openzfs/module/os/linux/zfs/zfs_sysfs.c
48775 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/21/*22* Copyright (c) 2018, 2019 by Delphix. All rights reserved.23*/2425#include <sys/types.h>26#include <sys/param.h>27#include <sys/zfeature.h>28#include <sys/zfs_ioctl.h>29#include <sys/zfs_sysfs.h>30#include <sys/kmem.h>31#include <sys/fs/zfs.h>32#include <linux/kobject.h>3334#include "zfs_prop.h"3536#if !defined(_KERNEL)37#error kernel builds only38#endif3940/*41* ZFS Module sysfs support42*43* This extends our sysfs '/sys/module/zfs' entry to include feature44* and property attributes. The primary consumer of this information45* is user processes, like the zfs CLI, that need to know what the46* current loaded ZFS module supports. The libzfs binary will consult47* this information when instantiating the zfs|zpool property tables48* and the pool features table.49*50* The added top-level directories are:51* /sys/module/zfs52* ├── features.kernel53* ├── features.pool54* ├── properties.dataset55* └── properties.pool56*57* The local interface for the zfs kobjects includes:58* zfs_kobj_init()59* zfs_kobj_add()60* zfs_kobj_release()61* zfs_kobj_add_attr()62* zfs_kobj_fini()63*/6465/*66* A zfs_mod_kobj_t represents a zfs kobject under '/sys/module/zfs'67*/68typedef struct zfs_mod_kobj zfs_mod_kobj_t;69struct zfs_mod_kobj {70struct kobject zko_kobj;71struct kobj_type zko_kobj_type;72struct sysfs_ops zko_sysfs_ops;73size_t zko_attr_count;74struct attribute *zko_attr_list; /* allocated */75struct attribute_group zko_default_group; /* .attrs allocated */76const struct attribute_group *zko_default_groups[2];77size_t zko_child_count;78zfs_mod_kobj_t *zko_children; /* allocated */79};8081#define ATTR_TABLE_SIZE(cnt) (sizeof (struct attribute) * (cnt))82/* Note +1 for NULL terminator slot */83#define DEFAULT_ATTR_SIZE(cnt) (sizeof (struct attribute *) * (cnt + 1))84#define CHILD_TABLE_SIZE(cnt) (sizeof (zfs_mod_kobj_t) * (cnt))8586/*87* These are the top-level kobjects under '/sys/module/zfs/'88*/89static zfs_mod_kobj_t kernel_features_kobj;90static zfs_mod_kobj_t pool_features_kobj;91static zfs_mod_kobj_t dataset_props_kobj;92static zfs_mod_kobj_t vdev_props_kobj;93static zfs_mod_kobj_t pool_props_kobj;9495/*96* The show function is used to provide the content97* of an attribute into a PAGE_SIZE buffer.98*/99typedef ssize_t (*sysfs_show_func)(struct kobject *, struct attribute *,100char *);101102static void103zfs_kobj_fini(zfs_mod_kobj_t *zkobj)104{105/* finalize any child kobjects */106if (zkobj->zko_child_count != 0) {107ASSERT(zkobj->zko_children);108for (int i = 0; i < zkobj->zko_child_count; i++)109zfs_kobj_fini(&zkobj->zko_children[i]);110}111112/* kobject_put() will call zfs_kobj_release() to release memory */113kobject_del(&zkobj->zko_kobj);114kobject_put(&zkobj->zko_kobj);115}116117static void118zfs_kobj_release(struct kobject *kobj)119{120zfs_mod_kobj_t *zkobj = container_of(kobj, zfs_mod_kobj_t, zko_kobj);121122if (zkobj->zko_attr_list != NULL) {123ASSERT3S(zkobj->zko_attr_count, !=, 0);124kmem_free(zkobj->zko_attr_list,125ATTR_TABLE_SIZE(zkobj->zko_attr_count));126zkobj->zko_attr_list = NULL;127}128129if (zkobj->zko_default_group.attrs != NULL) {130kmem_free(zkobj->zko_default_group.attrs,131DEFAULT_ATTR_SIZE(zkobj->zko_attr_count));132zkobj->zko_default_group.attrs = NULL;133}134135if (zkobj->zko_child_count != 0) {136ASSERT(zkobj->zko_children);137138kmem_free(zkobj->zko_children,139CHILD_TABLE_SIZE(zkobj->zko_child_count));140zkobj->zko_child_count = 0;141zkobj->zko_children = NULL;142}143144zkobj->zko_attr_count = 0;145}146147#ifndef sysfs_attr_init148#define sysfs_attr_init(attr) do {} while (0)149#endif150151static void152zfs_kobj_add_attr(zfs_mod_kobj_t *zkobj, int attr_num, const char *attr_name)153{154VERIFY3U(attr_num, <, zkobj->zko_attr_count);155ASSERT(zkobj->zko_attr_list);156ASSERT(zkobj->zko_default_group.attrs);157158zkobj->zko_attr_list[attr_num].name = attr_name;159zkobj->zko_attr_list[attr_num].mode = 0444;160zkobj->zko_default_group.attrs[attr_num] =161&zkobj->zko_attr_list[attr_num];162sysfs_attr_init(&zkobj->zko_attr_list[attr_num]);163}164165static int166zfs_kobj_init(zfs_mod_kobj_t *zkobj, int attr_cnt, int child_cnt,167sysfs_show_func show_func)168{169/*170* Initialize object's attributes. Count can be zero.171*/172if (attr_cnt > 0) {173zkobj->zko_attr_list = kmem_zalloc(ATTR_TABLE_SIZE(attr_cnt),174KM_SLEEP);175if (zkobj->zko_attr_list == NULL)176return (ENOMEM);177}178/* this will always have at least one slot for NULL termination */179zkobj->zko_default_group.attrs =180kmem_zalloc(DEFAULT_ATTR_SIZE(attr_cnt), KM_SLEEP);181if (zkobj->zko_default_group.attrs == NULL) {182if (zkobj->zko_attr_list != NULL) {183kmem_free(zkobj->zko_attr_list,184ATTR_TABLE_SIZE(attr_cnt));185}186return (ENOMEM);187}188zkobj->zko_attr_count = attr_cnt;189zkobj->zko_default_groups[0] = &zkobj->zko_default_group;190#ifdef HAVE_SYSFS_DEFAULT_GROUPS191zkobj->zko_kobj_type.default_groups = zkobj->zko_default_groups;192#else193zkobj->zko_kobj_type.default_attrs = zkobj->zko_default_group.attrs;194#endif195196if (child_cnt > 0) {197zkobj->zko_children = kmem_zalloc(CHILD_TABLE_SIZE(child_cnt),198KM_SLEEP);199if (zkobj->zko_children == NULL) {200if (zkobj->zko_default_group.attrs != NULL) {201kmem_free(zkobj->zko_default_group.attrs,202DEFAULT_ATTR_SIZE(attr_cnt));203}204if (zkobj->zko_attr_list != NULL) {205kmem_free(zkobj->zko_attr_list,206ATTR_TABLE_SIZE(attr_cnt));207}208return (ENOMEM);209}210zkobj->zko_child_count = child_cnt;211}212213zkobj->zko_sysfs_ops.show = show_func;214zkobj->zko_kobj_type.sysfs_ops = &zkobj->zko_sysfs_ops;215zkobj->zko_kobj_type.release = zfs_kobj_release;216217return (0);218}219220static int221zfs_kobj_add(zfs_mod_kobj_t *zkobj, struct kobject *parent, const char *name)222{223/* zko_default_group.attrs must be NULL terminated */224ASSERT(zkobj->zko_default_group.attrs != NULL);225ASSERT0P(zkobj->zko_default_group.attrs[zkobj->zko_attr_count]);226227kobject_init(&zkobj->zko_kobj, &zkobj->zko_kobj_type);228return (kobject_add(&zkobj->zko_kobj, parent, name));229}230231/*232* Each zfs property has these common attributes233*/234static const char *const zprop_attrs[] = {235"type",236"readonly",237"setonce",238"visible",239"values",240"default",241"datasets" /* zfs properties only */242};243244#define ZFS_PROP_ATTR_COUNT ARRAY_SIZE(zprop_attrs)245#define ZPOOL_PROP_ATTR_COUNT (ZFS_PROP_ATTR_COUNT - 1)246247static const char *const zprop_types[] = {248"number",249"string",250"index",251};252253typedef struct zfs_type_map {254zfs_type_t ztm_type;255const char *ztm_name;256} zfs_type_map_t;257258static const zfs_type_map_t type_map[] = {259{ZFS_TYPE_FILESYSTEM, "filesystem"},260{ZFS_TYPE_SNAPSHOT, "snapshot"},261{ZFS_TYPE_VOLUME, "volume"},262{ZFS_TYPE_BOOKMARK, "bookmark"}263};264265/*266* Show the content for a zfs property attribute267*/268static ssize_t269zprop_sysfs_show(const char *attr_name, const zprop_desc_t *property,270char *buf, size_t buflen)271{272const char *show_str;273char number[32];274275/* For dataset properties list the dataset types that apply */276if (strcmp(attr_name, "datasets") == 0 &&277property->pd_types != ZFS_TYPE_POOL) {278int len = 0;279280for (int i = 0; i < ARRAY_SIZE(type_map); i++) {281if (type_map[i].ztm_type & property->pd_types) {282len += kmem_scnprintf(buf + len, buflen - len,283"%s ", type_map[i].ztm_name);284}285}286len += kmem_scnprintf(buf + len, buflen - len, "\n");287return (len);288}289290if (strcmp(attr_name, "type") == 0) {291show_str = zprop_types[property->pd_proptype];292} else if (strcmp(attr_name, "readonly") == 0) {293show_str = property->pd_attr == PROP_READONLY ? "1" : "0";294} else if (strcmp(attr_name, "setonce") == 0) {295show_str = property->pd_attr == PROP_ONETIME ? "1" : "0";296} else if (strcmp(attr_name, "visible") == 0) {297show_str = property->pd_visible ? "1" : "0";298} else if (strcmp(attr_name, "values") == 0) {299show_str = property->pd_values ? property->pd_values : "";300} else if (strcmp(attr_name, "default") == 0) {301switch (property->pd_proptype) {302case PROP_TYPE_NUMBER:303(void) snprintf(number, sizeof (number), "%llu",304(u_longlong_t)property->pd_numdefault);305show_str = number;306break;307case PROP_TYPE_STRING:308show_str = property->pd_strdefault ?309property->pd_strdefault : "";310break;311case PROP_TYPE_INDEX:312if (zprop_index_to_string(property->pd_propnum,313property->pd_numdefault, &show_str,314property->pd_types) != 0) {315show_str = "";316}317break;318default:319return (0);320}321} else {322return (0);323}324325return (snprintf(buf, buflen, "%s\n", show_str));326}327328static ssize_t329dataset_property_show(struct kobject *kobj, struct attribute *attr, char *buf)330{331zfs_prop_t prop = zfs_name_to_prop(kobject_name(kobj));332zprop_desc_t *prop_tbl = zfs_prop_get_table();333ssize_t len;334335ASSERT3U(prop, <, ZFS_NUM_PROPS);336337len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);338339return (len);340}341342static ssize_t343vdev_property_show(struct kobject *kobj, struct attribute *attr, char *buf)344{345vdev_prop_t prop = vdev_name_to_prop(kobject_name(kobj));346zprop_desc_t *prop_tbl = vdev_prop_get_table();347ssize_t len;348349ASSERT3U(prop, <, VDEV_NUM_PROPS);350351len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);352353return (len);354}355356static ssize_t357pool_property_show(struct kobject *kobj, struct attribute *attr, char *buf)358{359zpool_prop_t prop = zpool_name_to_prop(kobject_name(kobj));360zprop_desc_t *prop_tbl = zpool_prop_get_table();361ssize_t len;362363ASSERT3U(prop, <, ZPOOL_NUM_PROPS);364365len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);366367return (len);368}369370/*371* ZFS kernel feature attributes for '/sys/module/zfs/features.kernel'372*373* This list is intended for kernel features that don't have a pool feature374* association or that extend existing user kernel interfaces.375*376* A user process can easily check if the running zfs kernel module377* supports the new feature.378*/379static const char *const zfs_kernel_features[] = {380/* --> Add new kernel features here */381"com.delphix:vdev_initialize",382"org.zfsonlinux:vdev_trim",383"org.openzfs:l2arc_persistent",384};385386#define KERNEL_FEATURE_COUNT ARRAY_SIZE(zfs_kernel_features)387388static ssize_t389kernel_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)390{391if (strcmp(attr->name, "supported") == 0)392return (snprintf(buf, PAGE_SIZE, "yes\n"));393return (0);394}395396static void397kernel_feature_to_kobj(zfs_mod_kobj_t *parent, int slot, const char *name)398{399zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[slot];400401ASSERT3U(slot, <, KERNEL_FEATURE_COUNT);402ASSERT(name);403404int err = zfs_kobj_init(zfs_kobj, 1, 0, kernel_feature_show);405if (err)406return;407408zfs_kobj_add_attr(zfs_kobj, 0, "supported");409410err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);411if (err)412zfs_kobj_release(&zfs_kobj->zko_kobj);413}414415static int416zfs_kernel_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)417{418/*419* Create a parent kobject to host kernel features.420*421* '/sys/module/zfs/features.kernel'422*/423int err = zfs_kobj_init(zfs_kobj, 0, KERNEL_FEATURE_COUNT,424kernel_feature_show);425if (err)426return (err);427err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_KERNEL_FEATURES);428if (err) {429zfs_kobj_release(&zfs_kobj->zko_kobj);430return (err);431}432433/*434* Now create a kobject for each feature.435*436* '/sys/module/zfs/features.kernel/<feature>'437*/438for (int f = 0; f < KERNEL_FEATURE_COUNT; f++)439kernel_feature_to_kobj(zfs_kobj, f, zfs_kernel_features[f]);440441return (0);442}443444/*445* Each pool feature has these common attributes446*/447static const char *const pool_feature_attrs[] = {448"description",449"guid",450"uname",451"readonly_compatible",452"required_for_mos",453"activate_on_enable",454"per_dataset"455};456457#define ZPOOL_FEATURE_ATTR_COUNT ARRAY_SIZE(pool_feature_attrs)458459/*460* Show the content for the given zfs pool feature attribute461*/462static ssize_t463pool_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)464{465spa_feature_t fid;466467if (zfeature_lookup_guid(kobject_name(kobj), &fid) != 0)468return (0);469470ASSERT3U(fid, <, SPA_FEATURES);471472zfeature_flags_t flags = spa_feature_table[fid].fi_flags;473const char *show_str = NULL;474475if (strcmp(attr->name, "description") == 0) {476show_str = spa_feature_table[fid].fi_desc;477} else if (strcmp(attr->name, "guid") == 0) {478show_str = spa_feature_table[fid].fi_guid;479} else if (strcmp(attr->name, "uname") == 0) {480show_str = spa_feature_table[fid].fi_uname;481} else if (strcmp(attr->name, "readonly_compatible") == 0) {482show_str = flags & ZFEATURE_FLAG_READONLY_COMPAT ? "1" : "0";483} else if (strcmp(attr->name, "required_for_mos") == 0) {484show_str = flags & ZFEATURE_FLAG_MOS ? "1" : "0";485} else if (strcmp(attr->name, "activate_on_enable") == 0) {486show_str = flags & ZFEATURE_FLAG_ACTIVATE_ON_ENABLE ? "1" : "0";487} else if (strcmp(attr->name, "per_dataset") == 0) {488show_str = flags & ZFEATURE_FLAG_PER_DATASET ? "1" : "0";489}490if (show_str == NULL)491return (0);492493return (snprintf(buf, PAGE_SIZE, "%s\n", show_str));494}495496static void497pool_feature_to_kobj(zfs_mod_kobj_t *parent, spa_feature_t fid,498const char *name)499{500zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[fid];501502ASSERT3U(fid, <, SPA_FEATURES);503ASSERT(name);504505int err = zfs_kobj_init(zfs_kobj, ZPOOL_FEATURE_ATTR_COUNT, 0,506pool_feature_show);507if (err)508return;509510for (int i = 0; i < ZPOOL_FEATURE_ATTR_COUNT; i++)511zfs_kobj_add_attr(zfs_kobj, i, pool_feature_attrs[i]);512513err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);514if (err)515zfs_kobj_release(&zfs_kobj->zko_kobj);516}517518static int519zfs_pool_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)520{521/*522* Create a parent kobject to host pool features.523*524* '/sys/module/zfs/features.pool'525*/526int err = zfs_kobj_init(zfs_kobj, 0, SPA_FEATURES, pool_feature_show);527if (err)528return (err);529err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_POOL_FEATURES);530if (err) {531zfs_kobj_release(&zfs_kobj->zko_kobj);532return (err);533}534535/*536* Now create a kobject for each feature.537*538* '/sys/module/zfs/features.pool/<feature>'539*/540for (spa_feature_t i = 0; i < SPA_FEATURES; i++)541pool_feature_to_kobj(zfs_kobj, i, spa_feature_table[i].fi_guid);542543return (0);544}545546typedef struct prop_to_kobj_arg {547zprop_desc_t *p2k_table;548zfs_mod_kobj_t *p2k_parent;549sysfs_show_func p2k_show_func;550int p2k_attr_count;551} prop_to_kobj_arg_t;552553static int554zprop_to_kobj(int prop, void *args)555{556prop_to_kobj_arg_t *data = args;557zfs_mod_kobj_t *parent = data->p2k_parent;558zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[prop];559const char *name = data->p2k_table[prop].pd_name;560int err;561562ASSERT(name);563564err = zfs_kobj_init(zfs_kobj, data->p2k_attr_count, 0,565data->p2k_show_func);566if (err)567return (ZPROP_CONT);568569for (int i = 0; i < data->p2k_attr_count; i++)570zfs_kobj_add_attr(zfs_kobj, i, zprop_attrs[i]);571572err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);573if (err)574zfs_kobj_release(&zfs_kobj->zko_kobj);575576return (ZPROP_CONT);577}578579static int580zfs_sysfs_properties_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent,581zfs_type_t type)582{583prop_to_kobj_arg_t context;584const char *name;585int err;586587/*588* Create a parent kobject to host properties.589*590* '/sys/module/zfs/properties.<type>'591*/592if (type == ZFS_TYPE_POOL) {593name = ZFS_SYSFS_POOL_PROPERTIES;594context.p2k_table = zpool_prop_get_table();595context.p2k_attr_count = ZPOOL_PROP_ATTR_COUNT;596context.p2k_parent = zfs_kobj;597context.p2k_show_func = pool_property_show;598err = zfs_kobj_init(zfs_kobj, 0, ZPOOL_NUM_PROPS,599pool_property_show);600} else if (type == ZFS_TYPE_VDEV) {601name = ZFS_SYSFS_VDEV_PROPERTIES;602context.p2k_table = vdev_prop_get_table();603context.p2k_attr_count = ZPOOL_PROP_ATTR_COUNT;604context.p2k_parent = zfs_kobj;605context.p2k_show_func = vdev_property_show;606err = zfs_kobj_init(zfs_kobj, 0, VDEV_NUM_PROPS,607vdev_property_show);608} else {609name = ZFS_SYSFS_DATASET_PROPERTIES;610context.p2k_table = zfs_prop_get_table();611context.p2k_attr_count = ZFS_PROP_ATTR_COUNT;612context.p2k_parent = zfs_kobj;613context.p2k_show_func = dataset_property_show;614err = zfs_kobj_init(zfs_kobj, 0, ZFS_NUM_PROPS,615dataset_property_show);616}617618if (err)619return (err);620621err = zfs_kobj_add(zfs_kobj, parent, name);622if (err) {623zfs_kobj_release(&zfs_kobj->zko_kobj);624return (err);625}626627/*628* Create a kobject for each property.629*630* '/sys/module/zfs/properties.<type>/<property>'631*/632(void) zprop_iter_common(zprop_to_kobj, &context, B_TRUE,633B_FALSE, type);634635return (err);636}637638void639zfs_sysfs_init(void)640{641struct kobject *parent;642#if defined(CONFIG_ZFS) && !defined(CONFIG_ZFS_MODULE)643parent = kobject_create_and_add("zfs", fs_kobj);644#else645parent = &(((struct module *)(THIS_MODULE))->mkobj).kobj;646#endif647int err;648649if (parent == NULL)650return;651652err = zfs_kernel_features_init(&kernel_features_kobj, parent);653if (err)654return;655656err = zfs_pool_features_init(&pool_features_kobj, parent);657if (err) {658zfs_kobj_fini(&kernel_features_kobj);659return;660}661662err = zfs_sysfs_properties_init(&pool_props_kobj, parent,663ZFS_TYPE_POOL);664if (err) {665zfs_kobj_fini(&kernel_features_kobj);666zfs_kobj_fini(&pool_features_kobj);667return;668}669670err = zfs_sysfs_properties_init(&vdev_props_kobj, parent,671ZFS_TYPE_VDEV);672if (err) {673zfs_kobj_fini(&kernel_features_kobj);674zfs_kobj_fini(&pool_features_kobj);675zfs_kobj_fini(&pool_props_kobj);676return;677}678679err = zfs_sysfs_properties_init(&dataset_props_kobj, parent,680ZFS_TYPE_FILESYSTEM);681if (err) {682zfs_kobj_fini(&kernel_features_kobj);683zfs_kobj_fini(&pool_features_kobj);684zfs_kobj_fini(&pool_props_kobj);685zfs_kobj_fini(&vdev_props_kobj);686return;687}688}689690void691zfs_sysfs_fini(void)692{693/*694* Remove top-level kobjects; each will remove any children kobjects695*/696zfs_kobj_fini(&kernel_features_kobj);697zfs_kobj_fini(&pool_features_kobj);698zfs_kobj_fini(&pool_props_kobj);699zfs_kobj_fini(&vdev_props_kobj);700zfs_kobj_fini(&dataset_props_kobj);701}702703704