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 } 663