/* * 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_move_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_move(&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; }