s3:libsmb: add cli_write_send/recv which work with SMB1/2/3
[amitay/samba.git] / source3 / libsmb / clireadwrite.c
index adcd98bb0d9b6a2070405943568aa452a44936fe..6d47ccd872ac01e6086a7381a78082d7d063daab 100644 (file)
@@ -162,7 +162,7 @@ struct tevent_req *cli_read_andx_create(TALLOC_CTX *mem_ctx,
                }
        }
 
-       subreq = cli_smb_req_create(state, ev, cli, SMBreadX, 0, wct,
+       subreq = cli_smb_req_create(state, ev, cli, SMBreadX, 0, 0, wct,
                                    state->vwv, 0, NULL);
        if (subreq == NULL) {
                TALLOC_FREE(req);
@@ -296,7 +296,7 @@ struct cli_pull_state {
         * The maximum is 256:
         * - which would be a window of 256 MByte
         *   for SMB2 with multi-credit
-        *   or smb1 unix extentions.
+        *   or smb1 unix extensions.
         */
        uint16_t max_chunks;
        uint16_t num_chunks;
@@ -437,7 +437,7 @@ static void cli_pull_setup_chunks(struct tevent_req *req)
                state->next_offset += chunk->total_size;
                state->remaining -= chunk->total_size;
 
-               DLIST_ADD_END(state->chunks, chunk, NULL);
+               DLIST_ADD_END(state->chunks, chunk);
                state->num_chunks++;
                state->num_waiting++;
 
@@ -484,7 +484,7 @@ static void cli_pull_chunk_ship(struct cli_pull_chunk *chunk)
 
                if (chunk->tmp_size == 0) {
                        /*
-                        * we git a short read, we're done
+                        * we got a short read, we're done
                         */
                        tevent_req_done(req);
                        return;
@@ -500,7 +500,7 @@ static void cli_pull_chunk_ship(struct cli_pull_chunk *chunk)
 
                if (chunk->tmp_size < chunk->total_size) {
                        /*
-                        * we git a short read, we're done
+                        * we got a short read, we're done
                         */
                        tevent_req_done(req);
                        return;
@@ -690,8 +690,7 @@ NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum,
                goto fail;
        }
 
-       if (!tevent_req_poll(req, ev)) {
-               status = map_nt_error_from_unix(errno);
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }
 
@@ -701,6 +700,128 @@ NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum,
        return status;
 }
 
+struct cli_read_state {
+       struct cli_state *cli;
+       char *buf;
+       size_t buflen;
+       size_t received;
+};
+
+static void cli_read_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_read_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct cli_state *cli,
+       uint16_t fnum,
+       char *buf,
+       off_t offset,
+       size_t size)
+{
+       struct tevent_req *req, *subreq;
+       struct cli_read_state *state;
+
+       req = tevent_req_create(mem_ctx, &state, struct cli_read_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->cli = cli;
+       state->buf = buf;
+       state->buflen = size;
+
+       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+               uint32_t max_size;
+               bool ok;
+
+               ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+               if (!ok) {
+                       tevent_req_nterror(
+                               req,
+                               NT_STATUS_INSUFFICIENT_RESOURCES);
+                       return tevent_req_post(req, ev);
+               }
+
+               /*
+                * downgrade depending on the available credits
+                */
+               size = MIN(max_size, size);
+
+               subreq = cli_smb2_read_send(
+                       state, ev, cli, fnum, offset, size);
+               if (tevent_req_nomem(subreq, req)) {
+                       return tevent_req_post(req, ev);
+               }
+       } else {
+               bool ok;
+               ok = smb1cli_conn_req_possible(state->cli->conn);
+               if (!ok) {
+                       tevent_req_nterror(
+                               req,
+                               NT_STATUS_INSUFFICIENT_RESOURCES);
+                       return tevent_req_post(req, ev);
+               }
+
+               subreq = cli_read_andx_send(
+                       state, ev, cli, fnum, offset, size);
+               if (tevent_req_nomem(subreq, req)) {
+                       return tevent_req_post(req, ev);
+               }
+       }
+
+       tevent_req_set_callback(subreq, cli_read_done, req);
+
+       return req;
+}
+
+static void cli_read_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_read_state *state = tevent_req_data(
+               req, struct cli_read_state);
+       NTSTATUS status;
+       ssize_t received;
+       uint8_t *buf;
+
+       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+               status = cli_smb2_read_recv(subreq, &received, &buf);
+       } else {
+               status = cli_read_andx_recv(subreq, &received, &buf);
+       }
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
+               received = 0;
+               status = NT_STATUS_OK;
+       }
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       if ((received < 0) || (received > state->buflen)) {
+               state->received = 0;
+               tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR);
+               return;
+       }
+
+       memcpy(state->buf, buf, received);
+       state->received = received;
+       tevent_req_done(req);
+}
+
+NTSTATUS cli_read_recv(struct tevent_req *req, size_t *received)
+{
+       struct cli_read_state *state = tevent_req_data(
+               req, struct cli_read_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       if (received != NULL) {
+               *received = state->received;
+       }
+       return NT_STATUS_OK;
+}
+
 static NTSTATUS cli_read_sink(char *buf, size_t n, void *priv)
 {
        char **pbuf = (char **)priv;
@@ -864,7 +985,7 @@ struct tevent_req *cli_write_andx_create(TALLOC_CTX *mem_ctx,
        state->iov[1].iov_base = discard_const_p(void, buf);
        state->iov[1].iov_len = state->size;
 
-       subreq = cli_smb_req_create(state, ev, cli, SMBwriteX, 0, wct, vwv,
+       subreq = cli_smb_req_create(state, ev, cli, SMBwriteX, 0, 0, wct, vwv,
                                    2, state->iov);
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
@@ -944,7 +1065,123 @@ NTSTATUS cli_write_andx_recv(struct tevent_req *req, size_t *pwritten)
        return NT_STATUS_OK;
 }
 
-struct cli_writeall_state {
+struct cli_write_state {
+       struct cli_state *cli;
+       size_t written;
+};
+
+static void cli_write_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_write_send(TALLOC_CTX *mem_ctx,
+                                 struct tevent_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 = NULL;
+       struct cli_write_state *state = NULL;
+       struct tevent_req *subreq = NULL;
+
+       req = tevent_req_create(mem_ctx, &state, struct cli_write_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->cli = cli;
+
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               uint32_t max_size;
+               bool ok;
+
+               ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+               if (!ok) {
+                       tevent_req_nterror(
+                               req,
+                               NT_STATUS_INSUFFICIENT_RESOURCES);
+                       return tevent_req_post(req, ev);
+               }
+
+               /*
+                * downgrade depending on the available credits
+                */
+               size = MIN(max_size, size);
+
+               subreq = cli_smb2_write_send(state,
+                                            ev,
+                                            cli,
+                                            fnum,
+                                            mode,
+                                            buf,
+                                            offset,
+                                            size);
+       } else {
+               bool ok;
+
+               ok = smb1cli_conn_req_possible(state->cli->conn);
+               if (!ok) {
+                       tevent_req_nterror(
+                               req,
+                               NT_STATUS_INSUFFICIENT_RESOURCES);
+                       return tevent_req_post(req, ev);
+               }
+
+               subreq = cli_write_andx_send(state,
+                                            ev,
+                                            cli,
+                                            fnum,
+                                            mode,
+                                            buf,
+                                            offset,
+                                            size);
+       }
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_write_done, req);
+
+       return req;
+}
+
+static void cli_write_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct cli_write_state *state =
+               tevent_req_data(req,
+               struct cli_write_state);
+       NTSTATUS status;
+
+       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+               status = cli_smb2_write_recv(subreq, &state->written);
+       } else {
+               status = cli_write_andx_recv(subreq, &state->written);
+       }
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       tevent_req_done(req);
+}
+
+NTSTATUS cli_write_recv(struct tevent_req *req, size_t *pwritten)
+{
+       struct cli_write_state *state =
+               tevent_req_data(req,
+               struct cli_write_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return status;
+       }
+       if (pwritten != NULL) {
+               *pwritten = state->written;
+       }
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}
+
+struct cli_smb1_writeall_state {
        struct tevent_context *ev;
        struct cli_state *cli;
        uint16_t fnum;
@@ -955,20 +1192,21 @@ struct cli_writeall_state {
        size_t written;
 };
 
-static void cli_writeall_written(struct tevent_req *req);
+static void cli_smb1_writeall_written(struct tevent_req *req);
 
-static struct tevent_req *cli_writeall_send(TALLOC_CTX *mem_ctx,
-                                           struct tevent_context *ev,
-                                           struct cli_state *cli,
-                                           uint16_t fnum,
-                                           uint16_t mode,
-                                           const uint8_t *buf,
-                                           off_t offset, size_t size)
+static struct tevent_req *cli_smb1_writeall_send(TALLOC_CTX *mem_ctx,
+                                                struct tevent_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;
+       struct cli_smb1_writeall_state *state;
 
-       req = tevent_req_create(mem_ctx, &state, struct cli_writeall_state);
+       req = tevent_req_create(mem_ctx, &state,
+                               struct cli_smb1_writeall_state);
        if (req == NULL) {
                return NULL;
        }
@@ -987,16 +1225,16 @@ static struct tevent_req *cli_writeall_send(TALLOC_CTX *mem_ctx,
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
        }
-       tevent_req_set_callback(subreq, cli_writeall_written, req);
+       tevent_req_set_callback(subreq, cli_smb1_writeall_written, req);
        return req;
 }
 
-static void cli_writeall_written(struct tevent_req *subreq)
+static void cli_smb1_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);
+       struct cli_smb1_writeall_state *state = tevent_req_data(
+               req, struct cli_smb1_writeall_state);
        NTSTATUS status;
        size_t written, to_write;
 
@@ -1027,11 +1265,102 @@ static void cli_writeall_written(struct tevent_req *subreq)
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
-       tevent_req_set_callback(subreq, cli_writeall_written, req);
+       tevent_req_set_callback(subreq, cli_smb1_writeall_written, req);
+}
+
+static NTSTATUS cli_smb1_writeall_recv(struct tevent_req *req,
+                                      size_t *pwritten)
+{
+       struct cli_smb1_writeall_state *state = tevent_req_data(
+               req, struct cli_smb1_writeall_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       if (pwritten != NULL) {
+               *pwritten = state->written;
+       }
+       return NT_STATUS_OK;
 }
 
-static NTSTATUS cli_writeall_recv(struct tevent_req *req,
-                                 size_t *pwritten)
+struct cli_writeall_state {
+       struct cli_state *cli;
+       size_t written;
+};
+
+static void cli_writeall_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_writeall_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_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->cli = cli;
+
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               subreq = cli_smb2_writeall_send(
+                       state,
+                       ev,
+                       cli,
+                       fnum,
+                       mode,
+                       buf,
+                       offset,
+                       size);
+       } else {
+               subreq = cli_smb1_writeall_send(
+                       state,
+                       ev,
+                       cli,
+                       fnum,
+                       mode,
+                       buf,
+                       offset,
+                       size);
+       }
+
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_writeall_done, req);
+
+       return req;
+}
+
+static void cli_writeall_done(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;
+
+       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+               status = cli_smb2_writeall_recv(subreq, &state->written);
+       } else {
+               status = cli_smb1_writeall_recv(subreq, &state->written);
+       }
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       tevent_req_done(req);
+}
+
+NTSTATUS cli_writeall_recv(struct tevent_req *req, size_t *pwritten)
 {
        struct cli_writeall_state *state = tevent_req_data(
                req, struct cli_writeall_state);
@@ -1046,6 +1375,7 @@ static NTSTATUS cli_writeall_recv(struct tevent_req *req,
        return NT_STATUS_OK;
 }
 
+
 NTSTATUS cli_writeall(struct cli_state *cli, uint16_t fnum, uint16_t mode,
                      const uint8_t *buf, off_t offset, size_t size,
                      size_t *pwritten)
@@ -1066,25 +1396,14 @@ NTSTATUS cli_writeall(struct cli_state *cli, uint16_t fnum, uint16_t mode,
        if (ev == NULL) {
                goto fail;
        }
-       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
-               req = cli_smb2_writeall_send(frame, ev, cli, fnum, mode,
-                                            buf, offset, size);
-       } else {
-               req = cli_writeall_send(frame, ev, cli, fnum, mode,
-                                       buf, offset, size);
-       }
+       req = cli_writeall_send(frame, ev, cli, fnum, mode, buf, offset, size);
        if (req == NULL) {
                goto fail;
        }
-       if (!tevent_req_poll(req, ev)) {
-               status = map_nt_error_from_unix(errno);
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }
-       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
-               status = cli_smb2_writeall_recv(req, pwritten);
-       } else {
-               status = cli_writeall_recv(req, pwritten);
-       }
+       status = cli_writeall_recv(req, pwritten);
  fail:
        TALLOC_FREE(frame);
        return status;
@@ -1113,7 +1432,7 @@ struct cli_push_state {
         * The maximum is 256:
         * - which would be a window of 256 MByte
         *   for SMB2 with multi-credit
-        *   or smb1 unix extentions.
+        *   or smb1 unix extensions.
         */
        uint16_t max_chunks;
        uint16_t num_chunks;
@@ -1252,7 +1571,7 @@ static void cli_push_setup_chunks(struct tevent_req *req)
                }
                state->next_offset += chunk->total_size;
 
-               DLIST_ADD_END(state->chunks, chunk, NULL);
+               DLIST_ADD_END(state->chunks, chunk);
                state->num_chunks++;
                state->num_waiting++;
 
@@ -1435,8 +1754,7 @@ NTSTATUS cli_push(struct cli_state *cli, uint16_t fnum, uint16_t mode,
                goto fail;
        }
 
-       if (!tevent_req_poll(req, ev)) {
-               status = map_nt_error_from_unix(errno);
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }
 
@@ -1445,3 +1763,123 @@ NTSTATUS cli_push(struct cli_state *cli, uint16_t fnum, uint16_t mode,
        TALLOC_FREE(frame);
        return status;
 }
+
+#define SPLICE_BLOCK_SIZE 1024 * 1024
+
+static NTSTATUS cli_splice_fallback(TALLOC_CTX *frame,
+                                   struct cli_state *srccli,
+                                   struct cli_state *dstcli,
+                                   uint16_t src_fnum, uint16_t dst_fnum,
+                                   off_t initial_size,
+                                   off_t src_offset, off_t dst_offset,
+                                   off_t *written,
+                                   int (*splice_cb)(off_t n, void *priv),
+                                   void *priv)
+{
+       NTSTATUS status;
+       uint8_t *buf = talloc_size(frame, SPLICE_BLOCK_SIZE);
+       size_t nread;
+       off_t remaining = initial_size;
+       *written = 0;
+
+       while (remaining) {
+               size_t to_read = MIN(remaining, SPLICE_BLOCK_SIZE);
+
+               status = cli_read(srccli, src_fnum,
+                                 (char *)buf, src_offset, to_read,
+                                 &nread);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+
+               status = cli_writeall(dstcli, dst_fnum, 0,
+                                     buf, dst_offset, nread, NULL);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+
+               if ((src_offset > INT64_MAX - nread) ||
+                   (dst_offset > INT64_MAX - nread)) {
+                       return NT_STATUS_FILE_TOO_LARGE;
+               }
+               src_offset += nread;
+               dst_offset += nread;
+               *written += nread;
+               if (remaining < nread) {
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               remaining -= nread;
+               if (!splice_cb(initial_size - remaining, priv)) {
+                       return NT_STATUS_CANCELLED;
+               }
+       }
+
+       return NT_STATUS_OK;
+}
+
+NTSTATUS cli_splice(struct cli_state *srccli, struct cli_state *dstcli,
+                   uint16_t src_fnum, uint16_t dst_fnum,
+                   off_t size,
+                   off_t src_offset, off_t dst_offset,
+                   off_t *written,
+                   int (*splice_cb)(off_t n, void *priv), void *priv)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
+       bool retry_fallback = false;
+
+       if (smbXcli_conn_has_async_calls(srccli->conn) ||
+           smbXcli_conn_has_async_calls(dstcli->conn))
+       {
+               /*
+                * Can't use sync call while an async call is in flight
+                */
+               status = NT_STATUS_INVALID_PARAMETER;
+               goto out;
+       }
+
+       do {
+               ev = samba_tevent_context_init(frame);
+               if (ev == NULL) {
+                       goto out;
+               }
+               if (srccli == dstcli &&
+                   smbXcli_conn_protocol(srccli->conn) >= PROTOCOL_SMB2_02 &&
+                   !retry_fallback)
+               {
+                       req = cli_smb2_splice_send(frame, ev,
+                                                  srccli, src_fnum, dst_fnum,
+                                                  size, src_offset, dst_offset,
+                                                  splice_cb, priv);
+               } else {
+                       status = cli_splice_fallback(frame,
+                                                    srccli, dstcli,
+                                                    src_fnum, dst_fnum,
+                                                    size,
+                                                    src_offset, dst_offset,
+                                                    written,
+                                                    splice_cb, priv);
+                       goto out;
+               }
+               if (req == NULL) {
+                       goto out;
+               }
+               if (!tevent_req_poll(req, ev)) {
+                       status = map_nt_error_from_unix(errno);
+                       goto out;
+               }
+               status = cli_smb2_splice_recv(req, written);
+
+               /*
+                * Older versions of Samba don't support
+                * FSCTL_SRV_COPYCHUNK_WRITE so use the fallback.
+                */
+               retry_fallback = NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST);
+       } while (retry_fallback);
+
+ out:
+       TALLOC_FREE(frame);
+       return status;
+}