]> git.proxmox.com Git - mirror_zfs.git/blame - module/zfs/zfs_sysfs.c
features.kernel layout should match features.pool
[mirror_zfs.git] / module / zfs / zfs_sysfs.c
CommitLineData
e8bcb693
DB
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * 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.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
b4ddec7a 22 * Copyright (c) 2018, 2019 by Delphix. All rights reserved.
e8bcb693
DB
23 */
24
25#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>
33
34#include "zfs_prop.h"
35
36#if !defined(_KERNEL)
37#error kernel builds only
38#endif
39
40/*
41 * ZFS Module sysfs support
42 *
43 * This extends our sysfs '/sys/module/zfs' entry to include feature
44 * and property attributes. The primary consumer of this information
45 * is user processes, like the zfs CLI, that need to know what the
46 * current loaded ZFS module supports. The libzfs binary will consult
47 * this information when instantiating the zfs|zpool property tables
48 * and the pool features table.
49 *
50 * The added top-level directories are:
51 * /sys/module/zfs
52 * ├── features.kernel
53 * ├── features.pool
54 * ├── properties.dataset
55 * └── properties.pool
56 *
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 */
64
65/*
66 * A zfs_mod_kobj_t represents a zfs kobject under '/sys/module/zfs'
67 */
68struct zfs_mod_kobj;
69typedef struct zfs_mod_kobj zfs_mod_kobj_t;
70
71struct zfs_mod_kobj {
72 struct kobject zko_kobj;
73 struct kobj_type zko_kobj_type;
74 struct sysfs_ops zko_sysfs_ops;
75 size_t zko_attr_count;
76 struct attribute *zko_attr_list; /* allocated */
77 struct attribute **zko_default_attrs; /* allocated */
78 size_t zko_child_count;
79 zfs_mod_kobj_t *zko_children; /* allocated */
80};
81
82#define ATTR_TABLE_SIZE(cnt) (sizeof (struct attribute) * (cnt))
83/* Note +1 for NULL terminator slot */
84#define DEFAULT_ATTR_SIZE(cnt) (sizeof (struct attribute *) * (cnt + 1))
85#define CHILD_TABLE_SIZE(cnt) (sizeof (zfs_mod_kobj_t) * (cnt))
86
87/*
88 * These are the top-level kobjects under '/sys/module/zfs/'
89 */
90static zfs_mod_kobj_t kernel_features_kobj;
91static zfs_mod_kobj_t pool_features_kobj;
92static zfs_mod_kobj_t dataset_props_kobj;
93static zfs_mod_kobj_t pool_props_kobj;
94
95/*
96 * The show function is used to provide the content
97 * of an attribute into a PAGE_SIZE buffer.
98 */
99typedef ssize_t (*sysfs_show_func)(struct kobject *, struct attribute *,
100 char *);
101
102static void
103zfs_kobj_fini(zfs_mod_kobj_t *zkobj)
104{
7a23c813 105 /* finalize any child kobjects */
e8bcb693
DB
106 if (zkobj->zko_child_count != 0) {
107 ASSERT(zkobj->zko_children);
108 for (int i = 0; i < zkobj->zko_child_count; i++)
109 zfs_kobj_fini(&zkobj->zko_children[i]);
110 }
111
112 /* kobject_put() will call zfs_kobj_release() to release memory */
113 kobject_del(&zkobj->zko_kobj);
114 kobject_put(&zkobj->zko_kobj);
115}
116
117static void
118zfs_kobj_release(struct kobject *kobj)
119{
120 zfs_mod_kobj_t *zkobj = container_of(kobj, zfs_mod_kobj_t, zko_kobj);
121
7a23c813
BB
122 if (zkobj->zko_attr_list != NULL) {
123 ASSERT3S(zkobj->zko_attr_count, !=, 0);
e8bcb693
DB
124 kmem_free(zkobj->zko_attr_list,
125 ATTR_TABLE_SIZE(zkobj->zko_attr_count));
7a23c813
BB
126 zkobj->zko_attr_list = NULL;
127 }
128
129 if (zkobj->zko_default_attrs != NULL) {
e8bcb693
DB
130 kmem_free(zkobj->zko_default_attrs,
131 DEFAULT_ATTR_SIZE(zkobj->zko_attr_count));
e8bcb693
DB
132 zkobj->zko_default_attrs = NULL;
133 }
7a23c813 134
e8bcb693
DB
135 if (zkobj->zko_child_count != 0) {
136 ASSERT(zkobj->zko_children);
137
138 kmem_free(zkobj->zko_children,
139 CHILD_TABLE_SIZE(zkobj->zko_child_count));
140 zkobj->zko_child_count = 0;
141 zkobj->zko_children = NULL;
142 }
7a23c813
BB
143
144 zkobj->zko_attr_count = 0;
e8bcb693
DB
145}
146
147static void
148zfs_kobj_add_attr(zfs_mod_kobj_t *zkobj, int attr_num, const char *attr_name)
149{
150 VERIFY3U(attr_num, <, zkobj->zko_attr_count);
151 ASSERT(zkobj->zko_attr_list);
152 ASSERT(zkobj->zko_default_attrs);
153
154 zkobj->zko_attr_list[attr_num].name = attr_name;
155 zkobj->zko_attr_list[attr_num].mode = 0444;
156 zkobj->zko_default_attrs[attr_num] = &zkobj->zko_attr_list[attr_num];
157}
158
159static int
160zfs_kobj_init(zfs_mod_kobj_t *zkobj, int attr_cnt, int child_cnt,
161 sysfs_show_func show_func)
162{
163 /*
164 * Initialize object's attributes. Count can be zero.
165 */
166 if (attr_cnt > 0) {
167 zkobj->zko_attr_list = kmem_zalloc(ATTR_TABLE_SIZE(attr_cnt),
168 KM_SLEEP);
169 if (zkobj->zko_attr_list == NULL)
170 return (ENOMEM);
171 }
172 /* this will always have at least one slot for NULL termination */
173 zkobj->zko_default_attrs = kmem_zalloc(DEFAULT_ATTR_SIZE(attr_cnt),
174 KM_SLEEP);
175 if (zkobj->zko_default_attrs == NULL) {
7a23c813 176 if (zkobj->zko_attr_list != NULL) {
e8bcb693
DB
177 kmem_free(zkobj->zko_attr_list,
178 ATTR_TABLE_SIZE(attr_cnt));
179 }
180 return (ENOMEM);
181 }
182 zkobj->zko_attr_count = attr_cnt;
183 zkobj->zko_kobj_type.default_attrs = zkobj->zko_default_attrs;
184
185 if (child_cnt > 0) {
186 zkobj->zko_children = kmem_zalloc(CHILD_TABLE_SIZE(child_cnt),
187 KM_SLEEP);
188 if (zkobj->zko_children == NULL) {
7a23c813
BB
189 if (zkobj->zko_default_attrs != NULL) {
190 kmem_free(zkobj->zko_default_attrs,
e8bcb693 191 DEFAULT_ATTR_SIZE(attr_cnt));
7a23c813
BB
192 }
193 if (zkobj->zko_attr_list != NULL) {
e8bcb693
DB
194 kmem_free(zkobj->zko_attr_list,
195 ATTR_TABLE_SIZE(attr_cnt));
196 }
197 return (ENOMEM);
198 }
199 zkobj->zko_child_count = child_cnt;
200 }
201
202 zkobj->zko_sysfs_ops.show = show_func;
203 zkobj->zko_kobj_type.sysfs_ops = &zkobj->zko_sysfs_ops;
204 zkobj->zko_kobj_type.release = zfs_kobj_release;
205
206 return (0);
207}
208
209static int
210zfs_kobj_add(zfs_mod_kobj_t *zkobj, struct kobject *parent, const char *name)
211{
212 /* zko_default_attrs must be NULL terminated */
213 ASSERT(zkobj->zko_default_attrs != NULL);
214 ASSERT(zkobj->zko_default_attrs[zkobj->zko_attr_count] == NULL);
215
216 kobject_init(&zkobj->zko_kobj, &zkobj->zko_kobj_type);
217 return (kobject_add(&zkobj->zko_kobj, parent, name));
218}
219
220/*
221 * Each zfs property has these common attributes
222 */
223static const char *zprop_attrs[] = {
224 "type",
225 "readonly",
226 "setonce",
227 "visible",
228 "values",
229 "default",
230 "datasets" /* zfs properties only */
231};
232
233#define ZFS_PROP_ATTR_COUNT ARRAY_SIZE(zprop_attrs)
234#define ZPOOL_PROP_ATTR_COUNT (ZFS_PROP_ATTR_COUNT - 1)
235
236static const char *zprop_types[] = {
237 "number",
238 "string",
239 "index",
240};
241
242typedef struct zfs_type_map {
243 zfs_type_t ztm_type;
244 const char *ztm_name;
245} zfs_type_map_t;
246
247static zfs_type_map_t type_map[] = {
248 {ZFS_TYPE_FILESYSTEM, "filesystem"},
249 {ZFS_TYPE_SNAPSHOT, "snapshot"},
250 {ZFS_TYPE_VOLUME, "volume"},
251 {ZFS_TYPE_BOOKMARK, "bookmark"}
252};
253
254/*
255 * Show the content for a zfs property attribute
256 */
257static ssize_t
258zprop_sysfs_show(const char *attr_name, const zprop_desc_t *property,
259 char *buf, size_t buflen)
260{
261 const char *show_str;
262
263 /* For dataset properties list the dataset types that apply */
264 if (strcmp(attr_name, "datasets") == 0 &&
265 property->pd_types != ZFS_TYPE_POOL) {
266 int len = 0;
267
268 for (int i = 0; i < ARRAY_SIZE(type_map); i++) {
269 if (type_map[i].ztm_type & property->pd_types) {
270 len += snprintf(buf + len, buflen - len, "%s ",
271 type_map[i].ztm_name);
272 }
273 }
274 len += snprintf(buf + len, buflen - len, "\n");
275 return (len);
276 }
277
278 if (strcmp(attr_name, "type") == 0) {
279 show_str = zprop_types[property->pd_proptype];
280 } else if (strcmp(attr_name, "readonly") == 0) {
281 show_str = property->pd_attr == PROP_READONLY ? "1" : "0";
282 } else if (strcmp(attr_name, "setonce") == 0) {
283 show_str = property->pd_attr == PROP_ONETIME ? "1" : "0";
284 } else if (strcmp(attr_name, "visible") == 0) {
285 show_str = property->pd_visible ? "1" : "0";
286 } else if (strcmp(attr_name, "values") == 0) {
287 show_str = property->pd_values ? property->pd_values : "";
288 } else if (strcmp(attr_name, "default") == 0) {
289 char number[32];
290
291 switch (property->pd_proptype) {
292 case PROP_TYPE_NUMBER:
293 (void) snprintf(number, sizeof (number), "%llu",
294 (u_longlong_t)property->pd_numdefault);
295 show_str = number;
296 break;
297 case PROP_TYPE_STRING:
298 show_str = property->pd_strdefault ?
299 property->pd_strdefault : "";
300 break;
301 case PROP_TYPE_INDEX:
e7b677aa 302 if (zprop_index_to_string(property->pd_propnum,
e8bcb693 303 property->pd_numdefault, &show_str,
e7b677aa
DB
304 property->pd_types) != 0) {
305 show_str = "";
306 }
e8bcb693
DB
307 break;
308 default:
309 return (0);
310 }
311 } else {
312 return (0);
313 }
314
315 return (snprintf(buf, buflen, "%s\n", show_str));
316}
317
318static ssize_t
319dataset_property_show(struct kobject *kobj, struct attribute *attr, char *buf)
320{
321 zfs_prop_t prop = zfs_name_to_prop(kobject_name(kobj));
322 zprop_desc_t *prop_tbl = zfs_prop_get_table();
323 ssize_t len;
324
325 ASSERT3U(prop, <, ZFS_NUM_PROPS);
326
327 len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);
328
329 return (len);
330}
331
332static ssize_t
333pool_property_show(struct kobject *kobj, struct attribute *attr, char *buf)
334{
335 zpool_prop_t prop = zpool_name_to_prop(kobject_name(kobj));
336 zprop_desc_t *prop_tbl = zpool_prop_get_table();
337 ssize_t len;
338
339 ASSERT3U(prop, <, ZPOOL_NUM_PROPS);
340
341 len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);
342
343 return (len);
344}
345
346/*
347 * ZFS kernel feature attributes for '/sys/module/zfs/features.kernel'
348 *
349 * This list is intended for kernel features that don't have a pool feature
350 * association or that extend existing user kernel interfaces.
351 *
352 * A user processes can easily check if the running zfs kernel module
353 * supports the new feature.
e8bcb693 354 */
b4ddec7a
DB
355static const char *zfs_kernel_features[] = {
356 /* --> Add new kernel features here */
357 "com.delphix:vdev_initialize",
358 "org.zfsonlinux:vdev_trim",
e8bcb693
DB
359};
360
b4ddec7a 361#define KERNEL_FEATURE_COUNT ARRAY_SIZE(zfs_kernel_features)
e8bcb693
DB
362
363static ssize_t
364kernel_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)
365{
b4ddec7a
DB
366 if (strcmp(attr->name, "supported") == 0)
367 return (snprintf(buf, PAGE_SIZE, "yes\n"));
368 return (0);
369}
370
371static void
372kernel_feature_to_kobj(zfs_mod_kobj_t *parent, int slot, const char *name)
373{
374 zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[slot];
375
376 ASSERT3U(slot, <, KERNEL_FEATURE_COUNT);
377 ASSERT(name);
378
379 int err = zfs_kobj_init(zfs_kobj, 1, 0, kernel_feature_show);
380 if (err)
381 return;
382
383 zfs_kobj_add_attr(zfs_kobj, 0, "supported");
384
385 err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
386 if (err)
387 zfs_kobj_release(&zfs_kobj->zko_kobj);
e8bcb693
DB
388}
389
390static int
391zfs_kernel_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)
392{
b4ddec7a
DB
393 /*
394 * Create a parent kobject to host kernel features.
395 *
396 * '/sys/module/zfs/features.kernel'
397 */
398 int err = zfs_kobj_init(zfs_kobj, 0, KERNEL_FEATURE_COUNT,
e8bcb693
DB
399 kernel_feature_show);
400 if (err)
401 return (err);
e8bcb693 402 err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_KERNEL_FEATURES);
b4ddec7a 403 if (err) {
e8bcb693 404 zfs_kobj_release(&zfs_kobj->zko_kobj);
b4ddec7a
DB
405 return (err);
406 }
407
408 /*
409 * Now create a kobject for each feature.
410 *
411 * '/sys/module/zfs/features.kernel/<feature>'
412 */
413 for (int f = 0; f < KERNEL_FEATURE_COUNT; f++)
414 kernel_feature_to_kobj(zfs_kobj, f, zfs_kernel_features[f]);
e8bcb693 415
b4ddec7a 416 return (0);
e8bcb693
DB
417}
418
419/*
420 * Each pool feature has these common attributes
421 */
422static const char *pool_feature_attrs[] = {
423 "description",
424 "guid",
425 "uname",
426 "readonly_compatible",
427 "required_for_mos",
428 "activate_on_enable",
429 "per_dataset"
430};
431
432#define ZPOOL_FEATURE_ATTR_COUNT ARRAY_SIZE(pool_feature_attrs)
433
434/*
435 * Show the content for the given zfs pool feature attribute
436 */
437static ssize_t
438pool_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)
439{
440 spa_feature_t fid;
441
442 if (zfeature_lookup_guid(kobject_name(kobj), &fid) != 0)
443 return (0);
444
445 ASSERT3U(fid, <, SPA_FEATURES);
446
447 zfeature_flags_t flags = spa_feature_table[fid].fi_flags;
448 const char *show_str = NULL;
449
450 if (strcmp(attr->name, "description") == 0) {
451 show_str = spa_feature_table[fid].fi_desc;
452 } else if (strcmp(attr->name, "guid") == 0) {
453 show_str = spa_feature_table[fid].fi_guid;
454 } else if (strcmp(attr->name, "uname") == 0) {
455 show_str = spa_feature_table[fid].fi_uname;
456 } else if (strcmp(attr->name, "readonly_compatible") == 0) {
457 show_str = flags & ZFEATURE_FLAG_READONLY_COMPAT ? "1" : "0";
458 } else if (strcmp(attr->name, "required_for_mos") == 0) {
459 show_str = flags & ZFEATURE_FLAG_MOS ? "1" : "0";
460 } else if (strcmp(attr->name, "activate_on_enable") == 0) {
461 show_str = flags & ZFEATURE_FLAG_ACTIVATE_ON_ENABLE ? "1" : "0";
462 } else if (strcmp(attr->name, "per_dataset") == 0) {
463 show_str = flags & ZFEATURE_FLAG_PER_DATASET ? "1" : "0";
464 }
465 if (show_str == NULL)
466 return (0);
467
468 return (snprintf(buf, PAGE_SIZE, "%s\n", show_str));
469}
470
471static void
472pool_feature_to_kobj(zfs_mod_kobj_t *parent, spa_feature_t fid,
473 const char *name)
474{
475 zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[fid];
476
477 ASSERT3U(fid, <, SPA_FEATURES);
478 ASSERT(name);
479
480 int err = zfs_kobj_init(zfs_kobj, ZPOOL_FEATURE_ATTR_COUNT, 0,
481 pool_feature_show);
482 if (err)
483 return;
484
485 for (int i = 0; i < ZPOOL_FEATURE_ATTR_COUNT; i++)
486 zfs_kobj_add_attr(zfs_kobj, i, pool_feature_attrs[i]);
487
488 err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
489 if (err)
490 zfs_kobj_release(&zfs_kobj->zko_kobj);
491}
492
493static int
494zfs_pool_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)
495{
496 /*
497 * Create a parent kobject to host pool features.
498 *
499 * '/sys/module/zfs/features.pool'
500 */
501 int err = zfs_kobj_init(zfs_kobj, 0, SPA_FEATURES, pool_feature_show);
502 if (err)
503 return (err);
504 err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_POOL_FEATURES);
505 if (err) {
506 zfs_kobj_release(&zfs_kobj->zko_kobj);
507 return (err);
508 }
509
510 /*
511 * Now create a kobject for each feature.
512 *
513 * '/sys/module/zfs/features.pool/<feature>'
514 */
515 for (spa_feature_t i = 0; i < SPA_FEATURES; i++)
516 pool_feature_to_kobj(zfs_kobj, i, spa_feature_table[i].fi_guid);
517
518 return (0);
519}
520
521typedef struct prop_to_kobj_arg {
522 zprop_desc_t *p2k_table;
523 zfs_mod_kobj_t *p2k_parent;
524 sysfs_show_func p2k_show_func;
525 int p2k_attr_count;
526} prop_to_kobj_arg_t;
527
528static int
529zprop_to_kobj(int prop, void *args)
530{
531 prop_to_kobj_arg_t *data = args;
532 zfs_mod_kobj_t *parent = data->p2k_parent;
533 zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[prop];
534 const char *name = data->p2k_table[prop].pd_name;
535 int err;
536
537 ASSERT(name);
538
539 err = zfs_kobj_init(zfs_kobj, data->p2k_attr_count, 0,
540 data->p2k_show_func);
541 if (err)
542 return (ZPROP_CONT);
543
544 for (int i = 0; i < data->p2k_attr_count; i++)
545 zfs_kobj_add_attr(zfs_kobj, i, zprop_attrs[i]);
546
547 err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
548 if (err)
549 zfs_kobj_release(&zfs_kobj->zko_kobj);
550
551 return (ZPROP_CONT);
552}
553
554static int
555zfs_sysfs_properties_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent,
556 zfs_type_t type)
557{
558 prop_to_kobj_arg_t context;
559 const char *name;
560 int err;
561
562 /*
563 * Create a parent kobject to host properties.
564 *
565 * '/sys/module/zfs/properties.<type>'
566 */
567 if (type == ZFS_TYPE_POOL) {
568 name = ZFS_SYSFS_POOL_PROPERTIES;
569 context.p2k_table = zpool_prop_get_table();
570 context.p2k_attr_count = ZPOOL_PROP_ATTR_COUNT;
571 context.p2k_parent = zfs_kobj;
572 context.p2k_show_func = pool_property_show;
573 err = zfs_kobj_init(zfs_kobj, 0, ZPOOL_NUM_PROPS,
574 pool_property_show);
575 } else {
576 name = ZFS_SYSFS_DATASET_PROPERTIES;
577 context.p2k_table = zfs_prop_get_table();
578 context.p2k_attr_count = ZFS_PROP_ATTR_COUNT;
579 context.p2k_parent = zfs_kobj;
580 context.p2k_show_func = dataset_property_show;
581 err = zfs_kobj_init(zfs_kobj, 0, ZFS_NUM_PROPS,
582 dataset_property_show);
583 }
584
585 if (err)
586 return (err);
587
588 err = zfs_kobj_add(zfs_kobj, parent, name);
589 if (err) {
590 zfs_kobj_release(&zfs_kobj->zko_kobj);
591 return (err);
592 }
593
594 /*
595 * Create a kobject for each property.
596 *
597 * '/sys/module/zfs/properties.<type>/<property>'
598 */
599 (void) zprop_iter_common(zprop_to_kobj, &context, B_TRUE,
600 B_FALSE, type);
601
602 return (err);
603}
604
605void
606zfs_sysfs_init(void)
607{
73a5ec30
DB
608 struct kobject *parent;
609#if defined(CONFIG_ZFS) && !defined(CONFIG_ZFS_MODULE)
610 parent = kobject_create_and_add("zfs", fs_kobj);
611#else
612 parent = &(((struct module *)(THIS_MODULE))->mkobj).kobj;
613#endif
e8bcb693
DB
614 int err;
615
73a5ec30
DB
616 if (parent == NULL)
617 return;
e8bcb693
DB
618
619 err = zfs_kernel_features_init(&kernel_features_kobj, parent);
620 if (err)
621 return;
622
623 err = zfs_pool_features_init(&pool_features_kobj, parent);
624 if (err) {
625 zfs_kobj_fini(&kernel_features_kobj);
626 return;
627 }
628
629 err = zfs_sysfs_properties_init(&pool_props_kobj, parent,
630 ZFS_TYPE_POOL);
631 if (err) {
632 zfs_kobj_fini(&kernel_features_kobj);
633 zfs_kobj_fini(&pool_features_kobj);
634 return;
635 }
636
637 err = zfs_sysfs_properties_init(&dataset_props_kobj, parent,
638 ZFS_TYPE_FILESYSTEM);
639 if (err) {
640 zfs_kobj_fini(&kernel_features_kobj);
641 zfs_kobj_fini(&pool_features_kobj);
642 zfs_kobj_fini(&pool_props_kobj);
643 return;
644 }
645}
646
647void
648zfs_sysfs_fini(void)
649{
650 /*
651 * Remove top-level kobjects; each will remove any children kobjects
652 */
653 zfs_kobj_fini(&kernel_features_kobj);
654 zfs_kobj_fini(&pool_features_kobj);
655 zfs_kobj_fini(&dataset_props_kobj);
656 zfs_kobj_fini(&pool_props_kobj);
657}