#include "librpc/gen_ndr/ndr_ioctl.h"
#include "smb2_ioctl_private.h"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+/*
+ * 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->fsp_flags.is_sparse && !dst_fsp->fsp_flags.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,
+ struct tevent_context *ev,
+ struct files_struct *dst_fsp,
+ DATA_BLOB *in_input,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fsctl_dup_extents_state *state = NULL;
+ uint64_t src_fid_persistent = 0;
+ 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;
+ }
+
+ 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");
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, state, &state->dup_extents,
+ (ndr_pull_flags_fn_t)ndr_pull_fsctl_dup_extents_to_file);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DBG_ERR("failed to unmarshall dup extents to file req\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ src_fid_persistent = BVAL(state->dup_extents.source_fid, 0);
+ src_fid_volatile = BVAL(state->dup_extents.source_fid, 8);
+ src_fsp = file_fsp_get(smb2req, src_fid_persistent, src_fid_volatile);
+ if ((src_fsp == NULL)
+ || (src_fsp->file_id.devid != dst_fsp->file_id.devid)) {
+ /*
+ * [MS-FSCC] 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply
+ * STATUS_INVALID_PARAMETER:
+ * The FileHandle parameter is either invalid or does not
+ * represent a handle to an opened file on the same volume.
+ *
+ * Windows Server responds with NT_STATUS_INVALID_HANDLE instead
+ * of STATUS_INVALID_PARAMETER here, despite the above spec.
+ */
+ DBG_ERR("invalid src_fsp for dup_extents\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ 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);
+ }
+
+ 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;
+}
+
+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;
+ }
+
+ /* 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)
+{
+ 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);
+ off_t nb_chunk;
+ NTSTATUS status;
+
+ status = SMB_VFS_OFFLOAD_WRITE_RECV(state->conn, subreq, &nb_chunk);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (nb_chunk != state->dup_extents.byte_count) {
+ tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS fsctl_dup_extents_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
static NTSTATUS fsctl_get_cmprn(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct files_struct *fsp,
return status;
}
- if ((fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) == 0) {
- DEBUG(4, ("FS does not advertise compression support\n"));
- return NT_STATUS_NOT_SUPPORTED;
- }
-
ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &cmpr_state,
(ndr_pull_flags_fn_t)ndr_pull_compression_state);
if (ndr_ret != NDR_ERR_SUCCESS) {
return NT_STATUS_INVALID_PARAMETER;
}
- status = SMB_VFS_SET_COMPRESSION(fsp->conn,
- mem_ctx,
- fsp,
- cmpr_state.format);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ status = NT_STATUS_NOT_SUPPORTED;
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ status = SMB_VFS_SET_COMPRESSION(fsp->conn,
+ mem_ctx,
+ fsp,
+ cmpr_state.format);
+ } else if (cmpr_state.format == COMPRESSION_FORMAT_NONE) {
+ /*
+ * bso#12144: The underlying filesystem doesn't support
+ * compression. We should still accept set(FORMAT_NONE) requests
+ * (like WS2016 ReFS).
+ */
+ status = NT_STATUS_OK;
}
- return NT_STATUS_OK;
+ return status;
}
static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
WRITE_LOCK,
&lck);
- if (!SMB_VFS_STRICT_LOCK(fsp->conn, fsp, &lck)) {
+ if (!SMB_VFS_STRICT_LOCK_CHECK(fsp->conn, fsp, &lck)) {
DEBUG(2, ("failed to lock range for zero-data\n"));
return NT_STATUS_FILE_LOCK_CONFLICT;
}
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 (!fsp->is_sparse && lp_strict_allocate(SNUM(fsp->conn))) {
+ if (!fsp->fsp_flags.is_sparse && lp_strict_allocate(SNUM(fsp->conn))) {
/*
* File marked non-sparse and "strict allocate" is enabled -
* allocate the range that we just punched out.
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;
}
max_off = MIN(sbuf.st_ex_size,
qar_req.buf.file_off + qar_req.buf.len) - 1;
- if (!fsp->is_sparse) {
+ if (!fsp->fsp_flags.is_sparse) {
struct file_alloced_range_buf qar_buf;
/* file is non-sparse, claim file_off->max_off is allocated */
return NT_STATUS_OK;
}
+static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq);
+
struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code,
struct tevent_context *ev,
struct tevent_req *req,
}
return tevent_req_post(req, ev);
break;
+ case FSCTL_DUP_EXTENTS_TO_FILE: {
+ struct tevent_req *subreq = NULL;
+
+ subreq = fsctl_dup_extents_send(state, ev,
+ state->fsp,
+ &state->in_input,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb2_ioctl_filesys_dup_extents_done,
+ req);
+ return req;
+ break;
+ }
default: {
uint8_t *out_data = NULL;
uint32_t out_data_len = 0;
tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
return tevent_req_post(req, ev);
}
+
+static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ NTSTATUS status;
+
+ status = fsctl_dup_extents_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+}