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