vfs_default: use copy_file_range()
authorRalph Boehme <slow@samba.org>
Thu, 24 Jun 2021 14:21:42 +0000 (16:21 +0200)
committerJule Anger <janger@samba.org>
Wed, 11 Aug 2021 09:54:14 +0000 (09:54 +0000)
Original file on an XFS filesystem:

  $ ls -l /mnt/test/1048578-file
  -rw-rw-r--. 1 slow slow 1048578 Jun 25 11:40 /mnt/test/1048578-file

  $ xfs_bmap /mnt/test/1048578-file
  /mnt/test/1048578-file:
          0: [0..2055]: 192..2247

Copy created with cp --reflink=never:

  $ xfs_bmap /mnt/test/1048578-file-reflink-never
  /mnt/test/1048578-file-reflink-never:
          0: [0..2055]: 2248..4303

Copy created with cp --reflink=always

  $ xfs_bmap /mnt/test/1048578-file-reflink-always
  /mnt/test/1048578-file-reflink-always:
          0: [0..2055]: 192..2247

Copy done from a Windows client:

  $ xfs_bmap /mnt/test/1048578-file\ -\ Copy
  /mnt/test/1048578-file - Copy:
          0: [0..2055]: 192..2247

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12033
RN: smbd should support copy_file_range() for FSCTL_SRV_COPYCHUNK

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): Wed Jun 30 17:40:23 UTC 2021 on sn-devel-184

(cherry picked from commit accaa2f1f67a7f064a4ce03a120d7b2f8e847ccf)

source3/modules/vfs_default.c

index 4423483156c0b372765189b41e35965f98aea07c..65845d51b994a84a9d086b9b14cb39ca0a71c7d4 100644 (file)
@@ -1988,6 +1988,7 @@ static void vfswrap_offload_write_cleanup(struct tevent_req *req,
        state->dst_fsp = NULL;
 }
 
+static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req);
 static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req);
 
 static struct tevent_req *vfswrap_offload_write_send(
@@ -2127,6 +2128,16 @@ static struct tevent_req *vfswrap_offload_write_send(
                return tevent_req_post(req, ev);
        }
 
+       status = vfswrap_offload_copy_file_range(req);
+       if (NT_STATUS_IS_OK(status)) {
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+               tevent_req_nterror(req, status);
+               return tevent_req_post(req, ev);
+       }
+
        state->buf = talloc_array(state, uint8_t, num);
        if (tevent_req_nomem(state->buf, req)) {
                return tevent_req_post(req, ev);
@@ -2141,6 +2152,129 @@ static struct tevent_req *vfswrap_offload_write_send(
        return req;
 }
 
+static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req)
+{
+       struct vfswrap_offload_write_state *state = tevent_req_data(
+               req, struct vfswrap_offload_write_state);
+       struct lock_struct lck;
+       ssize_t nwritten;
+       NTSTATUS status;
+       bool same_file;
+       bool ok;
+
+#ifndef USE_COPY_FILE_RANGE
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+#endif
+
+       same_file = file_id_equal(&state->src_fsp->file_id,
+                                 &state->dst_fsp->file_id);
+       if (same_file &&
+           sys_io_ranges_overlap(state->remaining,
+                                 state->src_off,
+                                 state->remaining,
+                                 state->dst_off))
+       {
+               return NT_STATUS_MORE_PROCESSING_REQUIRED;
+       }
+
+       if (is_named_stream(state->src_fsp->fsp_name) ||
+           is_named_stream(state->dst_fsp->fsp_name))
+       {
+               return NT_STATUS_MORE_PROCESSING_REQUIRED;
+       }
+
+       init_strict_lock_struct(state->src_fsp,
+                               state->src_fsp->op->global->open_persistent_id,
+                               state->src_off,
+                               state->remaining,
+                               READ_LOCK,
+                               &lck);
+
+       ok = SMB_VFS_STRICT_LOCK_CHECK(state->src_fsp->conn,
+                                state->src_fsp,
+                                &lck);
+       if (!ok) {
+               return NT_STATUS_FILE_LOCK_CONFLICT;
+       }
+
+       ok = change_to_user_and_service_by_fsp(state->dst_fsp);
+       if (!ok) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       init_strict_lock_struct(state->dst_fsp,
+                               state->dst_fsp->op->global->open_persistent_id,
+                               state->dst_off,
+                               state->remaining,
+                               WRITE_LOCK,
+                               &lck);
+
+       ok = SMB_VFS_STRICT_LOCK_CHECK(state->dst_fsp->conn,
+                                      state->dst_fsp,
+                                      &lck);
+       if (!ok) {
+               return NT_STATUS_FILE_LOCK_CONFLICT;
+       }
+
+       while (state->remaining > 0) {
+               nwritten = copy_file_range(fsp_get_pathref_fd(state->src_fsp),
+                                          &state->src_off,
+                                          fsp_get_pathref_fd(state->dst_fsp),
+                                          &state->dst_off,
+                                          state->remaining,
+                                          0);
+               if (nwritten == -1) {
+                       DBG_DEBUG("copy_file_range src [%s]:[%jd] dst [%s]:[%jd] "
+                                 "n [%jd] failed: %s\n",
+                                 fsp_str_dbg(state->src_fsp),
+                                 (intmax_t)state->src_off,
+                                 fsp_str_dbg(state->dst_fsp),
+                                 (intmax_t)state->dst_off,
+                                 (intmax_t)state->remaining,
+                                 strerror(errno));
+                       switch (errno) {
+                       case EXDEV:
+                               status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+                               break;
+                       default:
+                               status = map_nt_error_from_unix(errno);
+                               if (NT_STATUS_EQUAL(
+                                           status,
+                                           NT_STATUS_MORE_PROCESSING_REQUIRED))
+                               {
+                                       /* Avoid triggering the fallback */
+                                       status = NT_STATUS_INTERNAL_ERROR;
+                               }
+                               break;
+                       }
+                       return status;
+               }
+
+               if (state->remaining < nwritten) {
+                       DBG_DEBUG("copy_file_range src [%s] dst [%s] "
+                                 "n [%jd] remaining [%jd]\n",
+                                 fsp_str_dbg(state->src_fsp),
+                                 fsp_str_dbg(state->dst_fsp),
+                                 (intmax_t)nwritten,
+                                 (intmax_t)state->remaining);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+
+               if (nwritten == 0) {
+                       break;
+               }
+               state->copied += nwritten;
+               state->remaining -= nwritten;
+       }
+
+       /*
+        * Tell the req cleanup function there's no need to call
+        * change_to_user_and_service_by_fsp() on the dst handle.
+        */
+       state->dst_fsp = NULL;
+       return NT_STATUS_OK;
+}
+
 static void vfswrap_offload_write_read_done(struct tevent_req *subreq);
 
 static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req)