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: for legacy mode, fall back to V9FS_ACCESS_ANY */ 442 if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) && 443 ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) { 444 445 v9ses->flags &= ~V9FS_ACCESS_MASK; 446 v9ses->flags |= V9FS_ACCESS_ANY; 447 v9ses->uid = INVALID_UID; 448 } 449 if (!v9fs_proto_dotl(v9ses) || 450 !((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { 451 /* 452 * We support ACL checks on client only if the protocol is 453 * 9P2000.L and access is V9FS_ACCESS_CLIENT. 454 */ 455 v9ses->flags &= ~V9FS_ACL_MASK; 456 } 457 458 fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID, 459 v9ses->aname); 460 if (IS_ERR(fid)) { 461 rc = PTR_ERR(fid); 462 p9_debug(P9_DEBUG_ERROR, "cannot attach\n"); 463 goto err_clnt; 464 } 465 466 if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE) 467 fid->uid = v9ses->uid; 468 else 469 fid->uid = INVALID_UID; 470 471 #ifdef CONFIG_9P_FSCACHE 472 /* register the session for caching */ 473 if (v9ses->cache & CACHE_FSCACHE) { 474 rc = v9fs_cache_session_get_cookie(v9ses, dev_name); 475 if (rc < 0) 476 goto err_clnt; 477 } 478 #endif 479 spin_lock(&v9fs_sessionlist_lock); 480 list_add(&v9ses->slist, &v9fs_sessionlist); 481 spin_unlock(&v9fs_sessionlist_lock); 482 483 return fid; 484 485 err_clnt: 486 #ifdef CONFIG_9P_FSCACHE 487 kfree(v9ses->cachetag); 488 #endif 489 p9_client_destroy(v9ses->clnt); 490 err_names: 491 kfree(v9ses->uname); 492 kfree(v9ses->aname); 493 return ERR_PTR(rc); 494 } 495 496 /** 497 * v9fs_session_close - shutdown a session 498 * @v9ses: session information structure 499 * 500 */ 501 502 void v9fs_session_close(struct v9fs_session_info *v9ses) 503 { 504 if (v9ses->clnt) { 505 p9_client_destroy(v9ses->clnt); 506 v9ses->clnt = NULL; 507 } 508 509 #ifdef CONFIG_9P_FSCACHE 510 fscache_relinquish_volume(v9fs_session_cache(v9ses), NULL, false); 511 kfree(v9ses->cachetag); 512 #endif 513 kfree(v9ses->uname); 514 kfree(v9ses->aname); 515 516 spin_lock(&v9fs_sessionlist_lock); 517 list_del(&v9ses->slist); 518 spin_unlock(&v9fs_sessionlist_lock); 519 } 520 521 /** 522 * v9fs_session_cancel - terminate a session 523 * @v9ses: session to terminate 524 * 525 * mark transport as disconnected and cancel all pending requests. 526 */ 527 528 void v9fs_session_cancel(struct v9fs_session_info *v9ses) 529 { 530 p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses); 531 p9_client_disconnect(v9ses->clnt); 532 } 533 534 /** 535 * v9fs_session_begin_cancel - Begin terminate of a session 536 * @v9ses: session to terminate 537 * 538 * After this call we don't allow any request other than clunk. 539 */ 540 541 void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses) 542 { 543 p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses); 544 p9_client_begin_disconnect(v9ses->clnt); 545 } 546 547 static struct kobject *v9fs_kobj; 548 549 #ifdef CONFIG_9P_FSCACHE 550 /* 551 * List caches associated with a session 552 */ 553 static ssize_t caches_show(struct kobject *kobj, 554 struct kobj_attribute *attr, 555 char *buf) 556 { 557 ssize_t n = 0, count = 0, limit = PAGE_SIZE; 558 struct v9fs_session_info *v9ses; 559 560 spin_lock(&v9fs_sessionlist_lock); 561 list_for_each_entry(v9ses, &v9fs_sessionlist, slist) { 562 if (v9ses->cachetag) { 563 n = snprintf(buf + count, limit, "%s\n", v9ses->cachetag); 564 if (n < 0) { 565 count = n; 566 break; 567 } 568 569 count += n; 570 limit -= n; 571 } 572 } 573 574 spin_unlock(&v9fs_sessionlist_lock); 575 return count; 576 } 577 578 static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); 579 #endif /* CONFIG_9P_FSCACHE */ 580 581 static struct attribute *v9fs_attrs[] = { 582 #ifdef CONFIG_9P_FSCACHE 583 &v9fs_attr_cache.attr, 584 #endif 585 NULL, 586 }; 587 588 static const struct attribute_group v9fs_attr_group = { 589 .attrs = v9fs_attrs, 590 }; 591 592 /** 593 * v9fs_sysfs_init - Initialize the v9fs sysfs interface 594 * 595 */ 596 597 static int __init v9fs_sysfs_init(void) 598 { 599 int ret; 600 601 v9fs_kobj = kobject_create_and_add("9p", fs_kobj); 602 if (!v9fs_kobj) 603 return -ENOMEM; 604 605 ret = sysfs_create_group(v9fs_kobj, &v9fs_attr_group); 606 if (ret) { 607 kobject_put(v9fs_kobj); 608 return ret; 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_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 /** 665 * init_v9fs - Initialize module 666 * 667 */ 668 669 static int __init init_v9fs(void) 670 { 671 int err; 672 673 pr_info("Installing v9fs 9p2000 file system support\n"); 674 /* TODO: Setup list of registered transport modules */ 675 676 err = v9fs_init_inode_cache(); 677 if (err < 0) { 678 pr_err("Failed to register v9fs for caching\n"); 679 return err; 680 } 681 682 err = v9fs_sysfs_init(); 683 if (err < 0) { 684 pr_err("Failed to register with sysfs\n"); 685 goto out_cache; 686 } 687 err = register_filesystem(&v9fs_fs_type); 688 if (err < 0) { 689 pr_err("Failed to register filesystem\n"); 690 goto out_sysfs_cleanup; 691 } 692 693 return 0; 694 695 out_sysfs_cleanup: 696 v9fs_sysfs_cleanup(); 697 698 out_cache: 699 v9fs_destroy_inode_cache(); 700 701 return err; 702 } 703 704 /** 705 * exit_v9fs - shutdown module 706 * 707 */ 708 709 static void __exit exit_v9fs(void) 710 { 711 v9fs_sysfs_cleanup(); 712 v9fs_destroy_inode_cache(); 713 unregister_filesystem(&v9fs_fs_type); 714 } 715 716 module_init(init_v9fs) 717 module_exit(exit_v9fs) 718 719 MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>"); 720 MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); 721 MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>"); 722 MODULE_DESCRIPTION("9P Client File System"); 723 MODULE_LICENSE("GPL"); 724