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