2005-04-17 02:20:36 +04:00
/*
* request . c
*
* Copyright ( C ) 2001 by Urban Widmark
*
* Please add a note about your changes to smbfs in the ChangeLog file .
*/
# include <linux/types.h>
# include <linux/fs.h>
# include <linux/slab.h>
# include <linux/net.h>
# include <linux/smb_fs.h>
# include <linux/smbno.h>
# include <linux/smb_mount.h>
# include "smb_debug.h"
# include "request.h"
# include "proto.h"
/* #define SMB_SLAB_DEBUG (SLAB_RED_ZONE | SLAB_POISON) */
# define SMB_SLAB_DEBUG 0
# define ROUND_UP(x) (((x)+3) & ~3)
/* cache for request structures */
static kmem_cache_t * req_cachep ;
static int smb_request_send_req ( struct smb_request * req ) ;
/*
/ proc / slabinfo :
name , active , num , objsize , active_slabs , num_slaps , # pages
*/
int smb_init_request_cache ( void )
{
req_cachep = kmem_cache_create ( " smb_request " ,
sizeof ( struct smb_request ) , 0 ,
SMB_SLAB_DEBUG | SLAB_HWCACHE_ALIGN ,
NULL , NULL ) ;
if ( req_cachep = = NULL )
return - ENOMEM ;
return 0 ;
}
void smb_destroy_request_cache ( void )
{
if ( kmem_cache_destroy ( req_cachep ) )
printk ( KERN_INFO " smb_destroy_request_cache: not all structures were freed \n " ) ;
}
/*
* Allocate and initialise a request structure
*/
static struct smb_request * smb_do_alloc_request ( struct smb_sb_info * server ,
int bufsize )
{
struct smb_request * req ;
unsigned char * buf = NULL ;
req = kmem_cache_alloc ( req_cachep , SLAB_KERNEL ) ;
VERBOSE ( " allocating request: %p \n " , req ) ;
if ( ! req )
goto out ;
if ( bufsize > 0 ) {
buf = smb_kmalloc ( bufsize , GFP_NOFS ) ;
if ( ! buf ) {
kmem_cache_free ( req_cachep , req ) ;
return NULL ;
}
}
memset ( req , 0 , sizeof ( struct smb_request ) ) ;
req - > rq_buffer = buf ;
req - > rq_bufsize = bufsize ;
req - > rq_server = server ;
init_waitqueue_head ( & req - > rq_wait ) ;
INIT_LIST_HEAD ( & req - > rq_queue ) ;
atomic_set ( & req - > rq_count , 1 ) ;
out :
return req ;
}
struct smb_request * smb_alloc_request ( struct smb_sb_info * server , int bufsize )
{
struct smb_request * req = NULL ;
for ( ; ; ) {
atomic_inc ( & server - > nr_requests ) ;
if ( atomic_read ( & server - > nr_requests ) < = MAX_REQUEST_HARD ) {
req = smb_do_alloc_request ( server , bufsize ) ;
if ( req ! = NULL )
break ;
}
#if 0
/*
* Try to free up at least one request in order to stay
* below the hard limit
*/
if ( nfs_try_to_free_pages ( server ) )
continue ;
if ( signalled ( ) & & ( server - > flags & NFS_MOUNT_INTR ) )
return ERR_PTR ( - ERESTARTSYS ) ;
current - > policy = SCHED_YIELD ;
schedule ( ) ;
# else
/* FIXME: we want something like nfs does above, but that
requires changes to all callers and can wait . */
break ;
# endif
}
return req ;
}
static void smb_free_request ( struct smb_request * req )
{
atomic_dec ( & req - > rq_server - > nr_requests ) ;
if ( req - > rq_buffer & & ! ( req - > rq_flags & SMB_REQ_STATIC ) )
smb_kfree ( req - > rq_buffer ) ;
if ( req - > rq_trans2buffer )
smb_kfree ( req - > rq_trans2buffer ) ;
kmem_cache_free ( req_cachep , req ) ;
}
/*
* What prevents a rget to race with a rput ? The count must never drop to zero
* while it is in use . Only rput if it is ok that it is free ' d .
*/
static void smb_rget ( struct smb_request * req )
{
atomic_inc ( & req - > rq_count ) ;
}
void smb_rput ( struct smb_request * req )
{
if ( atomic_dec_and_test ( & req - > rq_count ) ) {
list_del_init ( & req - > rq_queue ) ;
smb_free_request ( req ) ;
}
}
/* setup to receive the data part of the SMB */
static int smb_setup_bcc ( struct smb_request * req )
{
int result = 0 ;
req - > rq_rlen = smb_len ( req - > rq_header ) + 4 - req - > rq_bytes_recvd ;
if ( req - > rq_rlen > req - > rq_bufsize ) {
PARANOIA ( " Packet too large %d > %d \n " ,
req - > rq_rlen , req - > rq_bufsize ) ;
return - ENOBUFS ;
}
req - > rq_iov [ 0 ] . iov_base = req - > rq_buffer ;
req - > rq_iov [ 0 ] . iov_len = req - > rq_rlen ;
req - > rq_iovlen = 1 ;
return result ;
}
/*
* Prepare a " normal " request structure .
*/
static int smb_setup_request ( struct smb_request * req )
{
int len = smb_len ( req - > rq_header ) + 4 ;
req - > rq_slen = len ;
/* if we expect a data part in the reply we set the iov's to read it */
if ( req - > rq_resp_bcc )
req - > rq_setup_read = smb_setup_bcc ;
/* This tries to support re-using the same request */
req - > rq_bytes_sent = 0 ;
req - > rq_rcls = 0 ;
req - > rq_err = 0 ;
req - > rq_errno = 0 ;
req - > rq_fragment = 0 ;
if ( req - > rq_trans2buffer )
smb_kfree ( req - > rq_trans2buffer ) ;
return 0 ;
}
/*
* Prepare a transaction2 request structure
*/
static int smb_setup_trans2request ( struct smb_request * req )
{
struct smb_sb_info * server = req - > rq_server ;
int mparam , mdata ;
static unsigned char padding [ 4 ] ;
/* I know the following is very ugly, but I want to build the
smb packet as efficiently as possible . */
const int smb_parameters = 15 ;
const int header = SMB_HEADER_LEN + 2 * smb_parameters + 2 ;
const int oparam = ROUND_UP ( header + 3 ) ;
const int odata = ROUND_UP ( oparam + req - > rq_lparm ) ;
const int bcc = ( req - > rq_data ? odata + req - > rq_ldata :
oparam + req - > rq_lparm ) - header ;
if ( ( bcc + oparam ) > server - > opt . max_xmit )
return - ENOMEM ;
smb_setup_header ( req , SMBtrans2 , smb_parameters , bcc ) ;
/*
* max parameters + max data + max setup = = bufsize to make NT4 happy
* and not abort the transfer or split into multiple responses . It also
* makes smbfs happy as handling packets larger than the buffer size
* is extra work .
*
* OS / 2 is probably going to hate me for this . . .
*/
mparam = SMB_TRANS2_MAX_PARAM ;
mdata = req - > rq_bufsize - mparam ;
mdata = server - > opt . max_xmit - mparam - 100 ;
if ( mdata < 1024 ) {
mdata = 1024 ;
mparam = 20 ;
}
#if 0
/* NT/win2k has ~4k max_xmit, so with this we request more than it wants
to return as one SMB . Useful for testing the fragmented trans2
handling . */
mdata = 8192 ;
# endif
WSET ( req - > rq_header , smb_tpscnt , req - > rq_lparm ) ;
WSET ( req - > rq_header , smb_tdscnt , req - > rq_ldata ) ;
WSET ( req - > rq_header , smb_mprcnt , mparam ) ;
WSET ( req - > rq_header , smb_mdrcnt , mdata ) ;
WSET ( req - > rq_header , smb_msrcnt , 0 ) ; /* max setup always 0 ? */
WSET ( req - > rq_header , smb_flags , 0 ) ;
DSET ( req - > rq_header , smb_timeout , 0 ) ;
WSET ( req - > rq_header , smb_pscnt , req - > rq_lparm ) ;
WSET ( req - > rq_header , smb_psoff , oparam - 4 ) ;
WSET ( req - > rq_header , smb_dscnt , req - > rq_ldata ) ;
WSET ( req - > rq_header , smb_dsoff , req - > rq_data ? odata - 4 : 0 ) ;
* ( req - > rq_header + smb_suwcnt ) = 0x01 ; /* setup count */
* ( req - > rq_header + smb_suwcnt + 1 ) = 0x00 ; /* reserved */
WSET ( req - > rq_header , smb_setup0 , req - > rq_trans2_command ) ;
req - > rq_iovlen = 2 ;
req - > rq_iov [ 0 ] . iov_base = ( void * ) req - > rq_header ;
req - > rq_iov [ 0 ] . iov_len = oparam ;
req - > rq_iov [ 1 ] . iov_base = ( req - > rq_parm = = NULL ) ? padding : req - > rq_parm ;
req - > rq_iov [ 1 ] . iov_len = req - > rq_lparm ;
req - > rq_slen = oparam + req - > rq_lparm ;
if ( req - > rq_data ) {
req - > rq_iovlen + = 2 ;
req - > rq_iov [ 2 ] . iov_base = padding ;
req - > rq_iov [ 2 ] . iov_len = odata - oparam - req - > rq_lparm ;
req - > rq_iov [ 3 ] . iov_base = req - > rq_data ;
req - > rq_iov [ 3 ] . iov_len = req - > rq_ldata ;
req - > rq_slen = odata + req - > rq_ldata ;
}
/* always a data part for trans2 replies */
req - > rq_setup_read = smb_setup_bcc ;
return 0 ;
}
/*
* Add a request and tell smbiod to process it
*/
int smb_add_request ( struct smb_request * req )
{
long timeleft ;
struct smb_sb_info * server = req - > rq_server ;
int result = 0 ;
smb_setup_request ( req ) ;
if ( req - > rq_trans2_command ) {
if ( req - > rq_buffer = = NULL ) {
PARANOIA ( " trans2 attempted without response buffer! \n " ) ;
return - EIO ;
}
result = smb_setup_trans2request ( req ) ;
}
if ( result < 0 )
return result ;
# ifdef SMB_DEBUG_PACKET_SIZE
add_xmit_stats ( req ) ;
# endif
/* add 'req' to the queue of requests */
if ( smb_lock_server_interruptible ( server ) )
return - EINTR ;
/*
* Try to send the request as the process . If that fails we queue the
* request and let smbiod send it later .
*/
/* FIXME: each server has a number on the maximum number of parallel
requests . 10 , 50 or so . We should not allow more requests to be
active . */
if ( server - > mid > 0xf000 )
server - > mid = 0 ;
req - > rq_mid = server - > mid + + ;
WSET ( req - > rq_header , smb_mid , req - > rq_mid ) ;
result = 0 ;
if ( server - > state = = CONN_VALID ) {
if ( list_empty ( & server - > xmitq ) )
result = smb_request_send_req ( req ) ;
if ( result < 0 ) {
/* Connection lost? */
server - > conn_error = result ;
server - > state = CONN_INVALID ;
}
}
if ( result ! = 1 )
list_add_tail ( & req - > rq_queue , & server - > xmitq ) ;
smb_rget ( req ) ;
if ( server - > state ! = CONN_VALID )
smbiod_retry ( server ) ;
smb_unlock_server ( server ) ;
smbiod_wake_up ( ) ;
timeleft = wait_event_interruptible_timeout ( req - > rq_wait ,
req - > rq_flags & SMB_REQ_RECEIVED , 30 * HZ ) ;
if ( ! timeleft | | signal_pending ( current ) ) {
/*
* On timeout or on interrupt we want to try and remove the
* request from the recvq / xmitq .
*/
smb_lock_server ( server ) ;
if ( ! ( req - > rq_flags & SMB_REQ_RECEIVED ) ) {
list_del_init ( & req - > rq_queue ) ;
smb_rput ( req ) ;
}
smb_unlock_server ( server ) ;
}
if ( ! timeleft ) {
PARANOIA ( " request [%p, mid=%d] timed out! \n " ,
req , req - > rq_mid ) ;
VERBOSE ( " smb_com: %02x \n " , * ( req - > rq_header + smb_com ) ) ;
VERBOSE ( " smb_rcls: %02x \n " , * ( req - > rq_header + smb_rcls ) ) ;
VERBOSE ( " smb_flg: %02x \n " , * ( req - > rq_header + smb_flg ) ) ;
VERBOSE ( " smb_tid: %04x \n " , WVAL ( req - > rq_header , smb_tid ) ) ;
VERBOSE ( " smb_pid: %04x \n " , WVAL ( req - > rq_header , smb_pid ) ) ;
VERBOSE ( " smb_uid: %04x \n " , WVAL ( req - > rq_header , smb_uid ) ) ;
VERBOSE ( " smb_mid: %04x \n " , WVAL ( req - > rq_header , smb_mid ) ) ;
VERBOSE ( " smb_wct: %02x \n " , * ( req - > rq_header + smb_wct ) ) ;
req - > rq_rcls = ERRSRV ;
req - > rq_err = ERRtimeout ;
/* Just in case it was "stuck" */
smbiod_wake_up ( ) ;
}
VERBOSE ( " woke up, rcls=%d \n " , req - > rq_rcls ) ;
if ( req - > rq_rcls ! = 0 )
req - > rq_errno = smb_errno ( req ) ;
if ( signal_pending ( current ) )
req - > rq_errno = - ERESTARTSYS ;
return req - > rq_errno ;
}
/*
* Send a request and place it on the recvq if successfully sent .
* Must be called with the server lock held .
*/
static int smb_request_send_req ( struct smb_request * req )
{
struct smb_sb_info * server = req - > rq_server ;
int result ;
if ( req - > rq_bytes_sent = = 0 ) {
WSET ( req - > rq_header , smb_tid , server - > opt . tid ) ;
WSET ( req - > rq_header , smb_pid , 1 ) ;
WSET ( req - > rq_header , smb_uid , server - > opt . server_uid ) ;
}
result = smb_send_request ( req ) ;
if ( result < 0 & & result ! = - EAGAIN )
goto out ;
result = 0 ;
if ( ! ( req - > rq_flags & SMB_REQ_TRANSMITTED ) )
goto out ;
list_del_init ( & req - > rq_queue ) ;
list_add_tail ( & req - > rq_queue , & server - > recvq ) ;
result = 1 ;
out :
return result ;
}
/*
* Sends one request for this server . ( smbiod )
* Must be called with the server lock held .
* Returns : < 0 on error
* 0 if no request could be completely sent
* 1 if all data for one request was sent
*/
int smb_request_send_server ( struct smb_sb_info * server )
{
struct list_head * head ;
struct smb_request * req ;
int result ;
if ( server - > state ! = CONN_VALID )
return 0 ;
/* dequeue first request, if any */
req = NULL ;
head = server - > xmitq . next ;
if ( head ! = & server - > xmitq ) {
req = list_entry ( head , struct smb_request , rq_queue ) ;
}
if ( ! req )
return 0 ;
result = smb_request_send_req ( req ) ;
if ( result < 0 ) {
server - > conn_error = result ;
list_del_init ( & req - > rq_queue ) ;
list_add ( & req - > rq_queue , & server - > xmitq ) ;
result = - EIO ;
goto out ;
}
out :
return result ;
}
/*
* Try to find a request matching this " mid " . Typically the first entry will
* be the matching one .
*/
static struct smb_request * find_request ( struct smb_sb_info * server , int mid )
{
struct list_head * tmp ;
struct smb_request * req = NULL ;
list_for_each ( tmp , & server - > recvq ) {
req = list_entry ( tmp , struct smb_request , rq_queue ) ;
if ( req - > rq_mid = = mid ) {
break ;
}
req = NULL ;
}
if ( ! req ) {
VERBOSE ( " received reply with mid %d but no request! \n " ,
WVAL ( server - > header , smb_mid ) ) ;
server - > rstate = SMB_RECV_DROP ;
}
return req ;
}
/*
* Called when we have read the smb header and believe this is a response .
*/
static int smb_init_request ( struct smb_sb_info * server , struct smb_request * req )
{
int hdrlen , wct ;
memcpy ( req - > rq_header , server - > header , SMB_HEADER_LEN ) ;
wct = * ( req - > rq_header + smb_wct ) ;
if ( wct > 20 ) {
PARANOIA ( " wct too large, %d > 20 \n " , wct ) ;
server - > rstate = SMB_RECV_DROP ;
return 0 ;
}
req - > rq_resp_wct = wct ;
hdrlen = SMB_HEADER_LEN + wct * 2 + 2 ;
VERBOSE ( " header length: %d smb_wct: %2d \n " , hdrlen , wct ) ;
req - > rq_bytes_recvd = SMB_HEADER_LEN ;
req - > rq_rlen = hdrlen ;
req - > rq_iov [ 0 ] . iov_base = req - > rq_header ;
req - > rq_iov [ 0 ] . iov_len = hdrlen ;
req - > rq_iovlen = 1 ;
server - > rstate = SMB_RECV_PARAM ;
# ifdef SMB_DEBUG_PACKET_SIZE
add_recv_stats ( smb_len ( server - > header ) ) ;
# endif
return 0 ;
}
/*
* Reads the SMB parameters
*/
static int smb_recv_param ( struct smb_sb_info * server , struct smb_request * req )
{
int result ;
result = smb_receive ( server , req ) ;
if ( result < 0 )
return result ;
if ( req - > rq_bytes_recvd < req - > rq_rlen )
return 0 ;
VERBOSE ( " result: %d smb_bcc: %04x \n " , result ,
WVAL ( req - > rq_header , SMB_HEADER_LEN +
( * ( req - > rq_header + smb_wct ) * 2 ) ) ) ;
result = 0 ;
req - > rq_iov [ 0 ] . iov_base = NULL ;
req - > rq_rlen = 0 ;
if ( req - > rq_callback )
req - > rq_callback ( req ) ;
else if ( req - > rq_setup_read )
result = req - > rq_setup_read ( req ) ;
if ( result < 0 ) {
server - > rstate = SMB_RECV_DROP ;
return result ;
}
server - > rstate = req - > rq_rlen > 0 ? SMB_RECV_DATA : SMB_RECV_END ;
req - > rq_bytes_recvd = 0 ; // recvd out of the iov
VERBOSE ( " rlen: %d \n " , req - > rq_rlen ) ;
if ( req - > rq_rlen < 0 ) {
PARANOIA ( " Parameters read beyond end of packet! \n " ) ;
server - > rstate = SMB_RECV_END ;
return - EIO ;
}
return 0 ;
}
/*
* Reads the SMB data
*/
static int smb_recv_data ( struct smb_sb_info * server , struct smb_request * req )
{
int result ;
result = smb_receive ( server , req ) ;
if ( result < 0 )
goto out ;
if ( req - > rq_bytes_recvd < req - > rq_rlen )
goto out ;
server - > rstate = SMB_RECV_END ;
out :
VERBOSE ( " result: %d \n " , result ) ;
return result ;
}
/*
* Receive a transaction2 response
* Return : 0 if the response has been fully read
* 1 if there are further " fragments " to read
* < 0 if there is an error
*/
static int smb_recv_trans2 ( struct smb_sb_info * server , struct smb_request * req )
{
unsigned char * inbuf ;
unsigned int parm_disp , parm_offset , parm_count , parm_tot ;
unsigned int data_disp , data_offset , data_count , data_tot ;
int hdrlen = SMB_HEADER_LEN + req - > rq_resp_wct * 2 - 2 ;
VERBOSE ( " handling trans2 \n " ) ;
inbuf = req - > rq_header ;
data_tot = WVAL ( inbuf , smb_tdrcnt ) ;
parm_tot = WVAL ( inbuf , smb_tprcnt ) ;
parm_disp = WVAL ( inbuf , smb_prdisp ) ;
parm_offset = WVAL ( inbuf , smb_proff ) ;
parm_count = WVAL ( inbuf , smb_prcnt ) ;
data_disp = WVAL ( inbuf , smb_drdisp ) ;
data_offset = WVAL ( inbuf , smb_droff ) ;
data_count = WVAL ( inbuf , smb_drcnt ) ;
/* Modify offset for the split header/buffer we use */
if ( data_count | | data_offset ) {
if ( unlikely ( data_offset < hdrlen ) )
goto out_bad_data ;
else
data_offset - = hdrlen ;
}
if ( parm_count | | parm_offset ) {
if ( unlikely ( parm_offset < hdrlen ) )
goto out_bad_parm ;
else
parm_offset - = hdrlen ;
}
if ( parm_count = = parm_tot & & data_count = = data_tot ) {
/*
* This packet has all the trans2 data .
*
* We setup the request so that this will be the common
* case . It may be a server error to not return a
* response that fits .
*/
VERBOSE ( " single trans2 response "
" dcnt=%u, pcnt=%u, doff=%u, poff=%u \n " ,
data_count , parm_count ,
data_offset , parm_offset ) ;
req - > rq_ldata = data_count ;
req - > rq_lparm = parm_count ;
req - > rq_data = req - > rq_buffer + data_offset ;
req - > rq_parm = req - > rq_buffer + parm_offset ;
if ( unlikely ( parm_offset + parm_count > req - > rq_rlen ) )
goto out_bad_parm ;
if ( unlikely ( data_offset + data_count > req - > rq_rlen ) )
goto out_bad_data ;
return 0 ;
}
VERBOSE ( " multi trans2 response "
" frag=%d, dcnt=%u, pcnt=%u, doff=%u, poff=%u \n " ,
req - > rq_fragment ,
data_count , parm_count ,
data_offset , parm_offset ) ;
if ( ! req - > rq_fragment ) {
int buf_len ;
/* We got the first trans2 fragment */
req - > rq_fragment = 1 ;
req - > rq_total_data = data_tot ;
req - > rq_total_parm = parm_tot ;
req - > rq_ldata = 0 ;
req - > rq_lparm = 0 ;
buf_len = data_tot + parm_tot ;
if ( buf_len > SMB_MAX_PACKET_SIZE )
goto out_too_long ;
req - > rq_trans2bufsize = buf_len ;
req - > rq_trans2buffer = smb_kmalloc ( buf_len , GFP_NOFS ) ;
if ( ! req - > rq_trans2buffer )
goto out_no_mem ;
memset ( req - > rq_trans2buffer , 0 , buf_len ) ;
req - > rq_parm = req - > rq_trans2buffer ;
req - > rq_data = req - > rq_trans2buffer + parm_tot ;
} else if ( unlikely ( req - > rq_total_data < data_tot | |
req - > rq_total_parm < parm_tot ) )
goto out_data_grew ;
if ( unlikely ( parm_disp + parm_count > req - > rq_total_parm | |
parm_offset + parm_count > req - > rq_rlen ) )
goto out_bad_parm ;
if ( unlikely ( data_disp + data_count > req - > rq_total_data | |
data_offset + data_count > req - > rq_rlen ) )
goto out_bad_data ;
inbuf = req - > rq_buffer ;
memcpy ( req - > rq_parm + parm_disp , inbuf + parm_offset , parm_count ) ;
memcpy ( req - > rq_data + data_disp , inbuf + data_offset , data_count ) ;
req - > rq_ldata + = data_count ;
req - > rq_lparm + = parm_count ;
/*
* Check whether we ' ve received all of the data . Note that
* we use the packet totals - - total lengths might shrink !
*/
if ( req - > rq_ldata > = data_tot & & req - > rq_lparm > = parm_tot ) {
req - > rq_ldata = data_tot ;
req - > rq_lparm = parm_tot ;
return 0 ;
}
return 1 ;
out_too_long :
printk ( KERN_ERR " smb_trans2: data/param too long, data=%u, parm=%u \n " ,
data_tot , parm_tot ) ;
goto out_EIO ;
out_no_mem :
printk ( KERN_ERR " smb_trans2: couldn't allocate data area of %d bytes \n " ,
req - > rq_trans2bufsize ) ;
req - > rq_errno = - ENOMEM ;
goto out ;
out_data_grew :
printk ( KERN_ERR " smb_trans2: data/params grew! \n " ) ;
goto out_EIO ;
out_bad_parm :
printk ( KERN_ERR " smb_trans2: invalid parms, disp=%u, cnt=%u, tot=%u, ofs=%u \n " ,
parm_disp , parm_count , parm_tot , parm_offset ) ;
goto out_EIO ;
out_bad_data :
printk ( KERN_ERR " smb_trans2: invalid data, disp=%u, cnt=%u, tot=%u, ofs=%u \n " ,
data_disp , data_count , data_tot , data_offset ) ;
out_EIO :
req - > rq_errno = - EIO ;
out :
return req - > rq_errno ;
}
/*
* State machine for receiving responses . We handle the fact that we can ' t
* read the full response in one try by having states telling us how much we
* have read .
*
* Must be called with the server lock held ( only called from smbiod ) .
*
* Return : < 0 on error
*/
int smb_request_recv ( struct smb_sb_info * server )
{
struct smb_request * req = NULL ;
int result = 0 ;
if ( smb_recv_available ( server ) < = 0 )
return 0 ;
VERBOSE ( " state: %d \n " , server - > rstate ) ;
switch ( server - > rstate ) {
case SMB_RECV_DROP :
result = smb_receive_drop ( server ) ;
if ( result < 0 )
break ;
if ( server - > rstate = = SMB_RECV_DROP )
break ;
server - > rstate = SMB_RECV_START ;
/* fallthrough */
case SMB_RECV_START :
server - > smb_read = 0 ;
server - > rstate = SMB_RECV_HEADER ;
/* fallthrough */
case SMB_RECV_HEADER :
result = smb_receive_header ( server ) ;
if ( result < 0 )
break ;
if ( server - > rstate = = SMB_RECV_HEADER )
break ;
if ( ! ( * ( server - > header + smb_flg ) & SMB_FLAGS_REPLY ) ) {
server - > rstate = SMB_RECV_REQUEST ;
break ;
}
if ( server - > rstate ! = SMB_RECV_HCOMPLETE )
break ;
/* fallthrough */
case SMB_RECV_HCOMPLETE :
req = find_request ( server , WVAL ( server - > header , smb_mid ) ) ;
if ( ! req )
break ;
smb_init_request ( server , req ) ;
req - > rq_rcls = * ( req - > rq_header + smb_rcls ) ;
req - > rq_err = WVAL ( req - > rq_header , smb_err ) ;
if ( server - > rstate ! = SMB_RECV_PARAM )
break ;
/* fallthrough */
case SMB_RECV_PARAM :
if ( ! req )
req = find_request ( server , WVAL ( server - > header , smb_mid ) ) ;
if ( ! req )
break ;
result = smb_recv_param ( server , req ) ;
if ( result < 0 )
break ;
if ( server - > rstate ! = SMB_RECV_DATA )
break ;
/* fallthrough */
case SMB_RECV_DATA :
if ( ! req )
req = find_request ( server , WVAL ( server - > header , smb_mid ) ) ;
if ( ! req )
break ;
result = smb_recv_data ( server , req ) ;
if ( result < 0 )
break ;
break ;
/* We should never be called with any of these states */
case SMB_RECV_END :
case SMB_RECV_REQUEST :
2005-11-07 11:59:23 +03:00
BUG ( ) ;
2005-04-17 02:20:36 +04:00
}
if ( result < 0 ) {
/* We saw an error */
return result ;
}
if ( server - > rstate ! = SMB_RECV_END )
return 0 ;
result = 0 ;
if ( req - > rq_trans2_command & & req - > rq_rcls = = SUCCESS )
result = smb_recv_trans2 ( server , req ) ;
/*
* Response completely read . Drop any extra bytes sent by the server .
* ( Yes , servers sometimes add extra bytes to responses )
*/
VERBOSE ( " smb_len: %d smb_read: %d \n " ,
server - > smb_len , server - > smb_read ) ;
if ( server - > smb_read < server - > smb_len )
smb_receive_drop ( server ) ;
server - > rstate = SMB_RECV_START ;
if ( ! result ) {
list_del_init ( & req - > rq_queue ) ;
req - > rq_flags | = SMB_REQ_RECEIVED ;
smb_rput ( req ) ;
wake_up_interruptible ( & req - > rq_wait ) ;
}
return 0 ;
}