aboutsummaryrefslogtreecommitdiff
path: root/fs/smbfs/request.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smbfs/request.c')
-rw-r--r--fs/smbfs/request.c823
1 files changed, 823 insertions, 0 deletions
diff --git a/fs/smbfs/request.c b/fs/smbfs/request.c
new file mode 100644
index 00000000000..2d85dd7415b
--- /dev/null
+++ b/fs/smbfs/request.c
@@ -0,0 +1,823 @@
+/*
+ * 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:
+ server->rstate = SMB_RECV_END;
+ break;
+ }
+
+ 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;
+}