diff options
Diffstat (limited to 'fs/smbfs/request.c')
-rw-r--r-- | fs/smbfs/request.c | 823 |
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; +} |