s3/smbd: make copy chunk asynchronous
authorRalph Boehme <slow@samba.org>
Sun, 12 Mar 2017 17:13:48 +0000 (18:13 +0100)
committerJeremy Allison <jra@samba.org>
Tue, 28 Mar 2017 19:36:18 +0000 (21:36 +0200)
Just use SMB_VFS_PREAD_SEND/RECV and SMB_VFS_PWRITE_SEND/RECV in a
sensible loop.

Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
Autobuild-User(master): Jeremy Allison <jra@samba.org>
Autobuild-Date(master): Tue Mar 28 21:36:18 CEST 2017 on sn-devel-144

source3/modules/vfs_default.c

index 545e21a024bd11fe4d12fe66d4c4a4c46f086542..73af4f743482a2f618177399f0147df89a710cf1 100644 (file)
@@ -1592,10 +1592,23 @@ static NTSTATUS vfswrap_fset_dos_attributes(struct vfs_handle_struct *handle,
 }
 
 struct vfs_cc_state {
-       off_t copied;
+       struct tevent_context *ev;
        uint8_t *buf;
+       bool read_lck_locked;
+       struct lock_struct read_lck;
+       bool write_lck_locked;
+       struct lock_struct write_lck;
+       struct files_struct *src_fsp;
+       off_t src_off;
+       struct files_struct *dst_fsp;
+       off_t dst_off;
+       off_t to_copy;
+       off_t remaining;
+       size_t next_io_size;
 };
 
+static NTSTATUS copy_chunk_loop(struct tevent_req *req);
+
 static struct tevent_req *vfswrap_copy_chunk_send(struct vfs_handle_struct *handle,
                                                  TALLOC_CTX *mem_ctx,
                                                  struct tevent_context *ev,
@@ -1603,23 +1616,31 @@ static struct tevent_req *vfswrap_copy_chunk_send(struct vfs_handle_struct *hand
                                                  off_t src_off,
                                                  struct files_struct *dest_fsp,
                                                  off_t dest_off,
-                                                 off_t num)
+                                                 off_t to_copy)
 {
        struct tevent_req *req;
-       struct vfs_cc_state *vfs_cc_state;
+       struct vfs_cc_state *state = NULL;
+       size_t num = MIN(to_copy, COPYCHUNK_MAX_TOTAL_LEN);
        NTSTATUS status;
 
-       DEBUG(10, ("performing server side copy chunk of length %lu\n",
-                  (unsigned long)num));
+       DBG_DEBUG("server side copy chunk of length %" PRIu64 "\n", to_copy);
 
-       req = tevent_req_create(mem_ctx, &vfs_cc_state, struct vfs_cc_state);
+       req = tevent_req_create(mem_ctx, &state, struct vfs_cc_state);
        if (req == NULL) {
                return NULL;
        }
 
-       vfs_cc_state->buf = talloc_array(vfs_cc_state, uint8_t,
-                                        MIN(num, COPYCHUNK_MAX_TOTAL_LEN));
-       if (tevent_req_nomem(vfs_cc_state->buf, req)) {
+       *state = (struct vfs_cc_state) {
+               .ev = ev,
+               .src_fsp = src_fsp,
+               .src_off = src_off,
+               .dst_fsp = dest_fsp,
+               .dst_off = dest_off,
+               .to_copy = to_copy,
+               .remaining = to_copy,
+       };
+       state->buf = talloc_array(state, uint8_t, num);
+       if (tevent_req_nomem(state->buf, req)) {
                return tevent_req_post(req, ev);
        }
 
@@ -1652,106 +1673,178 @@ static struct tevent_req *vfswrap_copy_chunk_send(struct vfs_handle_struct *hand
                return tevent_req_post(req, ev);
        }
 
-       /* could use 2.6.33+ sendfile here to do this in kernel */
-       while (vfs_cc_state->copied < num) {
-               ssize_t ret;
-               struct lock_struct lck;
-               int saved_errno;
+       status = copy_chunk_loop(req);
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return tevent_req_post(req, ev);
+       }
 
-               off_t this_num = MIN(talloc_array_length(vfs_cc_state->buf),
-                                    num - vfs_cc_state->copied);
+       return req;
+}
 
-               init_strict_lock_struct(src_fsp,
-                                       src_fsp->op->global->open_persistent_id,
-                                       src_off,
-                                       this_num,
-                                       READ_LOCK,
-                                       &lck);
+static void vfswrap_copy_chunk_read_done(struct tevent_req *subreq);
 
-               if (!SMB_VFS_STRICT_LOCK(src_fsp->conn, src_fsp, &lck)) {
-                       tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
-                       return tevent_req_post(req, ev);
-               }
+static NTSTATUS copy_chunk_loop(struct tevent_req *req)
+{
+       struct vfs_cc_state *state = tevent_req_data(req, struct vfs_cc_state);
+       struct tevent_req *subreq = NULL;
+       bool ok;
 
-               ret = SMB_VFS_PREAD(src_fsp, vfs_cc_state->buf,
-                                   this_num, src_off);
-               if (ret == -1) {
-                       saved_errno = errno;
-               }
+       state->next_io_size = MIN(state->remaining, talloc_array_length(state->buf));
 
-               SMB_VFS_STRICT_UNLOCK(src_fsp->conn, src_fsp, &lck);
+       init_strict_lock_struct(state->src_fsp,
+                               state->src_fsp->op->global->open_persistent_id,
+                               state->src_off,
+                               state->next_io_size,
+                               READ_LOCK,
+                               &state->read_lck);
 
-               if (ret == -1) {
-                       errno = saved_errno;
-                       tevent_req_nterror(req, map_nt_error_from_unix(errno));
-                       return tevent_req_post(req, ev);
-               }
-               if (ret != this_num) {
-                       /* zero tolerance for short reads */
-                       tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
-                       return tevent_req_post(req, ev);
-               }
+       ok = SMB_VFS_STRICT_LOCK(state->src_fsp->conn,
+                                state->src_fsp,
+                                &state->read_lck);
+       if (!ok) {
+               return NT_STATUS_FILE_LOCK_CONFLICT;
+       }
 
-               src_off += ret;
+       subreq = SMB_VFS_PREAD_SEND(state,
+                                   state->src_fsp->conn->sconn->ev_ctx,
+                                   state->src_fsp,
+                                   state->buf,
+                                   state->next_io_size,
+                                   state->src_off);
+       if (subreq == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       tevent_req_set_callback(subreq, vfswrap_copy_chunk_read_done, req);
 
-               init_strict_lock_struct(dest_fsp,
-                                       dest_fsp->op->global->open_persistent_id,
-                                       dest_off,
-                                       this_num,
-                                       WRITE_LOCK,
-                                       &lck);
+       return NT_STATUS_OK;
+}
 
-               if (!SMB_VFS_STRICT_LOCK(dest_fsp->conn, dest_fsp, &lck)) {
-                       tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
-                       return tevent_req_post(req, ev);
-               }
+static void vfswrap_copy_chunk_write_done(struct tevent_req *subreq);
 
-               ret = SMB_VFS_PWRITE(dest_fsp, vfs_cc_state->buf,
-                                    this_num, dest_off);
-               if (ret == -1) {
-                       saved_errno = errno;
-               }
+static void vfswrap_copy_chunk_read_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct vfs_cc_state *state = tevent_req_data(req, struct vfs_cc_state);
+       struct vfs_aio_state aio_state;
+       ssize_t nread;
+       bool ok;
 
-               SMB_VFS_STRICT_UNLOCK(dest_fsp->conn, dest_fsp, &lck);
+       SMB_VFS_STRICT_UNLOCK(state->src_fsp->conn,
+                             state->src_fsp,
+                             &state->read_lck);
+       ZERO_STRUCT(state->read_lck);
 
-               if (ret == -1) {
-                       errno = saved_errno;
-                       tevent_req_nterror(req, map_nt_error_from_unix(errno));
-                       return tevent_req_post(req, ev);
-               }
-               if (ret != this_num) {
-                       /* zero tolerance for short writes */
-                       tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
-                       return tevent_req_post(req, ev);
-               }
-               dest_off += ret;
+       nread = SMB_VFS_PREAD_RECV(subreq, &aio_state);
+       TALLOC_FREE(subreq);
+       if (nread == -1) {
+               DBG_ERR("read failed: %s\n", strerror(errno));
+               tevent_req_nterror(req, map_nt_error_from_unix(aio_state.error));
+               return;
+       }
+       if (nread != state->next_io_size) {
+               DBG_ERR("Short read, only %zd of %zu\n",
+                       nread, state->next_io_size);
+               tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+               return;
+       }
+
+       state->src_off += nread;
 
-               vfs_cc_state->copied += this_num;
+       init_strict_lock_struct(state->dst_fsp,
+                               state->dst_fsp->op->global->open_persistent_id,
+                               state->dst_off,
+                               state->next_io_size,
+                               WRITE_LOCK,
+                               &state->write_lck);
+
+       ok = SMB_VFS_STRICT_LOCK(state->dst_fsp->conn,
+                                state->dst_fsp,
+                                &state->write_lck);
+       if (!ok) {
+               tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+               return;
        }
 
-       tevent_req_done(req);
-       return tevent_req_post(req, ev);
+       subreq = SMB_VFS_PWRITE_SEND(state,
+                                    state->ev,
+                                    state->dst_fsp,
+                                    state->buf,
+                                    state->next_io_size,
+                                    state->dst_off);
+       if (subreq == NULL) {
+               tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+               return;
+       }
+       tevent_req_set_callback(subreq, vfswrap_copy_chunk_write_done, req);
+}
+
+static void vfswrap_copy_chunk_write_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct vfs_cc_state *state = tevent_req_data(req, struct vfs_cc_state);
+       struct vfs_aio_state aio_state;
+       ssize_t nwritten;
+       NTSTATUS status;
+
+       SMB_VFS_STRICT_UNLOCK(state->dst_fsp->conn,
+                             state->dst_fsp,
+                             &state->write_lck);
+       ZERO_STRUCT(state->write_lck);
+
+       nwritten = SMB_VFS_PWRITE_RECV(subreq, &aio_state);
+       TALLOC_FREE(subreq);
+       if (nwritten == -1) {
+               DBG_ERR("write failed: %s\n", strerror(errno));
+               tevent_req_nterror(req, map_nt_error_from_unix(aio_state.error));
+               return;
+       }
+       if (nwritten != state->next_io_size) {
+               DBG_ERR("Short write, only %zd of %zu\n", nwritten, state->next_io_size);
+               tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+               return;
+       }
+
+       state->dst_off += nwritten;
+
+       if (state->remaining < nwritten) {
+               /* Paranoia check */
+               tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+               return;
+       }
+       state->remaining -= nwritten;
+       if (state->remaining == 0) {
+               tevent_req_done(req);
+               return;
+       }
+
+       status = copy_chunk_loop(req);
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return;
+       }
+
+       return;
 }
 
 static NTSTATUS vfswrap_copy_chunk_recv(struct vfs_handle_struct *handle,
                                        struct tevent_req *req,
                                        off_t *copied)
 {
-       struct vfs_cc_state *vfs_cc_state = tevent_req_data(req,
-                                                       struct vfs_cc_state);
+       struct vfs_cc_state *state = tevent_req_data(req, struct vfs_cc_state);
        NTSTATUS status;
 
        if (tevent_req_is_nterror(req, &status)) {
-               DEBUG(2, ("server side copy chunk failed: %s\n",
-                         nt_errstr(status)));
+               DBG_DEBUG("copy chunk failed: %s\n", nt_errstr(status));
                *copied = 0;
                tevent_req_received(req);
                return status;
        }
 
-       *copied = vfs_cc_state->copied;
-       DEBUG(10, ("server side copy chunk copied %lu\n",
-                  (unsigned long)*copied));
+       *copied = state->to_copy;
+       DBG_DEBUG("copy chunk copied %lu\n", (unsigned long)*copied);
        tevent_req_received(req);
 
        return NT_STATUS_OK;