]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/contrib/openzfs/module/os/linux/zfs/zfs_sysfs.c
ZFS: MFV 2.0-rc1-gfd20a8
[FreeBSD/FreeBSD.git] / sys / contrib / openzfs / module / os / linux / zfs / zfs_sysfs.c
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 /*
22  * Copyright (c) 2018, 2019 by Delphix. All rights reserved.
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  */
68 struct zfs_mod_kobj;
69 typedef struct zfs_mod_kobj zfs_mod_kobj_t;
70
71 struct 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  */
90 static zfs_mod_kobj_t kernel_features_kobj;
91 static zfs_mod_kobj_t pool_features_kobj;
92 static zfs_mod_kobj_t dataset_props_kobj;
93 static 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  */
99 typedef ssize_t (*sysfs_show_func)(struct kobject *, struct attribute *,
100     char *);
101
102 static void
103 zfs_kobj_fini(zfs_mod_kobj_t *zkobj)
104 {
105         /* finalize any child kobjects */
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
117 static void
118 zfs_kobj_release(struct kobject *kobj)
119 {
120         zfs_mod_kobj_t *zkobj = container_of(kobj, zfs_mod_kobj_t, zko_kobj);
121
122         if (zkobj->zko_attr_list != NULL) {
123                 ASSERT3S(zkobj->zko_attr_count, !=, 0);
124                 kmem_free(zkobj->zko_attr_list,
125                     ATTR_TABLE_SIZE(zkobj->zko_attr_count));
126                 zkobj->zko_attr_list = NULL;
127         }
128
129         if (zkobj->zko_default_attrs != NULL) {
130                 kmem_free(zkobj->zko_default_attrs,
131                     DEFAULT_ATTR_SIZE(zkobj->zko_attr_count));
132                 zkobj->zko_default_attrs = NULL;
133         }
134
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         }
143
144         zkobj->zko_attr_count = 0;
145 }
146
147 #ifndef sysfs_attr_init
148 #define sysfs_attr_init(attr) do {} while (0)
149 #endif
150
151 static void
152 zfs_kobj_add_attr(zfs_mod_kobj_t *zkobj, int attr_num, const char *attr_name)
153 {
154         VERIFY3U(attr_num, <, zkobj->zko_attr_count);
155         ASSERT(zkobj->zko_attr_list);
156         ASSERT(zkobj->zko_default_attrs);
157
158         zkobj->zko_attr_list[attr_num].name = attr_name;
159         zkobj->zko_attr_list[attr_num].mode = 0444;
160         zkobj->zko_default_attrs[attr_num] = &zkobj->zko_attr_list[attr_num];
161         sysfs_attr_init(&zkobj->zko_attr_list[attr_num]);
162 }
163
164 static int
165 zfs_kobj_init(zfs_mod_kobj_t *zkobj, int attr_cnt, int child_cnt,
166     sysfs_show_func show_func)
167 {
168         /*
169          * Initialize object's attributes. Count can be zero.
170          */
171         if (attr_cnt > 0) {
172                 zkobj->zko_attr_list = kmem_zalloc(ATTR_TABLE_SIZE(attr_cnt),
173                     KM_SLEEP);
174                 if (zkobj->zko_attr_list == NULL)
175                         return (ENOMEM);
176         }
177         /* this will always have at least one slot for NULL termination */
178         zkobj->zko_default_attrs = kmem_zalloc(DEFAULT_ATTR_SIZE(attr_cnt),
179             KM_SLEEP);
180         if (zkobj->zko_default_attrs == NULL) {
181                 if (zkobj->zko_attr_list != NULL) {
182                         kmem_free(zkobj->zko_attr_list,
183                             ATTR_TABLE_SIZE(attr_cnt));
184                 }
185                 return (ENOMEM);
186         }
187         zkobj->zko_attr_count = attr_cnt;
188         zkobj->zko_kobj_type.default_attrs = zkobj->zko_default_attrs;
189
190         if (child_cnt > 0) {
191                 zkobj->zko_children = kmem_zalloc(CHILD_TABLE_SIZE(child_cnt),
192                     KM_SLEEP);
193                 if (zkobj->zko_children == NULL) {
194                         if (zkobj->zko_default_attrs != NULL) {
195                                 kmem_free(zkobj->zko_default_attrs,
196                                     DEFAULT_ATTR_SIZE(attr_cnt));
197                         }
198                         if (zkobj->zko_attr_list != NULL) {
199                                 kmem_free(zkobj->zko_attr_list,
200                                     ATTR_TABLE_SIZE(attr_cnt));
201                         }
202                         return (ENOMEM);
203                 }
204                 zkobj->zko_child_count = child_cnt;
205         }
206
207         zkobj->zko_sysfs_ops.show = show_func;
208         zkobj->zko_kobj_type.sysfs_ops = &zkobj->zko_sysfs_ops;
209         zkobj->zko_kobj_type.release = zfs_kobj_release;
210
211         return (0);
212 }
213
214 static int
215 zfs_kobj_add(zfs_mod_kobj_t *zkobj, struct kobject *parent, const char *name)
216 {
217         /* zko_default_attrs must be NULL terminated */
218         ASSERT(zkobj->zko_default_attrs != NULL);
219         ASSERT(zkobj->zko_default_attrs[zkobj->zko_attr_count] == NULL);
220
221         kobject_init(&zkobj->zko_kobj, &zkobj->zko_kobj_type);
222         return (kobject_add(&zkobj->zko_kobj, parent, name));
223 }
224
225 /*
226  * Each zfs property has these common attributes
227  */
228 static const char *zprop_attrs[]  = {
229         "type",
230         "readonly",
231         "setonce",
232         "visible",
233         "values",
234         "default",
235         "datasets"      /* zfs properties only */
236 };
237
238 #define ZFS_PROP_ATTR_COUNT     ARRAY_SIZE(zprop_attrs)
239 #define ZPOOL_PROP_ATTR_COUNT   (ZFS_PROP_ATTR_COUNT - 1)
240
241 static const char *zprop_types[]  = {
242         "number",
243         "string",
244         "index",
245 };
246
247 typedef struct zfs_type_map {
248         zfs_type_t      ztm_type;
249         const char      *ztm_name;
250 } zfs_type_map_t;
251
252 static zfs_type_map_t type_map[] = {
253         {ZFS_TYPE_FILESYSTEM,   "filesystem"},
254         {ZFS_TYPE_SNAPSHOT,     "snapshot"},
255         {ZFS_TYPE_VOLUME,       "volume"},
256         {ZFS_TYPE_BOOKMARK,     "bookmark"}
257 };
258
259 /*
260  * Show the content for a zfs property attribute
261  */
262 static ssize_t
263 zprop_sysfs_show(const char *attr_name, const zprop_desc_t *property,
264     char *buf, size_t buflen)
265 {
266         const char *show_str;
267         char number[32];
268
269         /* For dataset properties list the dataset types that apply */
270         if (strcmp(attr_name, "datasets") == 0 &&
271             property->pd_types != ZFS_TYPE_POOL) {
272                 int len = 0;
273
274                 for (int i = 0; i < ARRAY_SIZE(type_map); i++) {
275                         if (type_map[i].ztm_type & property->pd_types)  {
276                                 len += snprintf(buf + len, buflen - len, "%s ",
277                                     type_map[i].ztm_name);
278                         }
279                 }
280                 len += snprintf(buf + len, buflen - len, "\n");
281                 return (len);
282         }
283
284         if (strcmp(attr_name, "type") == 0) {
285                 show_str = zprop_types[property->pd_proptype];
286         } else if (strcmp(attr_name, "readonly") == 0) {
287                 show_str = property->pd_attr == PROP_READONLY ? "1" : "0";
288         } else if (strcmp(attr_name, "setonce") == 0) {
289                 show_str = property->pd_attr == PROP_ONETIME ? "1" : "0";
290         } else if (strcmp(attr_name, "visible") == 0) {
291                 show_str = property->pd_visible ? "1" : "0";
292         } else if (strcmp(attr_name, "values") == 0) {
293                 show_str = property->pd_values ? property->pd_values : "";
294         } else if (strcmp(attr_name, "default") == 0) {
295                 switch (property->pd_proptype) {
296                 case PROP_TYPE_NUMBER:
297                         (void) snprintf(number, sizeof (number), "%llu",
298                             (u_longlong_t)property->pd_numdefault);
299                         show_str = number;
300                         break;
301                 case PROP_TYPE_STRING:
302                         show_str = property->pd_strdefault ?
303                             property->pd_strdefault : "";
304                         break;
305                 case PROP_TYPE_INDEX:
306                         if (zprop_index_to_string(property->pd_propnum,
307                             property->pd_numdefault, &show_str,
308                             property->pd_types) != 0) {
309                                 show_str = "";
310                         }
311                         break;
312                 default:
313                         return (0);
314                 }
315         } else {
316                 return (0);
317         }
318
319         return (snprintf(buf, buflen, "%s\n", show_str));
320 }
321
322 static ssize_t
323 dataset_property_show(struct kobject *kobj, struct attribute *attr, char *buf)
324 {
325         zfs_prop_t prop = zfs_name_to_prop(kobject_name(kobj));
326         zprop_desc_t *prop_tbl = zfs_prop_get_table();
327         ssize_t len;
328
329         ASSERT3U(prop, <, ZFS_NUM_PROPS);
330
331         len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);
332
333         return (len);
334 }
335
336 static ssize_t
337 pool_property_show(struct kobject *kobj, struct attribute *attr, char *buf)
338 {
339         zpool_prop_t prop = zpool_name_to_prop(kobject_name(kobj));
340         zprop_desc_t *prop_tbl = zpool_prop_get_table();
341         ssize_t len;
342
343         ASSERT3U(prop, <, ZPOOL_NUM_PROPS);
344
345         len = zprop_sysfs_show(attr->name, &prop_tbl[prop], buf, PAGE_SIZE);
346
347         return (len);
348 }
349
350 /*
351  * ZFS kernel feature attributes for '/sys/module/zfs/features.kernel'
352  *
353  * This list is intended for kernel features that don't have a pool feature
354  * association or that extend existing user kernel interfaces.
355  *
356  * A user process can easily check if the running zfs kernel module
357  * supports the new feature.
358  */
359 static const char *zfs_kernel_features[] = {
360         /* --> Add new kernel features here */
361         "com.delphix:vdev_initialize",
362         "org.zfsonlinux:vdev_trim",
363         "org.openzfs:l2arc_persistent",
364 };
365
366 #define KERNEL_FEATURE_COUNT    ARRAY_SIZE(zfs_kernel_features)
367
368 static ssize_t
369 kernel_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)
370 {
371         if (strcmp(attr->name, "supported") == 0)
372                 return (snprintf(buf, PAGE_SIZE, "yes\n"));
373         return (0);
374 }
375
376 static void
377 kernel_feature_to_kobj(zfs_mod_kobj_t *parent, int slot, const char *name)
378 {
379         zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[slot];
380
381         ASSERT3U(slot, <, KERNEL_FEATURE_COUNT);
382         ASSERT(name);
383
384         int err = zfs_kobj_init(zfs_kobj, 1, 0, kernel_feature_show);
385         if (err)
386                 return;
387
388         zfs_kobj_add_attr(zfs_kobj, 0, "supported");
389
390         err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
391         if (err)
392                 zfs_kobj_release(&zfs_kobj->zko_kobj);
393 }
394
395 static int
396 zfs_kernel_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)
397 {
398         /*
399          * Create a parent kobject to host kernel features.
400          *
401          * '/sys/module/zfs/features.kernel'
402          */
403         int err = zfs_kobj_init(zfs_kobj, 0, KERNEL_FEATURE_COUNT,
404             kernel_feature_show);
405         if (err)
406                 return (err);
407         err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_KERNEL_FEATURES);
408         if (err) {
409                 zfs_kobj_release(&zfs_kobj->zko_kobj);
410                 return (err);
411         }
412
413         /*
414          * Now create a kobject for each feature.
415          *
416          * '/sys/module/zfs/features.kernel/<feature>'
417          */
418         for (int f = 0; f < KERNEL_FEATURE_COUNT; f++)
419                 kernel_feature_to_kobj(zfs_kobj, f, zfs_kernel_features[f]);
420
421         return (0);
422 }
423
424 /*
425  * Each pool feature has these common attributes
426  */
427 static const char *pool_feature_attrs[]  = {
428         "description",
429         "guid",
430         "uname",
431         "readonly_compatible",
432         "required_for_mos",
433         "activate_on_enable",
434         "per_dataset"
435 };
436
437 #define ZPOOL_FEATURE_ATTR_COUNT        ARRAY_SIZE(pool_feature_attrs)
438
439 /*
440  * Show the content for the given zfs pool feature attribute
441  */
442 static ssize_t
443 pool_feature_show(struct kobject *kobj, struct attribute *attr, char *buf)
444 {
445         spa_feature_t fid;
446
447         if (zfeature_lookup_guid(kobject_name(kobj), &fid) != 0)
448                 return (0);
449
450         ASSERT3U(fid, <, SPA_FEATURES);
451
452         zfeature_flags_t flags = spa_feature_table[fid].fi_flags;
453         const char *show_str = NULL;
454
455         if (strcmp(attr->name, "description") == 0) {
456                 show_str = spa_feature_table[fid].fi_desc;
457         } else if (strcmp(attr->name, "guid") == 0) {
458                 show_str = spa_feature_table[fid].fi_guid;
459         } else if (strcmp(attr->name, "uname") == 0) {
460                 show_str = spa_feature_table[fid].fi_uname;
461         } else if (strcmp(attr->name, "readonly_compatible") == 0) {
462                 show_str = flags & ZFEATURE_FLAG_READONLY_COMPAT ? "1" : "0";
463         } else if (strcmp(attr->name, "required_for_mos") == 0) {
464                 show_str = flags & ZFEATURE_FLAG_MOS ? "1" : "0";
465         } else if (strcmp(attr->name, "activate_on_enable") == 0) {
466                 show_str = flags & ZFEATURE_FLAG_ACTIVATE_ON_ENABLE ? "1" : "0";
467         } else if (strcmp(attr->name, "per_dataset") == 0) {
468                 show_str = flags & ZFEATURE_FLAG_PER_DATASET ? "1" : "0";
469         }
470         if (show_str == NULL)
471                 return (0);
472
473         return (snprintf(buf, PAGE_SIZE, "%s\n", show_str));
474 }
475
476 static void
477 pool_feature_to_kobj(zfs_mod_kobj_t *parent, spa_feature_t fid,
478     const char *name)
479 {
480         zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[fid];
481
482         ASSERT3U(fid, <, SPA_FEATURES);
483         ASSERT(name);
484
485         int err = zfs_kobj_init(zfs_kobj, ZPOOL_FEATURE_ATTR_COUNT, 0,
486             pool_feature_show);
487         if (err)
488                 return;
489
490         for (int i = 0; i < ZPOOL_FEATURE_ATTR_COUNT; i++)
491                 zfs_kobj_add_attr(zfs_kobj, i, pool_feature_attrs[i]);
492
493         err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
494         if (err)
495                 zfs_kobj_release(&zfs_kobj->zko_kobj);
496 }
497
498 static int
499 zfs_pool_features_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent)
500 {
501         /*
502          * Create a parent kobject to host pool features.
503          *
504          * '/sys/module/zfs/features.pool'
505          */
506         int err = zfs_kobj_init(zfs_kobj, 0, SPA_FEATURES, pool_feature_show);
507         if (err)
508                 return (err);
509         err = zfs_kobj_add(zfs_kobj, parent, ZFS_SYSFS_POOL_FEATURES);
510         if (err) {
511                 zfs_kobj_release(&zfs_kobj->zko_kobj);
512                 return (err);
513         }
514
515         /*
516          * Now create a kobject for each feature.
517          *
518          * '/sys/module/zfs/features.pool/<feature>'
519          */
520         for (spa_feature_t i = 0; i < SPA_FEATURES; i++)
521                 pool_feature_to_kobj(zfs_kobj, i, spa_feature_table[i].fi_guid);
522
523         return (0);
524 }
525
526 typedef struct prop_to_kobj_arg {
527         zprop_desc_t    *p2k_table;
528         zfs_mod_kobj_t  *p2k_parent;
529         sysfs_show_func p2k_show_func;
530         int             p2k_attr_count;
531 } prop_to_kobj_arg_t;
532
533 static int
534 zprop_to_kobj(int prop, void *args)
535 {
536         prop_to_kobj_arg_t *data = args;
537         zfs_mod_kobj_t *parent = data->p2k_parent;
538         zfs_mod_kobj_t *zfs_kobj = &parent->zko_children[prop];
539         const char *name = data->p2k_table[prop].pd_name;
540         int err;
541
542         ASSERT(name);
543
544         err = zfs_kobj_init(zfs_kobj, data->p2k_attr_count, 0,
545             data->p2k_show_func);
546         if (err)
547                 return (ZPROP_CONT);
548
549         for (int i = 0; i < data->p2k_attr_count; i++)
550                 zfs_kobj_add_attr(zfs_kobj, i, zprop_attrs[i]);
551
552         err = zfs_kobj_add(zfs_kobj, &parent->zko_kobj, name);
553         if (err)
554                 zfs_kobj_release(&zfs_kobj->zko_kobj);
555
556         return (ZPROP_CONT);
557 }
558
559 static int
560 zfs_sysfs_properties_init(zfs_mod_kobj_t *zfs_kobj, struct kobject *parent,
561     zfs_type_t type)
562 {
563         prop_to_kobj_arg_t context;
564         const char *name;
565         int err;
566
567         /*
568          * Create a parent kobject to host properties.
569          *
570          * '/sys/module/zfs/properties.<type>'
571          */
572         if (type == ZFS_TYPE_POOL) {
573                 name = ZFS_SYSFS_POOL_PROPERTIES;
574                 context.p2k_table = zpool_prop_get_table();
575                 context.p2k_attr_count = ZPOOL_PROP_ATTR_COUNT;
576                 context.p2k_parent = zfs_kobj;
577                 context.p2k_show_func = pool_property_show;
578                 err = zfs_kobj_init(zfs_kobj, 0, ZPOOL_NUM_PROPS,
579                     pool_property_show);
580         } else {
581                 name = ZFS_SYSFS_DATASET_PROPERTIES;
582                 context.p2k_table = zfs_prop_get_table();
583                 context.p2k_attr_count = ZFS_PROP_ATTR_COUNT;
584                 context.p2k_parent = zfs_kobj;
585                 context.p2k_show_func = dataset_property_show;
586                 err = zfs_kobj_init(zfs_kobj, 0, ZFS_NUM_PROPS,
587                     dataset_property_show);
588         }
589
590         if (err)
591                 return (err);
592
593         err = zfs_kobj_add(zfs_kobj, parent, name);
594         if (err) {
595                 zfs_kobj_release(&zfs_kobj->zko_kobj);
596                 return (err);
597         }
598
599         /*
600          * Create a kobject for each property.
601          *
602          * '/sys/module/zfs/properties.<type>/<property>'
603          */
604         (void) zprop_iter_common(zprop_to_kobj, &context, B_TRUE,
605             B_FALSE, type);
606
607         return (err);
608 }
609
610 void
611 zfs_sysfs_init(void)
612 {
613         struct kobject *parent;
614 #if defined(CONFIG_ZFS) && !defined(CONFIG_ZFS_MODULE)
615         parent = kobject_create_and_add("zfs", fs_kobj);
616 #else
617         parent = &(((struct module *)(THIS_MODULE))->mkobj).kobj;
618 #endif
619         int err;
620
621         if (parent == NULL)
622                 return;
623
624         err = zfs_kernel_features_init(&kernel_features_kobj, parent);
625         if (err)
626                 return;
627
628         err = zfs_pool_features_init(&pool_features_kobj, parent);
629         if (err) {
630                 zfs_kobj_fini(&kernel_features_kobj);
631                 return;
632         }
633
634         err = zfs_sysfs_properties_init(&pool_props_kobj, parent,
635             ZFS_TYPE_POOL);
636         if (err) {
637                 zfs_kobj_fini(&kernel_features_kobj);
638                 zfs_kobj_fini(&pool_features_kobj);
639                 return;
640         }
641
642         err = zfs_sysfs_properties_init(&dataset_props_kobj, parent,
643             ZFS_TYPE_FILESYSTEM);
644         if (err) {
645                 zfs_kobj_fini(&kernel_features_kobj);
646                 zfs_kobj_fini(&pool_features_kobj);
647                 zfs_kobj_fini(&pool_props_kobj);
648                 return;
649         }
650 }
651
652 void
653 zfs_sysfs_fini(void)
654 {
655         /*
656          * Remove top-level kobjects; each will remove any children kobjects
657          */
658         zfs_kobj_fini(&kernel_features_kobj);
659         zfs_kobj_fini(&pool_features_kobj);
660         zfs_kobj_fini(&dataset_props_kobj);
661         zfs_kobj_fini(&pool_props_kobj);
662 }