torture/ioctl: add FSCTL_DUP_EXTENTS_TO_FILE tests
authorDavid Disseldorp <ddiss@samba.org>
Fri, 30 Jan 2015 11:59:42 +0000 (12:59 +0100)
committerJeremy Allison <jra@samba.org>
Thu, 22 Sep 2016 18:40:08 +0000 (20:40 +0200)
FSCTL_DUPLICATE_EXTENTS_TO_FILE is yet another copy offload mechanism,
this time only targeting COW FSes, where the request triggers a meta-
data only clone of the source range.

These tests attempt to cover most of the normal use cases, as well as
number of more exotic scenarios.

FILE_SUPPORTS_BLOCK_REFCOUNTING FS attribute presence is checked prior
to running the tests, so they will currently be skipped during Samba
self test (which lacks the flag).

Signed-off-by: David Disseldorp <ddiss@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
source4/torture/smb2/ioctl.c

index 89abf38a17c996608a249174a8b719763c6b975d..5e5faa3c25de09706ed01547216501700a1f4b6b 100644 (file)
@@ -3,7 +3,7 @@
 
    test suite for SMB2 ioctl operations
 
-   Copyright (C) David Disseldorp 2011-2015
+   Copyright (C) David Disseldorp 2011-2016
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -4852,8 +4852,1254 @@ static bool test_ioctl_trim_simple(struct torture_context *torture,
        return true;
 }
 
+static bool test_setup_dup_extents(struct torture_context *tctx,
+                                  struct smb2_tree *tree,
+                                  TALLOC_CTX *mem_ctx,
+                                  struct smb2_handle *src_h,
+                                  uint64_t src_size,
+                                  uint32_t src_desired_access,
+                                  struct smb2_handle *dest_h,
+                                  uint64_t dest_size,
+                                  uint32_t dest_desired_access,
+                                  struct fsctl_dup_extents_to_file *dup_ext_buf,
+                                  union smb_ioctl *ioctl)
+{
+       bool ok;
+
+       ok = test_setup_create_fill(tctx, tree, mem_ctx, FNAME,
+                                   src_h, src_size, src_desired_access,
+                                   FILE_ATTRIBUTE_NORMAL);
+       torture_assert(tctx, ok, "src file create fill");
+
+       ok = test_setup_create_fill(tctx, tree, mem_ctx, FNAME2,
+                                   dest_h, dest_size, dest_desired_access,
+                                   FILE_ATTRIBUTE_NORMAL);
+       torture_assert(tctx, ok, "dest file create fill");
+
+       ZERO_STRUCTPN(ioctl);
+       ioctl->smb2.level = RAW_IOCTL_SMB2;
+       ioctl->smb2.in.file.handle = *dest_h;
+       ioctl->smb2.in.function = FSCTL_DUP_EXTENTS_TO_FILE;
+       ioctl->smb2.in.max_response_size = 0;
+       ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;
+
+       ZERO_STRUCTPN(dup_ext_buf);
+       smb2_push_handle(dup_ext_buf->source_fid, src_h);
+
+       return true;
+}
+
+static bool test_ioctl_dup_extents_simple(struct torture_context *tctx,
+                                         struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_fileinfo io;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 4096, /* fill 4096 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       /* extend dest to match src len */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level =
+               RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = 4096;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 4096;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       /* the file size shouldn't have been changed by this operation! */
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                4096, "size after IO");
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+
+       /* reopen for pattern check */
+       ok = test_setup_open(tctx, tree, tmp_ctx, FNAME, &src_h,
+                            SEC_RIGHTS_FILE_ALL, FILE_ATTRIBUTE_NORMAL);
+       torture_assert_ntstatus_ok(tctx, status, "src open after dup");
+       ok = test_setup_open(tctx, tree, tmp_ctx, FNAME2, &dest_h,
+                            SEC_RIGHTS_FILE_ALL, FILE_ATTRIBUTE_NORMAL);
+       torture_assert_ntstatus_ok(tctx, status, "dest open after dup");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent src file data");
+       }
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent dest file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_len_beyond_dest(struct torture_context *tctx,
+                                                  struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_fileinfo io;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                0, "size after IO");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 32768;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       /*
+        * 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply - this should fail, but
+        * passes against WS2016 RTM!
+        */
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       /* the file sizes shouldn't have been changed */
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = src_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                32768, "size after IO");
+
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                0, "size after IO");
+
+       /* extend dest */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = 32768;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       ok = check_zero(tctx, tree, tmp_ctx, dest_h, 0, 32768);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       /* reissue ioctl, now with enough space */
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_len_beyond_src(struct torture_context *tctx,
+                                                 struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_fileinfo io;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                0, "size after IO");
+
+       /* exceed src file len */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 32768 * 2;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       /* the file sizes shouldn't have been changed */
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = src_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                32768, "size after IO");
+
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                0, "size after IO");
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_len_zero(struct torture_context *tctx,
+                                           struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_fileinfo io;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                0, "size after IO");
+
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 0;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       /* the file sizes shouldn't have been changed */
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = src_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                32768, "size after IO");
+
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = dest_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                0, "size after IO");
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_sparse_src(struct torture_context *tctx,
+                                             struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 0, /* filled after sparse flag */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING
+                                        | FILE_SUPPORTS_SPARSE_FILES, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx,
+                       "block refcounting and sparse files not supported\n");
+       }
+
+       /* set sparse flag on src */
+       status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, src_h, true);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE");
+
+       ok = write_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0);
+       torture_assert(tctx, ok, "write pattern");
+
+       /* extend dest */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = 4096;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 4096;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_sparse_dest(struct torture_context *tctx,
+                                              struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 4096, /* fill 4096 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING
+                                        | FILE_SUPPORTS_SPARSE_FILES, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx,
+                       "block refcounting and sparse files not supported\n");
+       }
+
+       /* set sparse flag on dest */
+       status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, dest_h, true);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE");
+
+       /* extend dest */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = dup_ext_buf.byte_count;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 4096;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED,
+                                     "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_sparse_both(struct torture_context *tctx,
+                                              struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 0, /* fill 4096 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0, /* 0 byte dest file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING
+                                        | FILE_SUPPORTS_SPARSE_FILES, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx,
+                       "block refcounting and sparse files not supported\n");
+       }
+
+       /* set sparse flag on src and dest */
+       status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, src_h, true);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE");
+       status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, dest_h, true);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE");
+
+       ok = write_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0);
+       torture_assert(tctx, ok, "write pattern");
+
+       /* extend dest */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = 4096;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 4096;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+
+       /* reopen for pattern check */
+       ok = test_setup_open(tctx, tree, tmp_ctx, FNAME2, &dest_h,
+                            SEC_RIGHTS_FILE_ALL, FILE_ATTRIBUTE_NORMAL);
+       torture_assert_ntstatus_ok(tctx, status, "dest open ater dup");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_src_is_dest(struct torture_context *tctx,
+                                          struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_fileinfo io;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+       /* dest_h not needed for this test */
+       smb2_util_close(tree, dest_h);
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       /* src and dest are the same file handle */
+       ioctl.smb2.in.file.handle = src_h;
+
+       /* no overlap between src and tgt */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 16384;
+       dup_ext_buf.byte_count = 16384;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       /* the file size shouldn't have been changed */
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = src_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                32768, "size after IO");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 16384, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+       ok = check_pattern(tctx, tree, tmp_ctx, src_h, 16384, 16384, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+/*
+ * unlike copy-chunk, dup extents doesn't support overlapping ranges between
+ * source and target. This makes it a *lot* cleaner to implement on the server.
+ */
+static bool
+test_ioctl_dup_extents_src_is_dest_overlap(struct torture_context *tctx,
+                                          struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_fileinfo io;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+       /* dest_h not needed for this test */
+       smb2_util_close(tree, dest_h);
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       /* src and dest are the same file handle */
+       ioctl.smb2.in.file.handle = src_h;
+
+       /* 8K overlap between src and tgt */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 8192;
+       dup_ext_buf.byte_count = 16384;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED,
+                                     "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       /* the file size and data should match beforehand */
+       ZERO_STRUCT(io);
+       io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
+       io.generic.in.file.handle = src_h;
+       status = smb2_getinfo_file(tree, tmp_ctx, &io);
+       torture_assert_ntstatus_ok(tctx, status, "getinfo");
+       torture_assert_int_equal(tctx, (int)io.all_info2.out.size,
+                                32768, "size after IO");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+/*
+ * The compression tests won't run against Windows servers yet - ReFS doesn't
+ * (yet) offer support for compression.
+ */
+static bool test_ioctl_dup_extents_compressed_src(struct torture_context *tctx,
+                                                 struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 0, /* filled after compressed flag */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING
+                                        | FILE_FILE_COMPRESSION, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx,
+                       "block refcounting and compressed files not supported\n");
+       }
+
+       /* set compressed flag on src */
+       status = test_ioctl_compress_set(tctx, tmp_ctx, tree, src_h,
+                                        COMPRESSION_FORMAT_DEFAULT);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_COMPRESSION");
+
+       ok = write_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0);
+       torture_assert(tctx, ok, "write pattern");
+
+       /* extend dest */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = 4096;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 4096;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_compressed_dest(struct torture_context *tctx,
+                                              struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       union smb_setfileinfo sinfo;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 4096,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING
+                                        | FILE_FILE_COMPRESSION, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx,
+                       "block refcounting and compressed files not supported\n");
+       }
+
+       /* set compressed flag on dest */
+       status = test_ioctl_compress_set(tctx, tmp_ctx, tree, dest_h,
+                                        COMPRESSION_FORMAT_DEFAULT);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_COMPRESSION");
+
+       /* extend dest */
+       ZERO_STRUCT(sinfo);
+       sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+       sinfo.end_of_file_info.in.file.handle = dest_h;
+       sinfo.end_of_file_info.in.size = dup_ext_buf.byte_count;
+       status = smb2_setinfo_file(tree, &sinfo);
+       torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file");
+
+       /* copy all src file data */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 4096;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED,
+                                     "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_bad_handle(struct torture_context *tctx,
+                                             struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       struct smb2_handle bogus_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       bool ok;
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 32768,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       /* open and close a file, keeping the handle as now a "bogus" handle */
+       ok = test_setup_create_fill(tctx, tree, tmp_ctx, "bogus_file",
+                                   &bogus_h, 0, SEC_RIGHTS_FILE_ALL,
+                                   FILE_ATTRIBUTE_NORMAL);
+       torture_assert(tctx, ok, "bogus file create fill");
+       smb2_util_close(tree, bogus_h);
+
+       /* bogus dest file handle */
+       ioctl.smb2.in.file.handle = bogus_h;
+
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 32768;
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_FILE_CLOSED,
+                                     "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       /* reinstate dest, add bogus src file handle */
+       ioctl.smb2.in.file.handle = dest_h;
+       smb2_push_handle(dup_ext_buf.source_fid, &bogus_h);
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_equal(tctx, status, NT_STATUS_INVALID_HANDLE,
+                                     "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_src_lck(struct torture_context *tctx,
+                                          struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle src_h2;
+       struct smb2_handle dest_h;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       bool ok;
+       struct smb2_lock lck;
+       struct smb2_lock_element el[1];
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       /* dest pattern is different to src */
+       ok = write_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 32768);
+       torture_assert(tctx, ok, "write pattern");
+
+       /* setup dup ext req, values used for locking */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 32768;
+
+       /* open and lock the dup extents src file */
+       status = torture_smb2_testfile(tree, FNAME, &src_h2);
+       torture_assert_ntstatus_ok(tctx, status, "2nd src open");
+
+       lck.in.lock_count       = 0x0001;
+       lck.in.lock_sequence    = 0x00000000;
+       lck.in.file.handle      = src_h2;
+       lck.in.locks            = el;
+       el[0].offset            = dup_ext_buf.source_off;
+       el[0].length            = dup_ext_buf.byte_count;
+       el[0].reserved          = 0;
+       el[0].flags             = SMB2_LOCK_FLAG_EXCLUSIVE;
+
+       status = smb2_lock(tree, &lck);
+       torture_assert_ntstatus_ok(tctx, status, "lock");
+
+       status = smb2_util_write(tree, src_h,
+                                "conflicted", 0, sizeof("conflicted"));
+       torture_assert_ntstatus_equal(tctx, status,
+                               NT_STATUS_FILE_LOCK_CONFLICT, "file write");
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       /*
+        * In contrast to copy-chunk, dup extents doesn't cause a lock conflict
+        * here.
+        */
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       lck.in.lock_count       = 0x0001;
+       lck.in.lock_sequence    = 0x00000001;
+       lck.in.file.handle      = src_h2;
+       lck.in.locks            = el;
+       el[0].offset            = dup_ext_buf.source_off;
+       el[0].length            = dup_ext_buf.byte_count;
+       el[0].reserved          = 0;
+       el[0].flags             = SMB2_LOCK_FLAG_UNLOCK;
+       status = smb2_lock(tree, &lck);
+       torture_assert_ntstatus_ok(tctx, status, "unlock");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE unlocked");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h2);
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
+static bool test_ioctl_dup_extents_dest_lck(struct torture_context *tctx,
+                                           struct smb2_tree *tree)
+{
+       struct smb2_handle src_h;
+       struct smb2_handle dest_h;
+       struct smb2_handle dest_h2;
+       NTSTATUS status;
+       union smb_ioctl ioctl;
+       TALLOC_CTX *tmp_ctx = talloc_new(tree);
+       struct fsctl_dup_extents_to_file dup_ext_buf;
+       enum ndr_err_code ndr_ret;
+       bool ok;
+       struct smb2_lock lck;
+       struct smb2_lock_element el[1];
+
+       ok = test_setup_dup_extents(tctx, tree, tmp_ctx,
+                                   &src_h, 32768, /* fill 32768 byte src file */
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dest_h, 0,
+                                   SEC_RIGHTS_FILE_ALL,
+                                   &dup_ext_buf,
+                                   &ioctl);
+       if (!ok) {
+               torture_fail(tctx, "setup dup extents error");
+       }
+
+       status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h,
+                                        FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok);
+       torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS");
+       if (!ok) {
+               smb2_util_close(tree, src_h);
+               smb2_util_close(tree, dest_h);
+               talloc_free(tmp_ctx);
+               torture_skip(tctx, "block refcounting not supported\n");
+       }
+
+       /* dest pattern is different to src */
+       ok = write_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 32768);
+       torture_assert(tctx, ok, "write pattern");
+
+       /* setup dup ext req, values used for locking */
+       dup_ext_buf.source_off = 0;
+       dup_ext_buf.target_off = 0;
+       dup_ext_buf.byte_count = 32768;
+
+       /* open and lock the dup extents dest file */
+       status = torture_smb2_testfile(tree, FNAME2, &dest_h2);
+       torture_assert_ntstatus_ok(tctx, status, "2nd src open");
+
+       lck.in.lock_count       = 0x0001;
+       lck.in.lock_sequence    = 0x00000000;
+       lck.in.file.handle      = dest_h2;
+       lck.in.locks            = el;
+       el[0].offset            = dup_ext_buf.source_off;
+       el[0].length            = dup_ext_buf.byte_count;
+       el[0].reserved          = 0;
+       el[0].flags             = SMB2_LOCK_FLAG_EXCLUSIVE;
+
+       status = smb2_lock(tree, &lck);
+       torture_assert_ntstatus_ok(tctx, status, "lock");
+
+       status = smb2_util_write(tree, dest_h,
+                                "conflicted", 0, sizeof("conflicted"));
+       torture_assert_ntstatus_equal(tctx, status,
+                               NT_STATUS_FILE_LOCK_CONFLICT, "file write");
+
+       ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+                                      &dup_ext_buf,
+                      (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file);
+       torture_assert_ndr_success(tctx, ndr_ret,
+                                  "ndr_push_fsctl_dup_extents_to_file");
+
+       /*
+        * In contrast to copy-chunk, dup extents doesn't cause a lock conflict
+        * here.
+        */
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE");
+
+       lck.in.lock_count       = 0x0001;
+       lck.in.lock_sequence    = 0x00000001;
+       lck.in.file.handle      = dest_h2;
+       lck.in.locks            = el;
+       el[0].offset            = dup_ext_buf.source_off;
+       el[0].length            = dup_ext_buf.byte_count;
+       el[0].reserved          = 0;
+       el[0].flags             = SMB2_LOCK_FLAG_UNLOCK;
+       status = smb2_lock(tree, &lck);
+       torture_assert_ntstatus_ok(tctx, status, "unlock");
+
+       status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+       torture_assert_ntstatus_ok(tctx, status,
+                                  "FSCTL_DUP_EXTENTS_TO_FILE unlocked");
+
+       ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0);
+       if (!ok) {
+               torture_fail(tctx, "inconsistent file data");
+       }
+
+       smb2_util_close(tree, src_h);
+       smb2_util_close(tree, dest_h);
+       smb2_util_close(tree, dest_h2);
+       talloc_free(tmp_ctx);
+       return true;
+}
+
 /*
- * basic testing of SMB2 ioctls
+ * testing of SMB2 ioctls
  */
 struct torture_suite *torture_smb2_ioctl_init(void)
 {
@@ -4955,6 +6201,34 @@ struct torture_suite *torture_smb2_ioctl_init(void)
                                     test_ioctl_sparse_qar_overflow);
        torture_suite_add_1smb2_test(suite, "trim_simple",
                                     test_ioctl_trim_simple);
+       torture_suite_add_1smb2_test(suite, "dup_extents_simple",
+                                    test_ioctl_dup_extents_simple);
+       torture_suite_add_1smb2_test(suite, "dup_extents_len_beyond_dest",
+                                    test_ioctl_dup_extents_len_beyond_dest);
+       torture_suite_add_1smb2_test(suite, "dup_extents_len_beyond_src",
+                                    test_ioctl_dup_extents_len_beyond_src);
+       torture_suite_add_1smb2_test(suite, "dup_extents_len_zero",
+                                    test_ioctl_dup_extents_len_zero);
+       torture_suite_add_1smb2_test(suite, "dup_extents_sparse_src",
+                                    test_ioctl_dup_extents_sparse_src);
+       torture_suite_add_1smb2_test(suite, "dup_extents_sparse_dest",
+                                    test_ioctl_dup_extents_sparse_dest);
+       torture_suite_add_1smb2_test(suite, "dup_extents_sparse_both",
+                                    test_ioctl_dup_extents_sparse_both);
+       torture_suite_add_1smb2_test(suite, "dup_extents_src_is_dest",
+                                    test_ioctl_dup_extents_src_is_dest);
+       torture_suite_add_1smb2_test(suite, "dup_extents_src_is_dest_overlap",
+                                    test_ioctl_dup_extents_src_is_dest_overlap);
+       torture_suite_add_1smb2_test(suite, "dup_extents_compressed_src",
+                                    test_ioctl_dup_extents_compressed_src);
+       torture_suite_add_1smb2_test(suite, "dup_extents_compressed_dest",
+                                    test_ioctl_dup_extents_compressed_dest);
+       torture_suite_add_1smb2_test(suite, "dup_extents_bad_handle",
+                                    test_ioctl_dup_extents_bad_handle);
+       torture_suite_add_1smb2_test(suite, "dup_extents_src_lock",
+                                    test_ioctl_dup_extents_src_lck);
+       torture_suite_add_1smb2_test(suite, "dup_extents_dest_lock",
+                                    test_ioctl_dup_extents_dest_lck);
 
        suite->description = talloc_strdup(suite, "SMB2-IOCTL tests");