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