[PATCH] Fix the new async copy-chunk for streams

Ralph Böhme slow at samba.org
Sat May 13 14:06:03 UTC 2017


Hi!

A few days ago I noticed that a recent macOS client requests copy-chunk of named
streams and we fail miserably. That's my fault: since making copy-chunk async
[1] in vfs_default, we use the async pread/pwrite VFS versions to do the actual
copy. :/

With vfs_streams_xattr this silently copies data in the default data stream, not
in the requested named stream, so the copy-chunk seems to succeed.

After requesting the copy-chunk, the client does a setinfo(size) on the stream,
so after this the stream has the correct size, alas it's all 0 -- voila, silent
(meta)data corruption.

Luckily the new async copy-chunk is only in master, so even though I filed a
bugreport, we don't need to backport and the alarm whistel can stay off. *phew*

The attached patch adds implementations of async pread and pwrite to
vfs_streams_xattr and vfs_fruit. vfs_streams_depot doesn't need changes, as it
works correctly with the vfs_default implementation of async pread/pwrite as it
provides usable fds.

Btw, with these changes in place, the only thing missing for allowing read/write aio
on named streams is flush_send/recv. Or was there any other reason for not
allowing aio on named streams?

Fwiw, a Windows server supports copy-chunk of named streams.

Please review & push if ok. Thanks!

-slow

[1] 60e45a2d25401eaf9a15a86d19114670ccfde259
-------------- next part --------------
From 214a0e2ac8efa4d39f89ffbbc53448abbe1f229a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 12 May 2017 07:58:01 +0200
Subject: [PATCH 1/5] vfs_streams_xattr: add pread_send/recv and
 pwrite_send/recv

This is needed to support copy-chunk of streams. vfs_default issues
calls to async pread and pwrite (send/recv versions) since commit
60e45a2d25401eaf9a15a86d19114670ccfde259.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=12787

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_streams_xattr.c | 165 ++++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)

diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c
index 2943e52..f481f27 100644
--- a/source3/modules/vfs_streams_xattr.c
+++ b/source3/modules/vfs_streams_xattr.c
@@ -25,6 +25,7 @@
 #include "smbd/smbd.h"
 #include "system/filesys.h"
 #include "../lib/crypto/md5.h"
+#include "lib/util/tevent_unix.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_VFS
@@ -1048,6 +1049,166 @@ static ssize_t streams_xattr_pread(vfs_handle_struct *handle,
         return overlap;
 }
 
+struct streams_xattr_pread_state {
+	ssize_t nread;
+	struct vfs_aio_state vfs_aio_state;
+};
+
+static void streams_xattr_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *streams_xattr_pread_send(
+	struct vfs_handle_struct *handle,
+	TALLOC_CTX *mem_ctx,
+	struct tevent_context *ev,
+	struct files_struct *fsp,
+	void *data,
+	size_t n, off_t offset)
+{
+	struct tevent_req *req = NULL;
+	struct tevent_req *subreq = NULL;
+	struct streams_xattr_pread_state *state = NULL;
+	struct stream_io *sio =
+		(struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+	req = tevent_req_create(mem_ctx, &state,
+				struct streams_xattr_pread_state);
+	if (req == NULL) {
+		return NULL;
+	}
+
+	if (sio == NULL) {
+		subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp,
+						 data, n, offset);
+		if (tevent_req_nomem(req, subreq)) {
+			return tevent_req_post(req, ev);
+		}
+		tevent_req_set_callback(subreq, streams_xattr_pread_done, req);
+		return req;
+	}
+
+	state->nread = SMB_VFS_PREAD(fsp, data, n, offset);
+	if (state->nread != n) {
+		if (state->nread != -1) {
+			errno = EIO;
+		}
+		tevent_req_error(req, errno);
+		return tevent_req_post(req, ev);
+	}
+
+	tevent_req_done(req);
+	return tevent_req_post(req, ev);
+}
+
+static void streams_xattr_pread_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req = tevent_req_callback_data(
+		subreq, struct tevent_req);
+	struct streams_xattr_pread_state *state = tevent_req_data(
+		req, struct streams_xattr_pread_state);
+
+	state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+	TALLOC_FREE(subreq);
+
+	if (tevent_req_error(req, state->vfs_aio_state.error)) {
+		return;
+	}
+	tevent_req_done(req);
+}
+
+static ssize_t streams_xattr_pread_recv(struct tevent_req *req,
+					struct vfs_aio_state *vfs_aio_state)
+{
+	struct streams_xattr_pread_state *state = tevent_req_data(
+		req, struct streams_xattr_pread_state);
+
+	if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+		return -1;
+	}
+
+	*vfs_aio_state = state->vfs_aio_state;
+	return state->nread;
+}
+
+struct streams_xattr_pwrite_state {
+	ssize_t nwritten;
+	struct vfs_aio_state vfs_aio_state;
+};
+
+static void streams_xattr_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *streams_xattr_pwrite_send(
+	struct vfs_handle_struct *handle,
+	TALLOC_CTX *mem_ctx,
+	struct tevent_context *ev,
+	struct files_struct *fsp,
+	const void *data,
+	size_t n, off_t offset)
+{
+	struct tevent_req *req = NULL;
+	struct tevent_req *subreq = NULL;
+	struct streams_xattr_pwrite_state *state = NULL;
+	struct stream_io *sio =
+		(struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+	req = tevent_req_create(mem_ctx, &state,
+				struct streams_xattr_pwrite_state);
+	if (req == NULL) {
+		return NULL;
+	}
+
+	if (sio == NULL) {
+		subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp,
+						  data, n, offset);
+		if (tevent_req_nomem(req, subreq)) {
+			return tevent_req_post(req, ev);
+		}
+		tevent_req_set_callback(subreq, streams_xattr_pwrite_done, req);
+		return req;
+	}
+
+	state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset);
+	if (state->nwritten != n) {
+		if (state->nwritten != -1) {
+			errno = EIO;
+		}
+		tevent_req_error(req, errno);
+		return tevent_req_post(req, ev);
+	}
+
+	tevent_req_done(req);
+	return tevent_req_post(req, ev);
+}
+
+static void streams_xattr_pwrite_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req = tevent_req_callback_data(
+		subreq, struct tevent_req);
+	struct streams_xattr_pwrite_state *state = tevent_req_data(
+		req, struct streams_xattr_pwrite_state);
+
+	state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+	TALLOC_FREE(subreq);
+
+	if (tevent_req_error(req, state->vfs_aio_state.error)) {
+		return;
+	}
+	tevent_req_done(req);
+}
+
+static ssize_t streams_xattr_pwrite_recv(struct tevent_req *req,
+					 struct vfs_aio_state *vfs_aio_state)
+{
+	struct streams_xattr_pwrite_state *state = tevent_req_data(
+		req, struct streams_xattr_pwrite_state);
+
+	if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+		return -1;
+	}
+
+	*vfs_aio_state = state->vfs_aio_state;
+	return state->nwritten;
+}
+
 static int streams_xattr_ftruncate(struct vfs_handle_struct *handle,
 					struct files_struct *fsp,
 					off_t offset)
@@ -1151,6 +1312,10 @@ static struct vfs_fn_pointers vfs_streams_xattr_fns = {
 	.lstat_fn = streams_xattr_lstat,
 	.pread_fn = streams_xattr_pread,
 	.pwrite_fn = streams_xattr_pwrite,
+	.pread_send_fn = streams_xattr_pread_send,
+	.pread_recv_fn = streams_xattr_pread_recv,
+	.pwrite_send_fn = streams_xattr_pwrite_send,
+	.pwrite_recv_fn = streams_xattr_pwrite_recv,
 	.unlink_fn = streams_xattr_unlink,
 	.rename_fn = streams_xattr_rename,
 	.ftruncate_fn = streams_xattr_ftruncate,
-- 
2.9.3


From 569ba0f4dbb65fafb3c7793967ae9318cfdf921d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 12 May 2017 14:40:03 +0200
Subject: [PATCH 2/5] vfs_fruit: add pread_send/recv and pwrite_send/recv

This is needed to support copy-chunk of streams. vfs_default issues
calls to async pread and pwrite (send/recv versions) since
commit60e45a2d25401eaf9a15a86d19114670ccfde259.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=12787

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 182 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 182 insertions(+)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 273540e..63acdf8 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -31,6 +31,7 @@
 #include "../libcli/smb/smb2_create_ctx.h"
 #include "lib/util/sys_rw.h"
 #include "lib/util/tevent_ntstatus.h"
+#include "lib/util/tevent_unix.h"
 
 /*
  * Enhanced OS X and Netatalk compatibility
@@ -3755,6 +3756,105 @@ static ssize_t fruit_pread(vfs_handle_struct *handle,
 	return nread;
 }
 
+static bool fruit_must_handle_aio_stream(struct fio *fio)
+{
+	if (fio == NULL) {
+		return false;
+	};
+
+	if ((fio->type == ADOUBLE_META) &&
+	    (fio->config->meta == FRUIT_META_NETATALK))
+	{
+		return true;
+	}
+
+	if ((fio->type == ADOUBLE_RSRC) &&
+	    (fio->config->rsrc == FRUIT_RSRC_ADFILE))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+struct fruit_pread_state {
+	ssize_t nread;
+	struct vfs_aio_state vfs_aio_state;
+};
+
+static void fruit_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *fruit_pread_send(
+	struct vfs_handle_struct *handle,
+	TALLOC_CTX *mem_ctx,
+	struct tevent_context *ev,
+	struct files_struct *fsp,
+	void *data,
+	size_t n, off_t offset)
+{
+	struct tevent_req *req = NULL;
+	struct tevent_req *subreq = NULL;
+	struct fruit_pread_state *state = NULL;
+	struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+	req = tevent_req_create(mem_ctx, &state,
+				struct fruit_pread_state);
+	if (req == NULL) {
+		return NULL;
+	}
+
+	if (fruit_must_handle_aio_stream(fio)) {
+		state->nread = SMB_VFS_PREAD(fsp, data, n, offset);
+		if (state->nread != n) {
+			if (state->nread != -1) {
+				errno = EIO;
+			}
+			tevent_req_error(req, errno);
+			return tevent_req_post(req, ev);
+		}
+		tevent_req_done(req);
+		return tevent_req_post(req, ev);
+	}
+
+	subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp,
+					 data, n, offset);
+	if (tevent_req_nomem(req, subreq)) {
+		return tevent_req_post(req, ev);
+	}
+	tevent_req_set_callback(subreq, fruit_pread_done, req);
+	return req;
+}
+
+static void fruit_pread_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req = tevent_req_callback_data(
+		subreq, struct tevent_req);
+	struct fruit_pread_state *state = tevent_req_data(
+		req, struct fruit_pread_state);
+
+	state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+	TALLOC_FREE(subreq);
+
+	if (tevent_req_error(req, state->vfs_aio_state.error)) {
+		return;
+	}
+	tevent_req_done(req);
+}
+
+static ssize_t fruit_pread_recv(struct tevent_req *req,
+					struct vfs_aio_state *vfs_aio_state)
+{
+	struct fruit_pread_state *state = tevent_req_data(
+		req, struct fruit_pread_state);
+
+	if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+		return -1;
+	}
+
+	*vfs_aio_state = state->vfs_aio_state;
+	return state->nread;
+}
+
 static ssize_t fruit_pwrite_meta_stream(vfs_handle_struct *handle,
 					files_struct *fsp, const void *data,
 					size_t n, off_t offset)
@@ -3979,6 +4079,84 @@ static ssize_t fruit_pwrite(vfs_handle_struct *handle,
 	return nwritten;
 }
 
+struct fruit_pwrite_state {
+	ssize_t nwritten;
+	struct vfs_aio_state vfs_aio_state;
+};
+
+static void fruit_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *fruit_pwrite_send(
+	struct vfs_handle_struct *handle,
+	TALLOC_CTX *mem_ctx,
+	struct tevent_context *ev,
+	struct files_struct *fsp,
+	const void *data,
+	size_t n, off_t offset)
+{
+	struct tevent_req *req = NULL;
+	struct tevent_req *subreq = NULL;
+	struct fruit_pwrite_state *state = NULL;
+	struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+	req = tevent_req_create(mem_ctx, &state,
+				struct fruit_pwrite_state);
+	if (req == NULL) {
+		return NULL;
+	}
+
+	if (fruit_must_handle_aio_stream(fio)) {
+		state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset);
+		if (state->nwritten != n) {
+			if (state->nwritten != -1) {
+				errno = EIO;
+			}
+			tevent_req_error(req, errno);
+			return tevent_req_post(req, ev);
+		}
+		tevent_req_done(req);
+		return tevent_req_post(req, ev);
+	}
+
+	subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp,
+					  data, n, offset);
+	if (tevent_req_nomem(req, subreq)) {
+		return tevent_req_post(req, ev);
+	}
+	tevent_req_set_callback(subreq, fruit_pwrite_done, req);
+	return req;
+}
+
+static void fruit_pwrite_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req = tevent_req_callback_data(
+		subreq, struct tevent_req);
+	struct fruit_pwrite_state *state = tevent_req_data(
+		req, struct fruit_pwrite_state);
+
+	state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+	TALLOC_FREE(subreq);
+
+	if (tevent_req_error(req, state->vfs_aio_state.error)) {
+		return;
+	}
+	tevent_req_done(req);
+}
+
+static ssize_t fruit_pwrite_recv(struct tevent_req *req,
+					 struct vfs_aio_state *vfs_aio_state)
+{
+	struct fruit_pwrite_state *state = tevent_req_data(
+		req, struct fruit_pwrite_state);
+
+	if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+		return -1;
+	}
+
+	*vfs_aio_state = state->vfs_aio_state;
+	return state->nwritten;
+}
+
 /**
  * Helper to stat/lstat the base file of an smb_fname.
  */
@@ -5427,6 +5605,10 @@ static struct vfs_fn_pointers vfs_fruit_fns = {
 	.open_fn = fruit_open,
 	.pread_fn = fruit_pread,
 	.pwrite_fn = fruit_pwrite,
+	.pread_send_fn = fruit_pread_send,
+	.pread_recv_fn = fruit_pread_recv,
+	.pwrite_send_fn = fruit_pwrite_send,
+	.pwrite_recv_fn = fruit_pwrite_recv,
 	.stat_fn = fruit_stat,
 	.lstat_fn = fruit_lstat,
 	.fstat_fn = fruit_fstat,
-- 
2.9.3


From b8e99bbef3f0dc37bd35a983a631c850d8d12ba4 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 12 May 2017 17:09:08 +0200
Subject: [PATCH 3/5] lib/torture: add two more ndr assert macros

Bug: https://bugzilla.samba.org/show_bug.cgi?id=12787

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 lib/torture/torture.h | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lib/torture/torture.h b/lib/torture/torture.h
index 668458a..6b373a9 100644
--- a/lib/torture/torture.h
+++ b/lib/torture/torture.h
@@ -293,6 +293,15 @@ void torture_result(struct torture_context *test,
 	}\
 	} while(0)
 
+#define torture_assert_ndr_err_equal_goto(torture_ctx,got,expected,ret,label,cmt) \
+	do { enum ndr_err_code __got = got, __expected = expected; \
+	if (__got != __expected) { \
+		torture_result(torture_ctx, TORTURE_FAIL, __location__": "#got" was %d (%s), expected %d (%s): %s", __got, ndr_errstr(__got), __expected, __STRING(expected), cmt); \
+		ret = false; \
+		goto label; \
+	}\
+	} while(0)
+
 #define torture_assert_hresult_equal(torture_ctx, got, expected, cmt) \
 	do { HRESULT __got = got, __expected = expected; \
 	if (!HRES_IS_EQUAL(__got, __expected)) { \
@@ -647,6 +656,9 @@ static inline void torture_dump_data_str_cb(const char *buf, void *private_data)
 #define torture_assert_ndr_success(torture_ctx,expr,cmt) \
 		torture_assert_ndr_err_equal(torture_ctx,expr,NDR_ERR_SUCCESS,cmt)
 
+#define torture_assert_ndr_success_goto(torture_ctx,expr,ret,label,cmt) \
+		torture_assert_ndr_err_equal_goto(torture_ctx,expr,NDR_ERR_SUCCESS,ret,label,cmt)
+
 #define torture_assert_hresult_ok(torture_ctx,expr,cmt) \
 		torture_assert_hresult_equal(torture_ctx,expr,HRES_ERROR(0), cmt)
 
-- 
2.9.3


From 956e5360a173eccb5ed84c186ba38b404511b858 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 12 May 2017 14:56:53 +0200
Subject: [PATCH 4/5] s4/torture: vfs_fruit: add src and dst path args to
 test_setup_copy_chunk

Just let the caller pass in the paths, no change in behaviour. A new
test in a subsequent commit will use it to pass paths to streams.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=12787

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 96edec2..7d59fee 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -2353,9 +2353,11 @@ static bool test_setup_create_fill(struct torture_context *torture,
 static bool test_setup_copy_chunk(struct torture_context *torture,
 				  struct smb2_tree *tree, TALLOC_CTX *mem_ctx,
 				  uint32_t nchunks,
+				  const char *src_name,
 				  struct smb2_handle *src_h,
 				  uint64_t src_size,
 				  uint32_t src_desired_access,
+				  const char *dst_name,
 				  struct smb2_handle *dest_h,
 				  uint64_t dest_size,
 				  uint32_t dest_desired_access,
@@ -2367,12 +2369,12 @@ static bool test_setup_copy_chunk(struct torture_context *torture,
 	NTSTATUS status;
 	enum ndr_err_code ndr_ret;
 
-	ok = test_setup_create_fill(torture, tree, mem_ctx, FNAME_CC_SRC,
+	ok = test_setup_create_fill(torture, tree, mem_ctx, src_name,
 				    src_h, src_size, src_desired_access,
 				    FILE_ATTRIBUTE_NORMAL);
 	torture_assert(torture, ok, "src file create fill");
 
-	ok = test_setup_create_fill(torture, tree, mem_ctx, FNAME_CC_DST,
+	ok = test_setup_create_fill(torture, tree, mem_ctx, dst_name,
 				    dest_h, dest_size, dest_desired_access,
 				    FILE_ATTRIBUTE_NORMAL);
 	torture_assert(torture, ok, "dest file create fill");
@@ -2533,8 +2535,10 @@ static bool test_copyfile(struct torture_context *torture,
 
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   0, /* 0 chunks, copyfile semantics */
+				   FNAME_CC_SRC,
 				   &src_h, 4096, /* fill 4096 byte src file */
 				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   FNAME_CC_DST,
 				   &dest_h, 0,	/* 0 byte dest file */
 				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
 				   &cc_copy,
@@ -2600,8 +2604,10 @@ static bool test_copyfile(struct torture_context *torture,
 
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   0, /* 0 chunks, copyfile semantics */
+				   FNAME_CC_SRC,
 				   &src_h, 4096, /* fill 4096 byte src file */
 				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   FNAME_CC_DST,
 				   &dest_h, 0,	/* 0 byte dest file */
 				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
 				   &cc_copy,
-- 
2.9.3


From 9d8027a8d3a9f010376541b7c7c1627918facb46 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 12 May 2017 17:10:07 +0200
Subject: [PATCH 5/5] s4/torture: vfs_fruit: test copy-chunk on streams

Bug: https://bugzilla.samba.org/show_bug.cgi?id=12787

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 225 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 225 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 7d59fee..2ab153a 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -4048,6 +4048,230 @@ done:
 	return ret;
 }
 
+static bool copy_one_stream(struct torture_context *torture,
+			    struct smb2_tree *tree,
+			    TALLOC_CTX *tmp_ctx,
+			    const char *src_sname,
+			    const char *dst_sname)
+{
+	struct smb2_handle src_h = {{0}};
+	struct smb2_handle dest_h = {{0}};
+	NTSTATUS status;
+	union smb_ioctl io;
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok = false;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   src_sname,
+				   &src_h, 256, /* fill 256 byte src file */
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   dst_sname,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   &cc_copy,
+				   &io);
+	torture_assert_goto(torture, ok == true, ok, done,
+			    "setup copy chunk error\n");
+
+	/* copy all src file data (via a single chunk desc) */
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 256;
+
+	ndr_ret = ndr_push_struct_blob(
+		&io.smb2.in.out, tmp_ctx, &cc_copy,
+		(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+
+	torture_assert_ndr_success_goto(torture, ndr_ret, ok, done,
+				   "ndr_push_srv_copychunk_copy\n");
+
+	status = smb2_ioctl(tree, tmp_ctx, &io.smb2);
+	torture_assert_ntstatus_ok_goto(torture, status, ok, done,
+					"FSCTL_SRV_COPYCHUNK\n");
+
+	ndr_ret = ndr_pull_struct_blob(
+		&io.smb2.out.out, tmp_ctx, &cc_rsp,
+		(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+
+	torture_assert_ndr_success_goto(torture, ndr_ret, ok, done,
+				   "ndr_pull_srv_copychunk_rsp\n");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  256); /* total bytes written */
+	torture_assert_goto(torture, ok == true, ok, done,
+			    "bad copy chunk response data\n");
+
+	ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 256, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data\n");
+	}
+
+done:
+	if (!smb2_util_handle_empty(src_h)) {
+		smb2_util_close(tree, src_h);
+	}
+	if (!smb2_util_handle_empty(dest_h)) {
+		smb2_util_close(tree, dest_h);
+	}
+
+	return ok;
+}
+
+static bool copy_finderinfo_stream(struct torture_context *torture,
+				   struct smb2_tree *tree,
+				   TALLOC_CTX *tmp_ctx,
+				   const char *src_name,
+				   const char *dst_name)
+{
+	struct smb2_handle src_h = {{0}};
+	struct smb2_handle dest_h = {{0}};
+	NTSTATUS status;
+	union smb_ioctl io;
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	const char *type_creator = "SMB,OLE!";
+	AfpInfo *info = NULL;
+	const char *src_name_afpinfo = NULL;
+	const char *dst_name_afpinfo = NULL;
+	bool ok = false;
+
+	src_name_afpinfo = talloc_asprintf(tmp_ctx, "%s%s", src_name,
+					   AFPINFO_STREAM);
+	torture_assert_not_null_goto(torture, src_name_afpinfo, ok, done,
+				     "talloc_asprintf failed\n");
+
+	dst_name_afpinfo = talloc_asprintf(tmp_ctx, "%s%s", dst_name,
+					   AFPINFO_STREAM);
+	torture_assert_not_null_goto(torture, dst_name_afpinfo, ok, done,
+				     "talloc_asprintf failed\n");
+
+	info = torture_afpinfo_new(tmp_ctx);
+	torture_assert_not_null_goto(torture, info, ok, done,
+				     "torture_afpinfo_new failed\n");
+
+	memcpy(info->afpi_FinderInfo, type_creator, 8);
+	ok = torture_write_afpinfo(tree, torture, tmp_ctx, src_name, info);
+	torture_assert_goto(torture, ok == true, ok, done,
+			    "torture_write_afpinfo failed\n");
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   src_name_afpinfo,
+				   &src_h, 0,
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   dst_name_afpinfo,
+				   &dest_h, 0,
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   &cc_copy,
+				   &io);
+	torture_assert_goto(torture, ok == true, ok, done,
+			    "setup copy chunk error\n");
+
+	/* copy all src file data (via a single chunk desc) */
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 60;
+
+	ndr_ret = ndr_push_struct_blob(
+		&io.smb2.in.out, tmp_ctx, &cc_copy,
+		(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+
+	torture_assert_ndr_success_goto(torture, ndr_ret, ok, done,
+				   "ndr_push_srv_copychunk_copy\n");
+
+	status = smb2_ioctl(tree, tmp_ctx, &io.smb2);
+	torture_assert_ntstatus_ok_goto(torture, status, ok, done,
+					"FSCTL_SRV_COPYCHUNK\n");
+
+	ndr_ret = ndr_pull_struct_blob(
+		&io.smb2.out.out, tmp_ctx, &cc_rsp,
+		(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+
+	torture_assert_ndr_success_goto(torture, ndr_ret, ok, done,
+				   "ndr_pull_srv_copychunk_rsp\n");
+
+	smb2_util_close(tree, src_h);
+	ZERO_STRUCT(src_h);
+	smb2_util_close(tree, dest_h);
+	ZERO_STRUCT(dest_h);
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  60); /* total bytes written */
+	torture_assert_goto(torture, ok == true, ok, done,
+			    "bad copy chunk response data\n");
+
+	ok = check_stream(tree, __location__, torture, tmp_ctx,
+			  dst_name, AFPINFO_STREAM,
+			  0, 60, 16, 8, type_creator);
+	torture_assert_goto(torture, ok == true, ok, done, "check_stream failed\n");
+
+done:
+	if (!smb2_util_handle_empty(src_h)) {
+		smb2_util_close(tree, src_h);
+	}
+	if (!smb2_util_handle_empty(dest_h)) {
+		smb2_util_close(tree, dest_h);
+	}
+
+	return ok;
+}
+
+static bool test_copy_chunk_streams(struct torture_context *torture,
+				    struct smb2_tree *tree)
+{
+	const char *src_name = "src";
+	const char *dst_name = "dst";
+	struct names {
+		const char *src_sname;
+		const char *dst_sname;
+	} names[] = {
+		{ "src:foo", "dst:foo" },
+		{ "src" AFPRESOURCE_STREAM, "dst" AFPRESOURCE_STREAM }
+	};
+	int i;
+	TALLOC_CTX *tmp_ctx = NULL;
+	bool ok = false;
+
+	tmp_ctx = talloc_new(tree);
+	torture_assert_not_null_goto(torture, tmp_ctx, ok, done,
+				     "torture_setup_file\n");
+
+	smb2_util_unlink(tree, src_name);
+	smb2_util_unlink(tree, dst_name);
+
+	ok = torture_setup_file(torture, tree, src_name, false);
+	torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n");
+	ok = torture_setup_file(torture, tree, dst_name, false);
+	torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n");
+
+	for (i = 0; i < ARRAY_SIZE(names); i++) {
+		ok = copy_one_stream(torture, tree, tmp_ctx,
+				     names[i].src_sname,
+				     names[i].dst_sname);
+		torture_assert_goto(torture, ok == true, ok, done,
+				    "copy_one_stream failed\n");
+	}
+
+	ok = copy_finderinfo_stream(torture, tree, tmp_ctx,
+				    src_name, dst_name);
+	torture_assert_goto(torture, ok == true, ok, done,
+			    "copy_finderinfo_stream failed\n");
+
+done:
+	smb2_util_unlink(tree, src_name);
+	smb2_util_unlink(tree, dst_name);
+	talloc_free(tmp_ctx);
+	return ok;
+}
+
 /*
  * Note: This test depends on "vfs objects = catia fruit streams_xattr".  For
  * some tests torture must be run on the host it tests and takes an additional
@@ -4086,6 +4310,7 @@ struct torture_suite *torture_vfs_fruit(TALLOC_CTX *ctx)
 	torture_suite_add_1smb2_test(suite, "readdir_attr with names with illegal ntfs characters", test_readdir_attr_illegal_ntfs);
 	torture_suite_add_2ns_smb2_test(suite, "invalid AFP_AfpInfo", test_invalid_afpinfo);
 	torture_suite_add_1smb2_test(suite, "creating rsrc with read-only access", test_rfork_create_ro);
+	torture_suite_add_1smb2_test(suite, "copy-chunk streams", test_copy_chunk_streams);
 
 	return suite;
 }
-- 
2.9.3



More information about the samba-technical mailing list