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 */ 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 */ 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 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=%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 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; 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 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 221 /* no need to store exactly p, we just need something non-null */ 222 error = idr_get_new(&p->pool, p, &i); 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 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 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; 278 struct v9fs_str *version; 279 280 v9ses->name = __getname(); 281 if (!v9ses->name) 282 return -ENOMEM; 283 284 v9ses->remotename = __getname(); 285 if (!v9ses->remotename) { 286 __putname(v9ses->name); 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 298 /* id pools that are session-dependent: fids and tags */ 299 idr_init(&v9ses->fidpool.pool); 300 init_MUTEX(&v9ses->fidpool.lock); 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; 313 break; 314 default: 315 printk(KERN_ERR "v9fs: Bad mount protocol %d\n", v9ses->proto); 316 retval = -ENOPROTOOPT; 317 goto SessCleanUp; 318 }; 319 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)); 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 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; 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 361 version = &fcall->params.rversion.version; 362 if (version->len==8 && !memcmp(version->str, "9P2000.u", 8)) { 363 dprintk(DEBUG_9P, "9P2000 UNIX extensions enabled\n"); 364 v9ses->extended = 1; 365 } else if (version->len==6 && !memcmp(version->str, "9P2000", 6)) { 366 dprintk(DEBUG_9P, "9P2000 legacy mode enabled\n"); 367 v9ses->extended = 0; 368 } else { 369 retval = -EREMOTEIO; 370 goto FreeFcall; 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) { 406 dprintk(DEBUG_ERROR, "afid not equal to ~0\n"); 407 if (v9fs_t_clunk(v9ses, v9ses->afid)) 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 { 429 if (v9ses->mux) { 430 v9fs_mux_destroy(v9ses->mux); 431 v9ses->mux = NULL; 432 } 433 434 if (v9ses->transport) { 435 v9ses->transport->close(v9ses->transport); 436 kfree(v9ses->transport); 437 v9ses->transport = NULL; 438 } 439 440 __putname(v9ses->name); 441 __putname(v9ses->remotename); 442 } 443 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) { 449 dprintk(DEBUG_ERROR, "cancel session %p\n", v9ses); 450 v9ses->transport->status = Disconnected; 451 v9fs_mux_cancel(v9ses->mux, -EIO); 452 } 453 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 { 463 int ret; 464 465 v9fs_error_init(); 466 467 printk(KERN_INFO "Installing v9fs 9p2000 file system support\n"); 468 469 ret = v9fs_mux_global_init(); 470 if (ret) { 471 printk(KERN_WARNING "v9fs: starting mux failed\n"); 472 return ret; 473 } 474 ret = register_filesystem(&v9fs_fs_type); 475 if (ret) { 476 printk(KERN_WARNING "v9fs: registering file system failed\n"); 477 v9fs_mux_global_exit(); 478 } 479 480 return ret; 481 } 482 483 /** 484 * v9fs_init - shutdown module 485 * 486 */ 487 488 static void __exit exit_v9fs(void) 489 { 490 v9fs_mux_global_exit(); 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"); 500