X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Flibsmb%2Fclireadwrite.c;h=d38de1950800d4f4d25ac6b4ef9558b056f9de07;hb=05379f01252fc8b449f36130bcf00c321d7a1c37;hp=6b39a885f04262467bb8ec267ef158cc3360cc07;hpb=7ff128ef03ed03e6b9ce17642cda793255f17c0e;p=amitay%2Fsamba.git diff --git a/source3/libsmb/clireadwrite.c b/source3/libsmb/clireadwrite.c index 6b39a885f04..d38de195080 100644 --- a/source3/libsmb/clireadwrite.c +++ b/source3/libsmb/clireadwrite.c @@ -20,275 +20,567 @@ #include "includes.h" /**************************************************************************** -Issue a single SMBread and don't wait for a reply. + Calculate the recommended read buffer size ****************************************************************************/ +static size_t cli_read_max_bufsize(struct cli_state *cli) +{ + if (!client_is_signing_on(cli) && !cli_encryption_on(cli) + && (cli->posix_capabilities & CIFS_UNIX_LARGE_READ_CAP)) { + return CLI_SAMBA_MAX_POSIX_LARGE_READX_SIZE; + } + if (cli->capabilities & CAP_LARGE_READX) { + return cli->is_samba + ? CLI_SAMBA_MAX_LARGE_READX_SIZE + : CLI_WINDOWS_MAX_LARGE_READX_SIZE; + } + return (cli->max_xmit - (smb_size+32)) & ~1023; +} -static bool cli_issue_read(struct cli_state *cli, int fnum, off_t offset, - size_t size, int i) +/**************************************************************************** + Calculate the recommended write buffer size +****************************************************************************/ +static size_t cli_write_max_bufsize(struct cli_state *cli, uint16_t write_mode) { - bool bigoffset = False; + if (write_mode == 0 && + !client_is_signing_on(cli) && + !cli_encryption_on(cli) && + (cli->posix_capabilities & CIFS_UNIX_LARGE_WRITE_CAP) && + (cli->capabilities & CAP_LARGE_FILES)) { + /* Only do massive writes if we can do them direct + * with no signing or encrypting - not on a pipe. */ + return CLI_SAMBA_MAX_POSIX_LARGE_WRITEX_SIZE; + } - memset(cli->outbuf,'\0',smb_size); - memset(cli->inbuf,'\0',smb_size); + if (cli->is_samba) { + return CLI_SAMBA_MAX_LARGE_WRITEX_SIZE; + } - if ((SMB_BIG_UINT)offset >> 32) - bigoffset = True; + if (((cli->capabilities & CAP_LARGE_WRITEX) == 0) + || client_is_signing_on(cli) + || strequal(cli->dev, "LPT1:")) { - cli_set_message(cli->outbuf,bigoffset ? 12 : 10,0,True); + /* + * Printer devices are restricted to max_xmit writesize in + * Vista and XPSP3 as are signing connections. + */ - SCVAL(cli->outbuf,smb_com,SMBreadX); - SSVAL(cli->outbuf,smb_tid,cli->cnum); - cli_setup_packet(cli); + return (cli->max_xmit - (smb_size+32)) & ~1023; + } - SCVAL(cli->outbuf,smb_vwv0,0xFF); - SSVAL(cli->outbuf,smb_vwv2,fnum); - SIVAL(cli->outbuf,smb_vwv3,offset); - SSVAL(cli->outbuf,smb_vwv5,size); - SSVAL(cli->outbuf,smb_vwv6,size); - SSVAL(cli->outbuf,smb_vwv7,(size >> 16)); - SSVAL(cli->outbuf,smb_mid,cli->mid + i); + return CLI_WINDOWS_MAX_LARGE_WRITEX_SIZE; +} - if (bigoffset) { - SIVAL(cli->outbuf,smb_vwv10,(((SMB_BIG_UINT)offset)>>32) & 0xffffffff); +struct cli_read_andx_state { + size_t size; + uint16_t vwv[12]; + NTSTATUS status; + size_t received; + uint8_t *buf; +}; + +static void cli_read_andx_done(struct tevent_req *subreq); + +struct tevent_req *cli_read_andx_create(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, uint16_t fnum, + off_t offset, size_t size, + struct tevent_req **psmbreq) +{ + struct tevent_req *req, *subreq; + struct cli_read_andx_state *state; + bool bigoffset = False; + uint8_t wct = 10; + + if (size > cli_read_max_bufsize(cli)) { + DEBUG(0, ("cli_read_andx_send got size=%d, can only handle " + "size=%d\n", (int)size, + (int)cli_read_max_bufsize(cli))); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct cli_read_andx_state); + if (req == NULL) { + return NULL; + } + state->size = size; + + SCVAL(state->vwv + 0, 0, 0xFF); + SCVAL(state->vwv + 0, 1, 0); + SSVAL(state->vwv + 1, 0, 0); + SSVAL(state->vwv + 2, 0, fnum); + SIVAL(state->vwv + 3, 0, offset); + SSVAL(state->vwv + 5, 0, size); + SSVAL(state->vwv + 6, 0, size); + SSVAL(state->vwv + 7, 0, (size >> 16)); + SSVAL(state->vwv + 8, 0, 0); + SSVAL(state->vwv + 9, 0, 0); + + if ((uint64_t)offset >> 32) { + bigoffset = true; + SIVAL(state->vwv + 10, 0, + (((uint64_t)offset)>>32) & 0xffffffff); + wct += 2; } - return cli_send_smb(cli); + subreq = cli_smb_req_create(state, ev, cli, SMBreadX, 0, wct, + state->vwv, 0, NULL); + if (subreq == NULL) { + TALLOC_FREE(req); + return NULL; + } + tevent_req_set_callback(subreq, cli_read_andx_done, req); + *psmbreq = subreq; + return req; } -/**************************************************************************** - Read size bytes at offset offset using SMBreadX. -****************************************************************************/ +struct tevent_req *cli_read_andx_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, uint16_t fnum, + off_t offset, size_t size) +{ + struct tevent_req *req, *subreq; + NTSTATUS status; -ssize_t cli_read(struct cli_state *cli, int fnum, char *buf, off_t offset, size_t size) + req = cli_read_andx_create(mem_ctx, ev, cli, fnum, offset, size, + &subreq); + if (req == NULL) { + return NULL; + } + + status = cli_smb_req_send(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + return req; +} + +static void cli_read_andx_done(struct tevent_req *subreq) { - char *p; - size_t size2; - size_t readsize; - ssize_t total = 0; - /* We can only do direct reads if not signing or encrypting. */ - bool direct_reads = !client_is_signing_on(cli) && !cli_encryption_on(cli); + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cli_read_andx_state *state = tevent_req_data( + req, struct cli_read_andx_state); + uint8_t *inbuf; + uint8_t wct; + uint16_t *vwv; + uint32_t num_bytes; + uint8_t *bytes; + + state->status = cli_smb_recv(subreq, 12, &wct, &vwv, &num_bytes, + &bytes); + if (NT_STATUS_IS_ERR(state->status)) { + tevent_req_nterror(req, state->status); + return; + } - if (size == 0) - return 0; + /* size is the number of bytes the server returned. + * Might be zero. */ + state->received = SVAL(vwv + 5, 0); + state->received |= (((unsigned int)SVAL(vwv + 7, 0)) << 16); + + if (state->received > state->size) { + DEBUG(5,("server returned more than we wanted!\n")); + tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR); + return; + } /* - * Set readsize to the maximum size we can handle in one readX, - * rounded down to a multiple of 1024. + * bcc field must be valid for small reads, for large reads the 16-bit + * bcc field can't be correct. */ - if (client_is_signing_on(cli) == false && - cli_encryption_on(cli) == false && - (cli->posix_capabilities & CIFS_UNIX_LARGE_READ_CAP)) { - readsize = CLI_SAMBA_MAX_POSIX_LARGE_READX_SIZE; - } else if (cli->capabilities & CAP_LARGE_READX) { - if (cli->is_samba) { - readsize = CLI_SAMBA_MAX_LARGE_READX_SIZE; - } else { - readsize = CLI_WINDOWS_MAX_LARGE_READX_SIZE; - } - } else { - readsize = (cli->max_xmit - (smb_size+32)) & ~1023; + if ((state->received < 0xffff) && (state->received > num_bytes)) { + DEBUG(5, ("server announced more bytes than sent\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; } - while (total < size) { - readsize = MIN(readsize, size-total); + inbuf = cli_smb_inbuf(subreq); + state->buf = (uint8_t *)smb_base(inbuf) + SVAL(vwv+6, 0); - /* Issue a read and receive a reply */ + if (trans_oob(smb_len(inbuf), SVAL(vwv+6, 0), state->received) + || (state->buf < bytes)) { + DEBUG(5, ("server returned invalid read&x data offset\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + tevent_req_done(req); +} - if (!cli_issue_read(cli, fnum, offset, readsize, 0)) - return -1; +/* + * Pull the data out of a finished async read_and_x request. rcvbuf is + * talloced from the request, so better make sure that you copy it away before + * you talloc_free(req). "rcvbuf" is NOT a talloc_ctx of its own, so do not + * talloc_move it! + */ + +NTSTATUS cli_read_andx_recv(struct tevent_req *req, ssize_t *received, + uint8_t **rcvbuf) +{ + struct cli_read_andx_state *state = tevent_req_data( + req, struct cli_read_andx_state); + NTSTATUS status; - if (direct_reads) { - if (!cli_receive_smb_readX_header(cli)) - return -1; - } else { - if (!cli_receive_smb(cli)) - return -1; - } + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *received = state->received; + *rcvbuf = state->buf; + return NT_STATUS_OK; +} - /* Check for error. Make sure to check for DOS and NT - errors. */ +struct cli_pull_subreq { + struct tevent_req *req; + ssize_t received; + uint8_t *buf; +}; - if (cli_is_error(cli)) { - bool recoverable_error = False; - NTSTATUS status = NT_STATUS_OK; - uint8 eclass = 0; - uint32 ecode = 0; +/* + * Parallel read support. + * + * cli_pull sends as many read&x requests as the server would allow via + * max_mux at a time. When replies flow back in, the data is written into + * the callback function "sink" in the right order. + */ - if (cli_is_nt_error(cli)) - status = cli_nt_error(cli); - else - cli_dos_error(cli, &eclass, &ecode); +struct cli_pull_state { + struct tevent_req *req; - /* - * ERRDOS ERRmoredata or STATUS_MORE_ENRTIES is a - * recoverable error, plus we have valid data in the - * packet so don't error out here. - */ + struct event_context *ev; + struct cli_state *cli; + uint16_t fnum; + off_t start_offset; + SMB_OFF_T size; - if ((eclass == ERRDOS && ecode == ERRmoredata) || - NT_STATUS_V(status) == NT_STATUS_V(STATUS_MORE_ENTRIES)) - recoverable_error = True; + NTSTATUS (*sink)(char *buf, size_t n, void *priv); + void *priv; - if (!recoverable_error) - return -1; - } + size_t chunk_size; - size2 = SVAL(cli->inbuf, smb_vwv5); - size2 |= (((unsigned int)(SVAL(cli->inbuf, smb_vwv7))) << 16); + /* + * Outstanding requests + */ + int num_reqs; + struct cli_pull_subreq *reqs; - if (size2 > readsize) { - DEBUG(5,("server returned more than we wanted!\n")); - return -1; - } else if (size2 < 0) { - DEBUG(5,("read return < 0!\n")); - return -1; - } + /* + * For how many bytes did we send requests already? + */ + SMB_OFF_T requested; - if (!direct_reads) { - /* Copy data into buffer */ - p = smb_base(cli->inbuf) + SVAL(cli->inbuf,smb_vwv6); - memcpy(buf + total, p, size2); - } else { - /* Ensure the remaining data matches the return size. */ - ssize_t toread = smb_len_large(cli->inbuf) - SVAL(cli->inbuf,smb_vwv6); - - /* Ensure the size is correct. */ - if (toread != size2) { - DEBUG(5,("direct read logic fail toread (%d) != size2 (%u)\n", - (int)toread, (unsigned int)size2 )); - return -1; - } + /* + * Next request index to push into "sink". This walks around the "req" + * array, taking care that the requests are pushed to "sink" in the + * right order. If necessary (i.e. replies don't come in in the right + * order), replies are held back in "reqs". + */ + int top_req; - /* Read data directly into buffer */ - toread = cli_receive_smb_data(cli,buf+total,size2); - if (toread != size2) { - DEBUG(5,("direct read read failure toread (%d) != size2 (%u)\n", - (int)toread, (unsigned int)size2 )); - return -1; - } - } + /* + * How many bytes did we push into "sink"? + */ - total += size2; - offset += size2; + SMB_OFF_T pushed; +}; - /* - * If the server returned less than we asked for we're at EOF. - */ +static char *cli_pull_print(struct tevent_req *req, TALLOC_CTX *mem_ctx) +{ + struct cli_pull_state *state = tevent_req_data( + req, struct cli_pull_state); + char *result; - if (size2 < readsize) - break; + result = tevent_req_print(mem_ctx, req); + if (result == NULL) { + return NULL; } - return total; + return talloc_asprintf_append_buffer( + result, "num_reqs=%d, top_req=%d", + state->num_reqs, state->top_req); } -#if 0 /* relies on client_receive_smb(), now a static in libsmb/clientgen.c */ +static void cli_pull_read_done(struct tevent_req *read_req); -/* This call is INCOMPATIBLE with SMB signing. If you remove the #if 0 - you must fix ensure you don't attempt to sign the packets - data - *will* be currupted */ +/* + * Prepare an async pull request + */ + +struct tevent_req *cli_pull_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, + uint16_t fnum, off_t start_offset, + SMB_OFF_T size, size_t window_size, + NTSTATUS (*sink)(char *buf, size_t n, + void *priv), + void *priv) +{ + struct tevent_req *req; + struct cli_pull_state *state; + int i; -/**************************************************************************** -Issue a single SMBreadraw and don't wait for a reply. -****************************************************************************/ + req = tevent_req_create(mem_ctx, &state, struct cli_pull_state); + if (req == NULL) { + return NULL; + } + tevent_req_set_print_fn(req, cli_pull_print); + state->req = req; + + state->cli = cli; + state->ev = ev; + state->fnum = fnum; + state->start_offset = start_offset; + state->size = size; + state->sink = sink; + state->priv = priv; + + state->pushed = 0; + state->top_req = 0; + + if (size == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } -static bool cli_issue_readraw(struct cli_state *cli, int fnum, off_t offset, - size_t size, int i) -{ + state->chunk_size = cli_read_max_bufsize(cli); + + state->num_reqs = MAX(window_size/state->chunk_size, 1); + state->num_reqs = MIN(state->num_reqs, cli->max_mux); - if (!cli->sign_info.use_smb_signing) { - DEBUG(0, ("Cannot use readraw and SMB Signing\n")); - return False; + state->reqs = TALLOC_ZERO_ARRAY(state, struct cli_pull_subreq, + state->num_reqs); + if (state->reqs == NULL) { + goto failed; } - - memset(cli->outbuf,'\0',smb_size); - memset(cli->inbuf,'\0',smb_size); - cli_set_message(cli->outbuf,10,0,True); - - SCVAL(cli->outbuf,smb_com,SMBreadbraw); - SSVAL(cli->outbuf,smb_tid,cli->cnum); - cli_setup_packet(cli); + state->requested = 0; - SSVAL(cli->outbuf,smb_vwv0,fnum); - SIVAL(cli->outbuf,smb_vwv1,offset); - SSVAL(cli->outbuf,smb_vwv2,size); - SSVAL(cli->outbuf,smb_vwv3,size); - SSVAL(cli->outbuf,smb_mid,cli->mid + i); + for (i=0; inum_reqs; i++) { + struct cli_pull_subreq *subreq = &state->reqs[i]; + SMB_OFF_T size_left; + size_t request_thistime; - return cli_send_smb(cli); + if (state->requested >= size) { + state->num_reqs = i; + break; + } + + size_left = size - state->requested; + request_thistime = MIN(size_left, state->chunk_size); + + subreq->req = cli_read_andx_send( + state->reqs, ev, cli, fnum, + state->start_offset + state->requested, + request_thistime); + + if (subreq->req == NULL) { + goto failed; + } + tevent_req_set_callback(subreq->req, cli_pull_read_done, req); + state->requested += request_thistime; + } + return req; + +failed: + TALLOC_FREE(req); + return NULL; } -/**************************************************************************** - Tester for the readraw call. -****************************************************************************/ +/* + * Handle incoming read replies, push the data into sink and send out new + * requests if necessary. + */ -ssize_t cli_readraw(struct cli_state *cli, int fnum, char *buf, off_t offset, size_t size) +static void cli_pull_read_done(struct tevent_req *subreq) { - char *p; - int size2; - size_t readsize; - ssize_t total = 0; + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cli_pull_state *state = tevent_req_data( + req, struct cli_pull_state); + struct cli_pull_subreq *pull_subreq = NULL; + NTSTATUS status; + int i; + + for (i = 0; i < state->num_reqs; i++) { + pull_subreq = &state->reqs[i]; + if (subreq == pull_subreq->req) { + break; + } + } + if (i == state->num_reqs) { + /* Huh -- received something we did not send?? */ + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } - if (size == 0) - return 0; + status = cli_read_andx_recv(subreq, &pull_subreq->received, + &pull_subreq->buf); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(state->req, status); + return; + } /* - * Set readsize to the maximum size we can handle in one readraw. + * This loop is the one to take care of out-of-order replies. All + * pending requests are in state->reqs, state->reqs[top_req] is the + * one that is to be pushed next. If however a request later than + * top_req is replied to, then we can't push yet. If top_req is + * replied to at a later point then, we need to push all the finished + * requests. */ - readsize = 0xFFFF; + while (state->reqs[state->top_req].req != NULL) { + struct cli_pull_subreq *top_subreq; - while (total < size) { - readsize = MIN(readsize, size-total); + DEBUG(11, ("cli_pull_read_done: top_req = %d\n", + state->top_req)); - /* Issue a read and receive a reply */ + top_subreq = &state->reqs[state->top_req]; - if (!cli_issue_readraw(cli, fnum, offset, readsize, 0)) - return -1; - - if (!client_receive_smb(cli->fd, cli->inbuf, cli->timeout)) - return -1; + if (tevent_req_is_in_progress(top_subreq->req)) { + DEBUG(11, ("cli_pull_read_done: top request not yet " + "done\n")); + return; + } - size2 = smb_len(cli->inbuf); + DEBUG(10, ("cli_pull_read_done: Pushing %d bytes, %d already " + "pushed\n", (int)top_subreq->received, + (int)state->pushed)); - if (size2 > readsize) { - DEBUG(5,("server returned more than we wanted!\n")); - return -1; - } else if (size2 < 0) { - DEBUG(5,("read return < 0!\n")); - return -1; + status = state->sink((char *)top_subreq->buf, + top_subreq->received, state->priv); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(state->req, status); + return; } + state->pushed += top_subreq->received; + + TALLOC_FREE(state->reqs[state->top_req].req); + + if (state->requested < state->size) { + struct tevent_req *new_req; + SMB_OFF_T size_left; + size_t request_thistime; + + size_left = state->size - state->requested; + request_thistime = MIN(size_left, state->chunk_size); - /* Copy data into buffer */ + DEBUG(10, ("cli_pull_read_done: Requesting %d bytes " + "at %d, position %d\n", + (int)request_thistime, + (int)(state->start_offset + + state->requested), + state->top_req)); - if (size2) { - p = cli->inbuf + 4; - memcpy(buf + total, p, size2); + new_req = cli_read_andx_send( + state->reqs, state->ev, state->cli, + state->fnum, + state->start_offset + state->requested, + request_thistime); + + if (tevent_req_nomem(new_req, state->req)) { + return; + } + tevent_req_set_callback(new_req, cli_pull_read_done, + req); + + state->reqs[state->top_req].req = new_req; + state->requested += request_thistime; } - total += size2; - offset += size2; + state->top_req = (state->top_req+1) % state->num_reqs; + } + tevent_req_done(req); +} + +NTSTATUS cli_pull_recv(struct tevent_req *req, SMB_OFF_T *received) +{ + struct cli_pull_state *state = tevent_req_data( + req, struct cli_pull_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *received = state->pushed; + return NT_STATUS_OK; +} + +NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum, + off_t start_offset, SMB_OFF_T size, size_t window_size, + NTSTATUS (*sink)(char *buf, size_t n, void *priv), + void *priv, SMB_OFF_T *received) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_OK; + + if (cli_has_async_calls(cli)) { /* - * If the server returned less than we asked for we're at EOF. + * Can't use sync call while an async call is in flight */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } - if (size2 < readsize) - break; + ev = event_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; } - return total; + req = cli_pull_send(frame, ev, cli, fnum, start_offset, size, + window_size, sink, priv); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll(req, ev)) { + status = map_nt_error_from_unix(errno); + goto fail; + } + + status = cli_pull_recv(req, received); + fail: + TALLOC_FREE(frame); + if (!NT_STATUS_IS_OK(status)) { + cli_set_error(cli, status); + } + return status; +} + +static NTSTATUS cli_read_sink(char *buf, size_t n, void *priv) +{ + char **pbuf = (char **)priv; + memcpy(*pbuf, buf, n); + *pbuf += n; + return NT_STATUS_OK; +} + +ssize_t cli_read(struct cli_state *cli, uint16_t fnum, char *buf, + off_t offset, size_t size) +{ + NTSTATUS status; + SMB_OFF_T ret; + + status = cli_pull(cli, fnum, offset, size, size, + cli_read_sink, &buf, &ret); + if (!NT_STATUS_IS_OK(status)) { + cli_set_error(cli, status); + return -1; + } + return ret; } -#endif /**************************************************************************** Issue a single SMBwrite and don't wait for a reply. ****************************************************************************/ static bool cli_issue_write(struct cli_state *cli, - int fnum, + uint16_t fnum, off_t offset, uint16 mode, const char *buf, @@ -351,7 +643,7 @@ static bool cli_issue_write(struct cli_state *cli, smb_buf(cli->outbuf) - smb_base(cli->outbuf) + 1); if (large_writex) { - SIVAL(cli->outbuf,smb_vwv12,(((SMB_BIG_UINT)offset)>>32) & 0xffffffff); + SIVAL(cli->outbuf,smb_vwv12,(((uint64_t)offset)>>32) & 0xffffffff); } p = smb_base(cli->outbuf) + SVAL(cli->outbuf,smb_vwv11) -1; @@ -388,7 +680,7 @@ static bool cli_issue_write(struct cli_state *cli, ****************************************************************************/ ssize_t cli_write(struct cli_state *cli, - int fnum, uint16 write_mode, + uint16_t fnum, uint16 write_mode, const char *buf, off_t offset, size_t size) { ssize_t bwritten = 0; @@ -404,26 +696,7 @@ ssize_t cli_write(struct cli_state *cli, mpx = 1; } - /* Default (small) writesize. */ - writesize = (cli->max_xmit - (smb_size+32)) & ~1023; - - if (write_mode == 0 && - !client_is_signing_on(cli) && - !cli_encryption_on(cli) && - (cli->posix_capabilities & CIFS_UNIX_LARGE_WRITE_CAP) && - (cli->capabilities & CAP_LARGE_FILES)) { - /* Only do massive writes if we can do them direct - * with no signing or encrypting - not on a pipe. */ - writesize = CLI_SAMBA_MAX_POSIX_LARGE_WRITEX_SIZE; - } else if (cli->capabilities & CAP_LARGE_WRITEX) { - if (cli->is_samba) { - writesize = CLI_SAMBA_MAX_LARGE_WRITEX_SIZE; - } else if (!client_is_signing_on(cli)) { - /* Windows restricts signed writes to max_xmit. - * Found by Volker. */ - writesize = CLI_WINDOWS_MAX_LARGE_WRITEX_SIZE; - } - } + writesize = cli_write_max_bufsize(cli, write_mode); blocks = (size + (writesize-1)) / writesize; @@ -451,7 +724,9 @@ ssize_t cli_write(struct cli_state *cli, break; bwritten += SVAL(cli->inbuf, smb_vwv2); - bwritten += (((int)(SVAL(cli->inbuf, smb_vwv4)))<<16); + if (writesize > 0xFFFF) { + bwritten += (((int)(SVAL(cli->inbuf, smb_vwv4)))<<16); + } } while (received < issued && cli_receive_smb(cli)) { @@ -466,7 +741,7 @@ ssize_t cli_write(struct cli_state *cli, ****************************************************************************/ ssize_t cli_smbwrite(struct cli_state *cli, - int fnum, char *buf, off_t offset, size_t size1) + uint16_t fnum, char *buf, off_t offset, size_t size1) { char *p; ssize_t total = 0; @@ -516,3 +791,462 @@ ssize_t cli_smbwrite(struct cli_state *cli, return total; } + +/* + * Send a write&x request + */ + +struct cli_write_andx_state { + size_t size; + uint16_t vwv[14]; + size_t written; + uint8_t pad; + struct iovec iov[2]; +}; + +static void cli_write_andx_done(struct tevent_req *subreq); + +struct tevent_req *cli_write_andx_create(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, uint16_t fnum, + uint16_t mode, const uint8_t *buf, + off_t offset, size_t size, + struct tevent_req **reqs_before, + int num_reqs_before, + struct tevent_req **psmbreq) +{ + struct tevent_req *req, *subreq; + struct cli_write_andx_state *state; + bool bigoffset = ((cli->capabilities & CAP_LARGE_FILES) != 0); + uint8_t wct = bigoffset ? 14 : 12; + size_t max_write = cli_write_max_bufsize(cli, mode); + uint16_t *vwv; + + req = tevent_req_create(mem_ctx, &state, struct cli_write_andx_state); + if (req == NULL) { + return NULL; + } + + size = MIN(size, max_write); + + vwv = state->vwv; + + SCVAL(vwv+0, 0, 0xFF); + SCVAL(vwv+0, 1, 0); + SSVAL(vwv+1, 0, 0); + SSVAL(vwv+2, 0, fnum); + SIVAL(vwv+3, 0, offset); + SIVAL(vwv+5, 0, 0); + SSVAL(vwv+7, 0, mode); + SSVAL(vwv+8, 0, 0); + SSVAL(vwv+9, 0, (size>>16)); + SSVAL(vwv+10, 0, size); + + SSVAL(vwv+11, 0, + cli_smb_wct_ofs(reqs_before, num_reqs_before) + + 1 /* the wct field */ + + wct * 2 /* vwv */ + + 2 /* num_bytes field */ + + 1 /* pad */); + + if (bigoffset) { + SIVAL(vwv+12, 0, (((uint64_t)offset)>>32) & 0xffffffff); + } + + state->pad = 0; + state->iov[0].iov_base = (void *)&state->pad; + state->iov[0].iov_len = 1; + state->iov[1].iov_base = CONST_DISCARD(void *, buf); + state->iov[1].iov_len = size; + + subreq = cli_smb_req_create(state, ev, cli, SMBwriteX, 0, wct, vwv, + 2, state->iov); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cli_write_andx_done, req); + *psmbreq = subreq; + return req; +} + +struct tevent_req *cli_write_andx_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, uint16_t fnum, + uint16_t mode, const uint8_t *buf, + off_t offset, size_t size) +{ + struct tevent_req *req, *subreq; + NTSTATUS status; + + req = cli_write_andx_create(mem_ctx, ev, cli, fnum, mode, buf, offset, + size, NULL, 0, &subreq); + if (req == NULL) { + return NULL; + } + + status = cli_smb_req_send(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + return req; +} + +static void cli_write_andx_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cli_write_andx_state *state = tevent_req_data( + req, struct cli_write_andx_state); + uint8_t wct; + uint16_t *vwv; + NTSTATUS status; + + status = cli_smb_recv(subreq, 6, &wct, &vwv, NULL, NULL); + if (NT_STATUS_IS_ERR(status)) { + TALLOC_FREE(subreq); + tevent_req_nterror(req, status); + return; + } + state->written = SVAL(vwv+2, 0); + state->written |= SVAL(vwv+4, 0)<<16; + tevent_req_done(req); +} + +NTSTATUS cli_write_andx_recv(struct tevent_req *req, size_t *pwritten) +{ + struct cli_write_andx_state *state = tevent_req_data( + req, struct cli_write_andx_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *pwritten = state->written; + return NT_STATUS_OK; +} + +struct cli_writeall_state { + struct event_context *ev; + struct cli_state *cli; + uint16_t fnum; + uint16_t mode; + const uint8_t *buf; + off_t offset; + size_t size; + size_t written; +}; + +static void cli_writeall_written(struct tevent_req *req); + +static struct tevent_req *cli_writeall_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, + uint16_t fnum, + uint16_t mode, + const uint8_t *buf, + off_t offset, size_t size) +{ + struct tevent_req *req, *subreq; + struct cli_writeall_state *state; + + req = tevent_req_create(mem_ctx, &state, struct cli_writeall_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->cli = cli; + state->fnum = fnum; + state->mode = mode; + state->buf = buf; + state->offset = offset; + state->size = size; + state->written = 0; + + subreq = cli_write_andx_send(state, state->ev, state->cli, state->fnum, + state->mode, state->buf, state->offset, + state->size); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cli_writeall_written, req); + return req; +} + +static void cli_writeall_written(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cli_writeall_state *state = tevent_req_data( + req, struct cli_writeall_state); + NTSTATUS status; + size_t written, to_write; + + status = cli_write_andx_recv(subreq, &written); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + state->written += written; + + if (state->written > state->size) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + to_write = state->size - state->written; + + if (to_write == 0) { + tevent_req_done(req); + return; + } + + subreq = cli_write_andx_send(state, state->ev, state->cli, state->fnum, + state->mode, + state->buf + state->written, + state->offset + state->written, to_write); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cli_writeall_written, req); +} + +static NTSTATUS cli_writeall_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +struct cli_push_write_state { + struct tevent_req *req;/* This is the main request! Not the subreq */ + uint32_t idx; + off_t ofs; + uint8_t *buf; + size_t size; +}; + +struct cli_push_state { + struct event_context *ev; + struct cli_state *cli; + uint16_t fnum; + uint16_t mode; + off_t start_offset; + size_t window_size; + + size_t (*source)(uint8_t *buf, size_t n, void *priv); + void *priv; + + bool eof; + + size_t chunk_size; + off_t next_offset; + + /* + * Outstanding requests + */ + uint32_t pending; + uint32_t num_reqs; + struct cli_push_write_state **reqs; +}; + +static void cli_push_written(struct tevent_req *req); + +static bool cli_push_write_setup(struct tevent_req *req, + struct cli_push_state *state, + uint32_t idx) +{ + struct cli_push_write_state *substate; + struct tevent_req *subreq; + + substate = talloc(state->reqs, struct cli_push_write_state); + if (!substate) { + return false; + } + substate->req = req; + substate->idx = idx; + substate->ofs = state->next_offset; + substate->buf = talloc_array(substate, uint8_t, state->chunk_size); + if (!substate->buf) { + talloc_free(substate); + return false; + } + substate->size = state->source(substate->buf, + state->chunk_size, + state->priv); + if (substate->size == 0) { + state->eof = true; + /* nothing to send */ + talloc_free(substate); + return true; + } + + subreq = cli_writeall_send(substate, + state->ev, state->cli, + state->fnum, state->mode, + substate->buf, + substate->ofs, + substate->size); + if (!subreq) { + talloc_free(substate); + return false; + } + tevent_req_set_callback(subreq, cli_push_written, substate); + + state->reqs[idx] = substate; + state->pending += 1; + state->next_offset += substate->size; + + return true; +} + +struct tevent_req *cli_push_send(TALLOC_CTX *mem_ctx, struct event_context *ev, + struct cli_state *cli, + uint16_t fnum, uint16_t mode, + off_t start_offset, size_t window_size, + size_t (*source)(uint8_t *buf, size_t n, + void *priv), + void *priv) +{ + struct tevent_req *req; + struct cli_push_state *state; + uint32_t i; + + req = tevent_req_create(mem_ctx, &state, struct cli_push_state); + if (req == NULL) { + return NULL; + } + state->cli = cli; + state->ev = ev; + state->fnum = fnum; + state->start_offset = start_offset; + state->mode = mode; + state->source = source; + state->priv = priv; + state->eof = false; + state->pending = 0; + state->next_offset = start_offset; + + state->chunk_size = cli_write_max_bufsize(cli, mode); + + if (window_size == 0) { + window_size = cli->max_mux * state->chunk_size; + } + state->num_reqs = window_size/state->chunk_size; + if ((window_size % state->chunk_size) > 0) { + state->num_reqs += 1; + } + state->num_reqs = MIN(state->num_reqs, cli->max_mux); + state->num_reqs = MAX(state->num_reqs, 1); + + state->reqs = TALLOC_ZERO_ARRAY(state, struct cli_push_write_state *, + state->num_reqs); + if (state->reqs == NULL) { + goto failed; + } + + for (i=0; inum_reqs; i++) { + if (!cli_push_write_setup(req, state, i)) { + goto failed; + } + + if (state->eof) { + break; + } + } + + if (state->pending == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + return req; + + failed: + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return tevent_req_post(req, ev); +} + +static void cli_push_written(struct tevent_req *subreq) +{ + struct cli_push_write_state *substate = tevent_req_callback_data( + subreq, struct cli_push_write_state); + struct tevent_req *req = substate->req; + struct cli_push_state *state = tevent_req_data( + req, struct cli_push_state); + NTSTATUS status; + uint32_t idx = substate->idx; + + state->reqs[idx] = NULL; + state->pending -= 1; + + status = cli_writeall_recv(subreq); + TALLOC_FREE(subreq); + TALLOC_FREE(substate); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + if (!state->eof) { + if (!cli_push_write_setup(req, state, idx)) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } + } + + if (state->pending == 0) { + tevent_req_done(req); + return; + } +} + +NTSTATUS cli_push_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS cli_push(struct cli_state *cli, uint16_t fnum, uint16_t mode, + off_t start_offset, size_t window_size, + size_t (*source)(uint8_t *buf, size_t n, void *priv), + void *priv) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_OK; + + if (cli_has_async_calls(cli)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + + ev = event_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = cli_push_send(frame, ev, cli, fnum, mode, start_offset, + window_size, source, priv); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll(req, ev)) { + status = map_nt_error_from_unix(errno); + goto fail; + } + + status = cli_push_recv(req); + fail: + TALLOC_FREE(frame); + if (!NT_STATUS_IS_OK(status)) { + cli_set_error(cli, status); + } + return status; +}