1/*
2 *  linux/fs/9p/v9fs.c
3 *
4 *  This file contains functions assisting in mapping VFS to 9P2000
5 *
6 *  Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
7 *  Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
8 *
9 *  This program is free software; you can redistribute it and/or modify
10 *  it under the terms of the GNU General Public License version 2
11 *  as published by the Free Software Foundation.
12 *
13 *  This program is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *  GNU General Public License for more details.
17 *
18 *  You should have received a copy of the GNU General Public License
19 *  along with this program; if not, write to:
20 *  Free Software Foundation
21 *  51 Franklin Street, Fifth Floor
22 *  Boston, MA  02111-1301  USA
23 *
24 */
25
26#include <linux/module.h>
27#include <linux/errno.h>
28#include <linux/fs.h>
29#include <linux/sched.h>
30#include <linux/parser.h>
31#include <linux/idr.h>
32#include <linux/slab.h>
33#include <net/9p/9p.h>
34#include <net/9p/client.h>
35#include <net/9p/transport.h>
36#include "v9fs.h"
37#include "v9fs_vfs.h"
38#include "cache.h"
39
40static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
41static LIST_HEAD(v9fs_sessionlist);
42
43/*
44 * Option Parsing (code inspired by NFS code)
45 *  NOTE: each transport will parse its own options
46 */
47
48enum {
49	/* Options that take integer arguments */
50	Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
51	/* String options */
52	Opt_uname, Opt_remotename, Opt_trans, Opt_cache, Opt_cachetag,
53	/* Options that take no arguments */
54	Opt_nodevmap,
55	/* Cache options */
56	Opt_cache_loose, Opt_fscache,
57	/* Access options */
58	Opt_access,
59	/* Error token */
60	Opt_err
61};
62
63static const match_table_t tokens = {
64	{Opt_debug, "debug=%x"},
65	{Opt_dfltuid, "dfltuid=%u"},
66	{Opt_dfltgid, "dfltgid=%u"},
67	{Opt_afid, "afid=%u"},
68	{Opt_uname, "uname=%s"},
69	{Opt_remotename, "aname=%s"},
70	{Opt_nodevmap, "nodevmap"},
71	{Opt_cache, "cache=%s"},
72	{Opt_cache_loose, "loose"},
73	{Opt_fscache, "fscache"},
74	{Opt_cachetag, "cachetag=%s"},
75	{Opt_access, "access=%s"},
76	{Opt_err, NULL}
77};
78
79/**
80 * v9fs_parse_options - parse mount options into session structure
81 * @v9ses: existing v9fs session information
82 *
83 * Return 0 upon success, -ERRNO upon failure.
84 */
85
86static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
87{
88	char *options, *tmp_options;
89	substring_t args[MAX_OPT_ARGS];
90	char *p;
91	int option = 0;
92	char *s, *e;
93	int ret = 0;
94
95	/* setup defaults */
96	v9ses->afid = ~0;
97	v9ses->debug = 0;
98	v9ses->cache = 0;
99#ifdef CONFIG_9P_FSCACHE
100	v9ses->cachetag = NULL;
101#endif
102
103	if (!opts)
104		return 0;
105
106	tmp_options = kstrdup(opts, GFP_KERNEL);
107	if (!tmp_options) {
108		ret = -ENOMEM;
109		goto fail_option_alloc;
110	}
111	options = tmp_options;
112
113	while ((p = strsep(&options, ",")) != NULL) {
114		int token;
115		if (!*p)
116			continue;
117		token = match_token(p, tokens, args);
118		if (token < Opt_uname) {
119			int r = match_int(&args[0], &option);
120			if (r < 0) {
121				P9_DPRINTK(P9_DEBUG_ERROR,
122					"integer field, but no integer?\n");
123				ret = r;
124				continue;
125			}
126		}
127		switch (token) {
128		case Opt_debug:
129			v9ses->debug = option;
130#ifdef CONFIG_NET_9P_DEBUG
131			p9_debug_level = option;
132#endif
133			break;
134
135		case Opt_dfltuid:
136			v9ses->dfltuid = option;
137			break;
138		case Opt_dfltgid:
139			v9ses->dfltgid = option;
140			break;
141		case Opt_afid:
142			v9ses->afid = option;
143			break;
144		case Opt_uname:
145			match_strlcpy(v9ses->uname, &args[0], PATH_MAX);
146			break;
147		case Opt_remotename:
148			match_strlcpy(v9ses->aname, &args[0], PATH_MAX);
149			break;
150		case Opt_nodevmap:
151			v9ses->nodev = 1;
152			break;
153		case Opt_cache_loose:
154			v9ses->cache = CACHE_LOOSE;
155			break;
156		case Opt_fscache:
157			v9ses->cache = CACHE_FSCACHE;
158			break;
159		case Opt_cachetag:
160#ifdef CONFIG_9P_FSCACHE
161			v9ses->cachetag = match_strdup(&args[0]);
162#endif
163			break;
164		case Opt_cache:
165			s = match_strdup(&args[0]);
166			if (!s) {
167				ret = -ENOMEM;
168				P9_DPRINTK(P9_DEBUG_ERROR,
169				  "problem allocating copy of cache arg\n");
170				goto free_and_return;
171			}
172
173			if (strcmp(s, "loose") == 0)
174				v9ses->cache = CACHE_LOOSE;
175			else if (strcmp(s, "fscache") == 0)
176				v9ses->cache = CACHE_FSCACHE;
177			else
178				v9ses->cache = CACHE_NONE;
179			kfree(s);
180			break;
181
182		case Opt_access:
183			s = match_strdup(&args[0]);
184			if (!s) {
185				ret = -ENOMEM;
186				P9_DPRINTK(P9_DEBUG_ERROR,
187				  "problem allocating copy of access arg\n");
188				goto free_and_return;
189			}
190
191			v9ses->flags &= ~V9FS_ACCESS_MASK;
192			if (strcmp(s, "user") == 0)
193				v9ses->flags |= V9FS_ACCESS_USER;
194			else if (strcmp(s, "any") == 0)
195				v9ses->flags |= V9FS_ACCESS_ANY;
196			else {
197				v9ses->flags |= V9FS_ACCESS_SINGLE;
198				v9ses->uid = simple_strtoul(s, &e, 10);
199				if (*e != '\0')
200					v9ses->uid = ~0;
201			}
202			kfree(s);
203			break;
204
205		default:
206			continue;
207		}
208	}
209
210free_and_return:
211	kfree(tmp_options);
212fail_option_alloc:
213	return ret;
214}
215
216/**
217 * v9fs_session_init - initialize session
218 * @v9ses: session information structure
219 * @dev_name: device being mounted
220 * @data: options
221 *
222 */
223
224struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
225		  const char *dev_name, char *data)
226{
227	int retval = -EINVAL;
228	struct p9_fid *fid;
229	int rc;
230
231	v9ses->uname = __getname();
232	if (!v9ses->uname)
233		return ERR_PTR(-ENOMEM);
234
235	v9ses->aname = __getname();
236	if (!v9ses->aname) {
237		__putname(v9ses->uname);
238		return ERR_PTR(-ENOMEM);
239	}
240	init_rwsem(&v9ses->rename_sem);
241
242	rc = bdi_setup_and_register(&v9ses->bdi, "9p", BDI_CAP_MAP_COPY);
243	if (rc) {
244		__putname(v9ses->aname);
245		__putname(v9ses->uname);
246		return ERR_PTR(rc);
247	}
248
249	spin_lock(&v9fs_sessionlist_lock);
250	list_add(&v9ses->slist, &v9fs_sessionlist);
251	spin_unlock(&v9fs_sessionlist_lock);
252
253	v9ses->flags = V9FS_ACCESS_USER;
254	strcpy(v9ses->uname, V9FS_DEFUSER);
255	strcpy(v9ses->aname, V9FS_DEFANAME);
256	v9ses->uid = ~0;
257	v9ses->dfltuid = V9FS_DEFUID;
258	v9ses->dfltgid = V9FS_DEFGID;
259
260	rc = v9fs_parse_options(v9ses, data);
261	if (rc < 0) {
262		retval = rc;
263		goto error;
264	}
265
266	v9ses->clnt = p9_client_create(dev_name, data);
267	if (IS_ERR(v9ses->clnt)) {
268		retval = PTR_ERR(v9ses->clnt);
269		v9ses->clnt = NULL;
270		P9_DPRINTK(P9_DEBUG_ERROR, "problem initializing 9p client\n");
271		goto error;
272	}
273
274	if (p9_is_proto_dotl(v9ses->clnt))
275		v9ses->flags |= V9FS_PROTO_2000L;
276	else if (p9_is_proto_dotu(v9ses->clnt))
277		v9ses->flags |= V9FS_PROTO_2000U;
278
279	v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ;
280
281	/* for legacy mode, fall back to V9FS_ACCESS_ANY */
282	if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) &&
283		((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) {
284
285		v9ses->flags &= ~V9FS_ACCESS_MASK;
286		v9ses->flags |= V9FS_ACCESS_ANY;
287		v9ses->uid = ~0;
288	}
289
290	fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, ~0,
291							v9ses->aname);
292	if (IS_ERR(fid)) {
293		retval = PTR_ERR(fid);
294		fid = NULL;
295		P9_DPRINTK(P9_DEBUG_ERROR, "cannot attach\n");
296		goto error;
297	}
298
299	if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
300		fid->uid = v9ses->uid;
301	else
302		fid->uid = ~0;
303
304#ifdef CONFIG_9P_FSCACHE
305	/* register the session for caching */
306	v9fs_cache_session_get_cookie(v9ses);
307#endif
308
309	return fid;
310
311error:
312	bdi_destroy(&v9ses->bdi);
313	return ERR_PTR(retval);
314}
315
316/**
317 * v9fs_session_close - shutdown a session
318 * @v9ses: session information structure
319 *
320 */
321
322void v9fs_session_close(struct v9fs_session_info *v9ses)
323{
324	if (v9ses->clnt) {
325		p9_client_destroy(v9ses->clnt);
326		v9ses->clnt = NULL;
327	}
328
329#ifdef CONFIG_9P_FSCACHE
330	if (v9ses->fscache) {
331		v9fs_cache_session_put_cookie(v9ses);
332		kfree(v9ses->cachetag);
333	}
334#endif
335	__putname(v9ses->uname);
336	__putname(v9ses->aname);
337
338	bdi_destroy(&v9ses->bdi);
339
340	spin_lock(&v9fs_sessionlist_lock);
341	list_del(&v9ses->slist);
342	spin_unlock(&v9fs_sessionlist_lock);
343}
344
345/**
346 * v9fs_session_cancel - terminate a session
347 * @v9ses: session to terminate
348 *
349 * mark transport as disconnected and cancel all pending requests.
350 */
351
352void v9fs_session_cancel(struct v9fs_session_info *v9ses) {
353	P9_DPRINTK(P9_DEBUG_ERROR, "cancel session %p\n", v9ses);
354	p9_client_disconnect(v9ses->clnt);
355}
356
357/**
358 * v9fs_session_begin_cancel - Begin terminate of a session
359 * @v9ses: session to terminate
360 *
361 * After this call we don't allow any request other than clunk.
362 */
363
364void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses)
365{
366	P9_DPRINTK(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
367	p9_client_begin_disconnect(v9ses->clnt);
368}
369
370extern int v9fs_error_init(void);
371
372static struct kobject *v9fs_kobj;
373
374#ifdef CONFIG_9P_FSCACHE
375/**
376 * caches_show - list caches associated with a session
377 *
378 * Returns the size of buffer written.
379 */
380
381static ssize_t caches_show(struct kobject *kobj,
382			   struct kobj_attribute *attr,
383			   char *buf)
384{
385	ssize_t n = 0, count = 0, limit = PAGE_SIZE;
386	struct v9fs_session_info *v9ses;
387
388	spin_lock(&v9fs_sessionlist_lock);
389	list_for_each_entry(v9ses, &v9fs_sessionlist, slist) {
390		if (v9ses->cachetag) {
391			n = snprintf(buf, limit, "%s\n", v9ses->cachetag);
392			if (n < 0) {
393				count = n;
394				break;
395			}
396
397			count += n;
398			limit -= n;
399		}
400	}
401
402	spin_unlock(&v9fs_sessionlist_lock);
403	return count;
404}
405
406static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches);
407#endif /* CONFIG_9P_FSCACHE */
408
409static struct attribute *v9fs_attrs[] = {
410#ifdef CONFIG_9P_FSCACHE
411	&v9fs_attr_cache.attr,
412#endif
413	NULL,
414};
415
416static struct attribute_group v9fs_attr_group = {
417	.attrs = v9fs_attrs,
418};
419
420/**
421 * v9fs_sysfs_init - Initialize the v9fs sysfs interface
422 *
423 */
424
425static int v9fs_sysfs_init(void)
426{
427	v9fs_kobj = kobject_create_and_add("9p", fs_kobj);
428	if (!v9fs_kobj)
429		return -ENOMEM;
430
431	if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) {
432		kobject_put(v9fs_kobj);
433		return -ENOMEM;
434	}
435
436	return 0;
437}
438
439/**
440 * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface
441 *
442 */
443
444static void v9fs_sysfs_cleanup(void)
445{
446	sysfs_remove_group(v9fs_kobj, &v9fs_attr_group);
447	kobject_put(v9fs_kobj);
448}
449
450/**
451 * init_v9fs - Initialize module
452 *
453 */
454
455static int __init init_v9fs(void)
456{
457	int err;
458	printk(KERN_INFO "Installing v9fs 9p2000 file system support\n");
459	/* TODO: Setup list of registered trasnport modules */
460	err = register_filesystem(&v9fs_fs_type);
461	if (err < 0) {
462		printk(KERN_ERR "Failed to register filesystem\n");
463		return err;
464	}
465
466	err = v9fs_cache_register();
467	if (err < 0) {
468		printk(KERN_ERR "Failed to register v9fs for caching\n");
469		goto out_fs_unreg;
470	}
471
472	err = v9fs_sysfs_init();
473	if (err < 0) {
474		printk(KERN_ERR "Failed to register with sysfs\n");
475		goto out_sysfs_cleanup;
476	}
477
478	return 0;
479
480out_sysfs_cleanup:
481	v9fs_sysfs_cleanup();
482
483out_fs_unreg:
484	unregister_filesystem(&v9fs_fs_type);
485
486	return err;
487}
488
489/**
490 * exit_v9fs - shutdown module
491 *
492 */
493
494static void __exit exit_v9fs(void)
495{
496	v9fs_sysfs_cleanup();
497	v9fs_cache_unregister();
498	unregister_filesystem(&v9fs_fs_type);
499}
500
501module_init(init_v9fs)
502module_exit(exit_v9fs)
503
504MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
505MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
506MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
507MODULE_LICENSE("GPL");
508