s3/vfs: remove SMB_VFS_STRICT_UNLOCK
[kamenim/samba-autobuild/.git] / source3 / smbd / smb2_ioctl_filesys.c
index a4385254bf074ffc8a985ee1bbd533bd6b59d361..054fd936fcc89c4149585e0c2741de8ed42b96cf 100644 (file)
 #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,
@@ -52,22 +181,25 @@ 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");
@@ -102,24 +234,73 @@ static struct tevent_req *fsctl_dup_extents_send(TALLOC_CTX *mem_ctx,
                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)
@@ -131,7 +312,7 @@ 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;
@@ -322,7 +503,6 @@ static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
                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;
        }
 
@@ -341,12 +521,10 @@ static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
                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;
 }