| /* |
| * 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 = 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)) |
| kfree(req->rq_buffer); |
| 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; |
| 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. |
| * First check if the request is still part of a queue. (May |
| * have been removed by some error condition) |
| */ |
| smb_lock_server(server); |
| if (!list_empty(&req->rq_queue)) { |
| 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 = kzalloc(buf_len, GFP_NOFS); |
| if (!req->rq_trans2buffer) |
| goto out_no_mem; |
| |
| 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: |
| BUG(); |
| } |
| |
| 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; |
| } |