[RFC PATCH] s3: libsmbclient: Add server-side copy support

Ross Lagerwall rosslagerwall at gmail.com
Mon Apr 6 05:05:14 MDT 2015


Introduce a new operation, splice, which copies data from one SMBCFILE
to another. Implement this operation using FSCTL_SRV_COPYCHUNK 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>
---

This is useful to use from gvfs and makes copying within
a share much faster. The only issue is that currently libsmbclient is
not thread safe so splice() blocks all other operations. But hopefuly
that will be fixed soon.

 libcli/smb/smb2cli_ioctl.c        |  18 ++-
 source3/include/libsmb_internal.h |  11 +-
 source3/include/libsmbclient.h    |   9 ++
 source3/libsmb/cli_smb2_fnum.c    | 255 ++++++++++++++++++++++++++++++++++++++
 source3/libsmb/cli_smb2_fnum.h    |   7 ++
 source3/libsmb/clireadwrite.c     |  98 +++++++++++++++
 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 +
 11 files changed, 522 insertions(+), 6 deletions(-)

diff --git a/libcli/smb/smb2cli_ioctl.c b/libcli/smb/smb2cli_ioctl.c
index b0f8eea..36ba9a1 100644
--- a/libcli/smb/smb2cli_ioctl.c
+++ b/libcli/smb/smb2cli_ioctl.c
@@ -195,12 +195,17 @@ 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 (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) &&
+			tevent_req_nterror(req, status)) {
 		return;
 	}
 
@@ -294,6 +299,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 +314,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) &&
+	    !NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
 		tevent_req_received(req);
 		return status;
 	}
@@ -321,7 +331,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/source3/include/libsmb_internal.h b/source3/include/libsmb_internal.h
index 65fad99..3ac86b7 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..196ec87 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,257 @@ 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;
+	uint32_t chunk_len;
+	uint32_t max_chunks;
+	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);
+	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) &&
+	    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)) {
+		state->chunk_len = cc_copy_rsp.chunk_bytes_written;
+		state->max_chunks = MIN(cc_copy_rsp.chunks_written,
+				        cc_copy_rsp.total_bytes_written / state->chunk_len);
+		TALLOC_FREE(state->cc_copy.chunks);
+		state->cc_copy.chunks = talloc_array(state,
+				                     struct srv_copychunk,
+						     state->max_chunks);
+	} else {
+		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 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(state->chunk_len * state->max_chunks,
+			       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,
+				                                   state->chunk_len);
+		req_len -= cc_copy->chunks[cc_copy->chunk_count].length;
+		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,
+			       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);
+	TALLOC_CTX *frame = talloc_stackframe();
+	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, frame,
+				    &out_input_buffer,
+				    &out_output_buffer);
+	TALLOC_FREE(subreq);
+	if (tevent_req_nterror(req, status)) {
+		goto out;
+	}
+
+	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);
+		goto out;
+	}
+
+	memcpy(&state->cc_copy.source_key,
+	       &state->resume_rsp.resume_key,
+	       sizeof state->resume_rsp.resume_key);
+	state->cc_copy.chunks = talloc_array(state,
+			                     struct srv_copychunk,
+					     state->max_chunks);
+
+	cli_splice_copychunk_send(state, req);
+
+out:
+	TALLOC_FREE(frame);
+}
+
+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;
+	/*
+	 * Samba and Windows servers accept a maximum of 16 MiB with a maximum
+	 * chunk length of 1 MiB.
+	 */
+	state->chunk_len = 1024 * 1024;
+	state->max_chunks = 16;
+
+	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 adcd98b..77f461f 100644
--- a/source3/libsmb/clireadwrite.c
+++ b/source3/libsmb/clireadwrite.c
@@ -1445,3 +1445,101 @@ 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;
+		}
+
+		src_offset += nread;
+		dst_offset += nread;
+		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;
+
+	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 fail;
+	}
+	ev = samba_tevent_context_init(frame);
+	if (ev == NULL) {
+		goto fail;
+	}
+	if (srccli == dstcli &&
+	    smbXcli_conn_protocol(srccli->conn) >= PROTOCOL_SMB2_02)
+	{
+		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 fail;
+	}
+	if (req == NULL) {
+		goto fail;
+	}
+	if (!tevent_req_poll(req, ev)) {
+		status = map_nt_error_from_unix(errno);
+		goto fail;
+	}
+	status = cli_smb2_splice_recv(req, written);
+
+ fail:
+	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 8fb7a2e..49ff776 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 f392a35..3c65fdb 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 d8b0bf3..d7b0d36 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.3.5



More information about the samba-technical mailing list