root/fs/9p/v9fs.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  This file contains functions assisting in mapping VFS to 9P2000
 *
 *  Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
 *  Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/cred.h>
#include <linux/fs_parser.h>
#include <linux/fs_context.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "cache.h"

static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
static LIST_HEAD(v9fs_sessionlist);
struct kmem_cache *v9fs_inode_cache;

/*
 * Option Parsing (code inspired by NFS code)
 *  NOTE: each transport will parse its own options
 */

enum {
        /* Mount-point source, we need to handle this explicitly because
         * the code below accepts unknown args and the vfs layer only handles
         * source if we rejected it as EINVAL */
        Opt_source,
        /* Options that take integer arguments */
        Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
        /* String options */
        Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag,
        /* Options that take no arguments */
        Opt_nodevmap, Opt_noxattr, Opt_directio, Opt_ignoreqv,
        /* Access options */
        Opt_access, Opt_posixacl,
        /* Lock timeout option */
        Opt_locktimeout,

        /* Client options */
        Opt_msize, Opt_trans, Opt_legacy, Opt_version,

        /* fd transport options */
        /* Options that take integer arguments */
        Opt_rfdno, Opt_wfdno,
        /* Options that take no arguments */

        /* rdma transport options */
        /* Options that take integer arguments */
        Opt_rq_depth, Opt_sq_depth, Opt_timeout,

        /* Options for both fd and rdma transports */
        Opt_port, Opt_privport,
};

static const struct constant_table p9_versions[] = {
        { "9p2000",     p9_proto_legacy },
        { "9p2000.u",   p9_proto_2000u },
        { "9p2000.L",   p9_proto_2000L },
        {}
};

/*
 * This structure contains all parameters used for the core code,
 * the client, and all the transports.
 */
const struct fs_parameter_spec v9fs_param_spec[] = {
        fsparam_string  ("source",      Opt_source),
        fsparam_u32hex  ("debug",       Opt_debug),
        fsparam_uid     ("dfltuid",     Opt_dfltuid),
        fsparam_gid     ("dfltgid",     Opt_dfltgid),
        fsparam_u32     ("afid",        Opt_afid),
        fsparam_string  ("uname",       Opt_uname),
        fsparam_string  ("aname",       Opt_remotename),
        fsparam_flag    ("nodevmap",    Opt_nodevmap),
        fsparam_flag    ("noxattr",     Opt_noxattr),
        fsparam_flag    ("directio",    Opt_directio),
        fsparam_flag    ("ignoreqv",    Opt_ignoreqv),
        fsparam_string  ("cache",       Opt_cache),
        fsparam_string  ("cachetag",    Opt_cachetag),
        fsparam_string  ("access",      Opt_access),
        fsparam_flag    ("posixacl",    Opt_posixacl),
        fsparam_u32     ("locktimeout", Opt_locktimeout),

        /* client options */
        fsparam_u32     ("msize",       Opt_msize),
        fsparam_flag    ("noextend",    Opt_legacy),
        fsparam_string  ("trans",       Opt_trans),
        fsparam_enum    ("version",     Opt_version, p9_versions),

        /* fd transport options */
        fsparam_u32     ("rfdno",       Opt_rfdno),
        fsparam_u32     ("wfdno",       Opt_wfdno),

        /* rdma transport options */
        fsparam_u32     ("sq",          Opt_sq_depth),
        fsparam_u32     ("rq",          Opt_rq_depth),
        fsparam_u32     ("timeout",     Opt_timeout),

        /* fd and rdma transprt options */
        fsparam_u32     ("port",        Opt_port),
        fsparam_flag    ("privport",    Opt_privport),
        {}
};

/* Interpret mount options for cache mode */
static int get_cache_mode(char *s)
{
        int version = -EINVAL;

        if (!strcmp(s, "loose")) {
                version = CACHE_SC_LOOSE;
                p9_debug(P9_DEBUG_9P, "Cache mode: loose\n");
        } else if (!strcmp(s, "fscache")) {
                version = CACHE_SC_FSCACHE;
                p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n");
        } else if (!strcmp(s, "mmap")) {
                version = CACHE_SC_MMAP;
                p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n");
        } else if (!strcmp(s, "readahead")) {
                version = CACHE_SC_READAHEAD;
                p9_debug(P9_DEBUG_9P, "Cache mode: readahead\n");
        } else if (!strcmp(s, "none")) {
                version = CACHE_SC_NONE;
                p9_debug(P9_DEBUG_9P, "Cache mode: none\n");
        } else if (kstrtoint(s, 0, &version) != 0) {
                version = -EINVAL;
                pr_info("Unknown Cache mode or invalid value %s\n", s);
        }
        return version;
}

/*
 * Display the mount options in /proc/mounts.
 */
int v9fs_show_options(struct seq_file *m, struct dentry *root)
{
        struct v9fs_session_info *v9ses = root->d_sb->s_fs_info;

        if (v9ses->debug)
                seq_printf(m, ",debug=%#x", v9ses->debug);
        if (!uid_eq(v9ses->dfltuid, V9FS_DEFUID))
                seq_printf(m, ",dfltuid=%u",
                           from_kuid_munged(&init_user_ns, v9ses->dfltuid));
        if (!gid_eq(v9ses->dfltgid, V9FS_DEFGID))
                seq_printf(m, ",dfltgid=%u",
                           from_kgid_munged(&init_user_ns, v9ses->dfltgid));
        if (v9ses->afid != ~0)
                seq_printf(m, ",afid=%u", v9ses->afid);
        if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0)
                seq_printf(m, ",uname=%s", v9ses->uname);
        if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0)
                seq_printf(m, ",aname=%s", v9ses->aname);
        if (v9ses->nodev)
                seq_puts(m, ",nodevmap");
        if (v9ses->cache)
                seq_printf(m, ",cache=%#x", v9ses->cache);
#ifdef CONFIG_9P_FSCACHE
        if (v9ses->cachetag && (v9ses->cache & CACHE_FSCACHE))
                seq_printf(m, ",cachetag=%s", v9ses->cachetag);
#endif

        switch (v9ses->flags & V9FS_ACCESS_MASK) {
        case V9FS_ACCESS_USER:
                seq_puts(m, ",access=user");
                break;
        case V9FS_ACCESS_ANY:
                seq_puts(m, ",access=any");
                break;
        case V9FS_ACCESS_CLIENT:
                seq_puts(m, ",access=client");
                break;
        case V9FS_ACCESS_SINGLE:
                seq_printf(m, ",access=%u",
                           from_kuid_munged(&init_user_ns, v9ses->uid));
                break;
        }

        if (v9ses->flags & V9FS_IGNORE_QV)
                seq_puts(m, ",ignoreqv");
        if (v9ses->flags & V9FS_DIRECT_IO)
                seq_puts(m, ",directio");
        if (v9ses->flags & V9FS_POSIX_ACL)
                seq_puts(m, ",posixacl");

        if (v9ses->flags & V9FS_NO_XATTR)
                seq_puts(m, ",noxattr");

        return p9_show_client_options(m, v9ses->clnt);
}

/**
 * v9fs_parse_param - parse a mount option into the filesystem context
 * @fc: the filesystem context
 * @param: the parameter to parse
 *
 * Return 0 upon success, -ERRNO upon failure.
 */
int v9fs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
        struct v9fs_context *ctx = fc->fs_private;
        struct fs_parse_result result;
        char *s;
        int r;
        int opt;
        struct p9_client_opts   *clnt = &ctx->client_opts;
        struct p9_fd_opts       *fd_opts = &ctx->fd_opts;
        struct p9_rdma_opts     *rdma_opts = &ctx->rdma_opts;
        struct p9_session_opts  *session_opts = &ctx->session_opts;

        opt = fs_parse(fc, v9fs_param_spec, param, &result);
        if (opt < 0) {
                /*
                 * We might like to report bad mount options here, but
                 * traditionally 9p has ignored unknown mount options
                 */
                if (opt == -ENOPARAM)
                        return 0;

                return opt;
        }

        switch (opt) {
        case Opt_source:
                if (fc->source) {
                        pr_info("p9: multiple sources not supported\n");
                        return -EINVAL;
                }
                fc->source = param->string;
                param->string = NULL;
                break;
        case Opt_debug:
                session_opts->debug = result.uint_32;
#ifdef CONFIG_NET_9P_DEBUG
                p9_debug_level = result.uint_32;
#endif
                break;

        case Opt_dfltuid:
                session_opts->dfltuid = result.uid;
                break;
        case Opt_dfltgid:
                session_opts->dfltgid = result.gid;
                break;
        case Opt_afid:
                session_opts->afid = result.uint_32;
                break;
        case Opt_uname:
                kfree(session_opts->uname);
                session_opts->uname = param->string;
                param->string = NULL;
                break;
        case Opt_remotename:
                kfree(session_opts->aname);
                session_opts->aname = param->string;
                param->string = NULL;
                break;
        case Opt_nodevmap:
                session_opts->nodev = 1;
                break;
        case Opt_noxattr:
                session_opts->flags |= V9FS_NO_XATTR;
                break;
        case Opt_directio:
                session_opts->flags |= V9FS_DIRECT_IO;
                break;
        case Opt_ignoreqv:
                session_opts->flags |= V9FS_IGNORE_QV;
                break;
        case Opt_cachetag:
#ifdef CONFIG_9P_FSCACHE
                kfree(session_opts->cachetag);
                session_opts->cachetag = param->string;
                param->string = NULL;
#endif
                break;
        case Opt_cache:
                r = get_cache_mode(param->string);
                if (r < 0)
                        return r;
                session_opts->cache = r;
                break;
        case Opt_access:
                s = param->string;
                session_opts->flags &= ~V9FS_ACCESS_MASK;
                if (strcmp(s, "user") == 0) {
                        session_opts->flags |= V9FS_ACCESS_USER;
                } else if (strcmp(s, "any") == 0) {
                        session_opts->flags |= V9FS_ACCESS_ANY;
                } else if (strcmp(s, "client") == 0) {
                        session_opts->flags |= V9FS_ACCESS_CLIENT;
                } else {
                        uid_t uid;

                        session_opts->flags |= V9FS_ACCESS_SINGLE;
                        r = kstrtouint(s, 10, &uid);
                        if (r) {
                                pr_info("Unknown access argument %s: %d\n",
                                        param->string, r);
                                return r;
                        }
                        session_opts->uid = make_kuid(current_user_ns(), uid);
                        if (!uid_valid(session_opts->uid)) {
                                pr_info("Unknown uid %s\n", s);
                                return -EINVAL;
                        }
                }
                break;

        case Opt_posixacl:
#ifdef CONFIG_9P_FS_POSIX_ACL
                session_opts->flags |= V9FS_POSIX_ACL;
#else
                p9_debug(P9_DEBUG_ERROR,
                         "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
#endif
                break;

        case Opt_locktimeout:
                if (result.uint_32 < 1) {
                        p9_debug(P9_DEBUG_ERROR,
                                 "locktimeout must be a greater than zero integer.\n");
                        return -EINVAL;
                }
                session_opts->session_lock_timeout = (long)result.uint_32 * HZ;
                break;

        /* Options for client */
        case Opt_msize:
                if (result.uint_32 < 4096) {
                        p9_debug(P9_DEBUG_ERROR, "msize should be at least 4k\n");
                        return -EINVAL;
                }
                if (result.uint_32 > INT_MAX) {
                        p9_debug(P9_DEBUG_ERROR, "msize too big\n");
                        return -EINVAL;
                }
                clnt->msize = result.uint_32;
                break;
        case Opt_trans:
                v9fs_put_trans(clnt->trans_mod);
                clnt->trans_mod = v9fs_get_trans_by_name(param->string);
                if (!clnt->trans_mod) {
                        pr_info("Could not find request transport: %s\n",
                                param->string);
                        return -EINVAL;
                }
                break;
        case Opt_legacy:
                clnt->proto_version = p9_proto_legacy;
                break;
        case Opt_version:
                clnt->proto_version = result.uint_32;
                p9_debug(P9_DEBUG_9P, "Protocol version: %s\n", param->string);
                break;
        /* Options for fd transport */
        case Opt_rfdno:
                fd_opts->rfd = result.uint_32;
                break;
        case Opt_wfdno:
                fd_opts->wfd = result.uint_32;
                break;
        /* Options for rdma transport */
        case Opt_sq_depth:
                rdma_opts->sq_depth = result.uint_32;
                break;
        case Opt_rq_depth:
                rdma_opts->rq_depth = result.uint_32;
                break;
        case Opt_timeout:
                rdma_opts->timeout = result.uint_32;
                break;
        /* Options for both fd and rdma transports */
        case Opt_port:
                fd_opts->port = result.uint_32;
                rdma_opts->port = result.uint_32;
                break;
        case Opt_privport:
                fd_opts->privport = true;
                rdma_opts->port = true;
                break;
        }

        return 0;
}

static void v9fs_apply_options(struct v9fs_session_info *v9ses,
                  struct fs_context *fc)
{
        struct v9fs_context     *ctx = fc->fs_private;

        v9ses->debug = ctx->session_opts.debug;
        v9ses->dfltuid = ctx->session_opts.dfltuid;
        v9ses->dfltgid = ctx->session_opts.dfltgid;
        v9ses->afid = ctx->session_opts.afid;
        v9ses->uname = ctx->session_opts.uname;
        ctx->session_opts.uname = NULL;
        v9ses->aname = ctx->session_opts.aname;
        ctx->session_opts.aname = NULL;
        v9ses->nodev = ctx->session_opts.nodev;
        /*
         * Note that we must |= flags here as session_init already
         * set basic flags. This adds in flags from parsed options.
         */
        v9ses->flags |= ctx->session_opts.flags;
#ifdef CONFIG_9P_FSCACHE
        v9ses->cachetag = ctx->session_opts.cachetag;
        ctx->session_opts.cachetag = NULL;
#endif
        v9ses->cache = ctx->session_opts.cache;
        v9ses->uid = ctx->session_opts.uid;
        v9ses->session_lock_timeout = ctx->session_opts.session_lock_timeout;
}

/**
 * v9fs_session_init - initialize session
 * @v9ses: session information structure
 * @fc: the filesystem mount context
 *
 */

struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
                  struct fs_context *fc)
{
        struct p9_fid *fid;
        int rc = -ENOMEM;

        init_rwsem(&v9ses->rename_sem);

        v9ses->clnt = p9_client_create(fc);
        if (IS_ERR(v9ses->clnt)) {
                rc = PTR_ERR(v9ses->clnt);
                p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n");
                goto err_names;
        }

        /*
         * Initialize flags on the real v9ses. v9fs_apply_options below
         * will |= the additional flags from parsed options.
         */
        v9ses->flags = V9FS_ACCESS_USER;

        if (p9_is_proto_dotl(v9ses->clnt)) {
                v9ses->flags = V9FS_ACCESS_CLIENT;
                v9ses->flags |= V9FS_PROTO_2000L;
        } else if (p9_is_proto_dotu(v9ses->clnt)) {
                v9ses->flags |= V9FS_PROTO_2000U;
        }

        v9fs_apply_options(v9ses, fc);

        v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ;

        if (!v9fs_proto_dotl(v9ses) &&
            ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
                /*
                 * We support ACCESS_CLIENT only for dotl.
                 * Fall back to ACCESS_USER
                 */
                v9ses->flags &= ~V9FS_ACCESS_MASK;
                v9ses->flags |= V9FS_ACCESS_USER;
        }
        /* FIXME: for legacy mode, fall back to V9FS_ACCESS_ANY */
        if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) &&
                ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) {

                v9ses->flags &= ~V9FS_ACCESS_MASK;
                v9ses->flags |= V9FS_ACCESS_ANY;
                v9ses->uid = INVALID_UID;
        }
        if (!v9fs_proto_dotl(v9ses) ||
                !((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
                /*
                 * We support ACL checks on client only if the protocol is
                 * 9P2000.L and access is V9FS_ACCESS_CLIENT.
                 */
                v9ses->flags &= ~V9FS_ACL_MASK;
        }

        fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID,
                                                        v9ses->aname);
        if (IS_ERR(fid)) {
                rc = PTR_ERR(fid);
                p9_debug(P9_DEBUG_ERROR, "cannot attach\n");
                goto err_clnt;
        }

        if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
                fid->uid = v9ses->uid;
        else
                fid->uid = INVALID_UID;

#ifdef CONFIG_9P_FSCACHE
        /* register the session for caching */
        if (v9ses->cache & CACHE_FSCACHE) {
                rc = v9fs_cache_session_get_cookie(v9ses, fc->source);
                if (rc < 0)
                        goto err_clnt;
        }
#endif
        spin_lock(&v9fs_sessionlist_lock);
        list_add(&v9ses->slist, &v9fs_sessionlist);
        spin_unlock(&v9fs_sessionlist_lock);

        return fid;

err_clnt:
#ifdef CONFIG_9P_FSCACHE
        kfree(v9ses->cachetag);
#endif
        p9_client_destroy(v9ses->clnt);
err_names:
        kfree(v9ses->uname);
        kfree(v9ses->aname);
        return ERR_PTR(rc);
}

/**
 * v9fs_session_close - shutdown a session
 * @v9ses: session information structure
 *
 */

void v9fs_session_close(struct v9fs_session_info *v9ses)
{
        if (v9ses->clnt) {
                p9_client_destroy(v9ses->clnt);
                v9ses->clnt = NULL;
        }

#ifdef CONFIG_9P_FSCACHE
        fscache_relinquish_volume(v9fs_session_cache(v9ses), NULL, false);
        kfree(v9ses->cachetag);
#endif
        kfree(v9ses->uname);
        kfree(v9ses->aname);

        spin_lock(&v9fs_sessionlist_lock);
        list_del(&v9ses->slist);
        spin_unlock(&v9fs_sessionlist_lock);
}

/**
 * v9fs_session_cancel - terminate a session
 * @v9ses: session to terminate
 *
 * mark transport as disconnected and cancel all pending requests.
 */

void v9fs_session_cancel(struct v9fs_session_info *v9ses)
{
        p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses);
        p9_client_disconnect(v9ses->clnt);
}

/**
 * v9fs_session_begin_cancel - Begin terminate of a session
 * @v9ses: session to terminate
 *
 * After this call we don't allow any request other than clunk.
 */

void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses)
{
        p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
        p9_client_begin_disconnect(v9ses->clnt);
}

static struct kobject *v9fs_kobj;

#ifdef CONFIG_9P_FSCACHE
/*
 * List caches associated with a session
 */
static ssize_t caches_show(struct kobject *kobj,
                           struct kobj_attribute *attr,
                           char *buf)
{
        ssize_t n = 0, count = 0, limit = PAGE_SIZE;
        struct v9fs_session_info *v9ses;

        spin_lock(&v9fs_sessionlist_lock);
        list_for_each_entry(v9ses, &v9fs_sessionlist, slist) {
                if (v9ses->cachetag) {
                        n = snprintf(buf + count, limit, "%s\n", v9ses->cachetag);
                        if (n < 0) {
                                count = n;
                                break;
                        }

                        count += n;
                        limit -= n;
                }
        }

        spin_unlock(&v9fs_sessionlist_lock);
        return count;
}

static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches);
#endif /* CONFIG_9P_FSCACHE */

static struct attribute *v9fs_attrs[] = {
#ifdef CONFIG_9P_FSCACHE
        &v9fs_attr_cache.attr,
#endif
        NULL,
};

static const struct attribute_group v9fs_attr_group = {
        .attrs = v9fs_attrs,
};

/**
 * v9fs_sysfs_init - Initialize the v9fs sysfs interface
 *
 */

static int __init v9fs_sysfs_init(void)
{
        int ret;

        v9fs_kobj = kobject_create_and_add("9p", fs_kobj);
        if (!v9fs_kobj)
                return -ENOMEM;

        ret = sysfs_create_group(v9fs_kobj, &v9fs_attr_group);
        if (ret) {
                kobject_put(v9fs_kobj);
                return ret;
        }

        return 0;
}

/**
 * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface
 *
 */

static void v9fs_sysfs_cleanup(void)
{
        sysfs_remove_group(v9fs_kobj, &v9fs_attr_group);
        kobject_put(v9fs_kobj);
}

static void v9fs_inode_init_once(void *foo)
{
        struct v9fs_inode *v9inode = (struct v9fs_inode *)foo;

        memset(&v9inode->qid, 0, sizeof(v9inode->qid));
        inode_init_once(&v9inode->netfs.inode);
}

/**
 * v9fs_init_inode_cache - initialize a cache for 9P
 * Returns 0 on success.
 */
static int v9fs_init_inode_cache(void)
{
        v9fs_inode_cache = kmem_cache_create("v9fs_inode_cache",
                                          sizeof(struct v9fs_inode),
                                          0, (SLAB_RECLAIM_ACCOUNT|
                                              SLAB_ACCOUNT),
                                          v9fs_inode_init_once);
        if (!v9fs_inode_cache)
                return -ENOMEM;

        return 0;
}

/**
 * v9fs_destroy_inode_cache - destroy the cache of 9P inode
 *
 */
static void v9fs_destroy_inode_cache(void)
{
        /*
         * Make sure all delayed rcu free inodes are flushed before we
         * destroy cache.
         */
        rcu_barrier();
        kmem_cache_destroy(v9fs_inode_cache);
}

/**
 * init_v9fs - Initialize module
 *
 */

static int __init init_v9fs(void)
{
        int err;

        pr_info("Installing v9fs 9p2000 file system support\n");
        /* TODO: Setup list of registered transport modules */

        err = v9fs_init_inode_cache();
        if (err < 0) {
                pr_err("Failed to register v9fs for caching\n");
                return err;
        }

        err = v9fs_sysfs_init();
        if (err < 0) {
                pr_err("Failed to register with sysfs\n");
                goto out_cache;
        }
        err = register_filesystem(&v9fs_fs_type);
        if (err < 0) {
                pr_err("Failed to register filesystem\n");
                goto out_sysfs_cleanup;
        }

        return 0;

out_sysfs_cleanup:
        v9fs_sysfs_cleanup();

out_cache:
        v9fs_destroy_inode_cache();

        return err;
}

/**
 * exit_v9fs - shutdown module
 *
 */

static void __exit exit_v9fs(void)
{
        v9fs_sysfs_cleanup();
        v9fs_destroy_inode_cache();
        unregister_filesystem(&v9fs_fs_type);
}

module_init(init_v9fs)
module_exit(exit_v9fs)

MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
MODULE_DESCRIPTION("9P Client File System");
MODULE_LICENSE("GPL");