1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * linux/fs/9p/v9fs.c 4 * 5 * This file contains functions assisting in mapping VFS to 9P2000 6 * 7 * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> 8 * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> 9 */ 10 11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 12 13 #include <linux/module.h> 14 #include <linux/errno.h> 15 #include <linux/fs.h> 16 #include <linux/sched.h> 17 #include <linux/cred.h> 18 #include <linux/parser.h> 19 #include <linux/idr.h> 20 #include <linux/slab.h> 21 #include <linux/seq_file.h> 22 #include <net/9p/9p.h> 23 #include <net/9p/client.h> 24 #include <net/9p/transport.h> 25 #include "v9fs.h" 26 #include "v9fs_vfs.h" 27 #include "cache.h" 28 29 static DEFINE_SPINLOCK(v9fs_sessionlist_lock); 30 static LIST_HEAD(v9fs_sessionlist); 31 struct kmem_cache *v9fs_inode_cache; 32 33 /* 34 * Option Parsing (code inspired by NFS code) 35 * NOTE: each transport will parse its own options 36 */ 37 38 enum { 39 /* Options that take integer arguments */ 40 Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, 41 /* String options */ 42 Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag, 43 /* Options that take no arguments */ 44 Opt_nodevmap, 45 /* Cache options */ 46 Opt_cache_loose, Opt_fscache, Opt_mmap, 47 /* Access options */ 48 Opt_access, Opt_posixacl, 49 /* Lock timeout option */ 50 Opt_locktimeout, 51 /* Error token */ 52 Opt_err 53 }; 54 55 static const match_table_t tokens = { 56 {Opt_debug, "debug=%x"}, 57 {Opt_dfltuid, "dfltuid=%u"}, 58 {Opt_dfltgid, "dfltgid=%u"}, 59 {Opt_afid, "afid=%u"}, 60 {Opt_uname, "uname=%s"}, 61 {Opt_remotename, "aname=%s"}, 62 {Opt_nodevmap, "nodevmap"}, 63 {Opt_cache, "cache=%s"}, 64 {Opt_cache_loose, "loose"}, 65 {Opt_fscache, "fscache"}, 66 {Opt_mmap, "mmap"}, 67 {Opt_cachetag, "cachetag=%s"}, 68 {Opt_access, "access=%s"}, 69 {Opt_posixacl, "posixacl"}, 70 {Opt_locktimeout, "locktimeout=%u"}, 71 {Opt_err, NULL} 72 }; 73 74 static const char *const v9fs_cache_modes[nr__p9_cache_modes] = { 75 [CACHE_NONE] = "none", 76 [CACHE_MMAP] = "mmap", 77 [CACHE_LOOSE] = "loose", 78 [CACHE_FSCACHE] = "fscache", 79 }; 80 81 /* Interpret mount options for cache mode */ 82 static int get_cache_mode(char *s) 83 { 84 int version = -EINVAL; 85 86 if (!strcmp(s, "loose")) { 87 version = CACHE_LOOSE; 88 p9_debug(P9_DEBUG_9P, "Cache mode: loose\n"); 89 } else if (!strcmp(s, "fscache")) { 90 version = CACHE_FSCACHE; 91 p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n"); 92 } else if (!strcmp(s, "mmap")) { 93 version = CACHE_MMAP; 94 p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n"); 95 } else if (!strcmp(s, "none")) { 96 version = CACHE_NONE; 97 p9_debug(P9_DEBUG_9P, "Cache mode: none\n"); 98 } else 99 pr_info("Unknown Cache mode %s\n", s); 100 return version; 101 } 102 103 /* 104 * Display the mount options in /proc/mounts. 105 */ 106 int v9fs_show_options(struct seq_file *m, struct dentry *root) 107 { 108 struct v9fs_session_info *v9ses = root->d_sb->s_fs_info; 109 110 if (v9ses->debug) 111 seq_printf(m, ",debug=%x", v9ses->debug); 112 if (!uid_eq(v9ses->dfltuid, V9FS_DEFUID)) 113 seq_printf(m, ",dfltuid=%u", 114 from_kuid_munged(&init_user_ns, v9ses->dfltuid)); 115 if (!gid_eq(v9ses->dfltgid, V9FS_DEFGID)) 116 seq_printf(m, ",dfltgid=%u", 117 from_kgid_munged(&init_user_ns, v9ses->dfltgid)); 118 if (v9ses->afid != ~0) 119 seq_printf(m, ",afid=%u", v9ses->afid); 120 if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0) 121 seq_printf(m, ",uname=%s", v9ses->uname); 122 if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0) 123 seq_printf(m, ",aname=%s", v9ses->aname); 124 if (v9ses->nodev) 125 seq_puts(m, ",nodevmap"); 126 if (v9ses->cache) 127 seq_printf(m, ",%s", v9fs_cache_modes[v9ses->cache]); 128 #ifdef CONFIG_9P_FSCACHE 129 if (v9ses->cachetag && v9ses->cache == CACHE_FSCACHE) 130 seq_printf(m, ",cachetag=%s", v9ses->cachetag); 131 #endif 132 133 switch (v9ses->flags & V9FS_ACCESS_MASK) { 134 case V9FS_ACCESS_USER: 135 seq_puts(m, ",access=user"); 136 break; 137 case V9FS_ACCESS_ANY: 138 seq_puts(m, ",access=any"); 139 break; 140 case V9FS_ACCESS_CLIENT: 141 seq_puts(m, ",access=client"); 142 break; 143 case V9FS_ACCESS_SINGLE: 144 seq_printf(m, ",access=%u", 145 from_kuid_munged(&init_user_ns, v9ses->uid)); 146 break; 147 } 148 149 if (v9ses->flags & V9FS_POSIX_ACL) 150 seq_puts(m, ",posixacl"); 151 152 return p9_show_client_options(m, v9ses->clnt); 153 } 154 155 /** 156 * v9fs_parse_options - parse mount options into session structure 157 * @v9ses: existing v9fs session information 158 * @opts: The mount option string 159 * 160 * Return 0 upon success, -ERRNO upon failure. 161 */ 162 163 static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts) 164 { 165 char *options, *tmp_options; 166 substring_t args[MAX_OPT_ARGS]; 167 char *p; 168 int option = 0; 169 char *s, *e; 170 int ret = 0; 171 172 /* setup defaults */ 173 v9ses->afid = ~0; 174 v9ses->debug = 0; 175 v9ses->cache = CACHE_NONE; 176 #ifdef CONFIG_9P_FSCACHE 177 v9ses->cachetag = NULL; 178 #endif 179 v9ses->session_lock_timeout = P9_LOCK_TIMEOUT; 180 181 if (!opts) 182 return 0; 183 184 tmp_options = kstrdup(opts, GFP_KERNEL); 185 if (!tmp_options) { 186 ret = -ENOMEM; 187 goto fail_option_alloc; 188 } 189 options = tmp_options; 190 191 while ((p = strsep(&options, ",")) != NULL) { 192 int token, r; 193 if (!*p) 194 continue; 195 token = match_token(p, tokens, args); 196 switch (token) { 197 case Opt_debug: 198 r = match_int(&args[0], &option); 199 if (r < 0) { 200 p9_debug(P9_DEBUG_ERROR, 201 "integer field, but no integer?\n"); 202 ret = r; 203 } else { 204 v9ses->debug = option; 205 #ifdef CONFIG_NET_9P_DEBUG 206 p9_debug_level = option; 207 #endif 208 } 209 break; 210 211 case Opt_dfltuid: 212 r = match_int(&args[0], &option); 213 if (r < 0) { 214 p9_debug(P9_DEBUG_ERROR, 215 "integer field, but no integer?\n"); 216 ret = r; 217 continue; 218 } 219 v9ses->dfltuid = make_kuid(current_user_ns(), option); 220 if (!uid_valid(v9ses->dfltuid)) { 221 p9_debug(P9_DEBUG_ERROR, 222 "uid field, but not a uid?\n"); 223 ret = -EINVAL; 224 } 225 break; 226 case Opt_dfltgid: 227 r = match_int(&args[0], &option); 228 if (r < 0) { 229 p9_debug(P9_DEBUG_ERROR, 230 "integer field, but no integer?\n"); 231 ret = r; 232 continue; 233 } 234 v9ses->dfltgid = make_kgid(current_user_ns(), option); 235 if (!gid_valid(v9ses->dfltgid)) { 236 p9_debug(P9_DEBUG_ERROR, 237 "gid field, but not a gid?\n"); 238 ret = -EINVAL; 239 } 240 break; 241 case Opt_afid: 242 r = match_int(&args[0], &option); 243 if (r < 0) { 244 p9_debug(P9_DEBUG_ERROR, 245 "integer field, but no integer?\n"); 246 ret = r; 247 } else { 248 v9ses->afid = option; 249 } 250 break; 251 case Opt_uname: 252 kfree(v9ses->uname); 253 v9ses->uname = match_strdup(&args[0]); 254 if (!v9ses->uname) { 255 ret = -ENOMEM; 256 goto free_and_return; 257 } 258 break; 259 case Opt_remotename: 260 kfree(v9ses->aname); 261 v9ses->aname = match_strdup(&args[0]); 262 if (!v9ses->aname) { 263 ret = -ENOMEM; 264 goto free_and_return; 265 } 266 break; 267 case Opt_nodevmap: 268 v9ses->nodev = 1; 269 break; 270 case Opt_cache_loose: 271 v9ses->cache = CACHE_LOOSE; 272 break; 273 case Opt_fscache: 274 v9ses->cache = CACHE_FSCACHE; 275 break; 276 case Opt_mmap: 277 v9ses->cache = CACHE_MMAP; 278 break; 279 case Opt_cachetag: 280 #ifdef CONFIG_9P_FSCACHE 281 kfree(v9ses->cachetag); 282 v9ses->cachetag = match_strdup(&args[0]); 283 if (!v9ses->cachetag) { 284 ret = -ENOMEM; 285 goto free_and_return; 286 } 287 #endif 288 break; 289 case Opt_cache: 290 s = match_strdup(&args[0]); 291 if (!s) { 292 ret = -ENOMEM; 293 p9_debug(P9_DEBUG_ERROR, 294 "problem allocating copy of cache arg\n"); 295 goto free_and_return; 296 } 297 r = get_cache_mode(s); 298 if (r < 0) 299 ret = r; 300 else 301 v9ses->cache = r; 302 303 kfree(s); 304 break; 305 306 case Opt_access: 307 s = match_strdup(&args[0]); 308 if (!s) { 309 ret = -ENOMEM; 310 p9_debug(P9_DEBUG_ERROR, 311 "problem allocating copy of access arg\n"); 312 goto free_and_return; 313 } 314 315 v9ses->flags &= ~V9FS_ACCESS_MASK; 316 if (strcmp(s, "user") == 0) 317 v9ses->flags |= V9FS_ACCESS_USER; 318 else if (strcmp(s, "any") == 0) 319 v9ses->flags |= V9FS_ACCESS_ANY; 320 else if (strcmp(s, "client") == 0) { 321 v9ses->flags |= V9FS_ACCESS_CLIENT; 322 } else { 323 uid_t uid; 324 v9ses->flags |= V9FS_ACCESS_SINGLE; 325 uid = simple_strtoul(s, &e, 10); 326 if (*e != '\0') { 327 ret = -EINVAL; 328 pr_info("Unknown access argument %s\n", 329 s); 330 kfree(s); 331 continue; 332 } 333 v9ses->uid = make_kuid(current_user_ns(), uid); 334 if (!uid_valid(v9ses->uid)) { 335 ret = -EINVAL; 336 pr_info("Unknown uid %s\n", s); 337 } 338 } 339 340 kfree(s); 341 break; 342 343 case Opt_posixacl: 344 #ifdef CONFIG_9P_FS_POSIX_ACL 345 v9ses->flags |= V9FS_POSIX_ACL; 346 #else 347 p9_debug(P9_DEBUG_ERROR, 348 "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n"); 349 #endif 350 break; 351 352 case Opt_locktimeout: 353 r = match_int(&args[0], &option); 354 if (r < 0) { 355 p9_debug(P9_DEBUG_ERROR, 356 "integer field, but no integer?\n"); 357 ret = r; 358 continue; 359 } 360 if (option < 1) { 361 p9_debug(P9_DEBUG_ERROR, 362 "locktimeout must be a greater than zero integer.\n"); 363 ret = -EINVAL; 364 continue; 365 } 366 v9ses->session_lock_timeout = (long)option * HZ; 367 break; 368 369 default: 370 continue; 371 } 372 } 373 374 free_and_return: 375 kfree(tmp_options); 376 fail_option_alloc: 377 return ret; 378 } 379 380 /** 381 * v9fs_session_init - initialize session 382 * @v9ses: session information structure 383 * @dev_name: device being mounted 384 * @data: options 385 * 386 */ 387 388 struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, 389 const char *dev_name, char *data) 390 { 391 struct p9_fid *fid; 392 int rc = -ENOMEM; 393 394 v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); 395 if (!v9ses->uname) 396 goto err_names; 397 398 v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL); 399 if (!v9ses->aname) 400 goto err_names; 401 init_rwsem(&v9ses->rename_sem); 402 403 v9ses->uid = INVALID_UID; 404 v9ses->dfltuid = V9FS_DEFUID; 405 v9ses->dfltgid = V9FS_DEFGID; 406 407 v9ses->clnt = p9_client_create(dev_name, data); 408 if (IS_ERR(v9ses->clnt)) { 409 rc = PTR_ERR(v9ses->clnt); 410 p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n"); 411 goto err_names; 412 } 413 414 v9ses->flags = V9FS_ACCESS_USER; 415 416 if (p9_is_proto_dotl(v9ses->clnt)) { 417 v9ses->flags = V9FS_ACCESS_CLIENT; 418 v9ses->flags |= V9FS_PROTO_2000L; 419 } else if (p9_is_proto_dotu(v9ses->clnt)) { 420 v9ses->flags |= V9FS_PROTO_2000U; 421 } 422 423 rc = v9fs_parse_options(v9ses, data); 424 if (rc < 0) 425 goto err_clnt; 426 427 v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ; 428 429 if (!v9fs_proto_dotl(v9ses) && 430 ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { 431 /* 432 * We support ACCESS_CLIENT only for dotl. 433 * Fall back to ACCESS_USER 434 */ 435 v9ses->flags &= ~V9FS_ACCESS_MASK; 436 v9ses->flags |= V9FS_ACCESS_USER; 437 } 438 /*FIXME !! */ 439 /* for legacy mode, fall back to V9FS_ACCESS_ANY */ 440 if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) && 441 ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) { 442 443 v9ses->flags &= ~V9FS_ACCESS_MASK; 444 v9ses->flags |= V9FS_ACCESS_ANY; 445 v9ses->uid = INVALID_UID; 446 } 447 if (!v9fs_proto_dotl(v9ses) || 448 !((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { 449 /* 450 * We support ACL checks on clinet only if the protocol is 451 * 9P2000.L and access is V9FS_ACCESS_CLIENT. 452 */ 453 v9ses->flags &= ~V9FS_ACL_MASK; 454 } 455 456 fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID, 457 v9ses->aname); 458 if (IS_ERR(fid)) { 459 rc = PTR_ERR(fid); 460 p9_debug(P9_DEBUG_ERROR, "cannot attach\n"); 461 goto err_clnt; 462 } 463 464 if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE) 465 fid->uid = v9ses->uid; 466 else 467 fid->uid = INVALID_UID; 468 469 #ifdef CONFIG_9P_FSCACHE 470 /* register the session for caching */ 471 v9fs_cache_session_get_cookie(v9ses); 472 #endif 473 spin_lock(&v9fs_sessionlist_lock); 474 list_add(&v9ses->slist, &v9fs_sessionlist); 475 spin_unlock(&v9fs_sessionlist_lock); 476 477 return fid; 478 479 err_clnt: 480 #ifdef CONFIG_9P_FSCACHE 481 kfree(v9ses->cachetag); 482 #endif 483 p9_client_destroy(v9ses->clnt); 484 err_names: 485 kfree(v9ses->uname); 486 kfree(v9ses->aname); 487 return ERR_PTR(rc); 488 } 489 490 /** 491 * v9fs_session_close - shutdown a session 492 * @v9ses: session information structure 493 * 494 */ 495 496 void v9fs_session_close(struct v9fs_session_info *v9ses) 497 { 498 if (v9ses->clnt) { 499 p9_client_destroy(v9ses->clnt); 500 v9ses->clnt = NULL; 501 } 502 503 #ifdef CONFIG_9P_FSCACHE 504 if (v9ses->fscache) 505 v9fs_cache_session_put_cookie(v9ses); 506 kfree(v9ses->cachetag); 507 #endif 508 kfree(v9ses->uname); 509 kfree(v9ses->aname); 510 511 spin_lock(&v9fs_sessionlist_lock); 512 list_del(&v9ses->slist); 513 spin_unlock(&v9fs_sessionlist_lock); 514 } 515 516 /** 517 * v9fs_session_cancel - terminate a session 518 * @v9ses: session to terminate 519 * 520 * mark transport as disconnected and cancel all pending requests. 521 */ 522 523 void v9fs_session_cancel(struct v9fs_session_info *v9ses) { 524 p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses); 525 p9_client_disconnect(v9ses->clnt); 526 } 527 528 /** 529 * v9fs_session_begin_cancel - Begin terminate of a session 530 * @v9ses: session to terminate 531 * 532 * After this call we don't allow any request other than clunk. 533 */ 534 535 void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses) 536 { 537 p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses); 538 p9_client_begin_disconnect(v9ses->clnt); 539 } 540 541 extern int v9fs_error_init(void); 542 543 static struct kobject *v9fs_kobj; 544 545 #ifdef CONFIG_9P_FSCACHE 546 /* 547 * List caches associated with a session 548 */ 549 static ssize_t caches_show(struct kobject *kobj, 550 struct kobj_attribute *attr, 551 char *buf) 552 { 553 ssize_t n = 0, count = 0, limit = PAGE_SIZE; 554 struct v9fs_session_info *v9ses; 555 556 spin_lock(&v9fs_sessionlist_lock); 557 list_for_each_entry(v9ses, &v9fs_sessionlist, slist) { 558 if (v9ses->cachetag) { 559 n = snprintf(buf, limit, "%s\n", v9ses->cachetag); 560 if (n < 0) { 561 count = n; 562 break; 563 } 564 565 count += n; 566 limit -= n; 567 } 568 } 569 570 spin_unlock(&v9fs_sessionlist_lock); 571 return count; 572 } 573 574 static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); 575 #endif /* CONFIG_9P_FSCACHE */ 576 577 static struct attribute *v9fs_attrs[] = { 578 #ifdef CONFIG_9P_FSCACHE 579 &v9fs_attr_cache.attr, 580 #endif 581 NULL, 582 }; 583 584 static const struct attribute_group v9fs_attr_group = { 585 .attrs = v9fs_attrs, 586 }; 587 588 /** 589 * v9fs_sysfs_init - Initialize the v9fs sysfs interface 590 * 591 */ 592 593 static int __init v9fs_sysfs_init(void) 594 { 595 v9fs_kobj = kobject_create_and_add("9p", fs_kobj); 596 if (!v9fs_kobj) 597 return -ENOMEM; 598 599 if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) { 600 kobject_put(v9fs_kobj); 601 return -ENOMEM; 602 } 603 604 return 0; 605 } 606 607 /** 608 * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface 609 * 610 */ 611 612 static void v9fs_sysfs_cleanup(void) 613 { 614 sysfs_remove_group(v9fs_kobj, &v9fs_attr_group); 615 kobject_put(v9fs_kobj); 616 } 617 618 static void v9fs_inode_init_once(void *foo) 619 { 620 struct v9fs_inode *v9inode = (struct v9fs_inode *)foo; 621 #ifdef CONFIG_9P_FSCACHE 622 v9inode->fscache = NULL; 623 #endif 624 memset(&v9inode->qid, 0, sizeof(v9inode->qid)); 625 inode_init_once(&v9inode->vfs_inode); 626 } 627 628 /** 629 * v9fs_init_inode_cache - initialize a cache for 9P 630 * Returns 0 on success. 631 */ 632 static int v9fs_init_inode_cache(void) 633 { 634 v9fs_inode_cache = kmem_cache_create("v9fs_inode_cache", 635 sizeof(struct v9fs_inode), 636 0, (SLAB_RECLAIM_ACCOUNT| 637 SLAB_MEM_SPREAD|SLAB_ACCOUNT), 638 v9fs_inode_init_once); 639 if (!v9fs_inode_cache) 640 return -ENOMEM; 641 642 return 0; 643 } 644 645 /** 646 * v9fs_destroy_inode_cache - destroy the cache of 9P inode 647 * 648 */ 649 static void v9fs_destroy_inode_cache(void) 650 { 651 /* 652 * Make sure all delayed rcu free inodes are flushed before we 653 * destroy cache. 654 */ 655 rcu_barrier(); 656 kmem_cache_destroy(v9fs_inode_cache); 657 } 658 659 static int v9fs_cache_register(void) 660 { 661 int ret; 662 ret = v9fs_init_inode_cache(); 663 if (ret < 0) 664 return ret; 665 #ifdef CONFIG_9P_FSCACHE 666 ret = fscache_register_netfs(&v9fs_cache_netfs); 667 if (ret < 0) 668 v9fs_destroy_inode_cache(); 669 #endif 670 return ret; 671 } 672 673 static void v9fs_cache_unregister(void) 674 { 675 v9fs_destroy_inode_cache(); 676 #ifdef CONFIG_9P_FSCACHE 677 fscache_unregister_netfs(&v9fs_cache_netfs); 678 #endif 679 } 680 681 /** 682 * init_v9fs - Initialize module 683 * 684 */ 685 686 static int __init init_v9fs(void) 687 { 688 int err; 689 pr_info("Installing v9fs 9p2000 file system support\n"); 690 /* TODO: Setup list of registered trasnport modules */ 691 692 err = v9fs_cache_register(); 693 if (err < 0) { 694 pr_err("Failed to register v9fs for caching\n"); 695 return err; 696 } 697 698 err = v9fs_sysfs_init(); 699 if (err < 0) { 700 pr_err("Failed to register with sysfs\n"); 701 goto out_cache; 702 } 703 err = register_filesystem(&v9fs_fs_type); 704 if (err < 0) { 705 pr_err("Failed to register filesystem\n"); 706 goto out_sysfs_cleanup; 707 } 708 709 return 0; 710 711 out_sysfs_cleanup: 712 v9fs_sysfs_cleanup(); 713 714 out_cache: 715 v9fs_cache_unregister(); 716 717 return err; 718 } 719 720 /** 721 * exit_v9fs - shutdown module 722 * 723 */ 724 725 static void __exit exit_v9fs(void) 726 { 727 v9fs_sysfs_cleanup(); 728 v9fs_cache_unregister(); 729 unregister_filesystem(&v9fs_fs_type); 730 } 731 732 module_init(init_v9fs) 733 module_exit(exit_v9fs) 734 735 MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>"); 736 MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); 737 MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>"); 738 MODULE_LICENSE("GPL"); 739