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