]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
vfs_default: implement FSCTL_DUP_EXTENTS_TO_FILE with copy_reflink()
authorRalph Boehme <slow@samba.org>
Sun, 24 Mar 2024 16:40:50 +0000 (17:40 +0100)
committerRalph Boehme <slow@samba.org>
Tue, 20 Aug 2024 05:41:32 +0000 (05:41 +0000)
According to MS-FSA 2.1.5.9.4 FSCTL_DUPLICATE_EXTENTS_TO_FILE ReFS on Windows
does not check for byte range lock conflicts:

 * The object store SHOULD<81> check for byte range lock conflicts ...

 * <81>: The ReFS file system in Windows Server 2016 and later does not check
 for byte range lock conflicts

To match Windows behaviour we also don't check for conflicts.

Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: David Disseldorp <ddiss@samba.org>
source3/modules/vfs_btrfs.c
source3/modules/vfs_default.c

index 903125242876febad9c86042210ae5683402f336..1c3bcbea971c11b0c68752f0fba7449d054f728e 100644 (file)
@@ -66,401 +66,12 @@ struct btrfs_ioctl_vol_args {
        char name[BTRFS_PATH_NAME_MAX + 1];
 };
 
-struct btrfs_ioctl_clone_range_args {
-       int64_t src_fd;
-       uint64_t src_offset;
-       uint64_t src_length;
-       uint64_t dest_offset;
-};
-
 #define BTRFS_IOCTL_MAGIC 0x94
-#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
-                                  struct btrfs_ioctl_clone_range_args)
 #define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \
                                    struct btrfs_ioctl_vol_args)
 #define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \
                                      struct btrfs_ioctl_vol_args_v2)
 
-static struct vfs_offload_ctx *btrfs_offload_ctx;
-
-struct btrfs_offload_read_state {
-       struct vfs_handle_struct *handle;
-       files_struct *fsp;
-       uint32_t flags;
-       uint64_t xferlen;
-       DATA_BLOB token;
-};
-
-static void btrfs_offload_read_done(struct tevent_req *subreq);
-
-static struct tevent_req *btrfs_offload_read_send(
-       TALLOC_CTX *mem_ctx,
-       struct tevent_context *ev,
-       struct vfs_handle_struct *handle,
-       files_struct *fsp,
-       uint32_t fsctl,
-       uint32_t ttl,
-       off_t offset,
-       size_t to_copy)
-{
-       struct tevent_req *req = NULL;
-       struct tevent_req *subreq = NULL;
-       struct btrfs_offload_read_state *state = NULL;
-       NTSTATUS status;
-
-       req = tevent_req_create(mem_ctx, &state,
-                               struct btrfs_offload_read_state);
-       if (req == NULL) {
-               return NULL;
-       }
-       *state = (struct btrfs_offload_read_state) {
-               .handle = handle,
-               .fsp = fsp,
-       };
-
-       status = vfs_offload_token_ctx_init(fsp->conn->sconn->client,
-                                           &btrfs_offload_ctx);
-       if (tevent_req_nterror(req, status)) {
-               return tevent_req_post(req, ev);
-       }
-
-       if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE) {
-               status = vfs_offload_token_create_blob(state, fsp, fsctl,
-                                                      &state->token);
-               if (tevent_req_nterror(req, status)) {
-                       return tevent_req_post(req, ev);
-               }
-
-               status = vfs_offload_token_db_store_fsp(btrfs_offload_ctx, fsp,
-                                                       &state->token);
-               if (tevent_req_nterror(req, status)) {
-                       return tevent_req_post(req, ev);
-               }
-               tevent_req_done(req);
-               return tevent_req_post(req, ev);
-       }
-
-       subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp,
-                                               fsctl, ttl, offset, to_copy);
-       if (tevent_req_nomem(subreq, req)) {
-               return tevent_req_post(req, ev);
-       }
-       tevent_req_set_callback(subreq, btrfs_offload_read_done, req);
-       return req;
-}
-
-static void btrfs_offload_read_done(struct tevent_req *subreq)
-{
-       struct tevent_req *req = tevent_req_callback_data(
-               subreq, struct tevent_req);
-       struct btrfs_offload_read_state *state = tevent_req_data(
-               req, struct btrfs_offload_read_state);
-       NTSTATUS status;
-
-       status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq,
-                                               state->handle,
-                                               state,
-                                               &state->flags,
-                                               &state->xferlen,
-                                               &state->token);
-       TALLOC_FREE(subreq);
-       if (tevent_req_nterror(req, status)) {
-               return;
-       }
-
-       status = vfs_offload_token_db_store_fsp(btrfs_offload_ctx,
-                                               state->fsp,
-                                               &state->token);
-       if (tevent_req_nterror(req, status)) {
-               return;
-       }
-
-       tevent_req_done(req);
-       return;
-}
-
-static NTSTATUS btrfs_offload_read_recv(struct tevent_req *req,
-                                       struct vfs_handle_struct *handle,
-                                       TALLOC_CTX *mem_ctx,
-                                       uint32_t *flags,
-                                       uint64_t *xferlen,
-                                       DATA_BLOB *token)
-{
-       struct btrfs_offload_read_state *state = tevent_req_data(
-               req, struct btrfs_offload_read_state);
-       NTSTATUS status;
-
-       if (tevent_req_is_nterror(req, &status)) {
-               tevent_req_received(req);
-               return status;
-       }
-
-       *flags = state->flags;
-       *xferlen = state->xferlen;
-       token->length = state->token.length;
-       token->data = talloc_move(mem_ctx, &state->token.data);
-
-       tevent_req_received(req);
-       return NT_STATUS_OK;
-}
-
-struct btrfs_offload_write_state {
-       struct vfs_handle_struct *handle;
-       off_t copied;
-       bool need_unbecome_user;
-};
-
-static void btrfs_offload_write_cleanup(struct tevent_req *req,
-                                       enum tevent_req_state req_state)
-{
-       struct btrfs_offload_write_state *state =
-               tevent_req_data(req,
-               struct btrfs_offload_write_state);
-       bool ok;
-
-       if (!state->need_unbecome_user) {
-               return;
-       }
-
-       ok = unbecome_user_without_service();
-       SMB_ASSERT(ok);
-       state->need_unbecome_user = false;
-}
-
-static void btrfs_offload_write_done(struct tevent_req *subreq);
-
-static struct tevent_req *btrfs_offload_write_send(struct vfs_handle_struct *handle,
-                                               TALLOC_CTX *mem_ctx,
-                                               struct tevent_context *ev,
-                                               uint32_t fsctl,
-                                               DATA_BLOB *token,
-                                               off_t transfer_offset,
-                                               struct files_struct *dest_fsp,
-                                               off_t dest_off,
-                                               off_t num)
-{
-       struct tevent_req *req = NULL;
-       struct btrfs_offload_write_state *state = NULL;
-       struct tevent_req *subreq = NULL;
-       struct btrfs_ioctl_clone_range_args cr_args;
-       struct lock_struct src_lck;
-       struct lock_struct dest_lck;
-       off_t src_off = transfer_offset;
-       files_struct *src_fsp = NULL;
-       int ret;
-       bool handle_offload_write = true;
-       bool do_locking = false;
-       NTSTATUS status;
-       bool ok;
-
-       req = tevent_req_create(mem_ctx, &state,
-                               struct btrfs_offload_write_state);
-       if (req == NULL) {
-               return NULL;
-       }
-
-       state->handle = handle;
-
-       tevent_req_set_cleanup_fn(req, btrfs_offload_write_cleanup);
-
-       status = vfs_offload_token_db_fetch_fsp(btrfs_offload_ctx,
-                                               token, &src_fsp);
-       if (tevent_req_nterror(req, status)) {
-               return tevent_req_post(req, ev);
-       }
-
-       switch (fsctl) {
-       case FSCTL_SRV_COPYCHUNK:
-       case FSCTL_SRV_COPYCHUNK_WRITE:
-               do_locking = true;
-               break;
-
-       case FSCTL_DUP_EXTENTS_TO_FILE:
-               /* dup extents does not use locking */
-               break;
-
-       default:
-               handle_offload_write = false;
-               break;
-       }
-
-       if (num == 0) {
-               /*
-                * With a @src_length of zero, BTRFS_IOC_CLONE_RANGE clones
-                * all data from @src_offset->EOF! This is certainly not what
-                * the caller expects, and not what vfs_default does.
-                */
-               handle_offload_write = false;
-       }
-
-       if (!handle_offload_write) {
-               subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle,
-                                                        state,
-                                                        ev,
-                                                        fsctl,
-                                                        token,
-                                                        transfer_offset,
-                                                        dest_fsp,
-                                                        dest_off,
-                                                        num);
-               if (tevent_req_nomem(subreq, req)) {
-                       return tevent_req_post(req, ev);
-               }
-               tevent_req_set_callback(subreq,
-                                       btrfs_offload_write_done,
-                                       req);
-               return req;
-       }
-
-       status = vfs_offload_token_check_handles(
-               fsctl, src_fsp, dest_fsp);
-       if (!NT_STATUS_IS_OK(status)) {
-               tevent_req_nterror(req, status);
-               return tevent_req_post(req, ev);
-       }
-
-       ok = become_user_without_service_by_fsp(src_fsp);
-       if (!ok) {
-               tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
-               return tevent_req_post(req, ev);
-       }
-       state->need_unbecome_user = true;
-
-       status = vfs_stat_fsp(src_fsp);
-       if (tevent_req_nterror(req, status)) {
-               return tevent_req_post(req, ev);
-       }
-
-       if (src_fsp->fsp_name->st.st_ex_size < src_off + num) {
-               /* [MS-SMB2] Handling a Server-Side Data Copy Request */
-               tevent_req_nterror(req, NT_STATUS_INVALID_VIEW_SIZE);
-               return tevent_req_post(req, ev);
-       }
-
-       if (do_locking) {
-               init_strict_lock_struct(src_fsp,
-                                       src_fsp->op->global->open_persistent_id,
-                                       src_off,
-                                       num,
-                                       READ_LOCK,
-                                       lp_posix_cifsu_locktype(src_fsp),
-                                       &src_lck);
-               if (!SMB_VFS_STRICT_LOCK_CHECK(src_fsp->conn, src_fsp, &src_lck)) {
-                       tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
-                       return tevent_req_post(req, ev);
-               }
-       }
-
-       ok = unbecome_user_without_service();
-       SMB_ASSERT(ok);
-       state->need_unbecome_user = false;
-
-       if (do_locking) {
-               init_strict_lock_struct(dest_fsp,
-                                       dest_fsp->op->global->open_persistent_id,
-                                       dest_off,
-                                       num,
-                                       WRITE_LOCK,
-                                       lp_posix_cifsu_locktype(dest_fsp),
-                                       &dest_lck);
-
-               if (!SMB_VFS_STRICT_LOCK_CHECK(dest_fsp->conn, dest_fsp, &dest_lck)) {
-                       tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
-                       return tevent_req_post(req, ev);
-               }
-       }
-
-       ZERO_STRUCT(cr_args);
-       cr_args.src_fd = fsp_get_io_fd(src_fsp);
-       cr_args.src_offset = (uint64_t)src_off;
-       cr_args.dest_offset = (uint64_t)dest_off;
-       cr_args.src_length = (uint64_t)num;
-
-       ret = ioctl(fsp_get_io_fd(dest_fsp), BTRFS_IOC_CLONE_RANGE, &cr_args);
-       if (ret < 0) {
-               /*
-                * BTRFS_IOC_CLONE_RANGE only supports 'sectorsize' aligned
-                * cloning. Which is 4096 by default, therefore fall back to
-                * manual read/write on failure.
-                */
-               DEBUG(5, ("BTRFS_IOC_CLONE_RANGE failed: %s, length %llu, "
-                         "src fd: %lld off: %llu, dest fd: %d off: %llu\n",
-                         strerror(errno),
-                         (unsigned long long)cr_args.src_length,
-                         (long long)cr_args.src_fd,
-                         (unsigned long long)cr_args.src_offset,
-                         fsp_get_io_fd(dest_fsp),
-                         (unsigned long long)cr_args.dest_offset));
-               subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle,
-                                                        state,
-                                                        ev,
-                                                        fsctl,
-                                                        token,
-                                                        transfer_offset,
-                                                        dest_fsp,
-                                                        dest_off,
-                                                        num);
-               if (tevent_req_nomem(subreq, req)) {
-                       return tevent_req_post(req, ev);
-               }
-               /* wait for subreq completion */
-               tevent_req_set_callback(subreq,
-                                       btrfs_offload_write_done,
-                                       req);
-               return req;
-       }
-
-       DEBUG(5, ("BTRFS_IOC_CLONE_RANGE returned %d\n", ret));
-       /* BTRFS_IOC_CLONE_RANGE is all or nothing */
-       state->copied = num;
-       tevent_req_done(req);
-       return tevent_req_post(req, ev);
-}
-
-/* only used if the request is passed through to next VFS module */
-static void btrfs_offload_write_done(struct tevent_req *subreq)
-{
-       struct tevent_req *req =
-               tevent_req_callback_data(subreq,
-               struct tevent_req);
-       struct btrfs_offload_write_state *state =
-               tevent_req_data(req,
-               struct btrfs_offload_write_state);
-       NTSTATUS status;
-
-       status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle,
-                                                subreq,
-                                                &state->copied);
-       TALLOC_FREE(subreq);
-       if (tevent_req_nterror(req, status)) {
-               return;
-       }
-       tevent_req_done(req);
-}
-
-static NTSTATUS btrfs_offload_write_recv(struct vfs_handle_struct *handle,
-                                        struct tevent_req *req,
-                                        off_t *copied)
-{
-       struct btrfs_offload_write_state *state =
-               tevent_req_data(req,
-               struct btrfs_offload_write_state);
-       NTSTATUS status;
-
-       if (tevent_req_is_nterror(req, &status)) {
-               DEBUG(4, ("server side copy chunk failed: %s\n",
-                         nt_errstr(status)));
-               tevent_req_received(req);
-               return status;
-       }
-
-       DEBUG(10, ("server side copy chunk copied %llu\n",
-                  (unsigned long long)state->copied));
-       *copied = state->copied;
-       tevent_req_received(req);
-       return NT_STATUS_OK;
-}
-
 static NTSTATUS btrfs_fget_compression(struct vfs_handle_struct *handle,
                                       TALLOC_CTX *mem_ctx,
                                       struct files_struct *fsp,
@@ -868,10 +479,6 @@ static NTSTATUS btrfs_snap_delete(struct vfs_handle_struct *handle,
 
 static struct vfs_fn_pointers btrfs_fns = {
        .fs_capabilities_fn = btrfs_fs_capabilities,
-       .offload_read_send_fn = btrfs_offload_read_send,
-       .offload_read_recv_fn = btrfs_offload_read_recv,
-       .offload_write_send_fn = btrfs_offload_write_send,
-       .offload_write_recv_fn = btrfs_offload_write_recv,
        .fget_compression_fn = btrfs_fget_compression,
        .set_compression_fn = btrfs_set_compression,
        .snap_check_path_fn = btrfs_snap_check_path,
index 752906ba29a95d5084027d4041b39f01ec39491c..eac64f28326e7789748d32b3ee02b01d882c230e 100644 (file)
@@ -2040,7 +2040,16 @@ static struct tevent_req *vfswrap_offload_read_send(
                return tevent_req_post(req, ev);
        }
 
-       if (fsctl != FSCTL_SRV_REQUEST_RESUME_KEY) {
+       if (fsctl != FSCTL_SRV_REQUEST_RESUME_KEY &&
+           fsctl != FSCTL_DUP_EXTENTS_TO_FILE)
+       {
+               tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+               return tevent_req_post(req, ev);
+       }
+
+       if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE &&
+           !(fsp->conn->fs_capabilities & FILE_SUPPORTS_BLOCK_REFCOUNTING))
+       {
                tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
                return tevent_req_post(req, ev);
        }
@@ -2119,7 +2128,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_fast_copy(struct tevent_req *req, int fsctl);
 static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req);
 
 static struct tevent_req *vfswrap_offload_write_send(
@@ -2137,7 +2146,7 @@ static struct tevent_req *vfswrap_offload_write_send(
        struct vfswrap_offload_write_state *state = NULL;
        /* off_t is signed! */
        off_t max_offset = INT64_MAX - to_copy;
-       size_t num = MIN(to_copy, COPYCHUNK_MAX_TOTAL_LEN);
+       off_t num = to_copy;
        files_struct *src_fsp = NULL;
        NTSTATUS status;
        bool ok;
@@ -2167,28 +2176,23 @@ static struct tevent_req *vfswrap_offload_write_send(
        tevent_req_set_cleanup_fn(req, vfswrap_offload_write_cleanup);
 
        switch (fsctl) {
+       case FSCTL_DUP_EXTENTS_TO_FILE:
+               break;
+
        case FSCTL_SRV_COPYCHUNK:
        case FSCTL_SRV_COPYCHUNK_WRITE:
+               num = MIN(to_copy, COPYCHUNK_MAX_TOTAL_LEN);
                break;
 
        case FSCTL_OFFLOAD_WRITE:
                tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
                return tevent_req_post(req, ev);
 
-       case FSCTL_DUP_EXTENTS_TO_FILE:
-               DBG_DEBUG("COW clones not supported by vfs_default\n");
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-
        default:
                tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
                return tevent_req_post(req, ev);
        }
 
-       /*
-        * From here on we assume a copy-chunk fsctl
-        */
-
        if (to_copy == 0) {
                tevent_req_done(req);
                return tevent_req_post(req, ev);
@@ -2229,7 +2233,9 @@ static struct tevent_req *vfswrap_offload_write_send(
                return tevent_req_post(req, ev);
        }
 
-       DBG_DEBUG("server side copy chunk of length %" PRIu64 "\n", to_copy);
+       DBG_DEBUG("server side copy (%s) of length %" PRIu64 "\n",
+                 fsctl == FSCTL_DUP_EXTENTS_TO_FILE ? "reflink" : "chunk",
+                 to_copy);
 
        status = vfs_offload_token_check_handles(fsctl, src_fsp, dest_fsp);
        if (!NT_STATUS_IS_OK(status)) {
@@ -2265,7 +2271,7 @@ static struct tevent_req *vfswrap_offload_write_send(
                return tevent_req_post(req, ev);
        }
 
-       status = vfswrap_offload_copy_file_range(req);
+       status = vfswrap_offload_fast_copy(req, fsctl);
        if (NT_STATUS_IS_OK(status)) {
                tevent_req_done(req);
                return tevent_req_post(req, ev);
@@ -2289,7 +2295,7 @@ static struct tevent_req *vfswrap_offload_write_send(
        return req;
 }
 
-static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req)
+static NTSTATUS vfswrap_offload_fast_copy(struct tevent_req *req, int fsctl)
 {
        struct vfswrap_offload_write_state *state = tevent_req_data(
                req, struct vfswrap_offload_write_state);
@@ -2300,10 +2306,6 @@ static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req)
        bool ok;
        static bool try_copy_file_range = true;
 
-       if (!try_copy_file_range) {
-               return NT_STATUS_MORE_PROCESSING_REQUIRED;
-       }
-
        same_file = file_id_equal(&state->src_fsp->file_id,
                                  &state->dst_fsp->file_id);
        if (same_file &&
@@ -2312,12 +2314,44 @@ static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req)
                                  state->remaining,
                                  state->dst_off))
        {
+               if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
                return NT_STATUS_MORE_PROCESSING_REQUIRED;
        }
 
        if (fsp_is_alternate_stream(state->src_fsp) ||
            fsp_is_alternate_stream(state->dst_fsp))
        {
+               if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE) {
+                       return NT_STATUS_NOT_SUPPORTED;
+               }
+               return NT_STATUS_MORE_PROCESSING_REQUIRED;
+       }
+
+       if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE) {
+               int ret;
+
+               ok = change_to_user_and_service_by_fsp(state->dst_fsp);
+               if (!ok) {
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+
+               ret = copy_reflink(fsp_get_io_fd(state->src_fsp),
+                                  state->src_off,
+                                  fsp_get_io_fd(state->dst_fsp),
+                                  state->dst_off,
+                                  state->to_copy);
+               if (ret == -1) {
+                       DBG_INFO("copy_reflink() failed: %s\n", strerror(errno));
+                       return map_nt_error_from_unix(errno);
+               }
+
+               state->copied = state->to_copy;
+               goto done;
+       }
+
+       if (!try_copy_file_range) {
                return NT_STATUS_MORE_PROCESSING_REQUIRED;
        }
 
@@ -2412,6 +2446,7 @@ static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req)
                state->remaining -= nwritten;
        }
 
+done:
        /*
         * Tell the req cleanup function there's no need to call
         * change_to_user_and_service_by_fsp() on the dst handle.