[PATCH v3] s3: libsmbclient: Add server-side copy support
Ross Lagerwall
rosslagerwall at gmail.com
Wed May 27 16:13:15 MDT 2015
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 at gmail.com>
---
Thanks for the reviews. David, I'm not sure if you intended for me to
fold in the ABI change patch, so I kept it separate for now.
Changes in v3:
* Rebased against master.
* Added some overflow and underflow checks.
libcli/smb/smb2cli_ioctl.c | 51 +++++++-
libcli/smb/smbXcli_base.c | 32 +++++
libcli/smb/smbXcli_base.h | 6 +
source3/include/libsmb_internal.h | 11 +-
source3/include/libsmbclient.h | 9 ++
source3/libsmb/cli_smb2_fnum.c | 268 ++++++++++++++++++++++++++++++++++++++
source3/libsmb/cli_smb2_fnum.h | 7 +
source3/libsmb/clireadwrite.c | 116 +++++++++++++++++
source3/libsmb/libsmb_context.c | 1 +
source3/libsmb/libsmb_file.c | 109 ++++++++++++++++
source3/libsmb/libsmb_setget.c | 12 ++
source3/libsmb/proto.h | 7 +
source3/wscript_build | 1 +
13 files changed, 624 insertions(+), 6 deletions(-)
diff --git a/libcli/smb/smb2cli_ioctl.c b/libcli/smb/smb2cli_ioctl.c
index b0f8eea..42a424e 100644
--- a/libcli/smb/smb2cli_ioctl.c
+++ b/libcli/smb/smb2cli_ioctl.c
@@ -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,
diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c
index 0754203..2f47fe6 100644
--- a/libcli/smb/smbXcli_base.c
+++ b/libcli/smb/smbXcli_base.c
@@ -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)
diff --git a/libcli/smb/smbXcli_base.h b/libcli/smb/smbXcli_base.h
index 8f27c20..cf93135 100644
--- a/libcli/smb/smbXcli_base.h
+++ b/libcli/smb/smbXcli_base.h
@@ -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,
diff --git a/source3/include/libsmb_internal.h b/source3/include/libsmb_internal.h
index c9c83eb..fcdb513 100644
--- a/source3/include/libsmb_internal.h
+++ b/source3/include/libsmb_internal.h
@@ -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);
diff --git a/source3/include/libsmbclient.h b/source3/include/libsmbclient.h
index 42e41f7..faaab2e 100644
--- a/source3/include/libsmbclient.h
+++ b/source3/include/libsmbclient.h
@@ -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);
diff --git a/source3/libsmb/cli_smb2_fnum.c b/source3/libsmb/cli_smb2_fnum.c
index de4bd6f..21c0340 100644
--- a/source3/libsmb/cli_smb2_fnum.c
+++ b/source3/libsmb/cli_smb2_fnum.c
@@ -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;
+}
diff --git a/source3/libsmb/cli_smb2_fnum.h b/source3/libsmb/cli_smb2_fnum.h
index 173dba0..c97bc76 100644
--- a/source3/libsmb/cli_smb2_fnum.h
+++ b/source3/libsmb/cli_smb2_fnum.h
@@ -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__ */
diff --git a/source3/libsmb/clireadwrite.c b/source3/libsmb/clireadwrite.c
index 6bcbd02..f0cb7ad 100644
--- a/source3/libsmb/clireadwrite.c
+++ b/source3/libsmb/clireadwrite.c
@@ -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;
+}
diff --git a/source3/libsmb/libsmb_context.c b/source3/libsmb/libsmb_context.c
index f66c063..9541fc1 100644
--- a/source3/libsmb/libsmb_context.c
+++ b/source3/libsmb/libsmb_context.c
@@ -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);
diff --git a/source3/libsmb/libsmb_file.c b/source3/libsmb/libsmb_file.c
index 11d6d82..fe57035 100644
--- a/source3/libsmb/libsmb_file.c
+++ b/source3/libsmb/libsmb_file.c
@@ -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 ...
*/
diff --git a/source3/libsmb/libsmb_setget.c b/source3/libsmb/libsmb_setget.c
index d203b09..0bdb715 100644
--- a/source3/libsmb/libsmb_setget.c
+++ b/source3/libsmb/libsmb_setget.c
@@ -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)
{
diff --git a/source3/libsmb/proto.h b/source3/libsmb/proto.h
index 4bbdd44..5ebcf5f 100644
--- a/source3/libsmb/proto.h
+++ b/source3/libsmb/proto.h
@@ -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,
diff --git a/source3/wscript_build b/source3/wscript_build
index ef4c986..d9b85b8 100755
--- a/source3/wscript_build
+++ b/source3/wscript_build
@@ -414,6 +414,7 @@ bld.SAMBA3_LIBRARY('libsmb',
SPNEGO_PARSE
LIBTSOCKET
KRBCLIENT
+ NDR_IOCTL
cli_smb_common
util_cmdline
tevent''',
--
2.4.0
More information about the samba-technical
mailing list