#include "librpc/gen_ndr/ndr_ioctl.h"
#include "smb2_ioctl_private.h"
+/*
+ * XXX this may reduce dup_extents->byte_count so that it's less than the
+ * target file size.
+ */
+static NTSTATUS fsctl_dup_extents_check_lengths(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp,
+ struct fsctl_dup_extents_to_file *dup_extents)
+{
+ NTSTATUS status;
+
+ if ((dup_extents->source_off + dup_extents->byte_count
+ < dup_extents->source_off)
+ || (dup_extents->target_off + dup_extents->byte_count
+ < dup_extents->target_off)) {
+ return NT_STATUS_INVALID_PARAMETER; /* wrap */
+ }
+
+ status = vfs_stat_fsp(src_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * XXX vfs_btrfs and vfs_default have size checks in the copychunk
+ * handler, as this needs to be rechecked after the src has potentially
+ * been extended by a previous chunk in the compound copychunk req.
+ */
+ if (src_fsp->fsp_name->st.st_ex_size
+ < dup_extents->source_off + dup_extents->byte_count) {
+ DEBUG(2, ("dup_extents req exceeds src size\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = vfs_stat_fsp(dst_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (dst_fsp->fsp_name->st.st_ex_size
+ < dup_extents->target_off + dup_extents->byte_count) {
+
+ if (dst_fsp->fsp_name->st.st_ex_size - dup_extents->target_off
+ > dst_fsp->fsp_name->st.st_ex_size) {
+ return NT_STATUS_INVALID_PARAMETER; /* wrap */
+ }
+
+ /*
+ * this server behaviour is pretty hairy, but we need to match
+ * Windows, so...
+ */
+ DEBUG(2, ("dup_extents req exceeds target size, capping\n"));
+ dup_extents->byte_count = dst_fsp->fsp_name->st.st_ex_size
+ - dup_extents->target_off;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_dup_extents_check_overlap(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp,
+ struct fsctl_dup_extents_to_file *dup_extents)
+{
+ uint64_t src_off_last;
+ uint64_t tgt_off_last;
+
+ if (!file_id_equal(&src_fsp->file_id, &dst_fsp->file_id)) {
+ /* src and dest refer to different files */
+ return NT_STATUS_OK;
+ }
+
+ if (dup_extents->byte_count == 0) {
+ /* no range to overlap */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * [MS-FSCC] 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply
+ * STATUS_NOT_SUPPORTED:
+ * The source and target destination ranges overlap on the same file.
+ */
+
+ src_off_last = dup_extents->source_off + dup_extents->byte_count - 1;
+ if ((dup_extents->target_off >= dup_extents->source_off)
+ && (dup_extents->target_off <= src_off_last)) {
+ /*
+ * src: |-----------|
+ * tgt: |-----------|
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+
+ tgt_off_last = dup_extents->target_off + dup_extents->byte_count - 1;
+ if ((tgt_off_last >= dup_extents->source_off)
+ && (tgt_off_last <= src_off_last)) {
+ /*
+ * src: |-----------|
+ * tgt: |-----------|
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_dup_extents_check_sparse(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp)
+{
+ /*
+ * 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply...
+ * STATUS_NOT_SUPPORTED: Target file is sparse, while source
+ * is a non-sparse file.
+ *
+ * WS2016 has the following behaviour (MS are in the process of fixing
+ * the spec):
+ * STATUS_NOT_SUPPORTED is returned if the source is sparse, while the
+ * target is non-sparse. However, if target is sparse while the source
+ * is non-sparse, then FSCTL_DUPLICATE_EXTENTS_TO_FILE completes
+ * successfully.
+ */
+ if ((src_fsp->is_sparse) && (!dst_fsp->is_sparse)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
struct fsctl_dup_extents_state {
struct tevent_context *ev;
struct connection_struct *conn;
+ struct files_struct *dst_fsp;
struct fsctl_dup_extents_to_file dup_extents;
};
+static void fsctl_dup_extents_offload_read_done(struct tevent_req *subreq);
static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq);
static struct tevent_req *fsctl_dup_extents_send(TALLOC_CTX *mem_ctx,
uint64_t src_fid_volatile = 0;
struct files_struct *src_fsp = NULL;
int ndr_ret;
+ NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct fsctl_dup_extents_state);
if (req == NULL) {
return NULL;
}
- *state = (struct fsctl_dup_extents_state) {
- .conn = dst_fsp->conn,
- .ev = ev,
- };
if (dst_fsp == NULL) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
return tevent_req_post(req, ev);
}
+ *state = (struct fsctl_dup_extents_state) {
+ .conn = dst_fsp->conn,
+ .ev = ev,
+ .dst_fsp = dst_fsp,
+ };
+
if ((dst_fsp->conn->fs_capabilities
& FILE_SUPPORTS_BLOCK_REFCOUNTING) == 0) {
DBG_INFO("FS does not advertise block refcounting support\n");
return tevent_req_post(req, ev);
}
+ status = fsctl_dup_extents_check_lengths(src_fsp, dst_fsp,
+ &state->dup_extents);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
if (state->dup_extents.byte_count == 0) {
DBG_ERR("skipping zero length dup extents\n");
tevent_req_done(req);
return tevent_req_post(req, ev);
}
- subreq = SMB_VFS_COPY_CHUNK_SEND(dst_fsp->conn, state, ev,
- src_fsp, state->dup_extents.source_off,
- dst_fsp, state->dup_extents.target_off,
- state->dup_extents.byte_count,
- VFS_COPY_CHUNK_FL_MUST_CLONE);
+ status = fsctl_dup_extents_check_overlap(src_fsp, dst_fsp,
+ &state->dup_extents);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_sparse(src_fsp, dst_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_OFFLOAD_READ_SEND(state, ev, src_fsp,
+ FSCTL_DUP_EXTENTS_TO_FILE,
+ 0, 0, 0);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
+ tevent_req_set_callback(subreq, fsctl_dup_extents_offload_read_done,
+ req);
+ return req;
+}
- tevent_req_set_callback(subreq, fsctl_dup_extents_vfs_done, req);
+static void fsctl_dup_extents_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_dup_extents_state *state = tevent_req_data(
+ req, struct fsctl_dup_extents_state);
+ DATA_BLOB token;
+ NTSTATUS status;
+
+ status = SMB_VFS_OFFLOAD_READ_RECV(subreq, state->dst_fsp->conn,
+ state, &token);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
- return subreq;
+ /* tell the VFS to ignore locks across the clone, matching ReFS */
+ subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
+ state,
+ state->ev,
+ FSCTL_DUP_EXTENTS_TO_FILE,
+ &token,
+ state->dup_extents.source_off,
+ state->dst_fsp,
+ state->dup_extents.target_off,
+ state->dup_extents.byte_count);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fsctl_dup_extents_vfs_done, req);
+ return;
}
static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq)
off_t nb_chunk;
NTSTATUS status;
- status = SMB_VFS_COPY_CHUNK_RECV(state->conn, subreq, &nb_chunk);
+ status = SMB_VFS_OFFLOAD_WRITE_RECV(state->conn, subreq, &nb_chunk);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
status = map_nt_error_from_unix_common(errno);
DEBUG(2, ("zero-data fallocate(0x%x) failed: %s\n", mode,
strerror(errno)));
- SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
return status;
}
if (ret == -1) {
status = map_nt_error_from_unix_common(errno);
DEBUG(0, ("fallocate failed: %s\n", strerror(errno)));
- SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
return status;
}
}
- SMB_VFS_STRICT_UNLOCK(fsp->conn, fsp, &lck);
return NT_STATUS_OK;
}