Contributors: 30
Author Tokens Token Proportion Commits Commit Proportion
Eric Sandeen 1021 34.20% 3 3.61%
Eric Van Hensbergen 507 16.98% 21 25.30%
Abhishek Kulkarni 408 13.67% 2 2.41%
David Howells 341 11.42% 4 4.82%
Latchesar Ionkov 256 8.58% 7 8.43%
Aneesh Kumar K.V 157 5.26% 8 9.64%
Venkateswararao Jujjuri (JV) 65 2.18% 2 2.41%
Tejun Heo 27 0.90% 3 3.61%
Jeff Layton 22 0.74% 1 1.20%
Prem Karat 22 0.74% 1 1.20%
Dinu-Razvan Chis-Serban 20 0.67% 1 1.20%
Joe Perches 20 0.67% 1 1.20%
Eric W. Biedermann 18 0.60% 3 3.61%
Randall P. Embry 15 0.50% 3 3.61%
Sripathi Kodi 15 0.50% 3 3.61%
Dominique Martinet 14 0.47% 4 4.82%
Jim Meyering 14 0.47% 1 1.20%
Chengguang Xu 12 0.40% 3 3.61%
Al Viro 8 0.27% 1 1.20%
Kirill A. Shutemov 4 0.13% 1 1.20%
Tom Tucker 4 0.13% 1 1.20%
Andrew Morton 3 0.10% 1 1.20%
Ingo Molnar 3 0.10% 1 1.20%
Colin Ian King 3 0.10% 1 1.20%
Rikard Falkeborn 1 0.03% 1 1.20%
Thomas Gleixner 1 0.03% 1 1.20%
Adrian Bunk 1 0.03% 1 1.20%
Vladimir Davydov 1 0.03% 1 1.20%
Fabian Frederick 1 0.03% 1 1.20%
Jan Kara 1 0.03% 1 1.20%
Total 2985 83


// 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");