diff options
Diffstat (limited to 'fs/9p/v9fs.c')
-rw-r--r-- | fs/9p/v9fs.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c new file mode 100644 index 000000000000..14d663ebfcbc --- /dev/null +++ b/fs/9p/v9fs.c | |||
@@ -0,0 +1,448 @@ | |||
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 as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to: | ||
21 | * Free Software Foundation | ||
22 | * 51 Franklin Street, Fifth Floor | ||
23 | * Boston, MA 02111-1301 USA | ||
24 | * | ||
25 | */ | ||
26 | |||
27 | #include <linux/config.h> | ||
28 | #include <linux/module.h> | ||
29 | #include <linux/errno.h> | ||
30 | #include <linux/fs.h> | ||
31 | #include <linux/parser.h> | ||
32 | #include <linux/idr.h> | ||
33 | |||
34 | #include "debug.h" | ||
35 | #include "v9fs.h" | ||
36 | #include "9p.h" | ||
37 | #include "v9fs_vfs.h" | ||
38 | #include "transport.h" | ||
39 | #include "mux.h" | ||
40 | #include "conv.h" | ||
41 | |||
42 | /* TODO: sysfs or debugfs interface */ | ||
43 | int v9fs_debug_level = 0; /* feature-rific global debug level */ | ||
44 | |||
45 | /* | ||
46 | * Option Parsing (code inspired by NFS code) | ||
47 | * | ||
48 | */ | ||
49 | |||
50 | enum { | ||
51 | /* Options that take integer arguments */ | ||
52 | Opt_port, Opt_msize, Opt_uid, Opt_gid, Opt_afid, Opt_debug, | ||
53 | Opt_rfdno, Opt_wfdno, | ||
54 | /* String options */ | ||
55 | Opt_name, Opt_remotename, | ||
56 | /* Options that take no arguments */ | ||
57 | Opt_legacy, Opt_nodevmap, Opt_unix, Opt_tcp, Opt_fd, | ||
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"}, | ||
70 | {Opt_debug, "debug=%u"}, | ||
71 | {Opt_name, "name=%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_err, NULL} | ||
82 | }; | ||
83 | |||
84 | /* | ||
85 | * Parse option string. | ||
86 | */ | ||
87 | |||
88 | /** | ||
89 | * v9fs_parse_options - parse mount options into session structure | ||
90 | * @options: options string passed from mount | ||
91 | * @v9ses: existing v9fs session information | ||
92 | * | ||
93 | */ | ||
94 | |||
95 | static void v9fs_parse_options(char *options, struct v9fs_session_info *v9ses) | ||
96 | { | ||
97 | char *p; | ||
98 | substring_t args[MAX_OPT_ARGS]; | ||
99 | int option; | ||
100 | int ret; | ||
101 | |||
102 | /* setup defaults */ | ||
103 | v9ses->port = V9FS_PORT; | ||
104 | v9ses->maxdata = 9000; | ||
105 | v9ses->proto = PROTO_TCP; | ||
106 | v9ses->extended = 1; | ||
107 | v9ses->afid = ~0; | ||
108 | v9ses->debug = 0; | ||
109 | v9ses->rfdno = ~0; | ||
110 | v9ses->wfdno = ~0; | ||
111 | |||
112 | if (!options) | ||
113 | return; | ||
114 | |||
115 | while ((p = strsep(&options, ",")) != NULL) { | ||
116 | int token; | ||
117 | if (!*p) | ||
118 | continue; | ||
119 | token = match_token(p, tokens, args); | ||
120 | if (token < Opt_name) { | ||
121 | if ((ret = match_int(&args[0], &option)) < 0) { | ||
122 | dprintk(DEBUG_ERROR, | ||
123 | "integer field, but no integer?\n"); | ||
124 | continue; | ||
125 | } | ||
126 | |||
127 | } | ||
128 | switch (token) { | ||
129 | case Opt_port: | ||
130 | v9ses->port = option; | ||
131 | break; | ||
132 | case Opt_msize: | ||
133 | v9ses->maxdata = option; | ||
134 | break; | ||
135 | case Opt_uid: | ||
136 | v9ses->uid = option; | ||
137 | break; | ||
138 | case Opt_gid: | ||
139 | v9ses->gid = option; | ||
140 | break; | ||
141 | case Opt_afid: | ||
142 | v9ses->afid = option; | ||
143 | break; | ||
144 | case Opt_rfdno: | ||
145 | v9ses->rfdno = option; | ||
146 | break; | ||
147 | case Opt_wfdno: | ||
148 | v9ses->wfdno = option; | ||
149 | break; | ||
150 | case Opt_debug: | ||
151 | v9ses->debug = option; | ||
152 | break; | ||
153 | case Opt_tcp: | ||
154 | v9ses->proto = PROTO_TCP; | ||
155 | break; | ||
156 | case Opt_unix: | ||
157 | v9ses->proto = PROTO_UNIX; | ||
158 | break; | ||
159 | case Opt_fd: | ||
160 | v9ses->proto = PROTO_FD; | ||
161 | break; | ||
162 | case Opt_name: | ||
163 | match_strcpy(v9ses->name, &args[0]); | ||
164 | break; | ||
165 | case Opt_remotename: | ||
166 | match_strcpy(v9ses->remotename, &args[0]); | ||
167 | break; | ||
168 | case Opt_legacy: | ||
169 | v9ses->extended = 0; | ||
170 | break; | ||
171 | case Opt_nodevmap: | ||
172 | v9ses->nodev = 1; | ||
173 | break; | ||
174 | default: | ||
175 | continue; | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * v9fs_inode2v9ses - safely extract v9fs session info from super block | ||
182 | * @inode: inode to extract information from | ||
183 | * | ||
184 | * Paranoid function to extract v9ses information from superblock, | ||
185 | * if anything is missing it will report an error. | ||
186 | * | ||
187 | */ | ||
188 | |||
189 | struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode) | ||
190 | { | ||
191 | return (inode->i_sb->s_fs_info); | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * v9fs_get_idpool - allocate numeric id from pool | ||
196 | * @p - pool to allocate from | ||
197 | * | ||
198 | * XXX - This seems to be an awful generic function, should it be in idr.c with | ||
199 | * the lock included in struct idr? | ||
200 | */ | ||
201 | |||
202 | int v9fs_get_idpool(struct v9fs_idpool *p) | ||
203 | { | ||
204 | int i = 0; | ||
205 | int error; | ||
206 | |||
207 | retry: | ||
208 | if (idr_pre_get(&p->pool, GFP_KERNEL) == 0) | ||
209 | return 0; | ||
210 | |||
211 | if (down_interruptible(&p->lock) == -EINTR) { | ||
212 | eprintk(KERN_WARNING, "Interrupted while locking\n"); | ||
213 | return -1; | ||
214 | } | ||
215 | |||
216 | error = idr_get_new(&p->pool, NULL, &i); | ||
217 | up(&p->lock); | ||
218 | |||
219 | if (error == -EAGAIN) | ||
220 | goto retry; | ||
221 | else if (error) | ||
222 | return -1; | ||
223 | |||
224 | return i; | ||
225 | } | ||
226 | |||
227 | /** | ||
228 | * v9fs_put_idpool - release numeric id from pool | ||
229 | * @p - pool to allocate from | ||
230 | * | ||
231 | * XXX - This seems to be an awful generic function, should it be in idr.c with | ||
232 | * the lock included in struct idr? | ||
233 | */ | ||
234 | |||
235 | void v9fs_put_idpool(int id, struct v9fs_idpool *p) | ||
236 | { | ||
237 | if (down_interruptible(&p->lock) == -EINTR) { | ||
238 | eprintk(KERN_WARNING, "Interrupted while locking\n"); | ||
239 | return; | ||
240 | } | ||
241 | idr_remove(&p->pool, id); | ||
242 | up(&p->lock); | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * v9fs_session_init - initialize session | ||
247 | * @v9ses: session information structure | ||
248 | * @dev_name: device being mounted | ||
249 | * @data: options | ||
250 | * | ||
251 | */ | ||
252 | |||
253 | int | ||
254 | v9fs_session_init(struct v9fs_session_info *v9ses, | ||
255 | const char *dev_name, char *data) | ||
256 | { | ||
257 | struct v9fs_fcall *fcall = NULL; | ||
258 | struct v9fs_transport *trans_proto; | ||
259 | int n = 0; | ||
260 | int newfid = -1; | ||
261 | int retval = -EINVAL; | ||
262 | |||
263 | v9ses->name = __getname(); | ||
264 | if (!v9ses->name) | ||
265 | return -ENOMEM; | ||
266 | |||
267 | v9ses->remotename = __getname(); | ||
268 | if (!v9ses->remotename) { | ||
269 | putname(v9ses->name); | ||
270 | return -ENOMEM; | ||
271 | } | ||
272 | |||
273 | strcpy(v9ses->name, V9FS_DEFUSER); | ||
274 | strcpy(v9ses->remotename, V9FS_DEFANAME); | ||
275 | |||
276 | v9fs_parse_options(data, v9ses); | ||
277 | |||
278 | /* set global debug level */ | ||
279 | v9fs_debug_level = v9ses->debug; | ||
280 | |||
281 | /* id pools that are session-dependent: FIDs and TIDs */ | ||
282 | idr_init(&v9ses->fidpool.pool); | ||
283 | init_MUTEX(&v9ses->fidpool.lock); | ||
284 | idr_init(&v9ses->tidpool.pool); | ||
285 | init_MUTEX(&v9ses->tidpool.lock); | ||
286 | |||
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 | if((v9ses->wfdno == ~0) || (v9ses->rfdno == ~0)) { | ||
300 | printk(KERN_ERR "v9fs: Insufficient options for proto=fd\n"); | ||
301 | retval = -ENOPROTOOPT; | ||
302 | goto SessCleanUp; | ||
303 | } | ||
304 | break; | ||
305 | default: | ||
306 | printk(KERN_ERR "v9fs: Bad mount protocol %d\n", v9ses->proto); | ||
307 | retval = -ENOPROTOOPT; | ||
308 | goto SessCleanUp; | ||
309 | }; | ||
310 | |||
311 | v9ses->transport = trans_proto; | ||
312 | |||
313 | if ((retval = v9ses->transport->init(v9ses, dev_name, data)) < 0) { | ||
314 | eprintk(KERN_ERR, "problem initializing transport\n"); | ||
315 | goto SessCleanUp; | ||
316 | } | ||
317 | |||
318 | v9ses->inprogress = 0; | ||
319 | v9ses->shutdown = 0; | ||
320 | v9ses->session_hung = 0; | ||
321 | |||
322 | if ((retval = v9fs_mux_init(v9ses, dev_name)) < 0) { | ||
323 | dprintk(DEBUG_ERROR, "problem initializing mux\n"); | ||
324 | goto SessCleanUp; | ||
325 | } | ||
326 | |||
327 | if (v9ses->afid == ~0) { | ||
328 | if (v9ses->extended) | ||
329 | retval = | ||
330 | v9fs_t_version(v9ses, v9ses->maxdata, "9P2000.u", | ||
331 | &fcall); | ||
332 | else | ||
333 | retval = v9fs_t_version(v9ses, v9ses->maxdata, "9P2000", | ||
334 | &fcall); | ||
335 | |||
336 | if (retval < 0) { | ||
337 | dprintk(DEBUG_ERROR, "v9fs_t_version failed\n"); | ||
338 | goto FreeFcall; | ||
339 | } | ||
340 | |||
341 | /* Really should check for 9P1 and report error */ | ||
342 | if (!strcmp(fcall->params.rversion.version, "9P2000.u")) { | ||
343 | dprintk(DEBUG_9P, "9P2000 UNIX extensions enabled\n"); | ||
344 | v9ses->extended = 1; | ||
345 | } else { | ||
346 | dprintk(DEBUG_9P, "9P2000 legacy mode enabled\n"); | ||
347 | v9ses->extended = 0; | ||
348 | } | ||
349 | |||
350 | n = fcall->params.rversion.msize; | ||
351 | kfree(fcall); | ||
352 | |||
353 | if (n < v9ses->maxdata) | ||
354 | v9ses->maxdata = n; | ||
355 | } | ||
356 | |||
357 | newfid = v9fs_get_idpool(&v9ses->fidpool); | ||
358 | if (newfid < 0) { | ||
359 | eprintk(KERN_WARNING, "couldn't allocate FID\n"); | ||
360 | retval = -ENOMEM; | ||
361 | goto SessCleanUp; | ||
362 | } | ||
363 | /* it is a little bit ugly, but we have to prevent newfid */ | ||
364 | /* being the same as afid, so if it is, get a new fid */ | ||
365 | if (v9ses->afid != ~0 && newfid == v9ses->afid) { | ||
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 | } | ||
373 | |||
374 | if ((retval = | ||
375 | v9fs_t_attach(v9ses, v9ses->name, v9ses->remotename, newfid, | ||
376 | v9ses->afid, NULL)) | ||
377 | < 0) { | ||
378 | dprintk(DEBUG_ERROR, "cannot attach\n"); | ||
379 | goto SessCleanUp; | ||
380 | } | ||
381 | |||
382 | if (v9ses->afid != ~0) { | ||
383 | if (v9fs_t_clunk(v9ses, v9ses->afid, NULL)) | ||
384 | dprintk(DEBUG_ERROR, "clunk failed\n"); | ||
385 | } | ||
386 | |||
387 | return newfid; | ||
388 | |||
389 | FreeFcall: | ||
390 | kfree(fcall); | ||
391 | |||
392 | SessCleanUp: | ||
393 | v9fs_session_close(v9ses); | ||
394 | return retval; | ||
395 | } | ||
396 | |||
397 | /** | ||
398 | * v9fs_session_close - shutdown a session | ||
399 | * @v9ses: session information structure | ||
400 | * | ||
401 | */ | ||
402 | |||
403 | void v9fs_session_close(struct v9fs_session_info *v9ses) | ||
404 | { | ||
405 | if (v9ses->recvproc) { | ||
406 | send_sig(SIGKILL, v9ses->recvproc, 1); | ||
407 | wait_for_completion(&v9ses->proccmpl); | ||
408 | } | ||
409 | |||
410 | if (v9ses->transport) | ||
411 | v9ses->transport->close(v9ses->transport); | ||
412 | |||
413 | putname(v9ses->name); | ||
414 | putname(v9ses->remotename); | ||
415 | } | ||
416 | |||
417 | extern int v9fs_error_init(void); | ||
418 | |||
419 | /** | ||
420 | * v9fs_init - Initialize module | ||
421 | * | ||
422 | */ | ||
423 | |||
424 | static int __init init_v9fs(void) | ||
425 | { | ||
426 | v9fs_error_init(); | ||
427 | |||
428 | printk(KERN_INFO "Installing v9fs 9P2000 file system support\n"); | ||
429 | |||
430 | return register_filesystem(&v9fs_fs_type); | ||
431 | } | ||
432 | |||
433 | /** | ||
434 | * v9fs_init - shutdown module | ||
435 | * | ||
436 | */ | ||
437 | |||
438 | static void __exit exit_v9fs(void) | ||
439 | { | ||
440 | unregister_filesystem(&v9fs_fs_type); | ||
441 | } | ||
442 | |||
443 | module_init(init_v9fs) | ||
444 | module_exit(exit_v9fs) | ||
445 | |||
446 | MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); | ||
447 | MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>"); | ||
448 | MODULE_LICENSE("GPL"); | ||