[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