1/*
2 *  linux/fs/9p/v9fs.c
3 *
4 *  This file contains functions assisting in mapping VFS to 9P2000
5 *
6 *  Copyright (C) 2004 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
33#include "debug.h"
34#include "v9fs.h"
35#include "9p.h"
36#include "v9fs_vfs.h"
37#include "transport.h"
38#include "mux.h"
39
40/* TODO: sysfs or debugfs interface */
41int v9fs_debug_level = 0;	/* feature-rific global debug level  */
42
43/*
44  * Option Parsing (code inspired by NFS code)
45  *
46  */
47
48enum {
49	/* Options that take integer arguments */
50	Opt_port, Opt_msize, Opt_uid, Opt_gid, Opt_afid, Opt_debug,
51	Opt_rfdno, Opt_wfdno,
52	/* String options */
53	Opt_uname, Opt_remotename,
54	/* Options that take no arguments */
55	Opt_legacy, Opt_nodevmap, Opt_unix, Opt_tcp, Opt_fd,
56	/* Cache options */
57	Opt_cache_loose,
58	/* Error token */
59	Opt_err
60};
61
62static match_table_t tokens = {
63	{Opt_port, "port=%u"},
64	{Opt_msize, "msize=%u"},
65	{Opt_uid, "uid=%u"},
66	{Opt_gid, "gid=%u"},
67	{Opt_afid, "afid=%u"},
68	{Opt_rfdno, "rfdno=%u"},
69	{Opt_wfdno, "wfdno=%u"},
70	{Opt_debug, "debug=%x"},
71	{Opt_uname, "uname=%s"},
72	{Opt_remotename, "aname=%s"},
73	{Opt_unix, "proto=unix"},
74	{Opt_tcp, "proto=tcp"},
75	{Opt_fd, "proto=fd"},
76	{Opt_tcp, "tcp"},
77	{Opt_unix, "unix"},
78	{Opt_fd, "fd"},
79	{Opt_legacy, "noextend"},
80	{Opt_nodevmap, "nodevmap"},
81	{Opt_cache_loose, "cache=loose"},
82	{Opt_cache_loose, "loose"},
83	{Opt_err, NULL}
84};
85
86/*
87 *  Parse option string.
88 */
89
90/**
91 * v9fs_parse_options - parse mount options into session structure
92 * @options: options string passed from mount
93 * @v9ses: existing v9fs session information
94 *
95 */
96
97static void v9fs_parse_options(char *options, struct v9fs_session_info *v9ses)
98{
99	char *p;
100	substring_t args[MAX_OPT_ARGS];
101	int option;
102	int ret;
103
104	/* setup defaults */
105	v9ses->port = V9FS_PORT;
106	v9ses->maxdata = 9000;
107	v9ses->proto = PROTO_TCP;
108	v9ses->extended = 1;
109	v9ses->afid = ~0;
110	v9ses->debug = 0;
111	v9ses->rfdno = ~0;
112	v9ses->wfdno = ~0;
113	v9ses->cache = 0;
114
115	if (!options)
116		return;
117
118	while ((p = strsep(&options, ",")) != NULL) {
119		int token;
120		if (!*p)
121			continue;
122		token = match_token(p, tokens, args);
123		if (token < Opt_uname) {
124			if ((ret = match_int(&args[0], &option)) < 0) {
125				dprintk(DEBUG_ERROR,
126					"integer field, but no integer?\n");
127				continue;
128			}
129		}
130		switch (token) {
131		case Opt_port:
132			v9ses->port = option;
133			break;
134		case Opt_msize:
135			v9ses->maxdata = option;
136			break;
137		case Opt_uid:
138			v9ses->uid = option;
139			break;
140		case Opt_gid:
141			v9ses->gid = option;
142			break;
143		case Opt_afid:
144			v9ses->afid = option;
145			break;
146		case Opt_rfdno:
147			v9ses->rfdno = option;
148			break;
149		case Opt_wfdno:
150			v9ses->wfdno = option;
151			break;
152		case Opt_debug:
153			v9ses->debug = option;
154			break;
155		case Opt_tcp:
156			v9ses->proto = PROTO_TCP;
157			break;
158		case Opt_unix:
159			v9ses->proto = PROTO_UNIX;
160			break;
161		case Opt_fd:
162			v9ses->proto = PROTO_FD;
163			break;
164		case Opt_uname:
165			match_strcpy(v9ses->name, &args[0]);
166			break;
167		case Opt_remotename:
168			match_strcpy(v9ses->remotename, &args[0]);
169			break;
170		case Opt_legacy:
171			v9ses->extended = 0;
172			break;
173		case Opt_nodevmap:
174			v9ses->nodev = 1;
175			break;
176		case Opt_cache_loose:
177			v9ses->cache = CACHE_LOOSE;
178			break;
179		default:
180			continue;
181		}
182	}
183}
184
185/**
186 * v9fs_inode2v9ses - safely extract v9fs session info from super block
187 * @inode: inode to extract information from
188 *
189 * Paranoid function to extract v9ses information from superblock,
190 * if anything is missing it will report an error.
191 *
192 */
193
194struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode)
195{
196	return (inode->i_sb->s_fs_info);
197}
198
199
200int v9fs_get_idpool(struct v9fs_idpool *p)
201{
202	int i = 0;
203	int error;
204
205retry:
206	if (idr_pre_get(&p->pool, GFP_KERNEL) == 0)
207		return 0;
208
209	if (down_interruptible(&p->lock) == -EINTR) {
210		eprintk(KERN_WARNING, "Interrupted while locking\n");
211		return -1;
212	}
213
214	/* no need to store exactly p, we just need something non-null */
215	error = idr_get_new(&p->pool, p, &i);
216	up(&p->lock);
217
218	if (error == -EAGAIN)
219		goto retry;
220	else if (error)
221		return -1;
222
223	return i;
224}
225
226
227void v9fs_put_idpool(int id, struct v9fs_idpool *p)
228{
229	if (down_interruptible(&p->lock) == -EINTR) {
230		eprintk(KERN_WARNING, "Interrupted while locking\n");
231		return;
232	}
233	idr_remove(&p->pool, id);
234	up(&p->lock);
235}
236
237/**
238 * v9fs_check_idpool - check if the specified id is available
239 * @id - id to check
240 * @p - pool
241 */
242int v9fs_check_idpool(int id, struct v9fs_idpool *p)
243{
244	return idr_find(&p->pool, id) != NULL;
245}
246
247/**
248 * v9fs_session_init - initialize session
249 * @v9ses: session information structure
250 * @dev_name: device being mounted
251 * @data: options
252 *
253 */
254
255int
256v9fs_session_init(struct v9fs_session_info *v9ses,
257		  const char *dev_name, char *data)
258{
259	struct v9fs_fcall *fcall = NULL;
260	struct v9fs_transport *trans_proto;
261	int n = 0;
262	int newfid = -1;
263	int retval = -EINVAL;
264	struct v9fs_str *version;
265
266	v9ses->name = __getname();
267	if (!v9ses->name)
268		return -ENOMEM;
269
270	v9ses->remotename = __getname();
271	if (!v9ses->remotename) {
272		__putname(v9ses->name);
273		return -ENOMEM;
274	}
275
276	strcpy(v9ses->name, V9FS_DEFUSER);
277	strcpy(v9ses->remotename, V9FS_DEFANAME);
278
279	v9fs_parse_options(data, v9ses);
280
281	/* set global debug level */
282	v9fs_debug_level = v9ses->debug;
283
284	/* id pools that are session-dependent: fids and tags */
285	idr_init(&v9ses->fidpool.pool);
286	init_MUTEX(&v9ses->fidpool.lock);
287
288	switch (v9ses->proto) {
289	case PROTO_TCP:
290		trans_proto = &v9fs_trans_tcp;
291		break;
292	case PROTO_UNIX:
293		trans_proto = &v9fs_trans_unix;
294		*v9ses->remotename = 0;
295		break;
296	case PROTO_FD:
297		trans_proto = &v9fs_trans_fd;
298		*v9ses->remotename = 0;
299		break;
300	default:
301		printk(KERN_ERR "v9fs: Bad mount protocol %d\n", v9ses->proto);
302		retval = -ENOPROTOOPT;
303		goto SessCleanUp;
304	};
305
306	v9ses->transport = kmalloc(sizeof(*v9ses->transport), GFP_KERNEL);
307	if (!v9ses->transport) {
308		retval = -ENOMEM;
309		goto SessCleanUp;
310	}
311
312	memmove(v9ses->transport, trans_proto, sizeof(*v9ses->transport));
313
314	if ((retval = v9ses->transport->init(v9ses, dev_name, data)) < 0) {
315		eprintk(KERN_ERR, "problem initializing transport\n");
316		goto SessCleanUp;
317	}
318
319	v9ses->inprogress = 0;
320	v9ses->shutdown = 0;
321	v9ses->session_hung = 0;
322
323	v9ses->mux = v9fs_mux_init(v9ses->transport, v9ses->maxdata + V9FS_IOHDRSZ,
324		&v9ses->extended);
325
326	if (IS_ERR(v9ses->mux)) {
327		retval = PTR_ERR(v9ses->mux);
328		v9ses->mux = NULL;
329		dprintk(DEBUG_ERROR, "problem initializing mux\n");
330		goto SessCleanUp;
331	}
332
333	if (v9ses->afid == ~0) {
334		if (v9ses->extended)
335			retval =
336			    v9fs_t_version(v9ses, v9ses->maxdata, "9P2000.u",
337					   &fcall);
338		else
339			retval = v9fs_t_version(v9ses, v9ses->maxdata, "9P2000",
340						&fcall);
341
342		if (retval < 0) {
343			dprintk(DEBUG_ERROR, "v9fs_t_version failed\n");
344			goto FreeFcall;
345		}
346
347		version = &fcall->params.rversion.version;
348		if (version->len==8 && !memcmp(version->str, "9P2000.u", 8)) {
349			dprintk(DEBUG_9P, "9P2000 UNIX extensions enabled\n");
350			v9ses->extended = 1;
351		} else if (version->len==6 && !memcmp(version->str, "9P2000", 6)) {
352			dprintk(DEBUG_9P, "9P2000 legacy mode enabled\n");
353			v9ses->extended = 0;
354		} else {
355			retval = -EREMOTEIO;
356			goto FreeFcall;
357		}
358
359		n = fcall->params.rversion.msize;
360		kfree(fcall);
361
362		if (n < v9ses->maxdata)
363			v9ses->maxdata = n;
364	}
365
366	newfid = v9fs_get_idpool(&v9ses->fidpool);
367	if (newfid < 0) {
368		eprintk(KERN_WARNING, "couldn't allocate FID\n");
369		retval = -ENOMEM;
370		goto SessCleanUp;
371	}
372	/* it is a little bit ugly, but we have to prevent newfid */
373	/* being the same as afid, so if it is, get a new fid     */
374	if (v9ses->afid != ~0 && newfid == v9ses->afid) {
375		newfid = v9fs_get_idpool(&v9ses->fidpool);
376		if (newfid < 0) {
377			eprintk(KERN_WARNING, "couldn't allocate FID\n");
378			retval = -ENOMEM;
379			goto SessCleanUp;
380		}
381	}
382
383	if ((retval =
384	     v9fs_t_attach(v9ses, v9ses->name, v9ses->remotename, newfid,
385			   v9ses->afid, NULL))
386	    < 0) {
387		dprintk(DEBUG_ERROR, "cannot attach\n");
388		goto SessCleanUp;
389	}
390
391	if (v9ses->afid != ~0) {
392		dprintk(DEBUG_ERROR, "afid not equal to ~0\n");
393		if (v9fs_t_clunk(v9ses, v9ses->afid))
394			dprintk(DEBUG_ERROR, "clunk failed\n");
395	}
396
397	return newfid;
398
399      FreeFcall:
400	kfree(fcall);
401
402      SessCleanUp:
403	v9fs_session_close(v9ses);
404	return retval;
405}
406
407/**
408 * v9fs_session_close - shutdown a session
409 * @v9ses: session information structure
410 *
411 */
412
413void v9fs_session_close(struct v9fs_session_info *v9ses)
414{
415	if (v9ses->mux) {
416		v9fs_mux_destroy(v9ses->mux);
417		v9ses->mux = NULL;
418	}
419
420	if (v9ses->transport) {
421		v9ses->transport->close(v9ses->transport);
422		kfree(v9ses->transport);
423		v9ses->transport = NULL;
424	}
425
426	__putname(v9ses->name);
427	__putname(v9ses->remotename);
428}
429
430/**
431 * v9fs_session_cancel - mark transport as disconnected
432 * 	and cancel all pending requests.
433 */
434void v9fs_session_cancel(struct v9fs_session_info *v9ses) {
435	dprintk(DEBUG_ERROR, "cancel session %p\n", v9ses);
436	v9ses->transport->status = Disconnected;
437	v9fs_mux_cancel(v9ses->mux, -EIO);
438}
439
440extern int v9fs_error_init(void);
441
442/**
443 * v9fs_init - Initialize module
444 *
445 */
446
447static int __init init_v9fs(void)
448{
449	int ret;
450
451	v9fs_error_init();
452
453	printk(KERN_INFO "Installing v9fs 9p2000 file system support\n");
454
455	ret = v9fs_mux_global_init();
456	if (ret) {
457		printk(KERN_WARNING "v9fs: starting mux failed\n");
458		return ret;
459	}
460	ret = register_filesystem(&v9fs_fs_type);
461	if (ret) {
462		printk(KERN_WARNING "v9fs: registering file system failed\n");
463		v9fs_mux_global_exit();
464	}
465
466	return ret;
467}
468
469/**
470 * v9fs_init - shutdown module
471 *
472 */
473
474static void __exit exit_v9fs(void)
475{
476	v9fs_mux_global_exit();
477	unregister_filesystem(&v9fs_fs_type);
478}
479
480module_init(init_v9fs)
481module_exit(exit_v9fs)
482
483MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
484MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
485MODULE_LICENSE("GPL");
486