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,
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,
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);
}
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(
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;
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);
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)) {
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);
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);
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 &&
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;
}
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.