Commit | Line | Data |
---|---|---|
9e82cf6a EVH |
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 | |
42e8c509 EVH |
10 | * it under the terms of the GNU General Public License version 2 |
11 | * as published by the Free Software Foundation. | |
9e82cf6a EVH |
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 | ||
9e82cf6a EVH |
26 | #include <linux/module.h> |
27 | #include <linux/errno.h> | |
28 | #include <linux/fs.h> | |
914e2637 | 29 | #include <linux/sched.h> |
9e82cf6a EVH |
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" | |
9e82cf6a EVH |
39 | |
40 | /* TODO: sysfs or debugfs interface */ | |
41 | int v9fs_debug_level = 0; /* feature-rific global debug level */ | |
42 | ||
43 | /* | |
44 | * Option Parsing (code inspired by NFS code) | |
45 | * | |
46 | */ | |
47 | ||
48 | enum { | |
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 */ | |
67543e50 | 53 | Opt_uname, Opt_remotename, |
9e82cf6a EVH |
54 | /* Options that take no arguments */ |
55 | Opt_legacy, Opt_nodevmap, Opt_unix, Opt_tcp, Opt_fd, | |
e03abc0c EVH |
56 | /* Cache options */ |
57 | Opt_cache_loose, | |
9e82cf6a EVH |
58 | /* Error token */ |
59 | Opt_err | |
60 | }; | |
61 | ||
62 | static 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"}, | |
e1c92117 | 70 | {Opt_debug, "debug=%x"}, |
67543e50 | 71 | {Opt_uname, "uname=%s"}, |
9e82cf6a EVH |
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"}, | |
e03abc0c EVH |
81 | {Opt_cache_loose, "cache=loose"}, |
82 | {Opt_cache_loose, "loose"}, | |
9e82cf6a EVH |
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 | ||
97 | static 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; | |
e03abc0c | 113 | v9ses->cache = 0; |
9e82cf6a EVH |
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); | |
67543e50 | 123 | if (token < Opt_uname) { |
9e82cf6a EVH |
124 | if ((ret = match_int(&args[0], &option)) < 0) { |
125 | dprintk(DEBUG_ERROR, | |
126 | "integer field, but no integer?\n"); | |
127 | continue; | |
128 | } | |
9e82cf6a EVH |
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; | |
67543e50 | 164 | case Opt_uname: |
9e82cf6a EVH |
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; | |
e03abc0c EVH |
176 | case Opt_cache_loose: |
177 | v9ses->cache = CACHE_LOOSE; | |
178 | break; | |
9e82cf6a EVH |
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 | ||
194 | struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode) | |
195 | { | |
196 | return (inode->i_sb->s_fs_info); | |
197 | } | |
198 | ||
199 | /** | |
200 | * v9fs_get_idpool - allocate numeric id from pool | |
201 | * @p - pool to allocate from | |
202 | * | |
203 | * XXX - This seems to be an awful generic function, should it be in idr.c with | |
204 | * the lock included in struct idr? | |
205 | */ | |
206 | ||
207 | int v9fs_get_idpool(struct v9fs_idpool *p) | |
208 | { | |
209 | int i = 0; | |
210 | int error; | |
211 | ||
212 | retry: | |
213 | if (idr_pre_get(&p->pool, GFP_KERNEL) == 0) | |
214 | return 0; | |
215 | ||
216 | if (down_interruptible(&p->lock) == -EINTR) { | |
217 | eprintk(KERN_WARNING, "Interrupted while locking\n"); | |
218 | return -1; | |
219 | } | |
220 | ||
3cf6429a LI |
221 | /* no need to store exactly p, we just need something non-null */ |
222 | error = idr_get_new(&p->pool, p, &i); | |
9e82cf6a EVH |
223 | up(&p->lock); |
224 | ||
225 | if (error == -EAGAIN) | |
226 | goto retry; | |
227 | else if (error) | |
228 | return -1; | |
229 | ||
230 | return i; | |
231 | } | |
232 | ||
233 | /** | |
234 | * v9fs_put_idpool - release numeric id from pool | |
235 | * @p - pool to allocate from | |
236 | * | |
237 | * XXX - This seems to be an awful generic function, should it be in idr.c with | |
238 | * the lock included in struct idr? | |
239 | */ | |
240 | ||
241 | void v9fs_put_idpool(int id, struct v9fs_idpool *p) | |
242 | { | |
243 | if (down_interruptible(&p->lock) == -EINTR) { | |
244 | eprintk(KERN_WARNING, "Interrupted while locking\n"); | |
245 | return; | |
246 | } | |
247 | idr_remove(&p->pool, id); | |
248 | up(&p->lock); | |
249 | } | |
250 | ||
3cf6429a LI |
251 | /** |
252 | * v9fs_check_idpool - check if the specified id is available | |
253 | * @id - id to check | |
254 | * @p - pool | |
255 | */ | |
256 | int v9fs_check_idpool(int id, struct v9fs_idpool *p) | |
257 | { | |
258 | return idr_find(&p->pool, id) != NULL; | |
259 | } | |
260 | ||
9e82cf6a EVH |
261 | /** |
262 | * v9fs_session_init - initialize session | |
263 | * @v9ses: session information structure | |
264 | * @dev_name: device being mounted | |
265 | * @data: options | |
266 | * | |
267 | */ | |
268 | ||
269 | int | |
270 | v9fs_session_init(struct v9fs_session_info *v9ses, | |
271 | const char *dev_name, char *data) | |
272 | { | |
273 | struct v9fs_fcall *fcall = NULL; | |
274 | struct v9fs_transport *trans_proto; | |
275 | int n = 0; | |
276 | int newfid = -1; | |
277 | int retval = -EINVAL; | |
1dac06b2 | 278 | struct v9fs_str *version; |
9e82cf6a EVH |
279 | |
280 | v9ses->name = __getname(); | |
281 | if (!v9ses->name) | |
282 | return -ENOMEM; | |
283 | ||
284 | v9ses->remotename = __getname(); | |
285 | if (!v9ses->remotename) { | |
ce44eeb6 | 286 | __putname(v9ses->name); |
9e82cf6a EVH |
287 | return -ENOMEM; |
288 | } | |
289 | ||
290 | strcpy(v9ses->name, V9FS_DEFUSER); | |
291 | strcpy(v9ses->remotename, V9FS_DEFANAME); | |
292 | ||
293 | v9fs_parse_options(data, v9ses); | |
294 | ||
295 | /* set global debug level */ | |
296 | v9fs_debug_level = v9ses->debug; | |
297 | ||
4a26c242 | 298 | /* id pools that are session-dependent: fids and tags */ |
9e82cf6a EVH |
299 | idr_init(&v9ses->fidpool.pool); |
300 | init_MUTEX(&v9ses->fidpool.lock); | |
9e82cf6a EVH |
301 | |
302 | switch (v9ses->proto) { | |
303 | case PROTO_TCP: | |
304 | trans_proto = &v9fs_trans_tcp; | |
305 | break; | |
306 | case PROTO_UNIX: | |
307 | trans_proto = &v9fs_trans_unix; | |
308 | *v9ses->remotename = 0; | |
309 | break; | |
310 | case PROTO_FD: | |
311 | trans_proto = &v9fs_trans_fd; | |
312 | *v9ses->remotename = 0; | |
9e82cf6a EVH |
313 | break; |
314 | default: | |
315 | printk(KERN_ERR "v9fs: Bad mount protocol %d\n", v9ses->proto); | |
316 | retval = -ENOPROTOOPT; | |
317 | goto SessCleanUp; | |
318 | }; | |
319 | ||
a8e63bff LI |
320 | v9ses->transport = kmalloc(sizeof(*v9ses->transport), GFP_KERNEL); |
321 | if (!v9ses->transport) { | |
322 | retval = -ENOMEM; | |
323 | goto SessCleanUp; | |
324 | } | |
325 | ||
326 | memmove(v9ses->transport, trans_proto, sizeof(*v9ses->transport)); | |
9e82cf6a EVH |
327 | |
328 | if ((retval = v9ses->transport->init(v9ses, dev_name, data)) < 0) { | |
329 | eprintk(KERN_ERR, "problem initializing transport\n"); | |
330 | goto SessCleanUp; | |
331 | } | |
332 | ||
333 | v9ses->inprogress = 0; | |
334 | v9ses->shutdown = 0; | |
335 | v9ses->session_hung = 0; | |
336 | ||
3cf6429a LI |
337 | v9ses->mux = v9fs_mux_init(v9ses->transport, v9ses->maxdata + V9FS_IOHDRSZ, |
338 | &v9ses->extended); | |
339 | ||
340 | if (IS_ERR(v9ses->mux)) { | |
341 | retval = PTR_ERR(v9ses->mux); | |
342 | v9ses->mux = NULL; | |
9e82cf6a EVH |
343 | dprintk(DEBUG_ERROR, "problem initializing mux\n"); |
344 | goto SessCleanUp; | |
345 | } | |
346 | ||
347 | if (v9ses->afid == ~0) { | |
348 | if (v9ses->extended) | |
349 | retval = | |
350 | v9fs_t_version(v9ses, v9ses->maxdata, "9P2000.u", | |
351 | &fcall); | |
352 | else | |
353 | retval = v9fs_t_version(v9ses, v9ses->maxdata, "9P2000", | |
354 | &fcall); | |
355 | ||
356 | if (retval < 0) { | |
357 | dprintk(DEBUG_ERROR, "v9fs_t_version failed\n"); | |
358 | goto FreeFcall; | |
359 | } | |
360 | ||
1dac06b2 LI |
361 | version = &fcall->params.rversion.version; |
362 | if (version->len==8 && !memcmp(version->str, "9P2000.u", 8)) { | |
9e82cf6a EVH |
363 | dprintk(DEBUG_9P, "9P2000 UNIX extensions enabled\n"); |
364 | v9ses->extended = 1; | |
1dac06b2 | 365 | } else if (version->len==6 && !memcmp(version->str, "9P2000", 6)) { |
9e82cf6a EVH |
366 | dprintk(DEBUG_9P, "9P2000 legacy mode enabled\n"); |
367 | v9ses->extended = 0; | |
1dac06b2 LI |
368 | } else { |
369 | retval = -EREMOTEIO; | |
370 | goto FreeFcall; | |
9e82cf6a EVH |
371 | } |
372 | ||
373 | n = fcall->params.rversion.msize; | |
374 | kfree(fcall); | |
375 | ||
376 | if (n < v9ses->maxdata) | |
377 | v9ses->maxdata = n; | |
378 | } | |
379 | ||
380 | newfid = v9fs_get_idpool(&v9ses->fidpool); | |
381 | if (newfid < 0) { | |
382 | eprintk(KERN_WARNING, "couldn't allocate FID\n"); | |
383 | retval = -ENOMEM; | |
384 | goto SessCleanUp; | |
385 | } | |
386 | /* it is a little bit ugly, but we have to prevent newfid */ | |
387 | /* being the same as afid, so if it is, get a new fid */ | |
388 | if (v9ses->afid != ~0 && newfid == v9ses->afid) { | |
389 | newfid = v9fs_get_idpool(&v9ses->fidpool); | |
390 | if (newfid < 0) { | |
391 | eprintk(KERN_WARNING, "couldn't allocate FID\n"); | |
392 | retval = -ENOMEM; | |
393 | goto SessCleanUp; | |
394 | } | |
395 | } | |
396 | ||
397 | if ((retval = | |
398 | v9fs_t_attach(v9ses, v9ses->name, v9ses->remotename, newfid, | |
399 | v9ses->afid, NULL)) | |
400 | < 0) { | |
401 | dprintk(DEBUG_ERROR, "cannot attach\n"); | |
402 | goto SessCleanUp; | |
403 | } | |
404 | ||
405 | if (v9ses->afid != ~0) { | |
46f6dac2 | 406 | dprintk(DEBUG_ERROR, "afid not equal to ~0\n"); |
3cf6429a | 407 | if (v9fs_t_clunk(v9ses, v9ses->afid)) |
9e82cf6a EVH |
408 | dprintk(DEBUG_ERROR, "clunk failed\n"); |
409 | } | |
410 | ||
411 | return newfid; | |
412 | ||
413 | FreeFcall: | |
414 | kfree(fcall); | |
415 | ||
416 | SessCleanUp: | |
417 | v9fs_session_close(v9ses); | |
418 | return retval; | |
419 | } | |
420 | ||
421 | /** | |
422 | * v9fs_session_close - shutdown a session | |
423 | * @v9ses: session information structure | |
424 | * | |
425 | */ | |
426 | ||
427 | void v9fs_session_close(struct v9fs_session_info *v9ses) | |
428 | { | |
3cf6429a LI |
429 | if (v9ses->mux) { |
430 | v9fs_mux_destroy(v9ses->mux); | |
431 | v9ses->mux = NULL; | |
9e82cf6a EVH |
432 | } |
433 | ||
3cf6429a | 434 | if (v9ses->transport) { |
9e82cf6a | 435 | v9ses->transport->close(v9ses->transport); |
3cf6429a LI |
436 | kfree(v9ses->transport); |
437 | v9ses->transport = NULL; | |
438 | } | |
9e82cf6a | 439 | |
ce44eeb6 DA |
440 | __putname(v9ses->name); |
441 | __putname(v9ses->remotename); | |
9e82cf6a EVH |
442 | } |
443 | ||
322b329a EVH |
444 | /** |
445 | * v9fs_session_cancel - mark transport as disconnected | |
446 | * and cancel all pending requests. | |
447 | */ | |
448 | void v9fs_session_cancel(struct v9fs_session_info *v9ses) { | |
3cf6429a | 449 | dprintk(DEBUG_ERROR, "cancel session %p\n", v9ses); |
322b329a | 450 | v9ses->transport->status = Disconnected; |
3cf6429a | 451 | v9fs_mux_cancel(v9ses->mux, -EIO); |
322b329a EVH |
452 | } |
453 | ||
9e82cf6a EVH |
454 | extern int v9fs_error_init(void); |
455 | ||
456 | /** | |
457 | * v9fs_init - Initialize module | |
458 | * | |
459 | */ | |
460 | ||
461 | static int __init init_v9fs(void) | |
462 | { | |
1dac06b2 LI |
463 | int ret; |
464 | ||
9e82cf6a EVH |
465 | v9fs_error_init(); |
466 | ||
f94b3470 | 467 | printk(KERN_INFO "Installing v9fs 9p2000 file system support\n"); |
9e82cf6a | 468 | |
1dac06b2 | 469 | ret = v9fs_mux_global_init(); |
f94b3470 EVH |
470 | if (ret) { |
471 | printk(KERN_WARNING "v9fs: starting mux failed\n"); | |
d826380b | 472 | return ret; |
f94b3470 | 473 | } |
d826380b | 474 | ret = register_filesystem(&v9fs_fs_type); |
f94b3470 EVH |
475 | if (ret) { |
476 | printk(KERN_WARNING "v9fs: registering file system failed\n"); | |
d826380b | 477 | v9fs_mux_global_exit(); |
f94b3470 EVH |
478 | } |
479 | ||
1dac06b2 | 480 | return ret; |
9e82cf6a EVH |
481 | } |
482 | ||
483 | /** | |
484 | * v9fs_init - shutdown module | |
485 | * | |
486 | */ | |
487 | ||
488 | static void __exit exit_v9fs(void) | |
489 | { | |
3cf6429a | 490 | v9fs_mux_global_exit(); |
9e82cf6a EVH |
491 | unregister_filesystem(&v9fs_fs_type); |
492 | } | |
493 | ||
494 | module_init(init_v9fs) | |
495 | module_exit(exit_v9fs) | |
496 | ||
497 | MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); | |
498 | MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>"); | |
499 | MODULE_LICENSE("GPL"); |