smb2_ioctl: add support for FSCTL_SRV_COPYCHUNK
authorDavid Disseldorp <ddiss@samba.org>
Tue, 15 Jan 2013 16:23:00 +0000 (17:23 +0100)
committerJeremy Allison <jra@samba.org>
Wed, 16 Jan 2013 22:15:07 +0000 (23:15 +0100)
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>

source3/smbd/smb2_ioctl_network_fs.c

index 22eaf57a4b06bdf4c68e16d7664c3867b806761c..995874dbe5ed2170783e378de50c36e93acc8310 100644 (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
 #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);
+       }
+}