| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | *  request.c | 
|  | 3 | * | 
|  | 4 | *  Copyright (C) 2001 by Urban Widmark | 
|  | 5 | * | 
|  | 6 | *  Please add a note about your changes to smbfs in the ChangeLog file. | 
|  | 7 | */ | 
|  | 8 |  | 
| Milind Arun Choudhary | 1525dcc | 2007-05-08 00:29:03 -0700 | [diff] [blame] | 9 | #include <linux/kernel.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 | #include <linux/types.h> | 
|  | 11 | #include <linux/fs.h> | 
|  | 12 | #include <linux/slab.h> | 
|  | 13 | #include <linux/net.h> | 
| Alexey Dobriyan | e8edc6e | 2007-05-21 01:22:52 +0400 | [diff] [blame] | 14 | #include <linux/sched.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 15 |  | 
|  | 16 | #include <linux/smb_fs.h> | 
|  | 17 | #include <linux/smbno.h> | 
|  | 18 | #include <linux/smb_mount.h> | 
|  | 19 |  | 
|  | 20 | #include "smb_debug.h" | 
|  | 21 | #include "request.h" | 
|  | 22 | #include "proto.h" | 
|  | 23 |  | 
|  | 24 | /* #define SMB_SLAB_DEBUG	(SLAB_RED_ZONE | SLAB_POISON) */ | 
|  | 25 | #define SMB_SLAB_DEBUG	0 | 
|  | 26 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 27 | /* cache for request structures */ | 
| Christoph Lameter | e18b890 | 2006-12-06 20:33:20 -0800 | [diff] [blame] | 28 | static struct kmem_cache *req_cachep; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 29 |  | 
|  | 30 | static int smb_request_send_req(struct smb_request *req); | 
|  | 31 |  | 
|  | 32 | /* | 
|  | 33 | /proc/slabinfo: | 
|  | 34 | name, active, num, objsize, active_slabs, num_slaps, #pages | 
|  | 35 | */ | 
|  | 36 |  | 
|  | 37 |  | 
|  | 38 | int smb_init_request_cache(void) | 
|  | 39 | { | 
|  | 40 | req_cachep = kmem_cache_create("smb_request", | 
|  | 41 | sizeof(struct smb_request), 0, | 
|  | 42 | SMB_SLAB_DEBUG | SLAB_HWCACHE_ALIGN, | 
| Paul Mundt | 20c2df8 | 2007-07-20 10:11:58 +0900 | [diff] [blame] | 43 | NULL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 44 | if (req_cachep == NULL) | 
|  | 45 | return -ENOMEM; | 
|  | 46 |  | 
|  | 47 | return 0; | 
|  | 48 | } | 
|  | 49 |  | 
|  | 50 | void smb_destroy_request_cache(void) | 
|  | 51 | { | 
| Alexey Dobriyan | 1a1d92c | 2006-09-27 01:49:40 -0700 | [diff] [blame] | 52 | kmem_cache_destroy(req_cachep); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 53 | } | 
|  | 54 |  | 
|  | 55 | /* | 
|  | 56 | * Allocate and initialise a request structure | 
|  | 57 | */ | 
|  | 58 | static struct smb_request *smb_do_alloc_request(struct smb_sb_info *server, | 
|  | 59 | int bufsize) | 
|  | 60 | { | 
|  | 61 | struct smb_request *req; | 
|  | 62 | unsigned char *buf = NULL; | 
|  | 63 |  | 
| Robert P. J. Day | c376222 | 2007-02-10 01:45:03 -0800 | [diff] [blame] | 64 | req = kmem_cache_zalloc(req_cachep, GFP_KERNEL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 65 | VERBOSE("allocating request: %p\n", req); | 
|  | 66 | if (!req) | 
|  | 67 | goto out; | 
|  | 68 |  | 
|  | 69 | if (bufsize > 0) { | 
| Pekka Enberg | d063389 | 2006-01-14 13:21:13 -0800 | [diff] [blame] | 70 | buf = kmalloc(bufsize, GFP_NOFS); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 71 | if (!buf) { | 
|  | 72 | kmem_cache_free(req_cachep, req); | 
|  | 73 | return NULL; | 
|  | 74 | } | 
|  | 75 | } | 
|  | 76 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 77 | req->rq_buffer = buf; | 
|  | 78 | req->rq_bufsize = bufsize; | 
|  | 79 | req->rq_server = server; | 
|  | 80 | init_waitqueue_head(&req->rq_wait); | 
|  | 81 | INIT_LIST_HEAD(&req->rq_queue); | 
|  | 82 | atomic_set(&req->rq_count, 1); | 
|  | 83 |  | 
|  | 84 | out: | 
|  | 85 | return req; | 
|  | 86 | } | 
|  | 87 |  | 
|  | 88 | struct smb_request *smb_alloc_request(struct smb_sb_info *server, int bufsize) | 
|  | 89 | { | 
|  | 90 | struct smb_request *req = NULL; | 
|  | 91 |  | 
|  | 92 | for (;;) { | 
|  | 93 | atomic_inc(&server->nr_requests); | 
|  | 94 | if (atomic_read(&server->nr_requests) <= MAX_REQUEST_HARD) { | 
|  | 95 | req = smb_do_alloc_request(server, bufsize); | 
|  | 96 | if (req != NULL) | 
|  | 97 | break; | 
|  | 98 | } | 
|  | 99 |  | 
|  | 100 | #if 0 | 
|  | 101 | /* | 
|  | 102 | * Try to free up at least one request in order to stay | 
|  | 103 | * below the hard limit | 
|  | 104 | */ | 
|  | 105 | if (nfs_try_to_free_pages(server)) | 
|  | 106 | continue; | 
|  | 107 |  | 
| Matthew Wilcox | 2dfe485 | 2007-12-06 16:25:30 -0500 | [diff] [blame] | 108 | if (fatal_signal_pending(current)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 109 | return ERR_PTR(-ERESTARTSYS); | 
|  | 110 | current->policy = SCHED_YIELD; | 
|  | 111 | schedule(); | 
|  | 112 | #else | 
|  | 113 | /* FIXME: we want something like nfs does above, but that | 
|  | 114 | requires changes to all callers and can wait. */ | 
|  | 115 | break; | 
|  | 116 | #endif | 
|  | 117 | } | 
|  | 118 | return req; | 
|  | 119 | } | 
|  | 120 |  | 
|  | 121 | static void smb_free_request(struct smb_request *req) | 
|  | 122 | { | 
|  | 123 | atomic_dec(&req->rq_server->nr_requests); | 
|  | 124 | if (req->rq_buffer && !(req->rq_flags & SMB_REQ_STATIC)) | 
| Pekka Enberg | d063389 | 2006-01-14 13:21:13 -0800 | [diff] [blame] | 125 | kfree(req->rq_buffer); | 
|  | 126 | kfree(req->rq_trans2buffer); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 127 | kmem_cache_free(req_cachep, req); | 
|  | 128 | } | 
|  | 129 |  | 
|  | 130 | /* | 
|  | 131 | * What prevents a rget to race with a rput? The count must never drop to zero | 
|  | 132 | * while it is in use. Only rput if it is ok that it is free'd. | 
|  | 133 | */ | 
|  | 134 | static void smb_rget(struct smb_request *req) | 
|  | 135 | { | 
|  | 136 | atomic_inc(&req->rq_count); | 
|  | 137 | } | 
|  | 138 | void smb_rput(struct smb_request *req) | 
|  | 139 | { | 
|  | 140 | if (atomic_dec_and_test(&req->rq_count)) { | 
|  | 141 | list_del_init(&req->rq_queue); | 
|  | 142 | smb_free_request(req); | 
|  | 143 | } | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | /* setup to receive the data part of the SMB */ | 
|  | 147 | static int smb_setup_bcc(struct smb_request *req) | 
|  | 148 | { | 
|  | 149 | int result = 0; | 
|  | 150 | req->rq_rlen = smb_len(req->rq_header) + 4 - req->rq_bytes_recvd; | 
|  | 151 |  | 
|  | 152 | if (req->rq_rlen > req->rq_bufsize) { | 
|  | 153 | PARANOIA("Packet too large %d > %d\n", | 
|  | 154 | req->rq_rlen, req->rq_bufsize); | 
|  | 155 | return -ENOBUFS; | 
|  | 156 | } | 
|  | 157 |  | 
|  | 158 | req->rq_iov[0].iov_base = req->rq_buffer; | 
|  | 159 | req->rq_iov[0].iov_len  = req->rq_rlen; | 
|  | 160 | req->rq_iovlen = 1; | 
|  | 161 |  | 
|  | 162 | return result; | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | /* | 
|  | 166 | * Prepare a "normal" request structure. | 
|  | 167 | */ | 
|  | 168 | static int smb_setup_request(struct smb_request *req) | 
|  | 169 | { | 
|  | 170 | int len = smb_len(req->rq_header) + 4; | 
|  | 171 | req->rq_slen = len; | 
|  | 172 |  | 
|  | 173 | /* if we expect a data part in the reply we set the iov's to read it */ | 
|  | 174 | if (req->rq_resp_bcc) | 
|  | 175 | req->rq_setup_read = smb_setup_bcc; | 
|  | 176 |  | 
|  | 177 | /* This tries to support re-using the same request */ | 
|  | 178 | req->rq_bytes_sent = 0; | 
|  | 179 | req->rq_rcls = 0; | 
|  | 180 | req->rq_err = 0; | 
|  | 181 | req->rq_errno = 0; | 
|  | 182 | req->rq_fragment = 0; | 
| Pekka Enberg | d063389 | 2006-01-14 13:21:13 -0800 | [diff] [blame] | 183 | kfree(req->rq_trans2buffer); | 
| Vasily Averin | 1174cf7 | 2007-03-16 13:38:24 -0800 | [diff] [blame] | 184 | req->rq_trans2buffer = NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 185 |  | 
|  | 186 | return 0; | 
|  | 187 | } | 
|  | 188 |  | 
|  | 189 | /* | 
|  | 190 | * Prepare a transaction2 request structure | 
|  | 191 | */ | 
|  | 192 | static int smb_setup_trans2request(struct smb_request *req) | 
|  | 193 | { | 
|  | 194 | struct smb_sb_info *server = req->rq_server; | 
|  | 195 | int mparam, mdata; | 
|  | 196 | static unsigned char padding[4]; | 
|  | 197 |  | 
|  | 198 | /* I know the following is very ugly, but I want to build the | 
|  | 199 | smb packet as efficiently as possible. */ | 
|  | 200 |  | 
|  | 201 | const int smb_parameters = 15; | 
|  | 202 | const int header = SMB_HEADER_LEN + 2 * smb_parameters + 2; | 
| Milind Arun Choudhary | 1525dcc | 2007-05-08 00:29:03 -0700 | [diff] [blame] | 203 | const int oparam = ALIGN(header + 3, sizeof(u32)); | 
|  | 204 | const int odata  = ALIGN(oparam + req->rq_lparm, sizeof(u32)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 205 | const int bcc = (req->rq_data ? odata + req->rq_ldata : | 
|  | 206 | oparam + req->rq_lparm) - header; | 
|  | 207 |  | 
|  | 208 | if ((bcc + oparam) > server->opt.max_xmit) | 
|  | 209 | return -ENOMEM; | 
|  | 210 | smb_setup_header(req, SMBtrans2, smb_parameters, bcc); | 
|  | 211 |  | 
|  | 212 | /* | 
|  | 213 | * max parameters + max data + max setup == bufsize to make NT4 happy | 
|  | 214 | * and not abort the transfer or split into multiple responses. It also | 
|  | 215 | * makes smbfs happy as handling packets larger than the buffer size | 
|  | 216 | * is extra work. | 
|  | 217 | * | 
|  | 218 | * OS/2 is probably going to hate me for this ... | 
|  | 219 | */ | 
|  | 220 | mparam = SMB_TRANS2_MAX_PARAM; | 
|  | 221 | mdata = req->rq_bufsize - mparam; | 
|  | 222 |  | 
|  | 223 | mdata = server->opt.max_xmit - mparam - 100; | 
|  | 224 | if (mdata < 1024) { | 
|  | 225 | mdata = 1024; | 
|  | 226 | mparam = 20; | 
|  | 227 | } | 
|  | 228 |  | 
|  | 229 | #if 0 | 
|  | 230 | /* NT/win2k has ~4k max_xmit, so with this we request more than it wants | 
|  | 231 | to return as one SMB. Useful for testing the fragmented trans2 | 
|  | 232 | handling. */ | 
|  | 233 | mdata = 8192; | 
|  | 234 | #endif | 
|  | 235 |  | 
|  | 236 | WSET(req->rq_header, smb_tpscnt, req->rq_lparm); | 
|  | 237 | WSET(req->rq_header, smb_tdscnt, req->rq_ldata); | 
|  | 238 | WSET(req->rq_header, smb_mprcnt, mparam); | 
|  | 239 | WSET(req->rq_header, smb_mdrcnt, mdata); | 
|  | 240 | WSET(req->rq_header, smb_msrcnt, 0);    /* max setup always 0 ? */ | 
|  | 241 | WSET(req->rq_header, smb_flags, 0); | 
|  | 242 | DSET(req->rq_header, smb_timeout, 0); | 
|  | 243 | WSET(req->rq_header, smb_pscnt, req->rq_lparm); | 
|  | 244 | WSET(req->rq_header, smb_psoff, oparam - 4); | 
|  | 245 | WSET(req->rq_header, smb_dscnt, req->rq_ldata); | 
|  | 246 | WSET(req->rq_header, smb_dsoff, req->rq_data ? odata - 4 : 0); | 
|  | 247 | *(req->rq_header + smb_suwcnt) = 0x01;          /* setup count */ | 
|  | 248 | *(req->rq_header + smb_suwcnt + 1) = 0x00;      /* reserved */ | 
|  | 249 | WSET(req->rq_header, smb_setup0, req->rq_trans2_command); | 
|  | 250 |  | 
|  | 251 | req->rq_iovlen = 2; | 
|  | 252 | req->rq_iov[0].iov_base = (void *) req->rq_header; | 
|  | 253 | req->rq_iov[0].iov_len = oparam; | 
|  | 254 | req->rq_iov[1].iov_base = (req->rq_parm==NULL) ? padding : req->rq_parm; | 
|  | 255 | req->rq_iov[1].iov_len = req->rq_lparm; | 
|  | 256 | req->rq_slen = oparam + req->rq_lparm; | 
|  | 257 |  | 
|  | 258 | if (req->rq_data) { | 
|  | 259 | req->rq_iovlen += 2; | 
|  | 260 | req->rq_iov[2].iov_base = padding; | 
|  | 261 | req->rq_iov[2].iov_len = odata - oparam - req->rq_lparm; | 
|  | 262 | req->rq_iov[3].iov_base = req->rq_data; | 
|  | 263 | req->rq_iov[3].iov_len = req->rq_ldata; | 
|  | 264 | req->rq_slen = odata + req->rq_ldata; | 
|  | 265 | } | 
|  | 266 |  | 
|  | 267 | /* always a data part for trans2 replies */ | 
|  | 268 | req->rq_setup_read = smb_setup_bcc; | 
|  | 269 |  | 
|  | 270 | return 0; | 
|  | 271 | } | 
|  | 272 |  | 
|  | 273 | /* | 
|  | 274 | * Add a request and tell smbiod to process it | 
|  | 275 | */ | 
|  | 276 | int smb_add_request(struct smb_request *req) | 
|  | 277 | { | 
|  | 278 | long timeleft; | 
|  | 279 | struct smb_sb_info *server = req->rq_server; | 
|  | 280 | int result = 0; | 
|  | 281 |  | 
|  | 282 | smb_setup_request(req); | 
|  | 283 | if (req->rq_trans2_command) { | 
|  | 284 | if (req->rq_buffer == NULL) { | 
|  | 285 | PARANOIA("trans2 attempted without response buffer!\n"); | 
|  | 286 | return -EIO; | 
|  | 287 | } | 
|  | 288 | result = smb_setup_trans2request(req); | 
|  | 289 | } | 
|  | 290 | if (result < 0) | 
|  | 291 | return result; | 
|  | 292 |  | 
|  | 293 | #ifdef SMB_DEBUG_PACKET_SIZE | 
|  | 294 | add_xmit_stats(req); | 
|  | 295 | #endif | 
|  | 296 |  | 
|  | 297 | /* add 'req' to the queue of requests */ | 
|  | 298 | if (smb_lock_server_interruptible(server)) | 
|  | 299 | return -EINTR; | 
|  | 300 |  | 
|  | 301 | /* | 
|  | 302 | * Try to send the request as the process. If that fails we queue the | 
|  | 303 | * request and let smbiod send it later. | 
|  | 304 | */ | 
|  | 305 |  | 
|  | 306 | /* FIXME: each server has a number on the maximum number of parallel | 
|  | 307 | requests. 10, 50 or so. We should not allow more requests to be | 
|  | 308 | active. */ | 
|  | 309 | if (server->mid > 0xf000) | 
|  | 310 | server->mid = 0; | 
|  | 311 | req->rq_mid = server->mid++; | 
|  | 312 | WSET(req->rq_header, smb_mid, req->rq_mid); | 
|  | 313 |  | 
|  | 314 | result = 0; | 
|  | 315 | if (server->state == CONN_VALID) { | 
|  | 316 | if (list_empty(&server->xmitq)) | 
|  | 317 | result = smb_request_send_req(req); | 
|  | 318 | if (result < 0) { | 
|  | 319 | /* Connection lost? */ | 
|  | 320 | server->conn_error = result; | 
|  | 321 | server->state = CONN_INVALID; | 
|  | 322 | } | 
|  | 323 | } | 
|  | 324 | if (result != 1) | 
|  | 325 | list_add_tail(&req->rq_queue, &server->xmitq); | 
|  | 326 | smb_rget(req); | 
|  | 327 |  | 
|  | 328 | if (server->state != CONN_VALID) | 
|  | 329 | smbiod_retry(server); | 
|  | 330 |  | 
|  | 331 | smb_unlock_server(server); | 
|  | 332 |  | 
|  | 333 | smbiod_wake_up(); | 
|  | 334 |  | 
|  | 335 | timeleft = wait_event_interruptible_timeout(req->rq_wait, | 
|  | 336 | req->rq_flags & SMB_REQ_RECEIVED, 30*HZ); | 
|  | 337 | if (!timeleft || signal_pending(current)) { | 
|  | 338 | /* | 
|  | 339 | * On timeout or on interrupt we want to try and remove the | 
|  | 340 | * request from the recvq/xmitq. | 
| Jan Niehusmann | 48564e6 | 2006-05-15 09:44:12 -0700 | [diff] [blame] | 341 | * First check if the request is still part of a queue. (May | 
|  | 342 | * have been removed by some error condition) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 343 | */ | 
|  | 344 | smb_lock_server(server); | 
| Jan Niehusmann | 48564e6 | 2006-05-15 09:44:12 -0700 | [diff] [blame] | 345 | if (!list_empty(&req->rq_queue)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 346 | list_del_init(&req->rq_queue); | 
|  | 347 | smb_rput(req); | 
|  | 348 | } | 
|  | 349 | smb_unlock_server(server); | 
|  | 350 | } | 
|  | 351 |  | 
|  | 352 | if (!timeleft) { | 
|  | 353 | PARANOIA("request [%p, mid=%d] timed out!\n", | 
|  | 354 | req, req->rq_mid); | 
|  | 355 | VERBOSE("smb_com:  %02x\n", *(req->rq_header + smb_com)); | 
|  | 356 | VERBOSE("smb_rcls: %02x\n", *(req->rq_header + smb_rcls)); | 
|  | 357 | VERBOSE("smb_flg:  %02x\n", *(req->rq_header + smb_flg)); | 
|  | 358 | VERBOSE("smb_tid:  %04x\n", WVAL(req->rq_header, smb_tid)); | 
|  | 359 | VERBOSE("smb_pid:  %04x\n", WVAL(req->rq_header, smb_pid)); | 
|  | 360 | VERBOSE("smb_uid:  %04x\n", WVAL(req->rq_header, smb_uid)); | 
|  | 361 | VERBOSE("smb_mid:  %04x\n", WVAL(req->rq_header, smb_mid)); | 
|  | 362 | VERBOSE("smb_wct:  %02x\n", *(req->rq_header + smb_wct)); | 
|  | 363 |  | 
|  | 364 | req->rq_rcls = ERRSRV; | 
|  | 365 | req->rq_err  = ERRtimeout; | 
|  | 366 |  | 
|  | 367 | /* Just in case it was "stuck" */ | 
|  | 368 | smbiod_wake_up(); | 
|  | 369 | } | 
|  | 370 | VERBOSE("woke up, rcls=%d\n", req->rq_rcls); | 
|  | 371 |  | 
|  | 372 | if (req->rq_rcls != 0) | 
|  | 373 | req->rq_errno = smb_errno(req); | 
|  | 374 | if (signal_pending(current)) | 
|  | 375 | req->rq_errno = -ERESTARTSYS; | 
|  | 376 | return req->rq_errno; | 
|  | 377 | } | 
|  | 378 |  | 
|  | 379 | /* | 
|  | 380 | * Send a request and place it on the recvq if successfully sent. | 
|  | 381 | * Must be called with the server lock held. | 
|  | 382 | */ | 
|  | 383 | static int smb_request_send_req(struct smb_request *req) | 
|  | 384 | { | 
|  | 385 | struct smb_sb_info *server = req->rq_server; | 
|  | 386 | int result; | 
|  | 387 |  | 
|  | 388 | if (req->rq_bytes_sent == 0) { | 
|  | 389 | WSET(req->rq_header, smb_tid, server->opt.tid); | 
|  | 390 | WSET(req->rq_header, smb_pid, 1); | 
|  | 391 | WSET(req->rq_header, smb_uid, server->opt.server_uid); | 
|  | 392 | } | 
|  | 393 |  | 
|  | 394 | result = smb_send_request(req); | 
|  | 395 | if (result < 0 && result != -EAGAIN) | 
|  | 396 | goto out; | 
|  | 397 |  | 
|  | 398 | result = 0; | 
|  | 399 | if (!(req->rq_flags & SMB_REQ_TRANSMITTED)) | 
|  | 400 | goto out; | 
|  | 401 |  | 
| Akinobu Mita | f116629 | 2006-06-26 00:24:46 -0700 | [diff] [blame] | 402 | list_move_tail(&req->rq_queue, &server->recvq); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 403 | result = 1; | 
|  | 404 | out: | 
|  | 405 | return result; | 
|  | 406 | } | 
|  | 407 |  | 
|  | 408 | /* | 
|  | 409 | * Sends one request for this server. (smbiod) | 
|  | 410 | * Must be called with the server lock held. | 
|  | 411 | * Returns: <0 on error | 
|  | 412 | *           0 if no request could be completely sent | 
|  | 413 | *           1 if all data for one request was sent | 
|  | 414 | */ | 
|  | 415 | int smb_request_send_server(struct smb_sb_info *server) | 
|  | 416 | { | 
|  | 417 | struct list_head *head; | 
|  | 418 | struct smb_request *req; | 
|  | 419 | int result; | 
|  | 420 |  | 
|  | 421 | if (server->state != CONN_VALID) | 
|  | 422 | return 0; | 
|  | 423 |  | 
|  | 424 | /* dequeue first request, if any */ | 
|  | 425 | req = NULL; | 
|  | 426 | head = server->xmitq.next; | 
|  | 427 | if (head != &server->xmitq) { | 
|  | 428 | req = list_entry(head, struct smb_request, rq_queue); | 
|  | 429 | } | 
|  | 430 | if (!req) | 
|  | 431 | return 0; | 
|  | 432 |  | 
|  | 433 | result = smb_request_send_req(req); | 
|  | 434 | if (result < 0) { | 
|  | 435 | server->conn_error = result; | 
| Akinobu Mita | f116629 | 2006-06-26 00:24:46 -0700 | [diff] [blame] | 436 | list_move(&req->rq_queue, &server->xmitq); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 437 | result = -EIO; | 
|  | 438 | goto out; | 
|  | 439 | } | 
|  | 440 |  | 
|  | 441 | out: | 
|  | 442 | return result; | 
|  | 443 | } | 
|  | 444 |  | 
|  | 445 | /* | 
|  | 446 | * Try to find a request matching this "mid". Typically the first entry will | 
|  | 447 | * be the matching one. | 
|  | 448 | */ | 
|  | 449 | static struct smb_request *find_request(struct smb_sb_info *server, int mid) | 
|  | 450 | { | 
|  | 451 | struct list_head *tmp; | 
|  | 452 | struct smb_request *req = NULL; | 
|  | 453 |  | 
|  | 454 | list_for_each(tmp, &server->recvq) { | 
|  | 455 | req = list_entry(tmp, struct smb_request, rq_queue); | 
|  | 456 | if (req->rq_mid == mid) { | 
|  | 457 | break; | 
|  | 458 | } | 
|  | 459 | req = NULL; | 
|  | 460 | } | 
|  | 461 |  | 
|  | 462 | if (!req) { | 
|  | 463 | VERBOSE("received reply with mid %d but no request!\n", | 
|  | 464 | WVAL(server->header, smb_mid)); | 
|  | 465 | server->rstate = SMB_RECV_DROP; | 
|  | 466 | } | 
|  | 467 |  | 
|  | 468 | return req; | 
|  | 469 | } | 
|  | 470 |  | 
|  | 471 | /* | 
|  | 472 | * Called when we have read the smb header and believe this is a response. | 
|  | 473 | */ | 
|  | 474 | static int smb_init_request(struct smb_sb_info *server, struct smb_request *req) | 
|  | 475 | { | 
|  | 476 | int hdrlen, wct; | 
|  | 477 |  | 
|  | 478 | memcpy(req->rq_header, server->header, SMB_HEADER_LEN); | 
|  | 479 |  | 
|  | 480 | wct = *(req->rq_header + smb_wct); | 
|  | 481 | if (wct > 20) { | 
|  | 482 | PARANOIA("wct too large, %d > 20\n", wct); | 
|  | 483 | server->rstate = SMB_RECV_DROP; | 
|  | 484 | return 0; | 
|  | 485 | } | 
|  | 486 |  | 
|  | 487 | req->rq_resp_wct = wct; | 
|  | 488 | hdrlen = SMB_HEADER_LEN + wct*2 + 2; | 
|  | 489 | VERBOSE("header length: %d   smb_wct: %2d\n", hdrlen, wct); | 
|  | 490 |  | 
|  | 491 | req->rq_bytes_recvd = SMB_HEADER_LEN; | 
|  | 492 | req->rq_rlen = hdrlen; | 
|  | 493 | req->rq_iov[0].iov_base = req->rq_header; | 
|  | 494 | req->rq_iov[0].iov_len  = hdrlen; | 
|  | 495 | req->rq_iovlen = 1; | 
|  | 496 | server->rstate = SMB_RECV_PARAM; | 
|  | 497 |  | 
|  | 498 | #ifdef SMB_DEBUG_PACKET_SIZE | 
|  | 499 | add_recv_stats(smb_len(server->header)); | 
|  | 500 | #endif | 
|  | 501 | return 0; | 
|  | 502 | } | 
|  | 503 |  | 
|  | 504 | /* | 
|  | 505 | * Reads the SMB parameters | 
|  | 506 | */ | 
|  | 507 | static int smb_recv_param(struct smb_sb_info *server, struct smb_request *req) | 
|  | 508 | { | 
|  | 509 | int result; | 
|  | 510 |  | 
|  | 511 | result = smb_receive(server, req); | 
|  | 512 | if (result < 0) | 
|  | 513 | return result; | 
|  | 514 | if (req->rq_bytes_recvd < req->rq_rlen) | 
|  | 515 | return 0; | 
|  | 516 |  | 
|  | 517 | VERBOSE("result: %d   smb_bcc:  %04x\n", result, | 
|  | 518 | WVAL(req->rq_header, SMB_HEADER_LEN + | 
|  | 519 | (*(req->rq_header + smb_wct) * 2))); | 
|  | 520 |  | 
|  | 521 | result = 0; | 
|  | 522 | req->rq_iov[0].iov_base = NULL; | 
|  | 523 | req->rq_rlen = 0; | 
|  | 524 | if (req->rq_callback) | 
|  | 525 | req->rq_callback(req); | 
|  | 526 | else if (req->rq_setup_read) | 
|  | 527 | result = req->rq_setup_read(req); | 
|  | 528 | if (result < 0) { | 
|  | 529 | server->rstate = SMB_RECV_DROP; | 
|  | 530 | return result; | 
|  | 531 | } | 
|  | 532 |  | 
|  | 533 | server->rstate = req->rq_rlen > 0 ? SMB_RECV_DATA : SMB_RECV_END; | 
|  | 534 |  | 
|  | 535 | req->rq_bytes_recvd = 0;	// recvd out of the iov | 
|  | 536 |  | 
|  | 537 | VERBOSE("rlen: %d\n", req->rq_rlen); | 
|  | 538 | if (req->rq_rlen < 0) { | 
|  | 539 | PARANOIA("Parameters read beyond end of packet!\n"); | 
|  | 540 | server->rstate = SMB_RECV_END; | 
|  | 541 | return -EIO; | 
|  | 542 | } | 
|  | 543 | return 0; | 
|  | 544 | } | 
|  | 545 |  | 
|  | 546 | /* | 
|  | 547 | * Reads the SMB data | 
|  | 548 | */ | 
|  | 549 | static int smb_recv_data(struct smb_sb_info *server, struct smb_request *req) | 
|  | 550 | { | 
|  | 551 | int result; | 
|  | 552 |  | 
|  | 553 | result = smb_receive(server, req); | 
|  | 554 | if (result < 0) | 
|  | 555 | goto out; | 
|  | 556 | if (req->rq_bytes_recvd < req->rq_rlen) | 
|  | 557 | goto out; | 
|  | 558 | server->rstate = SMB_RECV_END; | 
|  | 559 | out: | 
|  | 560 | VERBOSE("result: %d\n", result); | 
|  | 561 | return result; | 
|  | 562 | } | 
|  | 563 |  | 
|  | 564 | /* | 
|  | 565 | * Receive a transaction2 response | 
|  | 566 | * Return: 0 if the response has been fully read | 
|  | 567 | *         1 if there are further "fragments" to read | 
|  | 568 | *        <0 if there is an error | 
|  | 569 | */ | 
|  | 570 | static int smb_recv_trans2(struct smb_sb_info *server, struct smb_request *req) | 
|  | 571 | { | 
|  | 572 | unsigned char *inbuf; | 
|  | 573 | unsigned int parm_disp, parm_offset, parm_count, parm_tot; | 
|  | 574 | unsigned int data_disp, data_offset, data_count, data_tot; | 
|  | 575 | int hdrlen = SMB_HEADER_LEN + req->rq_resp_wct*2 - 2; | 
|  | 576 |  | 
|  | 577 | VERBOSE("handling trans2\n"); | 
|  | 578 |  | 
|  | 579 | inbuf = req->rq_header; | 
|  | 580 | data_tot    = WVAL(inbuf, smb_tdrcnt); | 
|  | 581 | parm_tot    = WVAL(inbuf, smb_tprcnt); | 
|  | 582 | parm_disp   = WVAL(inbuf, smb_prdisp); | 
|  | 583 | parm_offset = WVAL(inbuf, smb_proff); | 
|  | 584 | parm_count  = WVAL(inbuf, smb_prcnt); | 
|  | 585 | data_disp   = WVAL(inbuf, smb_drdisp); | 
|  | 586 | data_offset = WVAL(inbuf, smb_droff); | 
|  | 587 | data_count  = WVAL(inbuf, smb_drcnt); | 
|  | 588 |  | 
|  | 589 | /* Modify offset for the split header/buffer we use */ | 
|  | 590 | if (data_count || data_offset) { | 
|  | 591 | if (unlikely(data_offset < hdrlen)) | 
|  | 592 | goto out_bad_data; | 
|  | 593 | else | 
|  | 594 | data_offset -= hdrlen; | 
|  | 595 | } | 
|  | 596 | if (parm_count || parm_offset) { | 
|  | 597 | if (unlikely(parm_offset < hdrlen)) | 
|  | 598 | goto out_bad_parm; | 
|  | 599 | else | 
|  | 600 | parm_offset -= hdrlen; | 
|  | 601 | } | 
|  | 602 |  | 
|  | 603 | if (parm_count == parm_tot && data_count == data_tot) { | 
|  | 604 | /* | 
|  | 605 | * This packet has all the trans2 data. | 
|  | 606 | * | 
|  | 607 | * We setup the request so that this will be the common | 
|  | 608 | * case. It may be a server error to not return a | 
|  | 609 | * response that fits. | 
|  | 610 | */ | 
|  | 611 | VERBOSE("single trans2 response  " | 
|  | 612 | "dcnt=%u, pcnt=%u, doff=%u, poff=%u\n", | 
|  | 613 | data_count, parm_count, | 
|  | 614 | data_offset, parm_offset); | 
|  | 615 | req->rq_ldata = data_count; | 
|  | 616 | req->rq_lparm = parm_count; | 
|  | 617 | req->rq_data = req->rq_buffer + data_offset; | 
|  | 618 | req->rq_parm = req->rq_buffer + parm_offset; | 
|  | 619 | if (unlikely(parm_offset + parm_count > req->rq_rlen)) | 
|  | 620 | goto out_bad_parm; | 
|  | 621 | if (unlikely(data_offset + data_count > req->rq_rlen)) | 
|  | 622 | goto out_bad_data; | 
|  | 623 | return 0; | 
|  | 624 | } | 
|  | 625 |  | 
|  | 626 | VERBOSE("multi trans2 response  " | 
|  | 627 | "frag=%d, dcnt=%u, pcnt=%u, doff=%u, poff=%u\n", | 
|  | 628 | req->rq_fragment, | 
|  | 629 | data_count, parm_count, | 
|  | 630 | data_offset, parm_offset); | 
|  | 631 |  | 
|  | 632 | if (!req->rq_fragment) { | 
|  | 633 | int buf_len; | 
|  | 634 |  | 
|  | 635 | /* We got the first trans2 fragment */ | 
|  | 636 | req->rq_fragment = 1; | 
|  | 637 | req->rq_total_data = data_tot; | 
|  | 638 | req->rq_total_parm = parm_tot; | 
|  | 639 | req->rq_ldata = 0; | 
|  | 640 | req->rq_lparm = 0; | 
|  | 641 |  | 
|  | 642 | buf_len = data_tot + parm_tot; | 
|  | 643 | if (buf_len > SMB_MAX_PACKET_SIZE) | 
|  | 644 | goto out_too_long; | 
|  | 645 |  | 
|  | 646 | req->rq_trans2bufsize = buf_len; | 
| Pekka Enberg | d063389 | 2006-01-14 13:21:13 -0800 | [diff] [blame] | 647 | req->rq_trans2buffer = kzalloc(buf_len, GFP_NOFS); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 648 | if (!req->rq_trans2buffer) | 
|  | 649 | goto out_no_mem; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 650 |  | 
|  | 651 | req->rq_parm = req->rq_trans2buffer; | 
|  | 652 | req->rq_data = req->rq_trans2buffer + parm_tot; | 
|  | 653 | } else if (unlikely(req->rq_total_data < data_tot || | 
|  | 654 | req->rq_total_parm < parm_tot)) | 
|  | 655 | goto out_data_grew; | 
|  | 656 |  | 
|  | 657 | if (unlikely(parm_disp + parm_count > req->rq_total_parm || | 
|  | 658 | parm_offset + parm_count > req->rq_rlen)) | 
|  | 659 | goto out_bad_parm; | 
|  | 660 | if (unlikely(data_disp + data_count > req->rq_total_data || | 
|  | 661 | data_offset + data_count > req->rq_rlen)) | 
|  | 662 | goto out_bad_data; | 
|  | 663 |  | 
|  | 664 | inbuf = req->rq_buffer; | 
|  | 665 | memcpy(req->rq_parm + parm_disp, inbuf + parm_offset, parm_count); | 
|  | 666 | memcpy(req->rq_data + data_disp, inbuf + data_offset, data_count); | 
|  | 667 |  | 
|  | 668 | req->rq_ldata += data_count; | 
|  | 669 | req->rq_lparm += parm_count; | 
|  | 670 |  | 
|  | 671 | /* | 
|  | 672 | * Check whether we've received all of the data. Note that | 
|  | 673 | * we use the packet totals -- total lengths might shrink! | 
|  | 674 | */ | 
|  | 675 | if (req->rq_ldata >= data_tot && req->rq_lparm >= parm_tot) { | 
|  | 676 | req->rq_ldata = data_tot; | 
|  | 677 | req->rq_lparm = parm_tot; | 
|  | 678 | return 0; | 
|  | 679 | } | 
|  | 680 | return 1; | 
|  | 681 |  | 
|  | 682 | out_too_long: | 
|  | 683 | printk(KERN_ERR "smb_trans2: data/param too long, data=%u, parm=%u\n", | 
|  | 684 | data_tot, parm_tot); | 
|  | 685 | goto out_EIO; | 
|  | 686 | out_no_mem: | 
|  | 687 | printk(KERN_ERR "smb_trans2: couldn't allocate data area of %d bytes\n", | 
|  | 688 | req->rq_trans2bufsize); | 
|  | 689 | req->rq_errno = -ENOMEM; | 
|  | 690 | goto out; | 
|  | 691 | out_data_grew: | 
|  | 692 | printk(KERN_ERR "smb_trans2: data/params grew!\n"); | 
|  | 693 | goto out_EIO; | 
|  | 694 | out_bad_parm: | 
|  | 695 | printk(KERN_ERR "smb_trans2: invalid parms, disp=%u, cnt=%u, tot=%u, ofs=%u\n", | 
|  | 696 | parm_disp, parm_count, parm_tot, parm_offset); | 
|  | 697 | goto out_EIO; | 
|  | 698 | out_bad_data: | 
|  | 699 | printk(KERN_ERR "smb_trans2: invalid data, disp=%u, cnt=%u, tot=%u, ofs=%u\n", | 
|  | 700 | data_disp, data_count, data_tot, data_offset); | 
|  | 701 | out_EIO: | 
|  | 702 | req->rq_errno = -EIO; | 
|  | 703 | out: | 
|  | 704 | return req->rq_errno; | 
|  | 705 | } | 
|  | 706 |  | 
|  | 707 | /* | 
|  | 708 | * State machine for receiving responses. We handle the fact that we can't | 
|  | 709 | * read the full response in one try by having states telling us how much we | 
|  | 710 | * have read. | 
|  | 711 | * | 
|  | 712 | * Must be called with the server lock held (only called from smbiod). | 
|  | 713 | * | 
|  | 714 | * Return: <0 on error | 
|  | 715 | */ | 
|  | 716 | int smb_request_recv(struct smb_sb_info *server) | 
|  | 717 | { | 
|  | 718 | struct smb_request *req = NULL; | 
|  | 719 | int result = 0; | 
|  | 720 |  | 
|  | 721 | if (smb_recv_available(server) <= 0) | 
|  | 722 | return 0; | 
|  | 723 |  | 
|  | 724 | VERBOSE("state: %d\n", server->rstate); | 
|  | 725 | switch (server->rstate) { | 
|  | 726 | case SMB_RECV_DROP: | 
|  | 727 | result = smb_receive_drop(server); | 
|  | 728 | if (result < 0) | 
|  | 729 | break; | 
|  | 730 | if (server->rstate == SMB_RECV_DROP) | 
|  | 731 | break; | 
|  | 732 | server->rstate = SMB_RECV_START; | 
|  | 733 | /* fallthrough */ | 
|  | 734 | case SMB_RECV_START: | 
|  | 735 | server->smb_read = 0; | 
|  | 736 | server->rstate = SMB_RECV_HEADER; | 
|  | 737 | /* fallthrough */ | 
|  | 738 | case SMB_RECV_HEADER: | 
|  | 739 | result = smb_receive_header(server); | 
|  | 740 | if (result < 0) | 
|  | 741 | break; | 
|  | 742 | if (server->rstate == SMB_RECV_HEADER) | 
|  | 743 | break; | 
|  | 744 | if (! (*(server->header + smb_flg) & SMB_FLAGS_REPLY) ) { | 
|  | 745 | server->rstate = SMB_RECV_REQUEST; | 
|  | 746 | break; | 
|  | 747 | } | 
|  | 748 | if (server->rstate != SMB_RECV_HCOMPLETE) | 
|  | 749 | break; | 
|  | 750 | /* fallthrough */ | 
|  | 751 | case SMB_RECV_HCOMPLETE: | 
|  | 752 | req = find_request(server, WVAL(server->header, smb_mid)); | 
|  | 753 | if (!req) | 
|  | 754 | break; | 
|  | 755 | smb_init_request(server, req); | 
|  | 756 | req->rq_rcls = *(req->rq_header + smb_rcls); | 
|  | 757 | req->rq_err  = WVAL(req->rq_header, smb_err); | 
|  | 758 | if (server->rstate != SMB_RECV_PARAM) | 
|  | 759 | break; | 
|  | 760 | /* fallthrough */ | 
|  | 761 | case SMB_RECV_PARAM: | 
|  | 762 | if (!req) | 
|  | 763 | req = find_request(server,WVAL(server->header,smb_mid)); | 
|  | 764 | if (!req) | 
|  | 765 | break; | 
|  | 766 | result = smb_recv_param(server, req); | 
|  | 767 | if (result < 0) | 
|  | 768 | break; | 
|  | 769 | if (server->rstate != SMB_RECV_DATA) | 
|  | 770 | break; | 
|  | 771 | /* fallthrough */ | 
|  | 772 | case SMB_RECV_DATA: | 
|  | 773 | if (!req) | 
|  | 774 | req = find_request(server,WVAL(server->header,smb_mid)); | 
|  | 775 | if (!req) | 
|  | 776 | break; | 
|  | 777 | result = smb_recv_data(server, req); | 
|  | 778 | if (result < 0) | 
|  | 779 | break; | 
|  | 780 | break; | 
|  | 781 |  | 
|  | 782 | /* We should never be called with any of these states */ | 
|  | 783 | case SMB_RECV_END: | 
|  | 784 | case SMB_RECV_REQUEST: | 
| Adrian Bunk | ccb6e36 | 2005-11-07 00:59:23 -0800 | [diff] [blame] | 785 | BUG(); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 786 | } | 
|  | 787 |  | 
|  | 788 | if (result < 0) { | 
|  | 789 | /* We saw an error */ | 
|  | 790 | return result; | 
|  | 791 | } | 
|  | 792 |  | 
|  | 793 | if (server->rstate != SMB_RECV_END) | 
|  | 794 | return 0; | 
|  | 795 |  | 
|  | 796 | result = 0; | 
|  | 797 | if (req->rq_trans2_command && req->rq_rcls == SUCCESS) | 
|  | 798 | result = smb_recv_trans2(server, req); | 
|  | 799 |  | 
|  | 800 | /* | 
|  | 801 | * Response completely read. Drop any extra bytes sent by the server. | 
|  | 802 | * (Yes, servers sometimes add extra bytes to responses) | 
|  | 803 | */ | 
|  | 804 | VERBOSE("smb_len: %d   smb_read: %d\n", | 
|  | 805 | server->smb_len, server->smb_read); | 
|  | 806 | if (server->smb_read < server->smb_len) | 
|  | 807 | smb_receive_drop(server); | 
|  | 808 |  | 
|  | 809 | server->rstate = SMB_RECV_START; | 
|  | 810 |  | 
|  | 811 | if (!result) { | 
|  | 812 | list_del_init(&req->rq_queue); | 
|  | 813 | req->rq_flags |= SMB_REQ_RECEIVED; | 
|  | 814 | smb_rput(req); | 
|  | 815 | wake_up_interruptible(&req->rq_wait); | 
|  | 816 | } | 
|  | 817 | return 0; | 
|  | 818 | } |