[PATCH] OS X SMB2 AAPL copyfile extension

Ralph Böhme rb at sernet.de
Wed Jun 17 02:49:10 MDT 2015


Hi all,

attached is a patchset, reviewed my metze, that implements Apple's
copyfile style copy_chunk semantics.

Shamelessly c/p from the bugreport [1] I had created for this:

  OS X SMB server and client use a special copychunk semantic that is
  triggered by a chunk count of zero.

  In response to this request, the server must copy the whole file at
  once and also copy all attached file metadata.

  OS X clients have a bug in the moment leading to file corruption if
  using standard copychunk ioctl with file sizes over 2^31 bytes, so
  this OS X style copyfile/copychunk ioctl can be used as a workaround
  until Apple fixed their client.

  Note that is indeed a single sync request that is expected to block
  the server while the copy is in progress.

Please review&push, thanks!

-Ralph

[1] <https://bugzilla.samba.org/show_bug.cgi?id=11317>

-- 
SerNet GmbH, Bahnhofsallee 1b, 37081 Göttingen
phone: +49-551-370000-0, fax: +49-551-370000-9
AG Göttingen, HRB 2816, GF: Dr. Johannes Loxen
http://www.sernet.de,mailto:kontakt@sernet.de
-------------- next part --------------
From c45ef0710f5e5b1adc06fa60851ce0595894401d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Jun 2015 18:31:23 +0200
Subject: [PATCH 1/6] vfs_fruit: simplify lp_parm_bool check

Signed-off-by: Ralph Boehme <slow at samba.org>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 source3/modules/vfs_fruit.c | 35 +++++++++++++----------------------
 1 file changed, 13 insertions(+), 22 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 2547838..b3470dd 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -1335,33 +1335,24 @@ static int init_fruit_config(vfs_handle_struct *handle)
 	}
 	config->encoding = (enum fruit_encoding)enumval;
 
-	if (lp_parm_bool(SNUM(handle->conn),
-			 FRUIT_PARAM_TYPE_NAME, "veto_appledouble", true)) {
-		config->veto_appledouble = true;
-	}
+	config->veto_appledouble = lp_parm_bool(
+		SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+		"veto_appledouble", true);
 
-	if (lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME, "aapl", true)) {
-		config->use_aapl = true;
-	}
+	config->use_aapl = lp_parm_bool(
+		-1, FRUIT_PARAM_TYPE_NAME, "aapl", true);
 
-	if (lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true)) {
-		config->unix_info_enabled = true;
-	}
+	config->unix_info_enabled = lp_parm_bool(
+		-1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true);
 
-	if (lp_parm_bool(SNUM(handle->conn),
-			 "readdir_attr", "aapl_rsize", true)) {
-		config->readdir_attr_rsize = true;
-	}
+	config->readdir_attr_rsize = lp_parm_bool(
+		SNUM(handle->conn), "readdir_attr", "aapl_rsize", true);
 
-	if (lp_parm_bool(SNUM(handle->conn),
-			 "readdir_attr", "aapl_finder_info", true)) {
-		config->readdir_attr_finder_info = true;
-	}
+	config->readdir_attr_finder_info = lp_parm_bool(
+		SNUM(handle->conn), "readdir_attr", "aapl_finder_info", true);
 
-	if (lp_parm_bool(SNUM(handle->conn),
-			 "readdir_attr", "aapl_max_access", true)) {
-		config->readdir_attr_max_access = true;
-	}
+	config->readdir_attr_max_access = lp_parm_bool(
+		SNUM(handle->conn), "readdir_attr", "aapl_max_access", true);
 
 	SMB_VFS_HANDLE_SET_DATA(handle, config,
 				NULL, struct fruit_config_data,
-- 
2.1.0


From c82586a600799a293bc56e9c731e452e2837b4ef Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 9 Jun 2015 17:47:31 +0200
Subject: [PATCH 2/6] smbd/smb2_ioctl: fix error handling

tevent_req_nterror must be called directly as the last step before
returning with tevent_req_post.

Signed-off-by: Ralph Boehme <slow at samba.org>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 source3/smbd/smb2_ioctl_network_fs.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/source3/smbd/smb2_ioctl_network_fs.c b/source3/smbd/smb2_ioctl_network_fs.c
index 5e70703..36b8e89 100644
--- a/source3/smbd/smb2_ioctl_network_fs.c
+++ b/source3/smbd/smb2_ioctl_network_fs.c
@@ -225,9 +225,10 @@ static struct tevent_req *fsctl_srv_copychunk_send(TALLOC_CTX *mem_ctx,
 	}
 
 	state->status = copychunk_check_limits(&cc_copy);
-	if (tevent_req_nterror(req, state->status)) {
+	if (!NT_STATUS_IS_OK(state->status)) {
 		DEBUG(3, ("copy chunk req exceeds limits\n"));
 		state->out_data = COPYCHUNK_OUT_LIMITS;
+		tevent_req_nterror(req, state->status);
 		return tevent_req_post(req, ev);
 	}
 
-- 
2.1.0


From cda8c24a676232bc5834c523407caef8ea9ff038 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 27 Apr 2015 12:16:16 +0200
Subject: [PATCH 3/6] s3:util: use pread/pwrite in transfer_file

read/write aren't overloaded in the streams VFS modules, using
pread/pwrite instead this makes it possible to use transfer_file() with
named streams.

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

Signed-off-by: Ralph Boehme <slow at samba.org>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 source3/include/transfer_file.h  |  4 ++--
 source3/lib/util_transfer_file.c | 23 +++++++++++++----------
 source3/smbd/vfs.c               | 10 +++++-----
 3 files changed, 20 insertions(+), 17 deletions(-)

diff --git a/source3/include/transfer_file.h b/source3/include/transfer_file.h
index 546104f..2f1bff4 100644
--- a/source3/include/transfer_file.h
+++ b/source3/include/transfer_file.h
@@ -24,8 +24,8 @@
 ssize_t transfer_file_internal(void *in_file,
 			       void *out_file,
 			       size_t n,
-			       ssize_t (*read_fn)(void *, void *, size_t),
-			       ssize_t (*write_fn)(void *, const void *, size_t));
+			       ssize_t (*pread_fn)(void *, void *, size_t, off_t),
+			       ssize_t (*pwrite_fn)(void *, const void *, size_t, off_t));
 
 off_t transfer_file(int infd, int outfd, off_t n);
 
diff --git a/source3/lib/util_transfer_file.c b/source3/lib/util_transfer_file.c
index d415d7f..91f4f6f 100644
--- a/source3/lib/util_transfer_file.c
+++ b/source3/lib/util_transfer_file.c
@@ -36,8 +36,8 @@
 ssize_t transfer_file_internal(void *in_file,
 			       void *out_file,
 			       size_t n,
-			       ssize_t (*read_fn)(void *, void *, size_t),
-			       ssize_t (*write_fn)(void *, const void *, size_t))
+			       ssize_t (*pread_fn)(void *, void *, size_t, off_t),
+			       ssize_t (*pwrite_fn)(void *, const void *, size_t, off_t))
 {
 	char *buf;
 	size_t total = 0;
@@ -45,6 +45,7 @@ ssize_t transfer_file_internal(void *in_file,
 	ssize_t write_ret;
 	size_t num_to_read_thistime;
 	size_t num_written = 0;
+	off_t offset = 0;
 
 	if (n == 0) {
 		return 0;
@@ -57,7 +58,7 @@ ssize_t transfer_file_internal(void *in_file,
 	do {
 		num_to_read_thistime = MIN((n - total), TRANSFER_BUF_SIZE);
 
-		read_ret = (*read_fn)(in_file, buf, num_to_read_thistime);
+		read_ret = (*pread_fn)(in_file, buf, num_to_read_thistime, offset);
 		if (read_ret == -1) {
 			DEBUG(0,("transfer_file_internal: read failure. "
 				 "Error = %s\n", strerror(errno) ));
@@ -71,8 +72,9 @@ ssize_t transfer_file_internal(void *in_file,
 		num_written = 0;
 
 		while (num_written < read_ret) {
-			write_ret = (*write_fn)(out_file, buf + num_written,
-					        read_ret - num_written);
+			write_ret = (*pwrite_fn)(out_file, buf + num_written,
+					        read_ret - num_written,
+						offset + num_written);
 
 			if (write_ret == -1) {
 				DEBUG(0,("transfer_file_internal: "
@@ -89,28 +91,29 @@ ssize_t transfer_file_internal(void *in_file,
 		}
 
 		total += (size_t)read_ret;
+		offset += (off_t)read_ret;
 	} while (total < n);
 
 	SAFE_FREE(buf);
 	return (ssize_t)total;
 }
 
-static ssize_t sys_read_fn(void *file, void *buf, size_t len)
+static ssize_t sys_pread_fn(void *file, void *buf, size_t len, off_t offset)
 {
 	int *fd = (int *)file;
 
-	return sys_read(*fd, buf, len);
+	return sys_pread(*fd, buf, len, offset);
 }
 
-static ssize_t sys_write_fn(void *file, const void *buf, size_t len)
+static ssize_t sys_pwrite_fn(void *file, const void *buf, size_t len, off_t offset)
 {
 	int *fd = (int *)file;
 
-	return sys_write(*fd, buf, len);
+	return sys_pwrite(*fd, buf, len, offset);
 }
 
 off_t transfer_file(int infd, int outfd, off_t n)
 {
 	return (off_t)transfer_file_internal(&infd, &outfd, (size_t)n,
-						 sys_read_fn, sys_write_fn);
+						 sys_pread_fn, sys_pwrite_fn);
 }
diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c
index 4ab7723..b267387 100644
--- a/source3/smbd/vfs.c
+++ b/source3/smbd/vfs.c
@@ -756,24 +756,24 @@ int vfs_fill_sparse(files_struct *fsp, off_t len)
  Transfer some data (n bytes) between two file_struct's.
 ****************************************************************************/
 
-static ssize_t vfs_read_fn(void *file, void *buf, size_t len)
+static ssize_t vfs_pread_fn(void *file, void *buf, size_t len, off_t offset)
 {
 	struct files_struct *fsp = (struct files_struct *)file;
 
-	return SMB_VFS_READ(fsp, buf, len);
+	return SMB_VFS_PREAD(fsp, buf, len, offset);
 }
 
-static ssize_t vfs_write_fn(void *file, const void *buf, size_t len)
+static ssize_t vfs_pwrite_fn(void *file, const void *buf, size_t len, off_t offset)
 {
 	struct files_struct *fsp = (struct files_struct *)file;
 
-	return SMB_VFS_WRITE(fsp, buf, len);
+	return SMB_VFS_PWRITE(fsp, buf, len, offset);
 }
 
 off_t vfs_transfer_file(files_struct *in, files_struct *out, off_t n)
 {
 	return transfer_file_internal((void *)in, (void *)out, n,
-				      vfs_read_fn, vfs_write_fn);
+				      vfs_pread_fn, vfs_pwrite_fn);
 }
 
 /*******************************************************************
-- 
2.1.0


From 58480da5066bd33bc73aeb72bd17bd4797c22110 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Apr 2015 22:29:16 +0200
Subject: [PATCH 4/6] smb2:ioctl: support for OS X AAPL copyfile style
 copy_chunk

Apple's special copy_chunk ioctl that requests a copy of the whole file
along with all attached metadata.

These copy_chunk requests have a chunk count of 0 that we translate to a
copy_chunk_send VFS call overloading the parameters src_off = dest_off =
num = 0.

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

Signed-off-by: Ralph Boehme <slow at samba.org>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 source3/include/vfs.h                |  2 ++
 source3/smbd/smb2_ioctl_network_fs.c | 39 +++++++++++++++++++++++++++++++++++-
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/source3/include/vfs.h b/source3/include/vfs.h
index aa1a880..081030c 100644
--- a/source3/include/vfs.h
+++ b/source3/include/vfs.h
@@ -165,6 +165,7 @@
 /* Bump to version 33 - Samba 4.3 will ship with that. */
 /* Version 33 - change fallocate mode flags param from enum->uint32_t */
 /* Version 33 - Add snapshot create/delete calls */
+/* Version 33 - Add OS X SMB2 AAPL copyfile extension flag to fsp */
 
 #define SMB_VFS_INTERFACE_VERSION 33
 
@@ -257,6 +258,7 @@ typedef struct files_struct {
 	bool is_sparse;
 	bool backup_intent; /* Handle was successfully opened with backup intent
 				and opener has privilege to do so. */
+	bool aapl_copyfile_supported;
 	struct smb_filename *fsp_name;
 	uint32_t name_hash;		/* Jenkins hash of full pathname. */
 	uint64_t mid;			/* Mid of the operation that created us. */
diff --git a/source3/smbd/smb2_ioctl_network_fs.c b/source3/smbd/smb2_ioctl_network_fs.c
index 36b8e89..01798ac 100644
--- a/source3/smbd/smb2_ioctl_network_fs.c
+++ b/source3/smbd/smb2_ioctl_network_fs.c
@@ -91,6 +91,7 @@ struct fsctl_srv_copychunk_state {
 		COPYCHUNK_OUT_LIMITS,
 		COPYCHUNK_OUT_RSP,
 	} out_data;
+	bool aapl_copyfile;
 };
 static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq);
 
@@ -235,6 +236,38 @@ static struct tevent_req *fsctl_srv_copychunk_send(TALLOC_CTX *mem_ctx,
 	/* any errors from here onwards should carry copychunk response data */
 	state->out_data = COPYCHUNK_OUT_RSP;
 
+	if (cc_copy.chunk_count == 0) {
+		struct tevent_req *vfs_subreq;
+		/*
+		 * Process as OS X copyfile request. This is currently
+		 * the only copychunk request with a chunk count of 0
+		 * we will process.
+		 */
+		if (!state->src_fsp->aapl_copyfile_supported) {
+			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+			return tevent_req_post(req, ev);
+		}
+		if (!state->dst_fsp->aapl_copyfile_supported) {
+			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+			return tevent_req_post(req, ev);
+		}
+		state->aapl_copyfile = true;
+		vfs_subreq = SMB_VFS_COPY_CHUNK_SEND(dst_fsp->conn,
+						     state, ev,
+						     state->src_fsp,
+						     0,
+						     state->dst_fsp,
+						     0,
+						     0);
+		if (tevent_req_nomem(vfs_subreq, req)) {
+			return tevent_req_post(req, ev);
+		}
+		tevent_req_set_callback(vfs_subreq,
+					fsctl_srv_copychunk_vfs_done, req);
+		state->dispatch_count++;
+		return req;
+	}
+
 	for (i = 0; i < cc_copy.chunk_count; i++) {
 		struct tevent_req *vfs_subreq;
 		chunk = &cc_copy.chunks[i];
@@ -327,7 +360,11 @@ static NTSTATUS fsctl_srv_copychunk_recv(struct tevent_req *req,
 		*pack_rsp = true;
 		break;
 	case COPYCHUNK_OUT_RSP:
-		cc_rsp->chunks_written = state->recv_count - state->bad_recv_count;
+		if (state->aapl_copyfile == true) {
+			cc_rsp->chunks_written = 0;
+		} else {
+			cc_rsp->chunks_written = state->recv_count - state->bad_recv_count;
+		}
 		cc_rsp->chunk_bytes_written = 0;
 		cc_rsp->total_bytes_written = state->total_written;
 		*pack_rsp = true;
-- 
2.1.0


From e34c879471fe6a4a5c88144394bf621e910cc82b Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Apr 2015 22:29:16 +0200
Subject: [PATCH 5/6] vfs:fruit: implement copyfile style copy_chunk

Implement Apple's special copy_chunk ioctl that requests a copy of the
whole file along with all attached metadata.

These copy_chunk requests have a chunk count of 0 that we translate to a
copy_chunk_send VFS call overloading the parameters src_off = dest_off =
num = 0.

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

Signed-off-by: Ralph Boehme <slow at samba.org>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 docs-xml/manpages/vfs_fruit.8.xml |  12 +++
 source3/modules/vfs_fruit.c       | 218 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 230 insertions(+)

diff --git a/docs-xml/manpages/vfs_fruit.8.xml b/docs-xml/manpages/vfs_fruit.8.xml
index e407b54..9f77d9b 100644
--- a/docs-xml/manpages/vfs_fruit.8.xml
+++ b/docs-xml/manpages/vfs_fruit.8.xml
@@ -214,6 +214,18 @@
 	    </listitem>
 	  </varlistentry>
 
+	  <varlistentry>
+	    <term>fruit:copyfile = yes | no</term>
+	    <listitem>
+	      <para>Whether to enable OS X specific copychunk ioctl
+	      that requests a copy of a whole file along with all
+	      attached metadata.</para>
+	      <para>WARNING: the copyfile request is blocking the
+	      client while the server does the copy.</para>.
+	      <para>The default is <emphasis>no</emphasis>.</para>
+	    </listitem>
+	  </varlistentry>
+
 	</variablelist>
 </refsect1>
 
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index b3470dd..a4272f5 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -30,6 +30,7 @@
 #include "libcli/security/security.h"
 #include "../libcli/smb/smb2_create_ctx.h"
 #include "lib/sys_rw.h"
+#include "lib/util/tevent_ntstatus.h"
 
 /*
  * Enhanced OS X and Netatalk compatibility
@@ -124,8 +125,10 @@ struct fruit_config_data {
 	enum fruit_locking locking;
 	enum fruit_encoding encoding;
 	bool use_aapl;
+	bool use_copyfile;
 	bool readdir_attr_enabled;
 	bool unix_info_enabled;
+	bool copyfile_enabled;
 	bool veto_appledouble;
 
 	/*
@@ -1345,6 +1348,9 @@ static int init_fruit_config(vfs_handle_struct *handle)
 	config->unix_info_enabled = lp_parm_bool(
 		-1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true);
 
+	config->use_copyfile = lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME,
+					   "copyfile", false);
+
 	config->readdir_attr_rsize = lp_parm_bool(
 		SNUM(handle->conn), "readdir_attr", "aapl_rsize", true);
 
@@ -1828,6 +1834,11 @@ static NTSTATUS check_aapl(vfs_handle_struct *handle,
 			config->readdir_attr_enabled = true;
 		}
 
+		if (config->use_copyfile) {
+			server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE;
+			config->copyfile_enabled = true;
+		}
+
 		/*
 		 * The client doesn't set the flag, so we can't check
 		 * for it and just set it unconditionally
@@ -3243,6 +3254,15 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
 		return status;
 	}
 
+	if (config->copyfile_enabled) {
+		/*
+		 * Set a flag in the fsp. Gets used in copychunk to
+		 * check whether the special Apple copyfile semantics
+		 * for copychunk should be allowed in a copychunk
+		 * request with a count of 0.
+		 */
+		(*result)->aapl_copyfile_supported = true;
+	}
 	if (is_ntfs_stream_smb_fname(smb_fname)
 	    || (*result == NULL)
 	    || ((*result)->is_directory)) {
@@ -3453,6 +3473,202 @@ static NTSTATUS fruit_fset_nt_acl(vfs_handle_struct *handle,
 	return NT_STATUS_OK;
 }
 
+struct fruit_copy_chunk_state {
+	struct vfs_handle_struct *handle;
+	off_t copied;
+	struct files_struct *src_fsp;
+	struct files_struct *dst_fsp;
+	bool is_copyfile;
+};
+
+static void fruit_copy_chunk_done(struct tevent_req *subreq);
+static struct tevent_req *fruit_copy_chunk_send(struct vfs_handle_struct *handle,
+						TALLOC_CTX *mem_ctx,
+						struct tevent_context *ev,
+						struct files_struct *src_fsp,
+						off_t src_off,
+						struct files_struct *dest_fsp,
+						off_t dest_off,
+						off_t num)
+{
+	struct tevent_req *req, *subreq;
+	struct fruit_copy_chunk_state *fruit_copy_chunk_state;
+	NTSTATUS status;
+	struct fruit_config_data *config;
+	off_t to_copy = num;
+
+	DEBUG(10,("soff: %zd, doff: %zd, len: %zd\n",
+		  src_off, dest_off, num));
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data,
+				return NULL);
+
+	req = tevent_req_create(mem_ctx, &fruit_copy_chunk_state,
+				struct fruit_copy_chunk_state);
+	if (req == NULL) {
+		return NULL;
+	}
+	fruit_copy_chunk_state->handle = handle;
+	fruit_copy_chunk_state->src_fsp = src_fsp;
+	fruit_copy_chunk_state->dst_fsp = dest_fsp;
+
+	/*
+	 * Check if this a OS X copyfile style copychunk request with
+	 * a requested chunk count of 0 that was translated to a
+	 * copy_chunk_send VFS call overloading the parameters src_off
+	 * = dest_off = num = 0.
+	 */
+	if ((src_off == 0) && (dest_off == 0) && (num == 0) &&
+	    src_fsp->aapl_copyfile_supported &&
+	    dest_fsp->aapl_copyfile_supported)
+	{
+		status = vfs_stat_fsp(src_fsp);
+		if (tevent_req_nterror(req, status)) {
+			return tevent_req_post(req, ev);
+		}
+
+		to_copy = src_fsp->fsp_name->st.st_ex_size;
+		fruit_copy_chunk_state->is_copyfile = true;
+	}
+
+	subreq = SMB_VFS_NEXT_COPY_CHUNK_SEND(handle,
+					      mem_ctx,
+					      ev,
+					      src_fsp,
+					      src_off,
+					      dest_fsp,
+					      dest_off,
+					      to_copy);
+	if (tevent_req_nomem(subreq, req)) {
+		return tevent_req_post(req, ev);
+	}
+
+	tevent_req_set_callback(subreq, fruit_copy_chunk_done, req);
+	return req;
+}
+
+static void fruit_copy_chunk_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req = tevent_req_callback_data(
+		subreq, struct tevent_req);
+	struct fruit_copy_chunk_state *state = tevent_req_data(
+		req, struct fruit_copy_chunk_state);
+	NTSTATUS status;
+	unsigned int num_streams = 0;
+	struct stream_struct *streams = NULL;
+	int i;
+	struct smb_filename *src_fname_tmp = NULL;
+	struct smb_filename *dst_fname_tmp = NULL;
+
+	status = SMB_VFS_NEXT_COPY_CHUNK_RECV(state->handle,
+					      subreq,
+					      &state->copied);
+	TALLOC_FREE(subreq);
+	if (tevent_req_nterror(req, status)) {
+		return;
+	}
+
+	if (!state->is_copyfile) {
+		tevent_req_done(req);
+		return;
+	}
+
+	/*
+	 * Now copy all reamining streams. We know the share supports
+	 * streams, because we're in vfs_fruit. We don't do this async
+	 * because streams are few and small.
+	 */
+	status = vfs_streaminfo(state->handle->conn, NULL,
+				state->src_fsp->fsp_name->base_name,
+				req, &num_streams, &streams);
+	if (tevent_req_nterror(req, status)) {
+		return;
+	}
+
+	if (num_streams == 1) {
+		/* There is always one stream, ::$DATA. */
+		tevent_req_done(req);
+		return;
+	}
+
+	for (i = 0; i < num_streams; i++) {
+		DEBUG(10, ("%s: stream: '%s'/%zd\n",
+			  __func__, streams[i].name, streams[i].size));
+
+		src_fname_tmp = synthetic_smb_fname(
+			req,
+			state->src_fsp->fsp_name->base_name,
+			streams[i].name,
+			NULL);
+		if (tevent_req_nomem(src_fname_tmp, req)) {
+			return;
+		}
+
+		if (is_ntfs_default_stream_smb_fname(src_fname_tmp)) {
+			TALLOC_FREE(src_fname_tmp);
+			continue;
+		}
+
+		dst_fname_tmp = synthetic_smb_fname(
+			req,
+			state->dst_fsp->fsp_name->base_name,
+			streams[i].name,
+			NULL);
+		if (tevent_req_nomem(dst_fname_tmp, req)) {
+			TALLOC_FREE(src_fname_tmp);
+			return;
+		}
+
+		status = copy_file(req,
+				   state->handle->conn,
+				   src_fname_tmp,
+				   dst_fname_tmp,
+				   OPENX_FILE_CREATE_IF_NOT_EXIST,
+				   0, false);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(1, ("%s: copy %s to %s failed: %s\n", __func__,
+				  smb_fname_str_dbg(src_fname_tmp),
+				  smb_fname_str_dbg(dst_fname_tmp),
+				  nt_errstr(status)));
+			TALLOC_FREE(src_fname_tmp);
+			TALLOC_FREE(dst_fname_tmp);
+			tevent_req_nterror(req, status);
+			return;
+		}
+
+		TALLOC_FREE(src_fname_tmp);
+		TALLOC_FREE(dst_fname_tmp);
+	}
+
+	TALLOC_FREE(streams);
+	TALLOC_FREE(src_fname_tmp);
+	TALLOC_FREE(dst_fname_tmp);
+	tevent_req_done(req);
+}
+
+static NTSTATUS fruit_copy_chunk_recv(struct vfs_handle_struct *handle,
+				      struct tevent_req *req,
+				      off_t *copied)
+{
+	struct fruit_copy_chunk_state *fruit_copy_chunk_state = tevent_req_data(
+		req, struct fruit_copy_chunk_state);
+	NTSTATUS status;
+
+	if (tevent_req_is_nterror(req, &status)) {
+		DEBUG(1, ("server side copy chunk failed: %s\n",
+			  nt_errstr(status)));
+		*copied = 0;
+		tevent_req_received(req);
+		return status;
+	}
+
+	*copied = fruit_copy_chunk_state->copied;
+	tevent_req_received(req);
+
+	return NT_STATUS_OK;
+}
+
 static struct vfs_fn_pointers vfs_fruit_fns = {
 	.connect_fn = fruit_connect,
 
@@ -3474,6 +3690,8 @@ static struct vfs_fn_pointers vfs_fruit_fns = {
 	.fallocate_fn = fruit_fallocate,
 	.create_file_fn = fruit_create_file,
 	.readdir_attr_fn = fruit_readdir_attr,
+	.copy_chunk_send_fn = fruit_copy_chunk_send,
+	.copy_chunk_recv_fn = fruit_copy_chunk_recv,
 
 	/* NT ACL operations */
 	.fget_nt_acl_fn = fruit_fget_nt_acl,
-- 
2.1.0


From 43820da1ca2ae09a030a510f42fc1b5d848f7fcc Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 10 Jun 2015 15:30:04 +0200
Subject: [PATCH 6/6] s4:torture:vfs_fruit: copyfile

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

Signed-off-by: Ralph Boehme <slow at samba.org>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 selftest/target/Samba3.pm   |   3 +
 selftest/target/Samba4.pm   |   3 +
 source4/torture/vfs/fruit.c | 450 +++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 455 insertions(+), 1 deletion(-)

diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index 50898f2..f761ba0 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1214,6 +1214,9 @@ sub provision($$$$$$$$)
 	# fsrvp server requires registry shares
 	registry shares = yes
 
+	# fruit:copyfile is a global option
+	fruit:copyfile = yes
+
 	# Begin extra options
 	$extra_options
 	# End extra options
diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index 3a5b409..4f9aaea 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -917,6 +917,9 @@ sub provision($$$$$$$$$$)
 	lanman auth = yes
 	allow nt4 crypto = yes
 
+	# fruit:copyfile is a global option
+	fruit:copyfile = yes
+
 	$extra_smbconf_options
 
 [tmp]
diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 162885e..e04d995 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -34,8 +34,11 @@
 #include "torture/util.h"
 #include "torture/smb2/proto.h"
 #include "torture/vfs/proto.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
 
 #define BASEDIR "vfs_fruit_dir"
+#define FNAME_CC_SRC "testfsctl.dat"
+#define FNAME_CC_DST "testfsctl2.dat"
 
 #define CHECK_STATUS(status, correct) do { \
 	if (!NT_STATUS_EQUAL(status, correct)) { \
@@ -1473,7 +1476,8 @@ static bool test_aapl(struct torture_context *tctx,
 	aapl_server_caps = BVAL(aapl->data.data, 16);
 	if (aapl_server_caps != (SMB2_CRTCTX_AAPL_UNIX_BASED |
 				 SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR |
-				 SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE)) {
+				 SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE |
+				 SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE)) {
 		torture_result(tctx, TORTURE_FAIL,
 			       "(%s) unexpected server_caps: %d",
 			       __location__, (int)aapl_server_caps);
@@ -1624,6 +1628,449 @@ done:
 	return ret;
 }
 
+static uint64_t patt_hash(uint64_t off)
+{
+	return off;
+}
+
+static bool write_pattern(struct torture_context *torture,
+			  struct smb2_tree *tree, TALLOC_CTX *mem_ctx,
+			  struct smb2_handle h, uint64_t off, uint64_t len,
+			  uint64_t patt_off)
+{
+	NTSTATUS status;
+	uint64_t i;
+	uint8_t *buf;
+	uint64_t io_sz = MIN(1024 * 64, len);
+
+	if (len == 0) {
+		return true;
+	}
+
+	torture_assert(torture, (len % 8) == 0, "invalid write len");
+
+	buf = talloc_zero_size(mem_ctx, io_sz);
+	torture_assert(torture, (buf != NULL), "no memory for file data buf");
+
+	while (len > 0) {
+		for (i = 0; i <= io_sz - 8; i += 8) {
+			SBVAL(buf, i, patt_hash(patt_off));
+			patt_off += 8;
+		}
+
+		status = smb2_util_write(tree, h,
+					 buf, off, io_sz);
+		torture_assert_ntstatus_ok(torture, status, "file write");
+
+		len -= io_sz;
+		off += io_sz;
+	}
+
+	talloc_free(buf);
+
+	return true;
+}
+
+static bool check_pattern(struct torture_context *torture,
+			  struct smb2_tree *tree, TALLOC_CTX *mem_ctx,
+			  struct smb2_handle h, uint64_t off, uint64_t len,
+			  uint64_t patt_off)
+{
+	if (len == 0) {
+		return true;
+	}
+
+	torture_assert(torture, (len % 8) == 0, "invalid read len");
+
+	while (len > 0) {
+		uint64_t i;
+		struct smb2_read r;
+		NTSTATUS status;
+		uint64_t io_sz = MIN(1024 * 64, len);
+
+		ZERO_STRUCT(r);
+		r.in.file.handle = h;
+		r.in.length      = io_sz;
+		r.in.offset      = off;
+		status = smb2_read(tree, mem_ctx, &r);
+		torture_assert_ntstatus_ok(torture, status, "read");
+
+		torture_assert_u64_equal(torture, r.out.data.length, io_sz,
+					 "read data len mismatch");
+
+		for (i = 0; i <= io_sz - 8; i += 8, patt_off += 8) {
+			uint64_t data = BVAL(r.out.data.data, i);
+			torture_assert_u64_equal(torture, data, patt_hash(patt_off),
+						 talloc_asprintf(torture, "read data "
+								 "pattern bad at %llu\n",
+								 (unsigned long long)off + i));
+		}
+		talloc_free(r.out.data.data);
+		len -= io_sz;
+		off += io_sz;
+	}
+
+	return true;
+}
+
+static bool test_setup_open(struct torture_context *torture,
+			    struct smb2_tree *tree, TALLOC_CTX *mem_ctx,
+			    const char *fname,
+			    struct smb2_handle *fh,
+			    uint32_t desired_access,
+			    uint32_t file_attributes)
+{
+	struct smb2_create io;
+	NTSTATUS status;
+
+	ZERO_STRUCT(io);
+	io.in.desired_access = desired_access;
+	io.in.file_attributes = file_attributes;
+	io.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	io.in.share_access =
+		NTCREATEX_SHARE_ACCESS_DELETE|
+		NTCREATEX_SHARE_ACCESS_READ|
+		NTCREATEX_SHARE_ACCESS_WRITE;
+	if (file_attributes & FILE_ATTRIBUTE_DIRECTORY) {
+		io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
+	}
+	io.in.fname = fname;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	torture_assert_ntstatus_ok(torture, status, "file create");
+
+	*fh = io.out.file.handle;
+
+	return true;
+}
+
+static bool test_setup_create_fill(struct torture_context *torture,
+				   struct smb2_tree *tree, TALLOC_CTX *mem_ctx,
+				   const char *fname,
+				   struct smb2_handle *fh,
+				   uint64_t size,
+				   uint32_t desired_access,
+				   uint32_t file_attributes)
+{
+	bool ok;
+
+	ok = test_setup_open(torture, tree, mem_ctx,
+			     fname,
+			     fh,
+			     desired_access,
+			     file_attributes);
+	torture_assert(torture, ok, "file open");
+
+	if (size > 0) {
+		ok = write_pattern(torture, tree, mem_ctx, *fh, 0, size, 0);
+		torture_assert(torture, ok, "write pattern");
+	}
+	return true;
+}
+
+static bool test_setup_copy_chunk(struct torture_context *torture,
+				  struct smb2_tree *tree, TALLOC_CTX *mem_ctx,
+				  uint32_t nchunks,
+				  struct smb2_handle *src_h,
+				  uint64_t src_size,
+				  uint32_t src_desired_access,
+				  struct smb2_handle *dest_h,
+				  uint64_t dest_size,
+				  uint32_t dest_desired_access,
+				  struct srv_copychunk_copy *cc_copy,
+				  union smb_ioctl *ioctl)
+{
+	struct req_resume_key_rsp res_key;
+	bool ok;
+	NTSTATUS status;
+	enum ndr_err_code ndr_ret;
+
+	ok = test_setup_create_fill(torture, tree, mem_ctx, FNAME_CC_SRC,
+				    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,
+				    dest_h, dest_size, dest_desired_access,
+				    FILE_ATTRIBUTE_NORMAL);
+	torture_assert(torture, ok, "dest file create fill");
+
+	ZERO_STRUCTPN(ioctl);
+	ioctl->smb2.level = RAW_IOCTL_SMB2;
+	ioctl->smb2.in.file.handle = *src_h;
+	ioctl->smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY;
+	/* Allow for Key + ContextLength + Context */
+	ioctl->smb2.in.max_response_size = 32;
+	ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;
+
+	status = smb2_ioctl(tree, mem_ctx, &ioctl->smb2);
+	torture_assert_ntstatus_ok(torture, status,
+				   "FSCTL_SRV_REQUEST_RESUME_KEY");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl->smb2.out.out, mem_ctx, &res_key,
+			(ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp);
+
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_req_resume_key_rsp");
+
+	ZERO_STRUCTPN(ioctl);
+	ioctl->smb2.level = RAW_IOCTL_SMB2;
+	ioctl->smb2.in.file.handle = *dest_h;
+	ioctl->smb2.in.function = FSCTL_SRV_COPYCHUNK;
+	ioctl->smb2.in.max_response_size = sizeof(struct srv_copychunk_rsp);
+	ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;
+
+	ZERO_STRUCTPN(cc_copy);
+	memcpy(cc_copy->source_key, res_key.resume_key, ARRAY_SIZE(cc_copy->source_key));
+	cc_copy->chunk_count = nchunks;
+	cc_copy->chunks = talloc_zero_array(mem_ctx, struct srv_copychunk, nchunks);
+	torture_assert(torture, (cc_copy->chunks != NULL), "no memory for chunks");
+
+	return true;
+}
+
+
+static bool check_copy_chunk_rsp(struct torture_context *torture,
+				 struct srv_copychunk_rsp *cc_rsp,
+				 uint32_t ex_chunks_written,
+				 uint32_t ex_chunk_bytes_written,
+				 uint32_t ex_total_bytes_written)
+{
+	torture_assert_int_equal(torture, cc_rsp->chunks_written,
+				 ex_chunks_written, "num chunks");
+	torture_assert_int_equal(torture, cc_rsp->chunk_bytes_written,
+				 ex_chunk_bytes_written, "chunk bytes written");
+	torture_assert_int_equal(torture, cc_rsp->total_bytes_written,
+				 ex_total_bytes_written, "chunk total bytes");
+	return true;
+}
+
+static bool neg_aapl_copyfile(struct torture_context *tctx,
+			      struct smb2_tree *tree,
+			      uint64_t flags)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	const char *fname = "aapl";
+	NTSTATUS status;
+	struct smb2_create io;
+	DATA_BLOB data;
+	struct smb2_create_blob *aapl = NULL;
+	uint32_t aapl_cmd;
+	uint32_t aapl_reply_bitmap;
+	uint32_t aapl_server_caps;
+	bool ret = true;
+
+	ZERO_STRUCT(io);
+	io.in.desired_access     = SEC_FLAG_MAXIMUM_ALLOWED;
+	io.in.file_attributes    = FILE_ATTRIBUTE_NORMAL;
+	io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF;
+	io.in.share_access = (NTCREATEX_SHARE_ACCESS_DELETE |
+			      NTCREATEX_SHARE_ACCESS_READ |
+			      NTCREATEX_SHARE_ACCESS_WRITE);
+	io.in.fname = fname;
+
+	data = data_blob_talloc(mem_ctx, NULL, 3 * sizeof(uint64_t));
+	SBVAL(data.data, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY);
+	SBVAL(data.data, 8, (SMB2_CRTCTX_AAPL_SERVER_CAPS));
+	SBVAL(data.data, 16, flags);
+
+	status = smb2_create_blob_add(tctx, &io.in.blobs, "AAPL", data);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	status = smb2_create(tree, tctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	aapl = smb2_create_blob_find(&io.out.blobs,
+				     SMB2_CREATE_TAG_AAPL);
+	if (aapl == NULL) {
+		ret = false;
+		goto done;
+
+	}
+	if (aapl->data.length < 24) {
+		ret = false;
+		goto done;
+	}
+
+	aapl_cmd = IVAL(aapl->data.data, 0);
+	if (aapl_cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) {
+		torture_result(tctx, TORTURE_FAIL,
+			       "(%s) unexpected cmd: %d",
+			       __location__, (int)aapl_cmd);
+		ret = false;
+		goto done;
+	}
+
+	aapl_reply_bitmap = BVAL(aapl->data.data, 8);
+	if (!(aapl_reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS)) {
+		torture_result(tctx, TORTURE_FAIL,
+			       "(%s) unexpected reply_bitmap: %d",
+			       __location__, (int)aapl_reply_bitmap);
+		ret = false;
+		goto done;
+	}
+
+	aapl_server_caps = BVAL(aapl->data.data, 16);
+	if (!(aapl_server_caps & flags)) {
+		torture_result(tctx, TORTURE_FAIL,
+			       "(%s) unexpected server_caps: %d",
+			       __location__, (int)aapl_server_caps);
+		ret = false;
+		goto done;
+	}
+
+done:
+	status = smb2_util_close(tree, io.out.file.handle);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	smb2_util_unlink(tree, "aapl");
+	talloc_free(mem_ctx);
+	return ret;
+}
+
+static bool test_copyfile(struct torture_context *torture,
+			  struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	/*
+	 * First test a copy_chunk with a 0 chunk count without having
+	 * enabled this via AAPL. The request must not fail and the
+	 * copied length in the response must be 0. This is verified
+	 * against Windows 2008r2.
+	 */
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   0, /* 0 chunks, copyfile semantics */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail_goto(torture, done, "setup copy chunk error");
+	}
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_ok_goto(torture, status, ok, done, "FSCTL_SRV_COPYCHUNK");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_srv_copychunk_rsp");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  0,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  0); /* total bytes written */
+	if (!ok) {
+		torture_fail_goto(torture, done, "bad copy chunk response data");
+	}
+
+	/*
+	 * Now enable AAPL copyfile and test again, the file and the
+	 * stream must be copied by the server.
+	 */
+	ok = neg_aapl_copyfile(torture, tree,
+			       SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE);
+	if (!ok) {
+		torture_skip_goto(torture, done, "missing AAPL copyfile");
+		goto done;
+	}
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	smb2_util_unlink(tree, FNAME_CC_SRC);
+	smb2_util_unlink(tree, FNAME_CC_DST);
+
+	ok = torture_setup_file(tmp_ctx, tree, FNAME_CC_SRC, false);
+	if (!ok) {
+		torture_fail(torture, "setup file error");
+	}
+	ok = write_stream(tree, __location__, torture, tmp_ctx,
+			    FNAME_CC_SRC, AFPRESOURCE_STREAM,
+			    10, 10, "1234567890");
+	if (!ok) {
+		torture_fail(torture, "setup stream error");
+	}
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   0, /* 0 chunks, copyfile semantics */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail_goto(torture, done, "setup copy chunk error");
+	}
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_ok_goto(torture, status, ok, done, "FSCTL_SRV_COPYCHUNK");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_srv_copychunk_rsp");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  0,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  4096); /* total bytes written */
+	if (!ok) {
+		torture_fail_goto(torture, done, "bad copy chunk response data");
+	}
+
+	ok = test_setup_open(torture, tree, tmp_ctx, FNAME_CC_DST, &dest_h,
+			     SEC_FILE_READ_DATA, FILE_ATTRIBUTE_NORMAL);
+	if (!ok) {
+		torture_fail_goto(torture, done,"open failed");
+	}
+	ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0);
+	if (!ok) {
+		torture_fail_goto(torture, done, "inconsistent file data");
+	}
+
+	ok = check_stream(tree, __location__, torture, tmp_ctx,
+			    FNAME_CC_DST, AFPRESOURCE_STREAM,
+			    0, 20, 10, 10, "1234567890");
+	if (!ok) {
+		torture_fail_goto(torture, done, "inconsistent stream data");
+	}
+
+done:
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	smb2_util_unlink(tree, FNAME_CC_SRC);
+	smb2_util_unlink(tree, FNAME_CC_DST);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
 /*
  * Note: This test depends on "vfs objects = catia fruit
  * streams_xattr".  Note: To run this test, use
@@ -1638,6 +2085,7 @@ struct torture_suite *torture_vfs_fruit(void)
 
 	suite->description = talloc_strdup(suite, "vfs_fruit tests");
 
+	torture_suite_add_1smb2_test(suite, "copyfile", test_copyfile);
 	torture_suite_add_2ns_smb2_test(suite, "read metadata", test_read_atalk_metadata);
 	torture_suite_add_2ns_smb2_test(suite, "write metadata", test_write_atalk_metadata);
 	torture_suite_add_2ns_smb2_test(suite, "resource fork IO", test_write_atalk_rfork_io);
-- 
2.1.0



More information about the samba-technical mailing list