#include "smb2_ioctl_private.h"
#include "../lib/tsocket/tsocket.h"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
static void copychunk_pack_limits(struct srv_copychunk_rsp *cc_rsp)
{
cc_rsp->chunks_written = COPYCHUNK_MAX_CHUNKS;
struct connection_struct *conn;
struct srv_copychunk_copy cc_copy;
uint32_t current_chunk;
- uint32_t next_chunk;
NTSTATUS status;
off_t total_written;
+ uint32_t ctl_code;
+ DATA_BLOB token;
struct files_struct *src_fsp;
struct files_struct *dst_fsp;
enum {
COPYCHUNK_OUT_LIMITS,
COPYCHUNK_OUT_RSP,
} out_data;
- bool aapl_copyfile;
};
static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq);
-static NTSTATUS copychunk_check_handles(uint32_t ctl_code,
- struct files_struct *src_fsp,
- struct files_struct *dst_fsp,
- struct smb_request *smb1req)
-{
- /*
- * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request
- * The server MUST fail the request with STATUS_ACCESS_DENIED if any of
- * the following are true:
- * - The Open.GrantedAccess of the destination file does not include
- * FILE_WRITE_DATA or FILE_APPEND_DATA.
- */
- if (!CHECK_WRITE(dst_fsp)) {
- DEBUG(5, ("copy chunk no write on dest handle (%s).\n",
- smb_fname_str_dbg(dst_fsp->fsp_name) ));
- return NT_STATUS_ACCESS_DENIED;
- }
- /*
- * - The Open.GrantedAccess of the destination file does not include
- * FILE_READ_DATA, and the CtlCode is FSCTL_SRV_COPYCHUNK.
- */
- if ((ctl_code == FSCTL_SRV_COPYCHUNK) &&
- !CHECK_READ_IOCTL(dst_fsp)) {
- DEBUG(5, ("copy chunk no read on dest handle (%s).\n",
- smb_fname_str_dbg(dst_fsp->fsp_name) ));
- return NT_STATUS_ACCESS_DENIED;
- }
- /*
- * - The Open.GrantedAccess of the source file does not include
- * FILE_READ_DATA access.
- */
- if (!CHECK_READ_SMB2(src_fsp)) {
- DEBUG(5, ("copy chunk no read on src handle (%s).\n",
- smb_fname_str_dbg(src_fsp->fsp_name) ));
- return NT_STATUS_ACCESS_DENIED;
- }
-
- if (src_fsp->is_directory) {
- DEBUG(5, ("copy chunk no read on src directory handle (%s).\n",
- smb_fname_str_dbg(src_fsp->fsp_name) ));
- return NT_STATUS_ACCESS_DENIED;
- }
-
- if (dst_fsp->is_directory) {
- DEBUG(5, ("copy chunk no read on dst directory handle (%s).\n",
- smb_fname_str_dbg(dst_fsp->fsp_name) ));
- return NT_STATUS_ACCESS_DENIED;
- }
-
- if (IS_IPC(src_fsp->conn) || IS_IPC(dst_fsp->conn)) {
- DEBUG(5, ("copy chunk no access on IPC$ handle.\n"));
- return NT_STATUS_ACCESS_DENIED;
- }
-
- if (IS_PRINT(src_fsp->conn) || IS_PRINT(dst_fsp->conn)) {
- DEBUG(5, ("copy chunk no access on PRINT handle.\n"));
- return NT_STATUS_ACCESS_DENIED;
- }
-
- return NT_STATUS_OK;
-}
-
static NTSTATUS fsctl_srv_copychunk_loop(struct tevent_req *req);
static struct tevent_req *fsctl_srv_copychunk_send(TALLOC_CTX *mem_ctx,
{
struct tevent_req *req = NULL;
struct fsctl_srv_copychunk_state *state = NULL;
- uint64_t src_persistent_h;
- uint64_t src_volatile_h;
enum ndr_err_code ndr_ret;
NTSTATUS status;
*state = (struct fsctl_srv_copychunk_state) {
.conn = dst_fsp->conn,
.ev = ev,
+ .ctl_code = ctl_code,
+ .dst_fsp = dst_fsp,
};
if (in_max_output < sizeof(struct srv_copychunk_rsp)) {
return tevent_req_post(req, ev);
}
- /* persistent/volatile keys sent as the resume key */
- src_persistent_h = BVAL(state->cc_copy.source_key, 0);
- src_volatile_h = BVAL(state->cc_copy.source_key, 8);
- state->src_fsp = file_fsp_get(smb2req, src_persistent_h, src_volatile_h);
- if (state->src_fsp == NULL) {
- DEBUG(3, ("invalid resume key in copy chunk req\n"));
- state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
- tevent_req_nterror(req, state->status);
- return tevent_req_post(req, ev);
- }
-
- state->dst_fsp = dst_fsp;
-
- state->status = copychunk_check_handles(ctl_code,
- state->src_fsp,
- state->dst_fsp,
- smb2req->smb1req);
- if (!NT_STATUS_IS_OK(state->status)) {
- tevent_req_nterror(req, state->status);
- return tevent_req_post(req, ev);
- }
+ state->token = data_blob_const(state->cc_copy.source_key,
+ sizeof(state->cc_copy.source_key));
state->status = copychunk_check_limits(&state->cc_copy);
if (!NT_STATUS_IS_OK(state->status)) {
return req;
}
-/*
- * Work out the next IO request from the remaining chunks starting with
- * state->current_chunk. Chunks with left to right adjacent ranges can be merged
- * into a single IO request.
- */
-static uint32_t next_merged_io(struct fsctl_srv_copychunk_state *state)
-{
- struct srv_copychunk *chunk = NULL;
- uint32_t length;
- off_t next_src_offset;
- off_t next_dst_offset;
-
- /*
- * We're expected to process at least one chunk and return it's length,
- * so let's do this first.
- */
-
- chunk = &state->cc_copy.chunks[state->current_chunk];
- length = chunk->length;
- state->next_chunk++;
-
- /*
- * Now process subsequent chunks merging chunks as long as ranges are
- * adjacent and the source file size is not exceeded (this is needed
- * for error reporting).
- */
-
- next_src_offset = chunk->source_off + chunk->length;
- next_dst_offset = chunk->target_off + chunk->length;
-
- while (state->next_chunk < state->cc_copy.chunk_count) {
- chunk = &state->cc_copy.chunks[state->next_chunk];
-
- if ((chunk->source_off != next_src_offset) ||
- (chunk->target_off != next_dst_offset))
- {
- /* Not adjacent, stop merging */
- break;
- }
-
- next_src_offset += chunk->length;
- next_dst_offset += chunk->length;
-
- if (next_src_offset > state->src_fsp->fsp_name->st.st_ex_size) {
- /* Source filesize exceeded, stop merging */
- break;
- }
-
- /*
- * Found a mergable chunk, merge it and continue searching.
- * Note: this can't wrap, limits were already checked in
- * copychunk_check_limits().
- */
- length += chunk->length;
-
- state->next_chunk++;
- }
-
- return length;
-}
-
static NTSTATUS fsctl_srv_copychunk_loop(struct tevent_req *req)
{
struct fsctl_srv_copychunk_state *state = tevent_req_data(
req, struct fsctl_srv_copychunk_state);
struct tevent_req *subreq = NULL;
- struct srv_copychunk *chunk = NULL;
- uint32_t length;
+ uint32_t length = 0;
+ off_t source_off = 0;
+ off_t target_off = 0;
- if (state->next_chunk > state->cc_copy.chunk_count) {
- DBG_ERR("Copy-chunk loop next_chunk [%d] chunk_count [%d]\n",
- state->next_chunk, state->cc_copy.chunk_count);
- return NT_STATUS_INTERNAL_ERROR;
- }
+ /*
+ * chunk_count can be 0 which must either just do nothing returning
+ * success saying number of copied chunks is 0 (verified against
+ * Windows).
+ *
+ * Or it can be a special macOS copyfile request, so we send this into
+ * the VFS, vfs_fruit if loaded implements the macOS copyile semantics.
+ */
+ if (state->cc_copy.chunk_count > 0) {
+ struct srv_copychunk *chunk = NULL;
- if (state->cc_copy.chunk_count == 0) {
- /*
- * 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 ||
- !state->dst_fsp->aapl_copyfile_supported)
- {
- /*
- * This must not produce an error but just return a
- * chunk count of 0 in the response.
- */
- tevent_req_done(req);
- tevent_req_post(req, state->ev);
- return NT_STATUS_OK;
- }
- state->aapl_copyfile = true;
-
- subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
- state,
- state->ev,
- state->src_fsp,
- 0,
- state->dst_fsp,
- 0,
- 0,
- 0);
- if (subreq == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
- tevent_req_set_callback(subreq,
- fsctl_srv_copychunk_vfs_done, req);
- return NT_STATUS_OK;
+ chunk = &state->cc_copy.chunks[state->current_chunk];
+ length = chunk->length;
+ source_off = chunk->source_off;
+ target_off = chunk->target_off;
}
- chunk = &state->cc_copy.chunks[state->current_chunk];
- length = next_merged_io(state);
-
subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
state,
state->ev,
- state->src_fsp,
- chunk->source_off,
+ state->ctl_code,
+ &state->token,
+ source_off,
state->dst_fsp,
- chunk->target_off,
- length,
- 0);
+ target_off,
+ length);
if (tevent_req_nomem(subreq, req)) {
return NT_STATUS_NO_MEMORY;
}
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("copy chunk failed [%s] chunk [%u] of [%u]\n",
nt_errstr(status),
- (unsigned int)state->next_chunk,
+ (unsigned int)state->current_chunk,
(unsigned int)state->cc_copy.chunk_count);
tevent_req_nterror(req, status);
return;
}
DBG_DEBUG("good copy chunk [%u] of [%u]\n",
- (unsigned int)state->next_chunk,
+ (unsigned int)state->current_chunk,
(unsigned int)state->cc_copy.chunk_count);
state->total_written += chunk_nwritten;
- state->current_chunk = state->next_chunk;
+ if (state->cc_copy.chunk_count == 0) {
+ /*
+ * This must not produce an error but just return a chunk count
+ * of 0 in the response.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ state->current_chunk++;
if (state->current_chunk == state->cc_copy.chunk_count) {
tevent_req_done(req);
return;
*pack_rsp = true;
break;
case COPYCHUNK_OUT_RSP:
- if (state->aapl_copyfile == true) {
- cc_rsp->chunks_written = 0;
- } else {
- cc_rsp->chunks_written = state->current_chunk;
- }
+ cc_rsp->chunks_written = state->current_chunk;
cc_rsp->chunk_bytes_written = 0;
cc_rsp->total_bytes_written = state->total_written;
*pack_rsp = true;