From 19c1720fc7f0c4377fc76ce9ad0c70089f21fd1f Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Mon, 27 Apr 2015 12:16:16 +0200 Subject: [PATCH 1/2] 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 --- 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 4441e312b939721c294c5f61c73cdebb431a49fa Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Wed, 22 Apr 2015 22:29:16 +0200 Subject: [PATCH 2/2] 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 --- docs-xml/manpages/vfs_fruit.8.xml | 10 ++ source3/modules/vfs_fruit.c | 206 +++++++++++++++++++++++++++++++++++ source3/smbd/smb2_ioctl_network_fs.c | 30 +++++ 3 files changed, 246 insertions(+) diff --git a/docs-xml/manpages/vfs_fruit.8.xml b/docs-xml/manpages/vfs_fruit.8.xml index e407b54..63cf925 100644 --- a/docs-xml/manpages/vfs_fruit.8.xml +++ b/docs-xml/manpages/vfs_fruit.8.xml @@ -214,6 +214,16 @@ + + fruit:copyfile = yes | no + + Whether to enable OS X specific copychunk ioctl + that requests a copy of a whole file along with all + attached metadata. + The default is no. + + + diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c index 2547838..8541032 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,6 +125,7 @@ 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 veto_appledouble; @@ -1348,6 +1350,11 @@ static int init_fruit_config(vfs_handle_struct *handle) config->unix_info_enabled = true; } + if (lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME, + "copyfile", false)) { + config->use_copyfile = true; + } + if (lp_parm_bool(SNUM(handle->conn), "readdir_attr", "aapl_rsize", true)) { config->readdir_attr_rsize = true; @@ -1837,6 +1844,10 @@ 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; + } + /* * The client doesn't set the flag, so we can't check * for it and just set it unconditionally @@ -3462,6 +3473,199 @@ static NTSTATUS fruit_fset_nt_acl(vfs_handle_struct *handle, return NT_STATUS_OK; } +struct fruit_cc_state { + struct vfs_handle_struct *handle; + off_t copied; + struct files_struct *src_fsp; + struct files_struct *dst_fsp; +}; + +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_cc_state *fruit_cc_state; + NTSTATUS status; + struct fruit_config_data *config; + off_t to_copy = num; + + DEBUG(10,("%s: soff: %zd, doff: %zd, len: %zd\n", + __func__, 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_cc_state, struct fruit_cc_state); + if (req == NULL) { + return NULL; + } + fruit_cc_state->handle = handle; + fruit_cc_state->src_fsp = src_fsp; + fruit_cc_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)) { + + if (!config->use_copyfile) { + tevent_req_error(req, EIO); + return tevent_req_post(req, ev); + } + + 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; + } + + 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_cc_state *state = tevent_req_data( + req, struct fruit_cc_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; + } + + /* + * 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 (src_fname_tmp == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + 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 (dst_fname_tmp == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + 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))); + 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_cc_state *fruit_cc_state = tevent_req_data( + req, struct fruit_cc_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_cc_state->copied; + DEBUG(1, ("server side copy chunk copied %lu\n", + (unsigned long)*copied)); + tevent_req_received(req); + + return NT_STATUS_OK; +} + static struct vfs_fn_pointers vfs_fruit_fns = { .connect_fn = fruit_connect, @@ -3483,6 +3687,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, diff --git a/source3/smbd/smb2_ioctl_network_fs.c b/source3/smbd/smb2_ioctl_network_fs.c index 5e70703..70eb3bc 100644 --- a/source3/smbd/smb2_ioctl_network_fs.c +++ b/source3/smbd/smb2_ioctl_network_fs.c @@ -234,6 +234,36 @@ 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) && + (lp_parm_bool(-1, "fruit", "copyfile", false) == true)) { + /* + * Process as OS X copyfile request. Setting src_off = + * dst_off = 0 and a length of 0 is what must be used + * in the VFS to distinguish this from "normal" + * copychunk requests. I delibiretaly overload the + * params that way in order to avoid a VFS version + * bump. + */ + struct tevent_req *vfs_subreq; + vfs_subreq = SMB_VFS_COPY_CHUNK_SEND(dst_fsp->conn, + state, ev, + state->src_fsp, + 0, + state->dst_fsp, + 0, + 0); + if (vfs_subreq == NULL) { + DEBUG(0, ("VFS copy chunk send failed\n")); + state->status = NT_STATUS_NO_MEMORY; + tevent_req_nterror(req, state->status); + 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]; -- 2.1.0