s3: libsmbclient: Add server-side copy support
authorRoss Lagerwall <rosslagerwall@gmail.com>
Wed, 27 May 2015 22:13:15 +0000 (23:13 +0100)
committerJeremy Allison <jra@samba.org>
Fri, 29 May 2015 00:37:18 +0000 (02:37 +0200)
Introduce a new operation, splice, which copies data from one SMBCFILE
to another. Implement this operation using FSCTL_SRV_COPYCHUNK_WRITE for
SMB2+ protocols and using read+write for older protocols. Since the
operation may be long running, it takes a callback which gets called
periodically to indicate progress to the application and given an
opportunity to stop it.

Signed-off-by: Ross Lagerwall <rosslagerwall@gmail.com>
Reviewed-by: David Disseldorp <ddiss@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
13 files changed:
libcli/smb/smb2cli_ioctl.c
libcli/smb/smbXcli_base.c
libcli/smb/smbXcli_base.h
source3/include/libsmb_internal.h
source3/include/libsmbclient.h
source3/libsmb/cli_smb2_fnum.c
source3/libsmb/cli_smb2_fnum.h
source3/libsmb/clireadwrite.c
source3/libsmb/libsmb_context.c
source3/libsmb/libsmb_file.c
source3/libsmb/libsmb_setget.c
source3/libsmb/proto.h
source3/wscript_build

index b0f8eea65b09b40e51ac72e0d23e3301ff77f85c..42a424e6b9faaad86e4acff12ed3716076a701dc 100644 (file)
@@ -23,6 +23,7 @@
 #include "smb_common.h"
 #include "smbXcli_base.h"
 #include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ioctl.h"
 
 struct smb2cli_ioctl_state {
        uint8_t fixed[0x38];
@@ -32,6 +33,7 @@ struct smb2cli_ioctl_state {
        struct iovec *recv_iov;
        DATA_BLOB out_input_buffer;
        DATA_BLOB out_output_buffer;
+       uint32_t ctl_code;
 };
 
 static void smb2cli_ioctl_done(struct tevent_req *subreq);
@@ -69,6 +71,7 @@ struct tevent_req *smb2cli_ioctl_send(TALLOC_CTX *mem_ctx,
        if (req == NULL) {
                return NULL;
        }
+       state->ctl_code = in_ctl_code;
        state->max_input_length = in_max_input_length;
        state->max_output_length = in_max_output_length;
 
@@ -158,6 +161,32 @@ struct tevent_req *smb2cli_ioctl_send(TALLOC_CTX *mem_ctx,
        return req;
 }
 
+/*
+ * 3.3.4.4 Sending an Error Response
+ * An error code other than one of the following indicates a failure:
+ */
+static bool smb2cli_ioctl_is_failure(uint32_t ctl_code, NTSTATUS status,
+                                    size_t data_size)
+{
+       if (NT_STATUS_IS_OK(status)) {
+               return false;
+       }
+
+       /*
+        * STATUS_INVALID_PARAMETER in a FSCTL_SRV_COPYCHUNK or
+        * FSCTL_SRV_COPYCHUNK_WRITE Response, when returning an
+        * SRV_COPYCHUNK_RESPONSE as described in section 2.2.32.1.
+        */
+       if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) &&
+           (ctl_code == FSCTL_SRV_COPYCHUNK ||
+            ctl_code == FSCTL_SRV_COPYCHUNK_WRITE) &&
+           data_size == sizeof(struct srv_copychunk_rsp)) {
+               return false;
+       }
+
+       return true;
+}
+
 static void smb2cli_ioctl_done(struct tevent_req *subreq)
 {
        struct tevent_req *req =
@@ -195,12 +224,16 @@ static void smb2cli_ioctl_done(struct tevent_req *subreq)
                .status = NT_STATUS_FILE_CLOSED,
                .body_size = 0x09,
        },
+       {
+               .status = NT_STATUS_INVALID_PARAMETER,
+               .body_size = 0x31
+       },
        };
 
        status = smb2cli_req_recv(subreq, state, &iov,
                                  expected, ARRAY_SIZE(expected));
        TALLOC_FREE(subreq);
-       if (tevent_req_nterror(req, status)) {
+       if (iov == NULL && tevent_req_nterror(req, status)) {
                return;
        }
 
@@ -214,6 +247,11 @@ static void smb2cli_ioctl_done(struct tevent_req *subreq)
        output_buffer_offset = IVAL(fixed, 0x20);
        output_buffer_length = IVAL(fixed, 0x24);
 
+       if (smb2cli_ioctl_is_failure(state->ctl_code, status, output_buffer_length) &&
+           tevent_req_nterror(req, status)) {
+               return;
+       }
+
        if ((input_buffer_offset > 0) && (input_buffer_length > 0)) {
                uint32_t ofs;
 
@@ -294,6 +332,10 @@ static void smb2cli_ioctl_done(struct tevent_req *subreq)
                state->out_output_buffer.length = output_buffer_length;
        }
 
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
        tevent_req_done(req);
 }
 
@@ -305,9 +347,10 @@ NTSTATUS smb2cli_ioctl_recv(struct tevent_req *req,
        struct smb2cli_ioctl_state *state =
                tevent_req_data(req,
                struct smb2cli_ioctl_state);
-       NTSTATUS status;
+       NTSTATUS status = NT_STATUS_OK;
 
-       if (tevent_req_is_nterror(req, &status)) {
+       if (tevent_req_is_nterror(req, &status) &&
+           smb2cli_ioctl_is_failure(state->ctl_code, status, state->out_output_buffer.length)) {
                tevent_req_received(req);
                return status;
        }
@@ -321,7 +364,7 @@ NTSTATUS smb2cli_ioctl_recv(struct tevent_req *req,
        }
 
        tevent_req_received(req);
-       return NT_STATUS_OK;
+       return status;
 }
 
 NTSTATUS smb2cli_ioctl(struct smbXcli_conn *conn,
index 075420323d0391cf7ec075003a60604cc6afc5c6..2f47fe60d4fd5a1f7abf97b4277d236e2c7cd58f 100644 (file)
@@ -130,6 +130,9 @@ struct smbXcli_conn {
                uint16_t cur_credits;
                uint16_t max_credits;
 
+               uint32_t cc_chunk_len;
+               uint32_t cc_max_chunks;
+
                uint8_t io_priority;
 
                uint8_t preauth_sha512[64];
@@ -409,6 +412,13 @@ struct smbXcli_conn *smbXcli_conn_create(TALLOC_CTX *mem_ctx,
        conn->smb2.max_credits = 0;
        conn->smb2.io_priority = 1;
 
+       /*
+        * Samba and Windows servers accept a maximum of 16 MiB with a maximum
+        * chunk length of 1 MiB.
+        */
+       conn->smb2.cc_chunk_len = 1024 * 1024;
+       conn->smb2.cc_max_chunks = 16;
+
        talloc_set_destructor(conn, smbXcli_conn_destructor);
        return conn;
 
@@ -2595,6 +2605,28 @@ void smb2cli_conn_set_io_priority(struct smbXcli_conn *conn,
        conn->smb2.io_priority = io_priority;
 }
 
+uint32_t smb2cli_conn_cc_chunk_len(struct smbXcli_conn *conn)
+{
+       return conn->smb2.cc_chunk_len;
+}
+
+void smb2cli_conn_set_cc_chunk_len(struct smbXcli_conn *conn,
+                                   uint32_t chunk_len)
+{
+       conn->smb2.cc_chunk_len = chunk_len;
+}
+
+uint32_t smb2cli_conn_cc_max_chunks(struct smbXcli_conn *conn)
+{
+       return conn->smb2.cc_max_chunks;
+}
+
+void smb2cli_conn_set_cc_max_chunks(struct smbXcli_conn *conn,
+                                   uint32_t max_chunks)
+{
+       conn->smb2.cc_max_chunks = max_chunks;
+}
+
 static void smb2cli_req_cancel_done(struct tevent_req *subreq);
 
 static bool smb2cli_req_cancel(struct tevent_req *req)
index 8f27c20557e035c7e54d1aa3b18fe42c7137cae4..cf93135cd9773674482b59fa15edc6a5c602d0eb 100644 (file)
@@ -306,6 +306,12 @@ void smb2cli_conn_set_max_credits(struct smbXcli_conn *conn,
 uint8_t smb2cli_conn_get_io_priority(struct smbXcli_conn *conn);
 void smb2cli_conn_set_io_priority(struct smbXcli_conn *conn,
                                  uint8_t io_priority);
+uint32_t smb2cli_conn_cc_chunk_len(struct smbXcli_conn *conn);
+void smb2cli_conn_set_cc_chunk_len(struct smbXcli_conn *conn,
+                                  uint32_t chunk_len);
+uint32_t smb2cli_conn_cc_max_chunks(struct smbXcli_conn *conn);
+void smb2cli_conn_set_cc_max_chunks(struct smbXcli_conn *conn,
+                                   uint32_t max_chunks);
 
 struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx,
                                      struct tevent_context *ev,
index c9c83eb3c45bef692b459e79da4be106d9a59ef5..fcdb51301540ee274efefb39de58f17f4d12a63b 100644 (file)
@@ -238,13 +238,12 @@ struct SMBC_internal_data {
         }               printing;
 #endif
 
-#if 0 /* None available yet */
         /* SMB high-level functions */
         struct
         {
+                smbc_splice_fn                  splice_fn;
         }               smb;
 
-#endif
        uint16_t        port;
 };     
 
@@ -365,6 +364,14 @@ SMBC_write_ctx(SMBCCTX *context,
                const void *buf,
                size_t count);
 
+off_t
+SMBC_splice_ctx(SMBCCTX *context,
+                SMBCFILE *srcfile,
+                SMBCFILE *dstfile,
+                off_t count,
+                int (*splice_cb)(off_t n, void *priv),
+                void *priv);
+
 int
 SMBC_close_ctx(SMBCCTX *context,
                SMBCFILE *file);
index 42e41f79ef48a6c3339261c56f58fbb665865797..faaab2e36889a1a7d064cba18ac57d977a86ec71 100644 (file)
@@ -872,6 +872,15 @@ typedef ssize_t (*smbc_write_fn)(SMBCCTX *c,
 smbc_write_fn smbc_getFunctionWrite(SMBCCTX *c);
 void smbc_setFunctionWrite(SMBCCTX *c, smbc_write_fn fn);
 
+typedef off_t (*smbc_splice_fn)(SMBCCTX *c,
+                                SMBCFILE *srcfile,
+                                SMBCFILE *dstfile,
+                                off_t count,
+                                int (*splice_cb)(off_t n, void *priv),
+                                void *priv);
+smbc_splice_fn smbc_getFunctionSplice(SMBCCTX *c);
+void smbc_setFunctionSplice(SMBCCTX *c, smbc_splice_fn fn);
+
 typedef int (*smbc_unlink_fn)(SMBCCTX *c,
                               const char *fname);
 smbc_unlink_fn smbc_getFunctionUnlink(SMBCCTX *c);
index de4bd6feb62c2f39bf8a522e96c659d2cab36c20..21c03408893b7ef99706e937d3ba2a7104061bf5 100644 (file)
@@ -38,6 +38,7 @@
 #include "lib/util/tevent_ntstatus.h"
 #include "../libcli/security/security.h"
 #include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
 
 struct smb2_hnd {
        uint64_t fid_persistent;
@@ -2578,3 +2579,270 @@ NTSTATUS cli_smb2_writeall_recv(struct tevent_req *req,
        }
        return NT_STATUS_OK;
 }
+
+struct cli_smb2_splice_state {
+       struct tevent_context *ev;
+       struct cli_state *cli;
+       struct smb2_hnd *src_ph;
+       struct smb2_hnd *dst_ph;
+       int (*splice_cb)(off_t n, void *priv);
+       void *priv;
+       off_t written;
+       off_t size;
+       off_t src_offset;
+       off_t dst_offset;
+       bool resized;
+       struct req_resume_key_rsp resume_rsp;
+       struct srv_copychunk_copy cc_copy;
+};
+
+static void cli_splice_copychunk_send(struct cli_smb2_splice_state *state,
+                                     struct tevent_req *req);
+
+static void cli_splice_copychunk_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb2_splice_state *state =
+               tevent_req_data(req,
+               struct cli_smb2_splice_state);
+       struct smbXcli_conn *conn = state->cli->conn;
+       DATA_BLOB out_input_buffer = data_blob_null;
+       DATA_BLOB out_output_buffer = data_blob_null;
+       struct srv_copychunk_rsp cc_copy_rsp;
+       enum ndr_err_code ndr_ret;
+       NTSTATUS status;
+
+       status = smb2cli_ioctl_recv(subreq, state,
+                                   &out_input_buffer,
+                                   &out_output_buffer);
+       TALLOC_FREE(subreq);
+       if ((!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) ||
+            state->resized) && tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       ndr_ret = ndr_pull_struct_blob(&out_output_buffer, state, &cc_copy_rsp,
+                       (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+       if (ndr_ret != NDR_ERR_SUCCESS) {
+               DEBUG(0, ("failed to unmarshall copy chunk rsp\n"));
+               tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+               return;
+       }
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+               uint32_t max_chunks = MIN(cc_copy_rsp.chunks_written,
+                            cc_copy_rsp.total_bytes_written / cc_copy_rsp.chunk_bytes_written);
+               if ((cc_copy_rsp.chunk_bytes_written > smb2cli_conn_cc_chunk_len(conn) ||
+                    max_chunks > smb2cli_conn_cc_max_chunks(conn)) &&
+                    tevent_req_nterror(req, status)) {
+                       return;
+               }
+
+               state->resized = true;
+               smb2cli_conn_set_cc_chunk_len(conn, cc_copy_rsp.chunk_bytes_written);
+               smb2cli_conn_set_cc_max_chunks(conn, max_chunks);
+       } else {
+               if ((state->src_offset > INT64_MAX - cc_copy_rsp.total_bytes_written) ||
+                   (state->dst_offset > INT64_MAX - cc_copy_rsp.total_bytes_written) ||
+                   (state->written > INT64_MAX - cc_copy_rsp.total_bytes_written)) {
+                       tevent_req_nterror(req, NT_STATUS_FILE_TOO_LARGE);
+                       return;
+               }
+               state->src_offset += cc_copy_rsp.total_bytes_written;
+               state->dst_offset += cc_copy_rsp.total_bytes_written;
+               state->written += cc_copy_rsp.total_bytes_written;
+               if (!state->splice_cb(state->written, state->priv)) {
+                       tevent_req_nterror(req, NT_STATUS_CANCELLED);
+                       return;
+               }
+       }
+
+       cli_splice_copychunk_send(state, req);
+}
+
+static void cli_splice_copychunk_send(struct cli_smb2_splice_state *state,
+                                     struct tevent_req *req)
+{
+       struct tevent_req *subreq;
+       enum ndr_err_code ndr_ret;
+       struct smbXcli_conn *conn = state->cli->conn;
+       struct srv_copychunk_copy *cc_copy = &state->cc_copy;
+       off_t src_offset = state->src_offset;
+       off_t dst_offset = state->dst_offset;
+       uint32_t req_len = MIN(smb2cli_conn_cc_chunk_len(conn) * smb2cli_conn_cc_max_chunks(conn),
+                              state->size - state->written);
+       DATA_BLOB in_input_buffer = data_blob_null;
+       DATA_BLOB in_output_buffer = data_blob_null;
+
+       if (state->size - state->written == 0) {
+               tevent_req_done(req);
+               return;
+       }
+
+       cc_copy->chunk_count = 0;
+       while (req_len) {
+               cc_copy->chunks[cc_copy->chunk_count].source_off = src_offset;
+               cc_copy->chunks[cc_copy->chunk_count].target_off = dst_offset;
+               cc_copy->chunks[cc_copy->chunk_count].length = MIN(req_len,
+                                                                  smb2cli_conn_cc_chunk_len(conn));
+               if (req_len < cc_copy->chunks[cc_copy->chunk_count].length) {
+                       tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+                       return;
+               }
+               req_len -= cc_copy->chunks[cc_copy->chunk_count].length;
+               if ((src_offset > INT64_MAX - cc_copy->chunks[cc_copy->chunk_count].length) ||
+                   (dst_offset > INT64_MAX - cc_copy->chunks[cc_copy->chunk_count].length)) {
+                       tevent_req_nterror(req, NT_STATUS_FILE_TOO_LARGE);
+                       return;
+               }
+               src_offset += cc_copy->chunks[cc_copy->chunk_count].length;
+               dst_offset += cc_copy->chunks[cc_copy->chunk_count].length;
+               cc_copy->chunk_count++;
+       }
+
+       ndr_ret = ndr_push_struct_blob(&in_input_buffer, state, cc_copy,
+                                      (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+       if (ndr_ret != NDR_ERR_SUCCESS) {
+               DEBUG(0, ("failed to marshall copy chunk req\n"));
+               tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+               return;
+       }
+
+       subreq = smb2cli_ioctl_send(state, state->ev, state->cli->conn,
+                              state->cli->timeout,
+                              state->cli->smb2.session,
+                              state->cli->smb2.tcon,
+                              state->dst_ph->fid_persistent, /* in_fid_persistent */
+                              state->dst_ph->fid_volatile, /* in_fid_volatile */
+                              FSCTL_SRV_COPYCHUNK_WRITE,
+                              0, /* in_max_input_length */
+                              &in_input_buffer,
+                              12, /* in_max_output_length */
+                              &in_output_buffer,
+                              SMB2_IOCTL_FLAG_IS_FSCTL);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+       tevent_req_set_callback(subreq,
+                               cli_splice_copychunk_done,
+                               req);
+}
+
+static void cli_splice_key_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb2_splice_state *state =
+               tevent_req_data(req,
+               struct cli_smb2_splice_state);
+       enum ndr_err_code ndr_ret;
+       NTSTATUS status;
+
+       DATA_BLOB out_input_buffer = data_blob_null;
+       DATA_BLOB out_output_buffer = data_blob_null;
+
+       status = smb2cli_ioctl_recv(subreq, state,
+                                   &out_input_buffer,
+                                   &out_output_buffer);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       ndr_ret = ndr_pull_struct_blob(&out_output_buffer,
+                       state, &state->resume_rsp,
+                       (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp);
+       if (ndr_ret != NDR_ERR_SUCCESS) {
+               DEBUG(0, ("failed to unmarshall resume key rsp\n"));
+               tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+               return;
+       }
+
+       memcpy(&state->cc_copy.source_key,
+              &state->resume_rsp.resume_key,
+              sizeof state->resume_rsp.resume_key);
+
+       cli_splice_copychunk_send(state, req);
+}
+
+struct tevent_req *cli_smb2_splice_send(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               struct cli_state *cli,
+                               uint16_t src_fnum, uint16_t dst_fnum,
+                               off_t size, off_t src_offset, off_t dst_offset,
+                               int (*splice_cb)(off_t n, void *priv),
+                               void *priv)
+{
+       struct tevent_req *req;
+       struct tevent_req *subreq;
+       struct cli_smb2_splice_state *state;
+       NTSTATUS status;
+       DATA_BLOB in_input_buffer = data_blob_null;
+       DATA_BLOB in_output_buffer = data_blob_null;
+
+       req = tevent_req_create(mem_ctx, &state, struct cli_smb2_splice_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->cli = cli;
+       state->ev = ev;
+       state->splice_cb = splice_cb;
+       state->priv = priv;
+       state->size = size;
+       state->written = 0;
+       state->src_offset = src_offset;
+       state->dst_offset = dst_offset;
+       state->cc_copy.chunks = talloc_array(state,
+                                            struct srv_copychunk,
+                                            smb2cli_conn_cc_max_chunks(cli->conn));
+       if (state->cc_copy.chunks == NULL) {
+               return NULL;
+       }
+
+       status = map_fnum_to_smb2_handle(cli, src_fnum, &state->src_ph);
+       if (tevent_req_nterror(req, status))
+               return tevent_req_post(req, ev);
+
+       status = map_fnum_to_smb2_handle(cli, dst_fnum, &state->dst_ph);
+       if (tevent_req_nterror(req, status))
+               return tevent_req_post(req, ev);
+
+       subreq = smb2cli_ioctl_send(state, ev, cli->conn,
+                              cli->timeout,
+                              cli->smb2.session,
+                              cli->smb2.tcon,
+                              state->src_ph->fid_persistent, /* in_fid_persistent */
+                              state->src_ph->fid_volatile, /* in_fid_volatile */
+                              FSCTL_SRV_REQUEST_RESUME_KEY,
+                              0, /* in_max_input_length */
+                              &in_input_buffer,
+                              32, /* in_max_output_length */
+                              &in_output_buffer,
+                              SMB2_IOCTL_FLAG_IS_FSCTL);
+       if (tevent_req_nomem(subreq, req)) {
+               return NULL;
+       }
+       tevent_req_set_callback(subreq,
+                               cli_splice_key_done,
+                               req);
+
+       return req;
+}
+
+NTSTATUS cli_smb2_splice_recv(struct tevent_req *req, off_t *written)
+{
+       struct cli_smb2_splice_state *state = tevent_req_data(
+               req, struct cli_smb2_splice_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return status;
+       }
+       if (written != NULL) {
+               *written = state->written;
+       }
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}
index 173dba06de5d902654db97c9641101853c48f2e0..c97bc76786ab13c149e7de4669e95193d9ecc1ab 100644 (file)
@@ -176,4 +176,11 @@ struct tevent_req *cli_smb2_writeall_send(TALLOC_CTX *mem_ctx,
                        size_t size);
 NTSTATUS cli_smb2_writeall_recv(struct tevent_req *req,
                        size_t *pwritten);
+struct tevent_req *cli_smb2_splice_send(TALLOC_CTX *mem_ctx,
+                       struct tevent_context *ev,
+                       struct cli_state *cli,
+                       uint16_t src_fnum, uint16_t dst_fnum,
+                       off_t size, off_t src_offset, off_t dst_offset,
+                       int (*splice_cb)(off_t n, void *priv), void *priv);
+NTSTATUS cli_smb2_splice_recv(struct tevent_req *req, off_t *written);
 #endif /* __SMB2CLI_FNUM_H__ */
index 6bcbd02b5e4c856b2b093703154fd166db9e9180..f0cb7ad291be5307fe33e9ca408e0b117ac8f37d 100644 (file)
@@ -1442,3 +1442,119 @@ 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;
+
+       while (remaining) {
+               status = cli_read(srccli, src_fnum,
+                                 (char *)buf, src_offset, SPLICE_BLOCK_SIZE,
+                                 &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;
+               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;
+}
index f66c0632bddb1a27a32179f681b425075ec78a46..9541fc1f816b44e9d0295fbc9043c8704fcc0e29 100644 (file)
@@ -194,6 +194,7 @@ smbc_new_context(void)
         smbc_setFunctionOpen(context, SMBC_open_ctx);
         smbc_setFunctionCreat(context, SMBC_creat_ctx);
         smbc_setFunctionRead(context, SMBC_read_ctx);
+        smbc_setFunctionSplice(context, SMBC_splice_ctx);
         smbc_setFunctionWrite(context, SMBC_write_ctx);
         smbc_setFunctionClose(context, SMBC_close_ctx);
         smbc_setFunctionUnlink(context, SMBC_unlink_ctx);
index 11d6d82780966b72b8a5bbe15826da9c9f0617d3..fe5703521eb7b64a399b2aabb3655ea63d43b176 100644 (file)
@@ -316,6 +316,115 @@ SMBC_read_ctx(SMBCCTX *context,
        return ret;  /* Success, ret bytes of data ... */
 }
 
+off_t
+SMBC_splice_ctx(SMBCCTX *context,
+                SMBCFILE *srcfile,
+                SMBCFILE *dstfile,
+                off_t count,
+                int (*splice_cb)(off_t n, void *priv),
+                void *priv)
+{
+       off_t written;
+       char *server = NULL, *share = NULL, *user = NULL, *password = NULL;
+       char *path = NULL;
+       char *targetpath = NULL;
+       struct cli_state *srccli = NULL;
+       struct cli_state *dstcli = NULL;
+       uint16_t port = 0;
+       TALLOC_CTX *frame = talloc_stackframe();
+       NTSTATUS status;
+
+       if (!context || !context->internal->initialized) {
+               errno = EINVAL;
+               TALLOC_FREE(frame);
+               return -1;
+       }
+
+       if (!srcfile ||
+           !SMBC_dlist_contains(context->internal->files, srcfile))
+       {
+               errno = EBADF;
+               TALLOC_FREE(frame);
+               return -1;
+       }
+
+       if (!dstfile ||
+           !SMBC_dlist_contains(context->internal->files, dstfile))
+       {
+               errno = EBADF;
+               TALLOC_FREE(frame);
+               return -1;
+       }
+
+       if (SMBC_parse_path(frame,
+                            context,
+                            srcfile->fname,
+                            NULL,
+                            &server,
+                            &port,
+                            &share,
+                            &path,
+                            &user,
+                            &password,
+                            NULL)) {
+                errno = EINVAL;
+               TALLOC_FREE(frame);
+                return -1;
+        }
+
+       status = cli_resolve_path(frame, "", context->internal->auth_info,
+                                 srcfile->srv->cli, path,
+                                 &srccli, &targetpath);
+       if (!NT_STATUS_IS_OK(status)) {
+               d_printf("Could not resolve %s\n", path);
+                errno = ENOENT;
+               TALLOC_FREE(frame);
+               return -1;
+       }
+
+       if (SMBC_parse_path(frame,
+                            context,
+                            dstfile->fname,
+                            NULL,
+                            &server,
+                            &port,
+                            &share,
+                            &path,
+                            &user,
+                            &password,
+                            NULL)) {
+                errno = EINVAL;
+               TALLOC_FREE(frame);
+                return -1;
+        }
+
+       status = cli_resolve_path(frame, "", context->internal->auth_info,
+                                 dstfile->srv->cli, path,
+                                 &dstcli, &targetpath);
+       if (!NT_STATUS_IS_OK(status)) {
+               d_printf("Could not resolve %s\n", path);
+                errno = ENOENT;
+               TALLOC_FREE(frame);
+               return -1;
+       }
+
+       status = cli_splice(srccli, dstcli,
+                           srcfile->cli_fd, dstfile->cli_fd,
+                           count, srcfile->offset, dstfile->offset, &written,
+                           splice_cb, priv);
+       if (!NT_STATUS_IS_OK(status)) {
+               errno = SMBC_errno(context, srccli);
+               TALLOC_FREE(frame);
+               return -1;
+       }
+
+       srcfile->offset += written;
+       dstfile->offset += written;
+
+       TALLOC_FREE(frame);
+       return written;
+}
+
 /*
  * Routine to write() a file ...
  */
index d203b09a4c9416c9b1c71eee640673adfcaac944..0bdb7150f5df3de71f61cc935477e9d60955884b 100644 (file)
@@ -695,6 +695,18 @@ smbc_setFunctionWrite(SMBCCTX *c, smbc_write_fn fn)
         c->write = fn;
 }
 
+smbc_splice_fn
+smbc_getFunctionSplice(SMBCCTX *c)
+{
+        return c->internal->smb.splice_fn;
+}
+
+void
+smbc_setFunctionSplice(SMBCCTX *c, smbc_splice_fn fn)
+{
+        c->internal->smb.splice_fn = fn;
+}
+
 smbc_unlink_fn
 smbc_getFunctionUnlink(SMBCCTX *c)
 {
index 4bbdd44ca898fd18cd2a4c3d16cc22bb3f89cf97..5ebcf5f072b9c0f75e60d94fab32daa9698595f6 100644 (file)
@@ -803,6 +803,13 @@ NTSTATUS cli_push(struct cli_state *cli, uint16_t fnum, uint16_t mode,
                  size_t (*source)(uint8_t *buf, size_t n, void *priv),
                  void *priv);
 
+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);
+
 /* The following definitions come from libsmb/clisecdesc.c  */
 
 NTSTATUS cli_query_security_descriptor(struct cli_state *cli,
index ef4c986bca6a2c2c734d75909e1319f3dd809b12..d9b85b88c267d6e83ee57a7aaca4ff99ee2b226c 100755 (executable)
@@ -414,6 +414,7 @@ bld.SAMBA3_LIBRARY('libsmb',
                    SPNEGO_PARSE
                    LIBTSOCKET
                    KRBCLIENT
+                   NDR_IOCTL
                    cli_smb_common
                    util_cmdline
                    tevent''',