From: Ralph Boehme Date: Sun, 24 Mar 2024 16:40:50 +0000 (+0100) Subject: vfs_default: implement FSCTL_DUP_EXTENTS_TO_FILE with copy_reflink() X-Git-Tag: tdb-1.4.13~1378 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=909edd6e8a5222c996553a3565b6d4b20da77e71;p=thirdparty%2Fsamba.git vfs_default: implement FSCTL_DUP_EXTENTS_TO_FILE with copy_reflink() 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 Reviewed-by: David Disseldorp --- diff --git a/source3/modules/vfs_btrfs.c b/source3/modules/vfs_btrfs.c index 90312524287..1c3bcbea971 100644 --- a/source3/modules/vfs_btrfs.c +++ b/source3/modules/vfs_btrfs.c @@ -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, diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c index 752906ba29a..eac64f28326 100644 --- a/source3/modules/vfs_default.c +++ b/source3/modules/vfs_default.c @@ -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.