1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-26 10:04:02 +03:00

smb2_ioctl: add support for FSCTL_SRV_COPYCHUNK

SMB2 clients can issue FSCTL_SRV_COPYCHUNK requests in order to copy
data between files on the server side only, rather than reading data
then writing back from the client. FSCTL_SRV_COPYCHUNK is used by
default for Explorer SMB2 file copies on Windows Server 2012.

2.2.32.1 SRV_COPYCHUNK_RESPONSE in [MS-SMB2] describes the requirement
for the server to provide maximum copychunk request size limits in ioctl
responses carrying STATUS_INVALID_PARAMETER.

Reviewed by: Jeremy Allison <jra@samba.org>
This commit is contained in:
David Disseldorp 2013-01-15 17:23:00 +01:00 committed by Jeremy Allison
parent ef00eb90e5
commit e38d9f71d9

View File

@ -3,6 +3,7 @@
Core SMB2 server
Copyright (C) Stefan Metzmacher 2009
Copyright (C) David Disseldorp 2012
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -29,6 +30,191 @@
#include "librpc/gen_ndr/ndr_ioctl.h"
#include "smb2_ioctl_private.h"
#define COPYCHUNK_MAX_CHUNKS 256 /* 2k8r2 & win8 = 256 */
#define COPYCHUNK_MAX_CHUNK_LEN 1048576 /* 2k8r2 & win8 = 1048576 */
#define COPYCHUNK_MAX_TOTAL_LEN 16777216 /* 2k8r2 & win8 = 16777216 */
static void copychunk_pack_limits(struct srv_copychunk_rsp *cc_rsp)
{
cc_rsp->chunks_written = COPYCHUNK_MAX_CHUNKS;
cc_rsp->chunk_bytes_written = COPYCHUNK_MAX_CHUNK_LEN;
cc_rsp->total_bytes_written = COPYCHUNK_MAX_TOTAL_LEN;
}
static NTSTATUS copychunk_check_limits(struct srv_copychunk_copy *cc_copy)
{
uint32_t i;
uint32_t total_len = 0;
if (cc_copy->chunk_count > COPYCHUNK_MAX_CHUNKS) {
return NT_STATUS_INVALID_PARAMETER;
}
for (i = 0; i < cc_copy->chunk_count; i++) {
if (cc_copy->chunks[i].length > COPYCHUNK_MAX_CHUNK_LEN) {
return NT_STATUS_INVALID_PARAMETER;
}
total_len += cc_copy->chunks[i].length;
}
if (total_len > COPYCHUNK_MAX_TOTAL_LEN) {
return NT_STATUS_INVALID_PARAMETER;
}
return NT_STATUS_OK;
}
struct fsctl_srv_copychunk_state {
struct connection_struct *conn;
uint32_t dispatch_count;
uint32_t recv_count;
uint32_t bad_recv_count;
NTSTATUS status;
off_t total_written;
};
static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq);
static struct tevent_req *fsctl_srv_copychunk_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct files_struct *dst_fsp,
DATA_BLOB *in_input,
struct smbd_server_connection *sconn)
{
struct tevent_req *req;
struct srv_copychunk_copy cc_copy;
enum ndr_err_code ndr_ret;
struct file_id src_file_id;
struct files_struct *src_fsp;
int i;
struct srv_copychunk *chunk;
struct fsctl_srv_copychunk_state *state;
req = tevent_req_create(mem_ctx, &state,
struct fsctl_srv_copychunk_state);
if (req == NULL) {
return NULL;
}
state->conn = dst_fsp->conn;
ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &cc_copy,
(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_copy);
if (ndr_ret != NDR_ERR_SUCCESS) {
DEBUG(0, ("failed to unmarshall copy chunk req\n"));
state->status = NT_STATUS_INVALID_PARAMETER;
tevent_req_nterror(req, state->status);
return tevent_req_post(req, ev);
}
/* file id is sent as a copychunk resume key */
ZERO_STRUCT(src_file_id);
BUILD_ASSERT(ARRAY_SIZE(cc_copy.source_key) == sizeof(src_file_id));
memcpy(&src_file_id, cc_copy.source_key, ARRAY_SIZE(cc_copy.source_key));
src_fsp = file_find_di_first(sconn, src_file_id);
if (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->status = copychunk_check_limits(&cc_copy);
if (tevent_req_nterror(req, state->status)) {
DEBUG(3, ("copy chunk req exceeds limits\n"));
return tevent_req_post(req, ev);
}
for (i = 0; i < cc_copy.chunk_count; i++) {
struct tevent_req *vfs_subreq;
chunk = &cc_copy.chunks[i];
vfs_subreq = SMB_VFS_COPY_CHUNK_SEND(dst_fsp->conn,
state, ev,
src_fsp, chunk->source_off,
dst_fsp, chunk->target_off,
chunk->length);
if (vfs_subreq == NULL) {
DEBUG(0, ("VFS copy chunk send failed\n"));
state->status = NT_STATUS_NO_MEMORY;
if (state->dispatch_count == 0) {
/* nothing dispatched, return immediately */
tevent_req_nterror(req, state->status);
return tevent_req_post(req, ev);
} else {
/*
* wait for dispatched to complete before
* returning error
*/
break;
}
}
tevent_req_set_callback(vfs_subreq,
fsctl_srv_copychunk_vfs_done, req);
state->dispatch_count++;
}
return req;
}
static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct fsctl_srv_copychunk_state *state = tevent_req_data(req,
struct fsctl_srv_copychunk_state);
off_t chunk_nwritten;
NTSTATUS status;
state->recv_count++;
status = SMB_VFS_COPY_CHUNK_RECV(state->conn, subreq,
&chunk_nwritten);
if (NT_STATUS_IS_OK(status)) {
DEBUG(10, ("good copy chunk recv %d of %d\n",
state->recv_count,
state->dispatch_count));
state->total_written += chunk_nwritten;
} else {
DEBUG(0, ("bad status in copy chunk recv %d of %d: %s\n",
state->recv_count,
state->dispatch_count,
nt_errstr(status)));
state->bad_recv_count++;
/* may overwrite previous failed status */
state->status = status;
}
if (state->recv_count != state->dispatch_count) {
/*
* Wait for all VFS copy_chunk requests to complete, even
* if an error is received for a specific chunk.
*/
return;
}
/* all VFS copy_chunk requests done */
if (!tevent_req_nterror(req, state->status)) {
tevent_req_done(req);
}
}
static NTSTATUS fsctl_srv_copychunk_recv(struct tevent_req *req,
struct srv_copychunk_rsp *cc_rsp)
{
struct fsctl_srv_copychunk_state *state = tevent_req_data(req,
struct fsctl_srv_copychunk_state);
NTSTATUS status;
if (NT_STATUS_EQUAL(state->status, NT_STATUS_INVALID_PARAMETER)) {
/* 2.2.32.1 - send back our maximum transfer size limits */
copychunk_pack_limits(cc_rsp);
tevent_req_received(req);
return NT_STATUS_INVALID_PARAMETER;
}
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;
status = state->status;
tevent_req_received(req);
return status;
}
static NTSTATUS fsctl_validate_neg_info(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct smbXsrv_connection *conn,
@ -151,14 +337,29 @@ static NTSTATUS fsctl_srv_req_resume_key(TALLOC_CTX *mem_ctx,
return NT_STATUS_OK;
}
NTSTATUS smb2_ioctl_network_fs(uint32_t ctl_code,
struct tevent_context *ev,
struct tevent_req *req,
struct smbd_smb2_ioctl_state *state)
static void smb2_ioctl_network_fs_copychunk_done(struct tevent_req *subreq);
struct tevent_req *smb2_ioctl_network_fs(uint32_t ctl_code,
struct tevent_context *ev,
struct tevent_req *req,
struct smbd_smb2_ioctl_state *state)
{
struct tevent_req *subreq;
NTSTATUS status;
switch (ctl_code) {
case FSCTL_SRV_COPYCHUNK:
subreq = fsctl_srv_copychunk_send(state, ev, state->fsp,
&state->in_input,
state->smb2req->sconn);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
smb2_ioctl_network_fs_copychunk_done,
req);
return req;
break;
case FSCTL_VALIDATE_NEGOTIATE_INFO:
status = fsctl_validate_neg_info(state, ev,
state->smbreq->sconn->conn,
@ -216,3 +417,29 @@ NTSTATUS smb2_ioctl_network_fs(uint32_t ctl_code,
tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
return tevent_req_post(req, ev);
}
static void smb2_ioctl_network_fs_copychunk_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct smbd_smb2_ioctl_state *ioctl_state = tevent_req_data(req,
struct smbd_smb2_ioctl_state);
struct srv_copychunk_rsp cc_rsp;
NTSTATUS status;
enum ndr_err_code ndr_ret;
ZERO_STRUCT(cc_rsp);
status = fsctl_srv_copychunk_recv(subreq, &cc_rsp);
ndr_ret = ndr_push_struct_blob(&ioctl_state->out_output,
ioctl_state,
&cc_rsp,
(ndr_push_flags_fn_t)ndr_push_srv_copychunk_rsp);
if (ndr_ret != NDR_ERR_SUCCESS) {
status = NT_STATUS_INTERNAL_ERROR;
}
if (!tevent_req_nterror(req, status)) {
tevent_req_done(req);
}
}