2005-09-10 00:04:20 +04:00
/*
* linux / fs / 9 p / v9fs . c
*
* This file contains functions assisting in mapping VFS to 9 P2000
*
* Copyright ( C ) 2004 by Eric Van Hensbergen < ericvh @ gmail . com >
* Copyright ( C ) 2002 by Ron Minnich < rminnich @ lanl . gov >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to :
* Free Software Foundation
* 51 Franklin Street , Fifth Floor
* Boston , MA 02111 - 1301 USA
*
*/
# include <linux/config.h>
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/parser.h>
# include <linux/idr.h>
# include "debug.h"
# include "v9fs.h"
# include "9p.h"
# include "v9fs_vfs.h"
# include "transport.h"
# include "mux.h"
/* TODO: sysfs or debugfs interface */
int v9fs_debug_level = 0 ; /* feature-rific global debug level */
/*
* Option Parsing ( code inspired by NFS code )
*
*/
enum {
/* Options that take integer arguments */
Opt_port , Opt_msize , Opt_uid , Opt_gid , Opt_afid , Opt_debug ,
Opt_rfdno , Opt_wfdno ,
/* String options */
Opt_name , Opt_remotename ,
/* Options that take no arguments */
Opt_legacy , Opt_nodevmap , Opt_unix , Opt_tcp , Opt_fd ,
/* Error token */
Opt_err
} ;
static match_table_t tokens = {
{ Opt_port , " port=%u " } ,
{ Opt_msize , " msize=%u " } ,
{ Opt_uid , " uid=%u " } ,
{ Opt_gid , " gid=%u " } ,
{ Opt_afid , " afid=%u " } ,
{ Opt_rfdno , " rfdno=%u " } ,
{ Opt_wfdno , " wfdno=%u " } ,
2006-02-21 05:28:05 +03:00
{ Opt_debug , " debug=%x " } ,
2005-09-10 00:04:20 +04:00
{ Opt_name , " name=%s " } ,
{ Opt_remotename , " aname=%s " } ,
{ Opt_unix , " proto=unix " } ,
{ Opt_tcp , " proto=tcp " } ,
{ Opt_fd , " proto=fd " } ,
{ Opt_tcp , " tcp " } ,
{ Opt_unix , " unix " } ,
{ Opt_fd , " fd " } ,
{ Opt_legacy , " noextend " } ,
{ Opt_nodevmap , " nodevmap " } ,
{ Opt_err , NULL }
} ;
/*
* Parse option string .
*/
/**
* v9fs_parse_options - parse mount options into session structure
* @ options : options string passed from mount
* @ v9ses : existing v9fs session information
*
*/
static void v9fs_parse_options ( char * options , struct v9fs_session_info * v9ses )
{
char * p ;
substring_t args [ MAX_OPT_ARGS ] ;
int option ;
int ret ;
/* setup defaults */
v9ses - > port = V9FS_PORT ;
v9ses - > maxdata = 9000 ;
v9ses - > proto = PROTO_TCP ;
v9ses - > extended = 1 ;
v9ses - > afid = ~ 0 ;
v9ses - > debug = 0 ;
v9ses - > rfdno = ~ 0 ;
v9ses - > wfdno = ~ 0 ;
if ( ! options )
return ;
while ( ( p = strsep ( & options , " , " ) ) ! = NULL ) {
int token ;
if ( ! * p )
continue ;
token = match_token ( p , tokens , args ) ;
if ( token < Opt_name ) {
if ( ( ret = match_int ( & args [ 0 ] , & option ) ) < 0 ) {
dprintk ( DEBUG_ERROR ,
" integer field, but no integer? \n " ) ;
continue ;
}
}
switch ( token ) {
case Opt_port :
v9ses - > port = option ;
break ;
case Opt_msize :
v9ses - > maxdata = option ;
break ;
case Opt_uid :
v9ses - > uid = option ;
break ;
case Opt_gid :
v9ses - > gid = option ;
break ;
case Opt_afid :
v9ses - > afid = option ;
break ;
case Opt_rfdno :
v9ses - > rfdno = option ;
break ;
case Opt_wfdno :
v9ses - > wfdno = option ;
break ;
case Opt_debug :
v9ses - > debug = option ;
break ;
case Opt_tcp :
v9ses - > proto = PROTO_TCP ;
break ;
case Opt_unix :
v9ses - > proto = PROTO_UNIX ;
break ;
case Opt_fd :
v9ses - > proto = PROTO_FD ;
break ;
case Opt_name :
match_strcpy ( v9ses - > name , & args [ 0 ] ) ;
break ;
case Opt_remotename :
match_strcpy ( v9ses - > remotename , & args [ 0 ] ) ;
break ;
case Opt_legacy :
v9ses - > extended = 0 ;
break ;
case Opt_nodevmap :
v9ses - > nodev = 1 ;
break ;
default :
continue ;
}
}
}
/**
* v9fs_inode2v9ses - safely extract v9fs session info from super block
* @ inode : inode to extract information from
*
* Paranoid function to extract v9ses information from superblock ,
* if anything is missing it will report an error .
*
*/
struct v9fs_session_info * v9fs_inode2v9ses ( struct inode * inode )
{
return ( inode - > i_sb - > s_fs_info ) ;
}
/**
* v9fs_get_idpool - allocate numeric id from pool
* @ p - pool to allocate from
*
* XXX - This seems to be an awful generic function , should it be in idr . c with
* the lock included in struct idr ?
*/
int v9fs_get_idpool ( struct v9fs_idpool * p )
{
int i = 0 ;
int error ;
retry :
if ( idr_pre_get ( & p - > pool , GFP_KERNEL ) = = 0 )
return 0 ;
if ( down_interruptible ( & p - > lock ) = = - EINTR ) {
eprintk ( KERN_WARNING , " Interrupted while locking \n " ) ;
return - 1 ;
}
2006-01-08 12:04:58 +03:00
/* no need to store exactly p, we just need something non-null */
error = idr_get_new ( & p - > pool , p , & i ) ;
2005-09-10 00:04:20 +04:00
up ( & p - > lock ) ;
if ( error = = - EAGAIN )
goto retry ;
else if ( error )
return - 1 ;
return i ;
}
/**
* v9fs_put_idpool - release numeric id from pool
* @ p - pool to allocate from
*
* XXX - This seems to be an awful generic function , should it be in idr . c with
* the lock included in struct idr ?
*/
void v9fs_put_idpool ( int id , struct v9fs_idpool * p )
{
if ( down_interruptible ( & p - > lock ) = = - EINTR ) {
eprintk ( KERN_WARNING , " Interrupted while locking \n " ) ;
return ;
}
idr_remove ( & p - > pool , id ) ;
up ( & p - > lock ) ;
}
2006-01-08 12:04:58 +03:00
/**
* v9fs_check_idpool - check if the specified id is available
* @ id - id to check
* @ p - pool
*/
int v9fs_check_idpool ( int id , struct v9fs_idpool * p )
{
return idr_find ( & p - > pool , id ) ! = NULL ;
}
2005-09-10 00:04:20 +04:00
/**
* v9fs_session_init - initialize session
* @ v9ses : session information structure
* @ dev_name : device being mounted
* @ data : options
*
*/
int
v9fs_session_init ( struct v9fs_session_info * v9ses ,
const char * dev_name , char * data )
{
struct v9fs_fcall * fcall = NULL ;
struct v9fs_transport * trans_proto ;
int n = 0 ;
int newfid = - 1 ;
int retval = - EINVAL ;
2006-01-08 12:05:02 +03:00
struct v9fs_str * version ;
2005-09-10 00:04:20 +04:00
v9ses - > name = __getname ( ) ;
if ( ! v9ses - > name )
return - ENOMEM ;
v9ses - > remotename = __getname ( ) ;
if ( ! v9ses - > remotename ) {
2005-11-07 11:59:36 +03:00
__putname ( v9ses - > name ) ;
2005-09-10 00:04:20 +04:00
return - ENOMEM ;
}
strcpy ( v9ses - > name , V9FS_DEFUSER ) ;
strcpy ( v9ses - > remotename , V9FS_DEFANAME ) ;
v9fs_parse_options ( data , v9ses ) ;
/* set global debug level */
v9fs_debug_level = v9ses - > debug ;
/* id pools that are session-dependent: FIDs and TIDs */
idr_init ( & v9ses - > fidpool . pool ) ;
init_MUTEX ( & v9ses - > fidpool . lock ) ;
switch ( v9ses - > proto ) {
case PROTO_TCP :
trans_proto = & v9fs_trans_tcp ;
break ;
case PROTO_UNIX :
trans_proto = & v9fs_trans_unix ;
* v9ses - > remotename = 0 ;
break ;
case PROTO_FD :
trans_proto = & v9fs_trans_fd ;
* v9ses - > remotename = 0 ;
break ;
default :
printk ( KERN_ERR " v9fs: Bad mount protocol %d \n " , v9ses - > proto ) ;
retval = - ENOPROTOOPT ;
goto SessCleanUp ;
} ;
2005-09-23 08:43:51 +04:00
v9ses - > transport = kmalloc ( sizeof ( * v9ses - > transport ) , GFP_KERNEL ) ;
if ( ! v9ses - > transport ) {
retval = - ENOMEM ;
goto SessCleanUp ;
}
memmove ( v9ses - > transport , trans_proto , sizeof ( * v9ses - > transport ) ) ;
2005-09-10 00:04:20 +04:00
if ( ( retval = v9ses - > transport - > init ( v9ses , dev_name , data ) ) < 0 ) {
eprintk ( KERN_ERR , " problem initializing transport \n " ) ;
goto SessCleanUp ;
}
v9ses - > inprogress = 0 ;
v9ses - > shutdown = 0 ;
v9ses - > session_hung = 0 ;
2006-01-08 12:04:58 +03:00
v9ses - > mux = v9fs_mux_init ( v9ses - > transport , v9ses - > maxdata + V9FS_IOHDRSZ ,
& v9ses - > extended ) ;
if ( IS_ERR ( v9ses - > mux ) ) {
retval = PTR_ERR ( v9ses - > mux ) ;
v9ses - > mux = NULL ;
2005-09-10 00:04:20 +04:00
dprintk ( DEBUG_ERROR , " problem initializing mux \n " ) ;
goto SessCleanUp ;
}
if ( v9ses - > afid = = ~ 0 ) {
if ( v9ses - > extended )
retval =
v9fs_t_version ( v9ses , v9ses - > maxdata , " 9P2000.u " ,
& fcall ) ;
else
retval = v9fs_t_version ( v9ses , v9ses - > maxdata , " 9P2000 " ,
& fcall ) ;
if ( retval < 0 ) {
dprintk ( DEBUG_ERROR , " v9fs_t_version failed \n " ) ;
goto FreeFcall ;
}
2006-01-08 12:05:02 +03:00
version = & fcall - > params . rversion . version ;
if ( version - > len = = 8 & & ! memcmp ( version - > str , " 9P2000.u " , 8 ) ) {
2005-09-10 00:04:20 +04:00
dprintk ( DEBUG_9P , " 9P2000 UNIX extensions enabled \n " ) ;
v9ses - > extended = 1 ;
2006-01-08 12:05:02 +03:00
} else if ( version - > len = = 6 & & ! memcmp ( version - > str , " 9P2000 " , 6 ) ) {
2005-09-10 00:04:20 +04:00
dprintk ( DEBUG_9P , " 9P2000 legacy mode enabled \n " ) ;
v9ses - > extended = 0 ;
2006-01-08 12:05:02 +03:00
} else {
retval = - EREMOTEIO ;
goto FreeFcall ;
2005-09-10 00:04:20 +04:00
}
n = fcall - > params . rversion . msize ;
kfree ( fcall ) ;
if ( n < v9ses - > maxdata )
v9ses - > maxdata = n ;
}
newfid = v9fs_get_idpool ( & v9ses - > fidpool ) ;
if ( newfid < 0 ) {
eprintk ( KERN_WARNING , " couldn't allocate FID \n " ) ;
retval = - ENOMEM ;
goto SessCleanUp ;
}
/* it is a little bit ugly, but we have to prevent newfid */
/* being the same as afid, so if it is, get a new fid */
if ( v9ses - > afid ! = ~ 0 & & newfid = = v9ses - > afid ) {
newfid = v9fs_get_idpool ( & v9ses - > fidpool ) ;
if ( newfid < 0 ) {
eprintk ( KERN_WARNING , " couldn't allocate FID \n " ) ;
retval = - ENOMEM ;
goto SessCleanUp ;
}
}
if ( ( retval =
v9fs_t_attach ( v9ses , v9ses - > name , v9ses - > remotename , newfid ,
v9ses - > afid , NULL ) )
< 0 ) {
dprintk ( DEBUG_ERROR , " cannot attach \n " ) ;
goto SessCleanUp ;
}
if ( v9ses - > afid ! = ~ 0 ) {
2006-03-02 13:54:33 +03:00
dprintk ( DEBUG_ERROR , " afid not equal to ~0 \n " ) ;
2006-01-08 12:04:58 +03:00
if ( v9fs_t_clunk ( v9ses , v9ses - > afid ) )
2005-09-10 00:04:20 +04:00
dprintk ( DEBUG_ERROR , " clunk failed \n " ) ;
}
return newfid ;
FreeFcall :
kfree ( fcall ) ;
SessCleanUp :
v9fs_session_close ( v9ses ) ;
return retval ;
}
/**
* v9fs_session_close - shutdown a session
* @ v9ses : session information structure
*
*/
void v9fs_session_close ( struct v9fs_session_info * v9ses )
{
2006-01-08 12:04:58 +03:00
if ( v9ses - > mux ) {
v9fs_mux_destroy ( v9ses - > mux ) ;
v9ses - > mux = NULL ;
2005-09-10 00:04:20 +04:00
}
2006-01-08 12:04:58 +03:00
if ( v9ses - > transport ) {
2005-09-10 00:04:20 +04:00
v9ses - > transport - > close ( v9ses - > transport ) ;
2006-01-08 12:04:58 +03:00
kfree ( v9ses - > transport ) ;
v9ses - > transport = NULL ;
}
2005-09-10 00:04:20 +04:00
2005-11-07 11:59:36 +03:00
__putname ( v9ses - > name ) ;
__putname ( v9ses - > remotename ) ;
2005-09-10 00:04:20 +04:00
}
2005-09-10 00:04:23 +04:00
/**
* v9fs_session_cancel - mark transport as disconnected
* and cancel all pending requests .
*/
void v9fs_session_cancel ( struct v9fs_session_info * v9ses ) {
2006-01-08 12:04:58 +03:00
dprintk ( DEBUG_ERROR , " cancel session %p \n " , v9ses ) ;
2005-09-10 00:04:23 +04:00
v9ses - > transport - > status = Disconnected ;
2006-01-08 12:04:58 +03:00
v9fs_mux_cancel ( v9ses - > mux , - EIO ) ;
2005-09-10 00:04:23 +04:00
}
2005-09-10 00:04:20 +04:00
extern int v9fs_error_init ( void ) ;
/**
* v9fs_init - Initialize module
*
*/
static int __init init_v9fs ( void )
{
2006-01-08 12:05:02 +03:00
int ret ;
2005-09-10 00:04:20 +04:00
v9fs_error_init ( ) ;
printk ( KERN_INFO " Installing v9fs 9P2000 file system support \n " ) ;
2006-01-08 12:05:02 +03:00
ret = v9fs_mux_global_init ( ) ;
if ( ! ret )
ret = register_filesystem ( & v9fs_fs_type ) ;
return ret ;
2005-09-10 00:04:20 +04:00
}
/**
* v9fs_init - shutdown module
*
*/
static void __exit exit_v9fs ( void )
{
2006-01-08 12:04:58 +03:00
v9fs_mux_global_exit ( ) ;
2005-09-10 00:04:20 +04:00
unregister_filesystem ( & v9fs_fs_type ) ;
}
module_init ( init_v9fs )
module_exit ( exit_v9fs )
MODULE_AUTHOR ( " Eric Van Hensbergen <ericvh@gmail.com> " ) ;
MODULE_AUTHOR ( " Ron Minnich <rminnich@lanl.gov> " ) ;
MODULE_LICENSE ( " GPL " ) ;