From 281ce600de9a8eafbecf82a5444e922a31311552 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Fri, 30 Jan 2015 12:59:42 +0100 Subject: [PATCH] torture/ioctl: add FSCTL_DUP_EXTENTS_TO_FILE tests 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 Reviewed-by: Jeremy Allison --- source4/torture/smb2/ioctl.c | 1278 +++++++++++++++++++++++++++++++++- 1 file changed, 1276 insertions(+), 2 deletions(-) diff --git a/source4/torture/smb2/ioctl.c b/source4/torture/smb2/ioctl.c index 89abf38a17c9..5e5faa3c25de 100644 --- a/source4/torture/smb2/ioctl.c +++ b/source4/torture/smb2/ioctl.c @@ -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");