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 break; 300 default: 301 printk(KERN_ERR "v9fs: Bad mount protocol %d\n", v9ses->proto); 302 retval = -ENOPROTOOPT; 303 goto SessCleanUp; 304 }; 305 306 v9ses->transport = kmalloc(sizeof(*v9ses->transport), GFP_KERNEL); 307 if (!v9ses->transport) { 308 retval = -ENOMEM; 309 goto SessCleanUp; 310 } 311 312 memmove(v9ses->transport, trans_proto, sizeof(*v9ses->transport)); 313 314 if ((retval = v9ses->transport->init(v9ses, dev_name, data)) < 0) { 315 eprintk(KERN_ERR, "problem initializing transport\n"); 316 goto SessCleanUp; 317 } 318 319 v9ses->inprogress = 0; 320 v9ses->shutdown = 0; 321 v9ses->session_hung = 0; 322 323 if ((retval = v9fs_mux_init(v9ses, dev_name)) < 0) { 324 dprintk(DEBUG_ERROR, "problem initializing mux\n"); 325 goto SessCleanUp; 326 } 327 328 if (v9ses->afid == ~0) { 329 if (v9ses->extended) 330 retval = 331 v9fs_t_version(v9ses, v9ses->maxdata, "9P2000.u", 332 &fcall); 333 else 334 retval = v9fs_t_version(v9ses, v9ses->maxdata, "9P2000", 335 &fcall); 336 337 if (retval < 0) { 338 dprintk(DEBUG_ERROR, "v9fs_t_version failed\n"); 339 goto FreeFcall; 340 } 341 342 /* Really should check for 9P1 and report error */ 343 if (!strcmp(fcall->params.rversion.version, "9P2000.u")) { 344 dprintk(DEBUG_9P, "9P2000 UNIX extensions enabled\n"); 345 v9ses->extended = 1; 346 } else { 347 dprintk(DEBUG_9P, "9P2000 legacy mode enabled\n"); 348 v9ses->extended = 0; 349 } 350 351 n = fcall->params.rversion.msize; 352 kfree(fcall); 353 354 if (n < v9ses->maxdata) 355 v9ses->maxdata = n; 356 } 357 358 newfid = v9fs_get_idpool(&v9ses->fidpool); 359 if (newfid < 0) { 360 eprintk(KERN_WARNING, "couldn't allocate FID\n"); 361 retval = -ENOMEM; 362 goto SessCleanUp; 363 } 364 /* it is a little bit ugly, but we have to prevent newfid */ 365 /* being the same as afid, so if it is, get a new fid */ 366 if (v9ses->afid != ~0 && newfid == v9ses->afid) { 367 newfid = v9fs_get_idpool(&v9ses->fidpool); 368 if (newfid < 0) { 369 eprintk(KERN_WARNING, "couldn't allocate FID\n"); 370 retval = -ENOMEM; 371 goto SessCleanUp; 372 } 373 } 374 375 if ((retval = 376 v9fs_t_attach(v9ses, v9ses->name, v9ses->remotename, newfid, 377 v9ses->afid, NULL)) 378 < 0) { 379 dprintk(DEBUG_ERROR, "cannot attach\n"); 380 goto SessCleanUp; 381 } 382 383 if (v9ses->afid != ~0) { 384 if (v9fs_t_clunk(v9ses, v9ses->afid, NULL)) 385 dprintk(DEBUG_ERROR, "clunk failed\n"); 386 } 387 388 return newfid; 389 390 FreeFcall: 391 kfree(fcall); 392 393 SessCleanUp: 394 v9fs_session_close(v9ses); 395 return retval; 396 } 397 398 /** 399 * v9fs_session_close - shutdown a session 400 * @v9ses: session information structure 401 * 402 */ 403 404 void v9fs_session_close(struct v9fs_session_info *v9ses) 405 { 406 if (v9ses->recvproc) { 407 send_sig(SIGKILL, v9ses->recvproc, 1); 408 wait_for_completion(&v9ses->proccmpl); 409 } 410 411 if (v9ses->transport) 412 v9ses->transport->close(v9ses->transport); 413 414 putname(v9ses->name); 415 putname(v9ses->remotename); 416 } 417 418 /** 419 * v9fs_session_cancel - mark transport as disconnected 420 * and cancel all pending requests. 421 */ 422 void v9fs_session_cancel(struct v9fs_session_info *v9ses) { 423 v9ses->transport->status = Disconnected; 424 v9fs_mux_cancel_requests(v9ses, -EIO); 425 } 426 427 extern int v9fs_error_init(void); 428 429 /** 430 * v9fs_init - Initialize module 431 * 432 */ 433 434 static int __init init_v9fs(void) 435 { 436 v9fs_error_init(); 437 438 printk(KERN_INFO "Installing v9fs 9P2000 file system support\n"); 439 440 return register_filesystem(&v9fs_fs_type); 441 } 442 443 /** 444 * v9fs_init - shutdown module 445 * 446 */ 447 448 static void __exit exit_v9fs(void) 449 { 450 unregister_filesystem(&v9fs_fs_type); 451 } 452 453 module_init(init_v9fs) 454 module_exit(exit_v9fs) 455 456 MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); 457 MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>"); 458 MODULE_LICENSE("GPL"); 459