v9fs.c 14.7 KB
Newer Older
1 2 3 4 5
/*
 *  linux/fs/9p/v9fs.c
 *
 *  This file contains functions assisting in mapping VFS to 9P2000
 *
6
 *  Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
7 8 9
 *  Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
 *
 *  This program is free software; you can redistribute it and/or modify
10 11
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to:
 *  Free Software Foundation
 *  51 Franklin Street, Fifth Floor
 *  Boston, MA  02111-1301  USA
 *
 */

26 27
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

28 29 30
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
31
#include <linux/sched.h>
32
#include <linux/cred.h>
33 34
#include <linux/parser.h>
#include <linux/idr.h>
35
#include <linux/slab.h>
36 37
#include <net/9p/9p.h>
#include <net/9p/client.h>
38
#include <net/9p/transport.h>
39 40
#include "v9fs.h"
#include "v9fs_vfs.h"
41 42 43 44
#include "cache.h"

static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
static LIST_HEAD(v9fs_sessionlist);
Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
45
struct kmem_cache *v9fs_inode_cache;
46 47

/*
48 49 50
 * Option Parsing (code inspired by NFS code)
 *  NOTE: each transport will parse its own options
 */
51 52 53

enum {
	/* Options that take integer arguments */
54
	Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
55
	/* String options */
56
	Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag,
57
	/* Options that take no arguments */
58
	Opt_nodevmap,
59
	/* Cache options */
60
	Opt_cache_loose, Opt_fscache, Opt_mmap,
Latchesar Ionkov's avatar
Latchesar Ionkov committed
61
	/* Access options */
62
	Opt_access, Opt_posixacl,
63 64 65 66
	/* Error token */
	Opt_err
};

67
static const match_table_t tokens = {
68
	{Opt_debug, "debug=%x"},
69 70
	{Opt_dfltuid, "dfltuid=%u"},
	{Opt_dfltgid, "dfltgid=%u"},
71
	{Opt_afid, "afid=%u"},
72
	{Opt_uname, "uname=%s"},
73 74
	{Opt_remotename, "aname=%s"},
	{Opt_nodevmap, "nodevmap"},
75
	{Opt_cache, "cache=%s"},
76
	{Opt_cache_loose, "loose"},
77
	{Opt_fscache, "fscache"},
78
	{Opt_mmap, "mmap"},
79
	{Opt_cachetag, "cachetag=%s"},
Latchesar Ionkov's avatar
Latchesar Ionkov committed
80
	{Opt_access, "access=%s"},
81
	{Opt_posixacl, "posixacl"},
82 83 84
	{Opt_err, NULL}
};

85 86 87 88 89 90 91
/* Interpret mount options for cache mode */
static int get_cache_mode(char *s)
{
	int version = -EINVAL;

	if (!strcmp(s, "loose")) {
		version = CACHE_LOOSE;
92
		p9_debug(P9_DEBUG_9P, "Cache mode: loose\n");
93 94
	} else if (!strcmp(s, "fscache")) {
		version = CACHE_FSCACHE;
95
		p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n");
96 97 98
	} else if (!strcmp(s, "mmap")) {
		version = CACHE_MMAP;
		p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n");
99 100
	} else if (!strcmp(s, "none")) {
		version = CACHE_NONE;
101
		p9_debug(P9_DEBUG_9P, "Cache mode: none\n");
102
	} else
103
		pr_info("Unknown Cache mode %s\n", s);
104 105 106
	return version;
}

107 108 109 110
/**
 * v9fs_parse_options - parse mount options into session structure
 * @v9ses: existing v9fs session information
 *
111
 * Return 0 upon success, -ERRNO upon failure.
112 113
 */

114
static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
115
{
Eric Van Hensbergen's avatar
Eric Van Hensbergen committed
116
	char *options, *tmp_options;
117
	substring_t args[MAX_OPT_ARGS];
118
	char *p;
119
	int option = 0;
Latchesar Ionkov's avatar
Latchesar Ionkov committed
120
	char *s, *e;
121
	int ret = 0;
122 123 124 125

	/* setup defaults */
	v9ses->afid = ~0;
	v9ses->debug = 0;
126
	v9ses->cache = CACHE_NONE;
127 128 129
#ifdef CONFIG_9P_FSCACHE
	v9ses->cachetag = NULL;
#endif
130

131
	if (!opts)
132
		return 0;
133

Eric Van Hensbergen's avatar
Eric Van Hensbergen committed
134
	tmp_options = kstrdup(opts, GFP_KERNEL);
135 136
	if (!tmp_options) {
		ret = -ENOMEM;
137
		goto fail_option_alloc;
138
	}
Eric Van Hensbergen's avatar
Eric Van Hensbergen committed
139
	options = tmp_options;
140

141
	while ((p = strsep(&options, ",")) != NULL) {
142
		int token, r;
143 144 145
		if (!*p)
			continue;
		token = match_token(p, tokens, args);
146 147 148
		switch (token) {
		case Opt_debug:
			r = match_int(&args[0], &option);
149
			if (r < 0) {
150 151
				p9_debug(P9_DEBUG_ERROR,
					 "integer field, but no integer?\n");
152
				ret = r;
153 154
				continue;
			}
155
			v9ses->debug = option;
156
#ifdef CONFIG_NET_9P_DEBUG
157
			p9_debug_level = option;
158
#endif
159
			break;
160

161
		case Opt_dfltuid:
162 163
			r = match_int(&args[0], &option);
			if (r < 0) {
164 165
				p9_debug(P9_DEBUG_ERROR,
					 "integer field, but no integer?\n");
166 167 168
				ret = r;
				continue;
			}
169 170 171 172 173 174 175
			v9ses->dfltuid = make_kuid(current_user_ns(), option);
			if (!uid_valid(v9ses->dfltuid)) {
				p9_debug(P9_DEBUG_ERROR,
					 "uid field, but not a uid?\n");
				ret = -EINVAL;
				continue;
			}
176
			break;
177
		case Opt_dfltgid:
178 179
			r = match_int(&args[0], &option);
			if (r < 0) {
180 181
				p9_debug(P9_DEBUG_ERROR,
					 "integer field, but no integer?\n");
182 183 184
				ret = r;
				continue;
			}
185 186 187 188 189 190 191
			v9ses->dfltgid = make_kgid(current_user_ns(), option);
			if (!gid_valid(v9ses->dfltgid)) {
				p9_debug(P9_DEBUG_ERROR,
					 "gid field, but not a gid?\n");
				ret = -EINVAL;
				continue;
			}
192 193
			break;
		case Opt_afid:
194 195
			r = match_int(&args[0], &option);
			if (r < 0) {
196 197
				p9_debug(P9_DEBUG_ERROR,
					 "integer field, but no integer?\n");
198 199 200
				ret = r;
				continue;
			}
201 202
			v9ses->afid = option;
			break;
203
		case Opt_uname:
204 205 206 207 208 209
			kfree(v9ses->uname);
			v9ses->uname = match_strdup(&args[0]);
			if (!v9ses->uname) {
				ret = -ENOMEM;
				goto free_and_return;
			}
210 211
			break;
		case Opt_remotename:
212 213 214 215 216 217
			kfree(v9ses->aname);
			v9ses->aname = match_strdup(&args[0]);
			if (!v9ses->aname) {
				ret = -ENOMEM;
				goto free_and_return;
			}
218 219 220 221
			break;
		case Opt_nodevmap:
			v9ses->nodev = 1;
			break;
222 223 224
		case Opt_cache_loose:
			v9ses->cache = CACHE_LOOSE;
			break;
225 226 227
		case Opt_fscache:
			v9ses->cache = CACHE_FSCACHE;
			break;
228 229 230
		case Opt_mmap:
			v9ses->cache = CACHE_MMAP;
			break;
231 232 233 234 235 236 237
		case Opt_cachetag:
#ifdef CONFIG_9P_FSCACHE
			v9ses->cachetag = match_strdup(&args[0]);
#endif
			break;
		case Opt_cache:
			s = match_strdup(&args[0]);
238 239
			if (!s) {
				ret = -ENOMEM;
240 241
				p9_debug(P9_DEBUG_ERROR,
					 "problem allocating copy of cache arg\n");
242 243
				goto free_and_return;
			}
244 245 246 247 248
			ret = get_cache_mode(s);
			if (ret == -EINVAL) {
				kfree(s);
				goto free_and_return;
			}
249

250
			v9ses->cache = ret;
251 252
			kfree(s);
			break;
Latchesar Ionkov's avatar
Latchesar Ionkov committed
253 254 255

		case Opt_access:
			s = match_strdup(&args[0]);
256 257
			if (!s) {
				ret = -ENOMEM;
258 259
				p9_debug(P9_DEBUG_ERROR,
					 "problem allocating copy of access arg\n");
260 261
				goto free_and_return;
			}
262

Latchesar Ionkov's avatar
Latchesar Ionkov committed
263 264 265 266 267
			v9ses->flags &= ~V9FS_ACCESS_MASK;
			if (strcmp(s, "user") == 0)
				v9ses->flags |= V9FS_ACCESS_USER;
			else if (strcmp(s, "any") == 0)
				v9ses->flags |= V9FS_ACCESS_ANY;
268 269 270
			else if (strcmp(s, "client") == 0) {
				v9ses->flags |= V9FS_ACCESS_CLIENT;
			} else {
271
				uid_t uid;
Latchesar Ionkov's avatar
Latchesar Ionkov committed
272
				v9ses->flags |= V9FS_ACCESS_SINGLE;
273
				uid = simple_strtoul(s, &e, 10);
274 275
				if (*e != '\0') {
					ret = -EINVAL;
276 277
					pr_info("Unknown access argument %s\n",
						s);
278 279 280
					kfree(s);
					goto free_and_return;
				}
281 282 283 284 285 286 287
				v9ses->uid = make_kuid(current_user_ns(), uid);
				if (!uid_valid(v9ses->uid)) {
					ret = -EINVAL;
					pr_info("Uknown uid %s\n", s);
					kfree(s);
					goto free_and_return;
				}
Latchesar Ionkov's avatar
Latchesar Ionkov committed
288
			}
289

Adrian Bunk's avatar
Adrian Bunk committed
290
			kfree(s);
Latchesar Ionkov's avatar
Latchesar Ionkov committed
291 292
			break;

293 294 295 296
		case Opt_posixacl:
#ifdef CONFIG_9P_FS_POSIX_ACL
			v9ses->flags |= V9FS_POSIX_ACL;
#else
297 298
			p9_debug(P9_DEBUG_ERROR,
				 "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
299 300 301
#endif
			break;

302 303 304 305
		default:
			continue;
		}
	}
Eric Van Hensbergen's avatar
Eric Van Hensbergen committed
306

307
free_and_return:
Eric Van Hensbergen's avatar
Eric Van Hensbergen committed
308
	kfree(tmp_options);
309
fail_option_alloc:
310
	return ret;
311 312 313 314 315 316 317 318 319 320
}

/**
 * v9fs_session_init - initialize session
 * @v9ses: session information structure
 * @dev_name: device being mounted
 * @data: options
 *
 */

321
struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
322 323
		  const char *dev_name, char *data)
{
324
	struct p9_fid *fid;
325
	int rc = -ENOMEM;
326

327
	v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL);
Latchesar Ionkov's avatar
Latchesar Ionkov committed
328
	if (!v9ses->uname)
329
		goto err_names;
330

331
	v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL);
332 333
	if (!v9ses->aname)
		goto err_names;
334
	init_rwsem(&v9ses->rename_sem);
335

336
	rc = bdi_setup_and_register(&v9ses->bdi, "9p");
337 338
	if (rc)
		goto err_names;
339

340
	v9ses->uid = INVALID_UID;
341 342
	v9ses->dfltuid = V9FS_DEFUID;
	v9ses->dfltgid = V9FS_DEFGID;
343

344
	v9ses->clnt = p9_client_create(dev_name, data);
345
	if (IS_ERR(v9ses->clnt)) {
346
		rc = PTR_ERR(v9ses->clnt);
347
		p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n");
348
		goto err_bdi;
349 350
	}

351 352 353 354
	v9ses->flags = V9FS_ACCESS_USER;

	if (p9_is_proto_dotl(v9ses->clnt)) {
		v9ses->flags = V9FS_ACCESS_CLIENT;
355
		v9ses->flags |= V9FS_PROTO_2000L;
356
	} else if (p9_is_proto_dotu(v9ses->clnt)) {
357
		v9ses->flags |= V9FS_PROTO_2000U;
358 359 360
	}

	rc = v9fs_parse_options(v9ses, data);
361 362
	if (rc < 0)
		goto err_clnt;
Latchesar Ionkov's avatar
Latchesar Ionkov committed
363

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

366 367 368 369 370 371 372 373 374 375
	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 !! */
Latchesar Ionkov's avatar
Latchesar Ionkov committed
376
	/* for legacy mode, fall back to V9FS_ACCESS_ANY */
377
	if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) &&
Latchesar Ionkov's avatar
Latchesar Ionkov committed
378 379 380 381
		((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) {

		v9ses->flags &= ~V9FS_ACCESS_MASK;
		v9ses->flags |= V9FS_ACCESS_ANY;
382
		v9ses->uid = INVALID_UID;
Latchesar Ionkov's avatar
Latchesar Ionkov committed
383
	}
384 385 386 387 388 389 390 391
	if (!v9fs_proto_dotl(v9ses) ||
		!((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
		/*
		 * We support ACL checks on clinet only if the protocol is
		 * 9P2000.L and access is V9FS_ACCESS_CLIENT.
		 */
		v9ses->flags &= ~V9FS_ACL_MASK;
	}
Latchesar Ionkov's avatar
Latchesar Ionkov committed
392

393
	fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID,
Latchesar Ionkov's avatar
Latchesar Ionkov committed
394
							v9ses->aname);
395
	if (IS_ERR(fid)) {
396
		rc = PTR_ERR(fid);
397
		p9_debug(P9_DEBUG_ERROR, "cannot attach\n");
398
		goto err_clnt;
399 400
	}

Latchesar Ionkov's avatar
Latchesar Ionkov committed
401 402 403
	if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
		fid->uid = v9ses->uid;
	else
404
		fid->uid = INVALID_UID;
Latchesar Ionkov's avatar
Latchesar Ionkov committed
405

406 407 408 409
#ifdef CONFIG_9P_FSCACHE
	/* register the session for caching */
	v9fs_cache_session_get_cookie(v9ses);
#endif
410 411 412
	spin_lock(&v9fs_sessionlist_lock);
	list_add(&v9ses->slist, &v9fs_sessionlist);
	spin_unlock(&v9fs_sessionlist_lock);
413

414
	return fid;
415

416 417 418
err_clnt:
	p9_client_destroy(v9ses->clnt);
err_bdi:
419
	bdi_destroy(&v9ses->bdi);
420 421 422 423
err_names:
	kfree(v9ses->uname);
	kfree(v9ses->aname);
	return ERR_PTR(rc);
424 425 426 427 428 429 430 431 432 433
}

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

void v9fs_session_close(struct v9fs_session_info *v9ses)
{
434 435 436
	if (v9ses->clnt) {
		p9_client_destroy(v9ses->clnt);
		v9ses->clnt = NULL;
437
	}
438

439 440 441 442 443 444
#ifdef CONFIG_9P_FSCACHE
	if (v9ses->fscache) {
		v9fs_cache_session_put_cookie(v9ses);
		kfree(v9ses->cachetag);
	}
#endif
445 446
	kfree(v9ses->uname);
	kfree(v9ses->aname);
447

448 449
	bdi_destroy(&v9ses->bdi);

450 451 452
	spin_lock(&v9fs_sessionlist_lock);
	list_del(&v9ses->slist);
	spin_unlock(&v9fs_sessionlist_lock);
453 454
}

455
/**
456 457 458 459
 * v9fs_session_cancel - terminate a session
 * @v9ses: session to terminate
 *
 * mark transport as disconnected and cancel all pending requests.
460
 */
461

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

467 468 469 470 471 472 473 474 475
/**
 * 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)
{
476
	p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
477 478 479
	p9_client_begin_disconnect(v9ses->clnt);
}

480 481
extern int v9fs_error_init(void);

482 483 484
static struct kobject *v9fs_kobj;

#ifdef CONFIG_9P_FSCACHE
485
/**
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
 * caches_show - list caches associated with a session
 *
 * Returns the size of buffer written.
 */

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, 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 struct attribute_group v9fs_attr_group = {
	.attrs = v9fs_attrs,
};

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

535
static int __init v9fs_sysfs_init(void)
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
{
	v9fs_kobj = kobject_create_and_add("9p", fs_kobj);
	if (!v9fs_kobj)
		return -ENOMEM;

	if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) {
		kobject_put(v9fs_kobj);
		return -ENOMEM;
	}

	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);
}

Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
560 561 562 563 564 565
static void v9fs_inode_init_once(void *foo)
{
	struct v9fs_inode *v9inode = (struct v9fs_inode *)foo;
#ifdef CONFIG_9P_FSCACHE
	v9inode->fscache = NULL;
#endif
566
	memset(&v9inode->qid, 0, sizeof(v9inode->qid));
Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
567 568 569 570 571 572 573 574 575 576 577 578
	inode_init_once(&v9inode->vfs_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|
579
					      SLAB_MEM_SPREAD|SLAB_ACCOUNT),
Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
580 581 582 583 584 585 586 587 588 589 590 591 592
					  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)
{
593 594 595 596 597
	/*
	 * Make sure all delayed rcu free inodes are flushed before we
	 * destroy cache.
	 */
	rcu_barrier();
Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
598 599 600 601 602 603 604 605 606 607
	kmem_cache_destroy(v9fs_inode_cache);
}

static int v9fs_cache_register(void)
{
	int ret;
	ret = v9fs_init_inode_cache();
	if (ret < 0)
		return ret;
#ifdef CONFIG_9P_FSCACHE
608 609 610
	ret = fscache_register_netfs(&v9fs_cache_netfs);
	if (ret < 0)
		v9fs_destroy_inode_cache();
Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
611
#endif
612
	return ret;
Aneesh Kumar K.V's avatar
Aneesh Kumar K.V committed
613 614 615 616 617 618 619 620 621 622
}

static void v9fs_cache_unregister(void)
{
	v9fs_destroy_inode_cache();
#ifdef CONFIG_9P_FSCACHE
	fscache_unregister_netfs(&v9fs_cache_netfs);
#endif
}

623 624
/**
 * init_v9fs - Initialize module
625 626 627 628 629
 *
 */

static int __init init_v9fs(void)
{
630
	int err;
631
	pr_info("Installing v9fs 9p2000 file system support\n");
632
	/* TODO: Setup list of registered trasnport modules */
633 634 635

	err = v9fs_cache_register();
	if (err < 0) {
636
		pr_err("Failed to register v9fs for caching\n");
637
		return err;
638 639 640 641
	}

	err = v9fs_sysfs_init();
	if (err < 0) {
642
		pr_err("Failed to register with sysfs\n");
643 644 645 646 647
		goto out_cache;
	}
	err = register_filesystem(&v9fs_fs_type);
	if (err < 0) {
		pr_err("Failed to register filesystem\n");
648 649 650 651 652 653 654 655
		goto out_sysfs_cleanup;
	}

	return 0;

out_sysfs_cleanup:
	v9fs_sysfs_cleanup();

656 657
out_cache:
	v9fs_cache_unregister();
658 659

	return err;
660 661 662
}

/**
663
 * exit_v9fs - shutdown module
664 665 666 667 668
 *
 */

static void __exit exit_v9fs(void)
{
669 670
	v9fs_sysfs_cleanup();
	v9fs_cache_unregister();
671 672 673 674 675 676
	unregister_filesystem(&v9fs_fs_type);
}

module_init(init_v9fs)
module_exit(exit_v9fs)

677
MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
678 679 680
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
MODULE_LICENSE("GPL");