diff options
Diffstat (limited to 'source4/torture/smb2/ioctl.c')
-rw-r--r-- | source4/torture/smb2/ioctl.c | 7549 |
1 files changed, 7549 insertions, 0 deletions
diff --git a/source4/torture/smb2/ioctl.c b/source4/torture/smb2/ioctl.c new file mode 100644 index 0000000..6ceaccf --- /dev/null +++ b/source4/torture/smb2/ioctl.c @@ -0,0 +1,7549 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 ioctl operations + + 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 + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "librpc/gen_ndr/ndr_ioctl.h" +#include "lib/cmdline/cmdline.h" +#include "libcli/resolve/resolve.h" +#include "lib/param/param.h" +#include "lib/util/tevent_ntstatus.h" + +#define FNAME "testfsctl.dat" +#define FNAME2 "testfsctl2.dat" +#define DNAME "testfsctl_dir" + +/* + basic testing of SMB2 shadow copy calls +*/ +static bool test_ioctl_get_shadow_copy(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[100]; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok(torture, status, "create write"); + + ZERO_ARRAY(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok(torture, status, "write"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_ENUM_SNAPS; + ioctl.smb2.in.max_output_response = 16; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) + || NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) { + torture_skip(torture, "FSCTL_SRV_ENUM_SNAPS not supported\n"); + } + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_ENUM_SNAPS"); + + return true; +} + +/* + basic testing of the SMB2 server side copy ioctls +*/ +static bool test_ioctl_req_resume_key(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[100]; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct req_resume_key_rsp res_key; + enum ndr_err_code ndr_ret; + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok(torture, status, "create write"); + + ZERO_ARRAY(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok(torture, status, "write"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + ioctl.smb2.in.max_output_response = 32; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + NDR_PRINT_DEBUG(req_resume_key_rsp, &res_key); + + talloc_free(tmp_ctx); + return true; +} + +/* + testing fetching a resume key twice for one file handle +*/ +static bool test_ioctl_req_two_resume_keys(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[100]; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct req_resume_key_rsp res_key; + enum ndr_err_code ndr_ret; + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok(torture, status, "create write"); + + ZERO_ARRAY(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok(torture, status, "write"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + ioctl.smb2.in.max_output_response = 32; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + NDR_PRINT_DEBUG(req_resume_key_rsp, &res_key); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + ioctl.smb2.in.max_output_response = 32; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + NDR_PRINT_DEBUG(req_resume_key_rsp, &res_key); + + talloc_free(tmp_ctx); + return true; +} + +static uint64_t patt_hash(uint64_t off) +{ + return off; +} + +static bool write_pattern(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_handle h, uint64_t off, uint64_t len, + uint64_t patt_off) +{ + NTSTATUS status; + uint64_t i; + uint8_t *buf; + uint64_t io_sz = MIN(1024 * 64, len); + + if (len == 0) { + return true; + } + + torture_assert(torture, (len % 8) == 0, "invalid write len"); + + buf = talloc_zero_size(mem_ctx, io_sz); + torture_assert(torture, (buf != NULL), "no memory for file data buf"); + + while (len > 0) { + for (i = 0; i <= io_sz - 8; i += 8) { + SBVAL(buf, i, patt_hash(patt_off)); + patt_off += 8; + } + + status = smb2_util_write(tree, h, + buf, off, io_sz); + torture_assert_ntstatus_ok(torture, status, "file write"); + + len -= io_sz; + off += io_sz; + } + + talloc_free(buf); + + return true; +} + +static bool check_pattern(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_handle h, uint64_t off, uint64_t len, + uint64_t patt_off) +{ + if (len == 0) { + return true; + } + + torture_assert(torture, (len % 8) == 0, "invalid read len"); + + while (len > 0) { + uint64_t i; + struct smb2_read r; + NTSTATUS status; + uint64_t io_sz = MIN(1024 * 64, len); + + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = io_sz; + r.in.offset = off; + status = smb2_read(tree, mem_ctx, &r); + torture_assert_ntstatus_ok(torture, status, "read"); + + torture_assert_u64_equal(torture, r.out.data.length, io_sz, + "read data len mismatch"); + + for (i = 0; i <= io_sz - 8; i += 8, patt_off += 8) { + uint64_t data = BVAL(r.out.data.data, i); + torture_assert_u64_equal(torture, data, patt_hash(patt_off), + talloc_asprintf(torture, "read data " + "pattern bad at %llu\n", + (unsigned long long)off + i)); + } + talloc_free(r.out.data.data); + len -= io_sz; + off += io_sz; + } + + return true; +} + +static bool check_zero(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_handle h, uint64_t off, uint64_t len) +{ + uint64_t i; + struct smb2_read r; + NTSTATUS status; + + if (len == 0) { + return true; + } + + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = len; + r.in.offset = off; + status = smb2_read(tree, mem_ctx, &r); + torture_assert_ntstatus_ok(torture, status, "read"); + + torture_assert_u64_equal(torture, r.out.data.length, len, + "read data len mismatch"); + + for (i = 0; i <= len - 8; i += 8) { + uint64_t data = BVAL(r.out.data.data, i); + torture_assert_u64_equal(torture, data, 0, + talloc_asprintf(mem_ctx, "read zero " + "bad at %llu\n", + (unsigned long long)i)); + } + + talloc_free(r.out.data.data); + return true; +} + +static bool test_setup_open(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + const char *fname, + struct smb2_handle *fh, + uint32_t desired_access, + uint32_t file_attributes) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.desired_access = desired_access; + io.in.file_attributes = file_attributes; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + if (file_attributes & FILE_ATTRIBUTE_DIRECTORY) { + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + } + io.in.fname = fname; + + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "file create"); + + *fh = io.out.file.handle; + + return true; +} + +static bool test_setup_create_fill(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + const char *fname, + struct smb2_handle *fh, + uint64_t size, + uint32_t desired_access, + uint32_t file_attributes) +{ + bool ok; + uint32_t initial_access = desired_access; + + if (size > 0) { + initial_access |= SEC_FILE_APPEND_DATA; + } + + smb2_util_unlink(tree, fname); + + ok = test_setup_open(torture, tree, mem_ctx, + fname, + fh, + initial_access, + file_attributes); + torture_assert(torture, ok, "file create"); + + if (size > 0) { + ok = write_pattern(torture, tree, mem_ctx, *fh, 0, size, 0); + torture_assert(torture, ok, "write pattern"); + } + + if (initial_access != desired_access) { + smb2_util_close(tree, *fh); + ok = test_setup_open(torture, tree, mem_ctx, + fname, + fh, + desired_access, + file_attributes); + torture_assert(torture, ok, "file open"); + } + + return true; +} + +static bool test_setup_copy_chunk(struct torture_context *torture, + struct smb2_tree *src_tree, + struct smb2_tree *dst_tree, + TALLOC_CTX *mem_ctx, + uint32_t nchunks, + const char *src_name, + struct smb2_handle *src_h, + uint64_t src_size, + uint32_t src_desired_access, + const char *dst_name, + struct smb2_handle *dest_h, + uint64_t dest_size, + uint32_t dest_desired_access, + struct srv_copychunk_copy *cc_copy, + union smb_ioctl *ioctl) +{ + struct req_resume_key_rsp res_key; + bool ok; + NTSTATUS status; + enum ndr_err_code ndr_ret; + + ok = test_setup_create_fill(torture, src_tree, mem_ctx, src_name, + src_h, src_size, src_desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "src file create fill"); + + ok = test_setup_create_fill(torture, dst_tree, mem_ctx, dst_name, + dest_h, dest_size, dest_desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "dest file create fill"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *src_h; + ioctl->smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + /* Allow for Key + ContextLength + Context */ + ioctl->smb2.in.max_output_response = 32; + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(src_tree, mem_ctx, &ioctl->smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl->smb2.out.out, mem_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *dest_h; + ioctl->smb2.in.function = FSCTL_SRV_COPYCHUNK; + ioctl->smb2.in.max_output_response = sizeof(struct srv_copychunk_rsp); + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_STRUCTPN(cc_copy); + memcpy(cc_copy->source_key, res_key.resume_key, ARRAY_SIZE(cc_copy->source_key)); + cc_copy->chunk_count = nchunks; + cc_copy->chunks = talloc_zero_array(mem_ctx, struct srv_copychunk, nchunks); + torture_assert(torture, (cc_copy->chunks != NULL), "no memory for chunks"); + + return true; +} + + +static bool check_copy_chunk_rsp(struct torture_context *torture, + struct srv_copychunk_rsp *cc_rsp, + uint32_t ex_chunks_written, + uint32_t ex_chunk_bytes_written, + uint32_t ex_total_bytes_written) +{ + torture_assert_int_equal(torture, cc_rsp->chunks_written, + ex_chunks_written, "num chunks"); + torture_assert_int_equal(torture, cc_rsp->chunk_bytes_written, + ex_chunk_bytes_written, "chunk bytes written"); + torture_assert_int_equal(torture, cc_rsp->total_bytes_written, + ex_total_bytes_written, "chunk total bytes"); + return true; +} + +static bool test_ioctl_copy_chunk_simple(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "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_copy_chunk_multi(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 8192, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data via two chunks */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + cc_copy.chunks[1].source_off = 4096; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_tiny(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 96, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data via two chunks, sub block size chunks */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 48; + + cc_copy.chunks[1].source_off = 48; + cc_copy.chunks[1].target_off = 48; + cc_copy.chunks[1].length = 48; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 96); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 96, 0); + if (!ok) { + torture_fail(torture, "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_copy_chunk_over(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 8192, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 4096, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* first chunk overwrites existing dest data */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* second chunk overwrites the first */ + cc_copy.chunks[1].source_off = 4096; + cc_copy.chunks[1].target_off = 0; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 4096); + if (!ok) { + torture_fail(torture, "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_copy_chunk_append(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* second chunk appends the same data to the first */ + cc_copy.chunks[1].source_off = 0; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 4096, 4096, 0); + if (!ok) { + torture_fail(torture, "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_copy_chunk_limits(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* send huge chunk length request */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = UINT_MAX; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, "marshalling request"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "bad oversize chunk response"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response"); + + torture_comment(torture, "limit max chunks, got %u\n", + cc_rsp.chunks_written); + torture_comment(torture, "limit max chunk len, got %u\n", + cc_rsp.chunk_bytes_written); + torture_comment(torture, "limit max total bytes, got %u\n", + cc_rsp.total_bytes_written); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_src_lck(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* open and lock the copychunk src file */ + status = torture_smb2_testfile(tree, FNAME, &src_h2); + torture_assert_ntstatus_ok(torture, 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 = cc_copy.chunks[0].source_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "lock"); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + /* + * 2k12 & Samba return lock_conflict, Windows 7 & 2k8 return success... + * + * Edgar Olougouna @ MS wrote: + * Regarding the FSCTL_SRV_COPYCHUNK and STATUS_FILE_LOCK_CONFLICT + * discrepancy observed between Windows versions, we confirm that the + * behavior change is expected. + * + * CopyChunk in Windows Server 2012 use regular Readfile/Writefile APIs + * to move the chunks from the source to the destination. + * These ReadFile/WriteFile APIs go through the byte-range lock checks, + * and this explains the observed STATUS_FILE_LOCK_CONFLICT error. + * + * Prior to Windows Server 2012, CopyChunk used mapped sections to move + * the data. And byte range locks are not enforced on mapped I/O, and + * this explains the STATUS_SUCCESS observed on Windows Server 2008 R2. + */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_FILE_LOCK_CONFLICT, + "FSCTL_SRV_COPYCHUNK locked"); + + /* should get cc response data with the lock conflict status */ + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 0, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 0); /* total bytes written */ + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = src_h2; + lck.in.locks = el; + el[0].offset = cc_copy.chunks[0].source_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "unlock"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK unlocked"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "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_copy_chunk_dest_lck(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 4096, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* open and lock the copychunk dest file */ + status = torture_smb2_testfile(tree, FNAME2, &dest_h2); + torture_assert_ntstatus_ok(torture, 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 = cc_copy.chunks[0].target_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "lock"); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_FILE_LOCK_CONFLICT, + "FSCTL_SRV_COPYCHUNK locked"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = dest_h2; + lck.in.locks = el; + el[0].offset = cc_copy.chunks[0].target_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "unlock"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK unlocked"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, dest_h2); + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_bad_key(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, + FNAME, + &src_h, 4096, + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* overwrite the resume key with a bogus value */ + memcpy(cc_copy.source_key, "deadbeefdeadbeefdeadbeef", 24); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + /* Server 2k12 returns NT_STATUS_OBJECT_NAME_NOT_FOUND */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_OBJECT_NAME_NOT_FOUND, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_src_is_dest(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, + FNAME, + &src_h, 8192, + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* the source is also the destination */ + ioctl.smb2.in.file.handle = src_h; + + /* non-overlapping */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 4096; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, src_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + ok = check_pattern(torture, tree, tmp_ctx, src_h, 4096, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +/* + * Test a single-chunk copychunk request, where the source and target ranges + * overlap, and the SourceKey refers to the same target file. E.g: + * + * Initial State + * ------------- + * File: src_and_dest + * Offset: 0123456789 + * Data: abcdefghij + * + * Request + * ------- + * FSCTL_SRV_COPYCHUNK(src_and_dest) + * SourceKey = SRV_REQUEST_RESUME_KEY(src_and_dest) + * ChunkCount = 1 + * Chunks[0].SourceOffset = 0 + * Chunks[0].TargetOffset = 4 + * Chunks[0].Length = 6 + * + * Resultant State + * --------------- + * File: src_and_dest + * Offset: 0123456789 + * Data: abcdabcdef + * + * The resultant contents of src_and_dest is dependent on the server's + * copy algorithm. In the above example, the server uses an IO buffer + * large enough to hold the entire six-byte source data before writing + * to TargetOffset. If the server were to use a four-byte IO buffer and + * started reads/writes from the lowest offset, then the two overlapping + * bytes in the above example would be overwritten before being read. The + * resultant file contents would be abcdabcdab. + * + * Windows 2008r2 appears to use a 2048 byte copy buffer, overlapping bytes + * after this offset are written before being read. Windows 2012 on the + * other hand appears to use a buffer large enough to hold its maximum + * supported chunk size (1M). Samba currently uses a 64k copy buffer by + * default (vfs_cc_state.buf). + * + * This test uses an 8-byte overlap at 2040-2048, so that it passes against + * Windows 2008r2, 2012 and Samba servers. Note, 2008GM fails, as it appears + * to use a different copy algorithm to 2008r2. + */ +static bool +test_ioctl_copy_chunk_src_is_dest_overlap(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + /* exceed the vfs_default copy buffer */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, + FNAME, + &src_h, 2048 * 2, + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* the source is also the destination */ + ioctl.smb2.in.file.handle = src_h; + + /* 8 bytes overlap between source and target ranges */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 2048 - 8; + cc_copy.chunks[0].length = 2048; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 2048); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, src_h, 0, 2048 - 8, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + ok = check_pattern(torture, tree, tmp_ctx, src_h, 2048 - 8, 2048, 0); + if (!ok) { + torture_fail(torture, "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_copy_chunk_bad_access(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + /* read permission on src */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE, + FNAME2, &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, tmp_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* execute permission on src */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_EXECUTE | SEC_FILE_READ_ATTRIBUTE, + FNAME2, &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, tmp_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* neither read nor execute permission on src */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_ATTRIBUTE, FNAME2, &dest_h, + 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* no write permission on dest */ + ok = test_setup_copy_chunk( + torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE, FNAME2, &dest_h, + 0, /* 0 byte dest file */ + (SEC_RIGHTS_FILE_ALL & + ~(SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)), + &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* no read permission on dest */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE, + FNAME2, &dest_h, 0, /* 0 byte dest file */ + (SEC_RIGHTS_FILE_ALL & ~SEC_FILE_READ_DATA), + &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + /* + * FSCTL_SRV_COPYCHUNK requires read permission on dest, + * FSCTL_SRV_COPYCHUNK_WRITE on the other hand does not. + */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + + return true; +} + +static bool test_ioctl_copy_chunk_write_access(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + + /* no read permission on dest with FSCTL_SRV_COPYCHUNK_WRITE */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + (SEC_RIGHTS_FILE_WRITE + | SEC_RIGHTS_FILE_EXECUTE), + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + ioctl.smb2.in.function = FSCTL_SRV_COPYCHUNK_WRITE; + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK_WRITE"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + + return true; +} + +static bool test_ioctl_copy_chunk_src_exceed(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* Request copy where off + length exceeds size of src */ + cc_copy.chunks[0].source_off = 1024; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_VIEW_SIZE, + "FSCTL_SRV_COPYCHUNK oversize"); + + /* Request copy where length exceeds size of src */ + cc_copy.chunks[0].source_off = 1024; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 3072; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK just right"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 3072); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 3072, 1024); + if (!ok) { + torture_fail(torture, "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_copy_chunk_src_exceed_multi(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* 2 chunks */ + FNAME, + &src_h, 8192, /* fill 8192 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* Request copy where off + length exceeds size of src */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + cc_copy.chunks[1].source_off = 4096; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 8192; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_VIEW_SIZE, + "FSCTL_SRV_COPYCHUNK oversize"); + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response"); + + /* first chunk should still be written */ + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "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_copy_chunk_sparse_dest(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + struct smb2_read r; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + int i; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 4096; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + /* check for zeros in first 4k */ + ZERO_STRUCT(r); + r.in.file.handle = dest_h; + r.in.length = 4096; + r.in.offset = 0; + status = smb2_read(tree, tmp_ctx, &r); + torture_assert_ntstatus_ok(torture, status, "read"); + + torture_assert_u64_equal(torture, r.out.data.length, 4096, + "read data len mismatch"); + + for (i = 0; i < 4096; i++) { + torture_assert(torture, (r.out.data.data[i] == 0), + "sparse did not pass class"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 4096, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +/* + * set the ioctl MaxOutputResponse size to less than + * sizeof(struct srv_copychunk_rsp) + */ +static bool test_ioctl_copy_chunk_max_output_sz(struct torture_context *torture, + 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 srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + /* req is valid, but use undersize max_output_response */ + ioctl.smb2.in.max_output_response = sizeof(struct srv_copychunk_rsp) - 1; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_zero_length(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + union smb_fileinfo q; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* zero length server-side copy (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 0; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "bad zero-length chunk response"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response"); + + ZERO_STRUCT(q); + q.all_info2.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + q.all_info2.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, torture, &q); + torture_assert_ntstatus_ok(torture, status, "getinfo"); + + torture_assert_int_equal(torture, q.all_info2.out.size, 0, + "size after zero len clone"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool copy_one_stream(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *tmp_ctx, + const char *src_sname, + const char *dst_sname) +{ + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + NTSTATUS status; + union smb_ioctl io; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok = false; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + src_sname, + &src_h, 256, /* fill 256 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, + dst_sname, + &dest_h, 0, /* 0 byte dest file */ + SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, + &cc_copy, + &io); + torture_assert_goto(torture, ok == true, ok, done, + "setup copy chunk error\n"); + + /* copy all src file data (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 256; + + ndr_ret = ndr_push_struct_blob( + &io.smb2.in.out, tmp_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + + torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree, tmp_ctx, &io.smb2); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, + "FSCTL_SRV_COPYCHUNK\n"); + + ndr_ret = ndr_pull_struct_blob( + &io.smb2.out.out, tmp_ctx, &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + + torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, + "ndr_pull_srv_copychunk_rsp\n"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 256); /* total bytes written */ + torture_assert_goto(torture, ok == true, ok, done, + "bad copy chunk response data\n"); + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 256, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data\n"); + } + +done: + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree, dest_h); + } + + return ok; +} + +/** + * Create a file + **/ +static bool torture_setup_file(TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + const char *name) +{ + struct smb2_create io; + NTSTATUS status; + + smb2_util_unlink(tree, name); + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = name; + + status = smb2_create(tree, mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + status = smb2_util_close(tree, io.out.file.handle); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + return true; +} + +static bool test_copy_chunk_streams(struct torture_context *torture, + struct smb2_tree *tree) +{ + const char *src_name = "src"; + const char *dst_name = "dst"; + struct names { + const char *src_sname; + const char *dst_sname; + } names[] = { + { "src:foo", "dst:foo" } + }; + int i; + TALLOC_CTX *tmp_ctx = NULL; + bool ok = false; + + tmp_ctx = talloc_new(tree); + torture_assert_not_null_goto(torture, tmp_ctx, ok, done, + "torture_setup_file\n"); + + ok = torture_setup_file(torture, tree, src_name); + torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n"); + ok = torture_setup_file(torture, tree, dst_name); + torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n"); + + for (i = 0; i < ARRAY_SIZE(names); i++) { + ok = copy_one_stream(torture, tree, tmp_ctx, + names[i].src_sname, + names[i].dst_sname); + torture_assert_goto(torture, ok == true, ok, done, + "copy_one_stream failed\n"); + } + +done: + smb2_util_unlink(tree, src_name); + smb2_util_unlink(tree, dst_name); + talloc_free(tmp_ctx); + return ok; +} + +static bool test_copy_chunk_across_shares(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + NTSTATUS status; + bool ok = false; + + mem_ctx = talloc_new(tctx); + torture_assert_not_null_goto(tctx, mem_ctx, ok, done, + "talloc_new\n"); + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree2); + torture_assert_goto(tctx, ok == true, ok, done, + "torture_smb2_tree_connect failed\n"); + + ok = test_setup_copy_chunk(tctx, tree, tree2, mem_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert_goto(tctx, ok == true, ok, done, + "test_setup_copy_chunk failed\n"); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, mem_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree2, mem_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "FSCTL_SRV_COPYCHUNK\n"); + + ndr_ret = ndr_pull_struct_blob( + &ioctl.smb2.out.out, mem_ctx, &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_pull_srv_copychunk_rsp\n"); + + ok = check_copy_chunk_rsp(tctx, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + torture_assert_goto(tctx, ok == true, ok, done, + "bad copy chunk response data\n"); + + ok = check_pattern(tctx, tree2, mem_ctx, dest_h, 0, 4096, 0); + torture_assert_goto(tctx, ok == true, ok, done, + "inconsistent file data\n"); + +done: + TALLOC_FREE(mem_ctx); + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree2, dest_h); + } + smb2_util_unlink(tree, FNAME); + smb2_util_unlink(tree2, FNAME2); + if (tree2 != NULL) { + smb2_tdis(tree2); + } + return ok; +} + +/* Test closing the src handle */ +static bool test_copy_chunk_across_shares2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + NTSTATUS status; + bool ok = false; + + mem_ctx = talloc_new(tctx); + torture_assert_not_null_goto(tctx, mem_ctx, ok, done, + "talloc_new\n"); + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree2); + torture_assert_goto(tctx, ok == true, ok, done, + "torture_smb2_tree_connect failed\n"); + + ok = test_setup_copy_chunk(tctx, tree, tree2, mem_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert_goto(tctx, ok == true, ok, done, + "test_setup_copy_chunk failed\n"); + + status = smb2_util_close(tree, src_h); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(src_h); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, mem_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree2, mem_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ok, done, "smb2_ioctl failed\n"); + +done: + TALLOC_FREE(mem_ctx); + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree2, dest_h); + } + smb2_util_unlink(tree, FNAME); + smb2_util_unlink(tree2, FNAME2); + if (tree2 != NULL) { + smb2_tdis(tree2); + } + return ok; +} + +/* Test offset works */ +static bool test_copy_chunk_across_shares3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + NTSTATUS status; + bool ok = false; + + mem_ctx = talloc_new(tctx); + torture_assert_not_null_goto(tctx, mem_ctx, ok, done, + "talloc_new\n"); + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree2); + torture_assert_goto(tctx, ok == true, ok, done, + "torture_smb2_tree_connect failed\n"); + + ok = test_setup_copy_chunk(tctx, tree, tree2, mem_ctx, + 2, /* 2 chunks */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert_goto(tctx, ok == true, ok, done, + "test_setup_copy_chunk failed\n"); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* second chunk appends the same data to the first */ + cc_copy.chunks[1].source_off = 0; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, mem_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree2, mem_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "smb2_ioctl failed\n"); + + ndr_ret = ndr_pull_struct_blob( + &ioctl.smb2.out.out, mem_ctx, &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_pull_srv_copychunk_rsp\n"); + + ok = check_copy_chunk_rsp(tctx, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + torture_assert_goto(tctx, ok == true, ok, done, + "check_copy_chunk_rsp failed\n"); + + ok = check_pattern(tctx, tree2, mem_ctx, dest_h, 0, 4096, 0); + torture_assert_goto(tctx, ok == true, ok, done, + "check_pattern failed\n"); + + ok = check_pattern(tctx, tree2, mem_ctx, dest_h, 4096, 4096, 0); + torture_assert_goto(tctx, ok == true, ok, done, + "check_pattern failed\n"); + +done: + TALLOC_FREE(mem_ctx); + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree2, dest_h); + } + smb2_util_unlink(tree, FNAME); + smb2_util_unlink(tree2, FNAME2); + if (tree2 != NULL) { + smb2_tdis(tree2); + } + return ok; +} + +static NTSTATUS test_ioctl_compress_fs_supported(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *fh, + bool *compress_support) +{ + NTSTATUS status; + union smb_fsinfo info; + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_ATTRIBUTE_INFORMATION; + info.generic.handle = *fh; + status = smb2_getinfo_fs(tree, tree, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (info.attribute_info.out.fs_attr & FILE_FILE_COMPRESSION) { + *compress_support = true; + } else { + *compress_support = false; + } + return NT_STATUS_OK; +} + +static NTSTATUS test_ioctl_compress_get(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + uint16_t *_compression_fmt) +{ + union smb_ioctl ioctl; + struct compression_state cmpr_state; + enum ndr_err_code ndr_ret; + NTSTATUS status; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_GET_COMPRESSION; + ioctl.smb2.in.max_output_response = sizeof(struct compression_state); + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, mem_ctx, + &cmpr_state, + (ndr_pull_flags_fn_t)ndr_pull_compression_state); + + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + *_compression_fmt = cmpr_state.format; + return NT_STATUS_OK; +} + +static NTSTATUS test_ioctl_compress_set(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + uint16_t compression_fmt) +{ + union smb_ioctl ioctl; + struct compression_state cmpr_state; + enum ndr_err_code ndr_ret; + NTSTATUS status; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_COMPRESSION; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + cmpr_state.format = compression_fmt; + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, mem_ctx, + &cmpr_state, + (ndr_push_flags_fn_t)ndr_push_compression_state); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + return status; +} + +static bool test_ioctl_compress_file_flag(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "invalid compression state after set"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_dir_inherit(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle dirh; + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint16_t compression_fmt; + bool ok; + char path_buf[PATH_MAX]; + + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup compression directory"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &dirh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + torture_skip(torture, "FS compression not supported\n"); + } + + /* set compression on parent dir, then check for inheritance */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, dirh, + COMPRESSION_FORMAT_LZNT1); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "invalid compression state after set"); + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + path_buf, &fh, 4096, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "compression attr not inherited by new file"); + + /* check compressed data is consistent */ + ok = check_pattern(torture, tree, tmp_ctx, fh, 0, 4096, 0); + + /* disable dir compression attr, file should remain compressed */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, dirh, + COMPRESSION_FORMAT_NONE); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "file compression attr removed after dir change"); + smb2_util_close(tree, fh); + + /* new files should no longer inherit compression attr */ + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME2); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + path_buf, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression attr present on new file"); + + smb2_util_close(tree, fh); + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_invalid_format(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + 0x0042); /* bogus */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "invalid FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_invalid_buf(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + union smb_ioctl ioctl; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_GET_COMPRESSION; + ioctl.smb2.in.max_output_response = 0; /* no room for rsp data */ + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_USER_BUFFER) + && !NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* neither Server 2k12 nor 2k8r2 response status */ + torture_assert(torture, true, + "invalid FSCTL_SET_COMPRESSION"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_query_file_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.all_info2.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr before set"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + (io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED), + "no compression attr after set"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + * Specify FILE_ATTRIBUTE_COMPRESSED on creation, Windows does not retain this + * attribute. + */ +static bool test_ioctl_compress_create_with_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh2; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint16_t compression_fmt; + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME2, &fh2, 0, SEC_RIGHTS_FILE_ALL, + (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_COMPRESSED)); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh2, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh2); + torture_skip(torture, "FS compression not supported\n"); + } + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh2, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh2; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.all_info2.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "incorrect compression attr"); + + smb2_util_close(tree, fh2); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_inherit_disable(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + struct smb2_handle dirh; + char path_buf[PATH_MAX]; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + struct smb2_create io; + + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup compression directory"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &dirh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + torture_skip(torture, "FS compression not supported\n"); + } + + /* set compression on parent dir, then check for inheritance */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, dirh, + COMPRESSION_FORMAT_LZNT1); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "invalid compression state after set"); + smb2_util_close(tree, dirh); + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + path_buf, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "compression attr not inherited by new file"); + smb2_util_close(tree, fh); + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME2); + + /* NO_COMPRESSION option should block inheritance */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = NTCREATEX_OPTIONS_NO_COMPRESSION; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.fname = path_buf; + + status = smb2_create(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "file create"); + + fh = io.out.file.handle; + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression attr inherited by NO_COMPRESSION file"); + smb2_util_close(tree, fh); + + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, DNAME); + ZERO_STRUCT(io); + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = (NTCREATEX_OPTIONS_NO_COMPRESSION + | NTCREATEX_OPTIONS_DIRECTORY); + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.fname = path_buf; + + status = smb2_create(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "dir create"); + + dirh = io.out.file.handle; + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression attr inherited by NO_COMPRESSION dir"); + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + + talloc_free(tmp_ctx); + return true; +} + +/* attempting to set compression via SetInfo should not stick */ +static bool test_ioctl_compress_set_file_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + struct smb2_handle dirh; + union smb_fileinfo io; + union smb_setfileinfo set_io; + uint16_t compression_fmt; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr before set"); + + ZERO_STRUCT(set_io); + set_io.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + set_io.basic_info.in.file.handle = fh; + set_io.basic_info.in.create_time = io.basic_info.out.create_time; + set_io.basic_info.in.access_time = io.basic_info.out.access_time; + set_io.basic_info.in.write_time = io.basic_info.out.write_time; + set_io.basic_info.in.change_time = io.basic_info.out.change_time; + set_io.basic_info.in.attrib = (io.basic_info.out.attrib + | FILE_ATTRIBUTE_COMPRESSED); + status = smb2_setinfo_file(tree, &set_io); + torture_assert_ntstatus_ok(torture, status, "SMB2_SETINFO_FILE"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr after set"); + + smb2_util_close(tree, fh); + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup compression directory"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = dirh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr before set"); + + ZERO_STRUCT(set_io); + set_io.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + set_io.basic_info.in.file.handle = dirh; + set_io.basic_info.in.create_time = io.basic_info.out.create_time; + set_io.basic_info.in.access_time = io.basic_info.out.access_time; + set_io.basic_info.in.write_time = io.basic_info.out.write_time; + set_io.basic_info.in.change_time = io.basic_info.out.change_time; + set_io.basic_info.in.attrib = (io.basic_info.out.attrib + | FILE_ATTRIBUTE_COMPRESSED); + status = smb2_setinfo_file(tree, &set_io); + torture_assert_ntstatus_ok(torture, status, "SMB2_SETINFO_FILE"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "dir compression set after SetInfo"); + + smb2_util_close(tree, dirh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_perms(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + uint16_t compression_fmt; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + smb2_util_close(tree, fh); + if (!ok) { + torture_skip(torture, "FS compression not supported\n"); + } + + /* attempt get compression without READ_ATTR permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_READ & ~(SEC_FILE_READ_ATTRIBUTE + | SEC_STD_READ_CONTROL + | SEC_FILE_READ_EA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression set after create"); + smb2_util_close(tree, fh); + + /* set compression without WRITE_ATTR permission should succeed */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & ~(SEC_FILE_WRITE_ATTRIBUTE + | SEC_STD_WRITE_DAC + | SEC_FILE_WRITE_EA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + (io.all_info2.out.attrib & FILE_ATTRIBUTE_COMPRESSED), + "incorrect compression attr"); + smb2_util_close(tree, fh); + + /* attempt get compression without READ_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_READ & ~SEC_FILE_READ_DATA), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression enabled after set"); + smb2_util_close(tree, fh); + + /* attempt get compression with only SYNCHRONIZE permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_STD_SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression not enabled after set"); + smb2_util_close(tree, fh); + + /* attempt to set compression without WRITE_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & (~SEC_FILE_WRITE_DATA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SET_COMPRESSION permission"); + smb2_util_close(tree, fh); + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & (~SEC_FILE_WRITE_DATA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_NONE); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SET_COMPRESSION permission"); + smb2_util_close(tree, fh); + + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_notsup_get(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + /* skip if the server DOES support compression */ + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression supported\n"); + } + + /* + * Despite not supporting compression, we should get a successful + * response indicating that the file is uncompressed - like WS2016. + */ + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_notsup_set(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + /* skip if the server DOES support compression */ + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression supported\n"); + } + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_NOT_SUPPORTED, + "FSCTL_SET_COMPRESSION default"); + + /* + * Despite not supporting compression, we should get a successful + * response for set(COMPRESSION_FORMAT_NONE) - like WS2016 ReFS. + */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_NONE); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SET_COMPRESSION none"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + basic testing of the SMB2 FSCTL_QUERY_NETWORK_INTERFACE_INFO ioctl +*/ +static bool test_ioctl_network_interface_info(struct torture_context *torture, + struct smb2_tree *tree) +{ + union smb_ioctl ioctl; + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_net_iface_info net_iface; + enum ndr_err_code ndr_ret; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(torture, "server doesn't support SMB2_CAP_MULTI_CHANNEL\n"); + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + fh.data[0] = UINT64_MAX; + fh.data[1] = UINT64_MAX; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_NETWORK_INTERFACE_INFO; + ioctl.smb2.in.max_output_response = 0x10000; /* Windows client sets this to 64KiB */ + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &net_iface, + (ndr_pull_flags_fn_t)ndr_pull_fsctl_net_iface_info); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_fsctl_net_iface_info"); + + NDR_PRINT_DEBUG(fsctl_net_iface_info, &net_iface); + + talloc_free(tmp_ctx); + return true; +} + +/* + * Check whether all @fs_support_flags are set in the server's + * RAW_QFS_ATTRIBUTE_INFORMATION FileSystemAttributes response. + */ +static NTSTATUS test_ioctl_fs_supported(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *fh, + uint64_t fs_support_flags, + bool *supported) +{ + NTSTATUS status; + union smb_fsinfo info; + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_ATTRIBUTE_INFORMATION; + info.generic.handle = *fh; + status = smb2_getinfo_fs(tree, tree, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((info.attribute_info.out.fs_attr & fs_support_flags) + == fs_support_flags) { + *supported = true; + } else { + *supported = false; + } + return NT_STATUS_OK; +} + +static NTSTATUS test_ioctl_sparse_req(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + bool set) +{ + union smb_ioctl ioctl; + NTSTATUS status; + uint8_t set_sparse; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + set_sparse = (set ? 0xFF : 0x0); + ioctl.smb2.in.out.data = &set_sparse; + ioctl.smb2.in.out.length = sizeof(set_sparse); + + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + return status; +} + +static NTSTATUS test_sparse_get(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + bool *_is_sparse) +{ + union smb_fileinfo io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *_is_sparse = !!(io.basic_info.out.attrib & FILE_ATTRIBUTE_SPARSE); + + return status; +} + +/* + * Manually test setting and clearing sparse flag. Intended for file system + * specifc tests to toggle the flag through SMB and check the status in the + * file system. + */ +bool test_ioctl_set_sparse(struct torture_context *tctx) +{ + bool set, ret = true; + const char *filename = NULL; + struct smb2_create create = { }; + struct smb2_tree *tree = NULL; + NTSTATUS status; + + set = torture_setting_bool(tctx, "set_sparse", true); + filename = torture_setting_string(tctx, "filename", NULL); + + if (filename == NULL) { + torture_fail(tctx, "Need to provide filename through " + "--option=torture:filename=testfile\n"); + return false; + } + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.fname = filename; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed.\n"); + + status = test_ioctl_sparse_req(tctx, tctx, tree, + create.out.file.handle, set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "FSCTL_SET_SPARSE failed.\n"); +done: + + return ret; +} + +static bool test_ioctl_sparse_file_flag(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.all_info2.out.attrib & FILE_ATTRIBUTE_SPARSE) == 0), + "sparse attr before set"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr after unset"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_file_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_SPARSE)); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr on open"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_dir_flag(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle dirh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup sparse directory"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &dirh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* set sparse dir should fail, check for 2k12 & 2k8 response */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, dirh, true); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "dir FSCTL_SET_SPARSE status"); + + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + talloc_free(tmp_ctx); + return true; +} + +/* + * FSCTL_SET_SPARSE can be sent with (already tested) or without a SetSparse + * buffer to indicate whether the flag should be set or cleared. When sent + * without a buffer, it must be handled as if SetSparse=TRUE. + */ +static bool test_ioctl_sparse_set_nobuf(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + /* ioctl.smb2.in.out is zeroed, no SetSparse buffer */ + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + /* second non-SetSparse request shouldn't toggle sparse */ + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after 2nd set"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr after unset"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_set_oversize(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + uint8_t buf[100]; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + /* + * Attach a request buffer larger than FILE_SET_SPARSE_BUFFER + * Windows still successfully processes the request. + */ + ZERO_ARRAY(buf); + buf[0] = 0xFF; /* attempt to set sparse */ + ioctl.smb2.in.out.data = buf; + ioctl.smb2.in.out.length = ARRAY_SIZE(buf); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_ARRAY(buf); /* clear sparse */ + ioctl.smb2.in.out.data = buf; + ioctl.smb2.in.out.length = ARRAY_SIZE(buf); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr after clear"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static NTSTATUS test_ioctl_qar_req(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + int64_t req_off, + int64_t req_len, + struct file_alloced_range_buf **_rsp, + uint64_t *_rsp_count) +{ + union smb_ioctl ioctl; + NTSTATUS status; + enum ndr_err_code ndr_ret; + struct file_alloced_range_buf far_buf; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + int i; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_ALLOCATED_RANGES; + ioctl.smb2.in.max_output_response = 1024; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + far_buf.file_off = req_off; + far_buf.len = req_len; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &far_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + if (ndr_ret != NDR_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + if (ioctl.smb2.out.out.length == 0) { + goto done; + } + + if ((ioctl.smb2.out.out.length % sizeof(far_buf)) != 0) { + torture_comment(torture, "invalid qry_alloced rsp len: %zd:", + ioctl.smb2.out.out.length); + status = NT_STATUS_INVALID_VIEW_SIZE; + goto err_out; + } + + far_count = (ioctl.smb2.out.out.length / sizeof(far_buf)); + far_rsp = talloc_array(mem_ctx, struct file_alloced_range_buf, + far_count); + if (far_rsp == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + for (i = 0; i < far_count; i++) { + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &far_rsp[i], + (ndr_pull_flags_fn_t)ndr_pull_file_alloced_range_buf); + if (ndr_ret != NDR_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + /* move to next buffer */ + ioctl.smb2.out.out.data += sizeof(far_buf); + ioctl.smb2.out.out.length -= sizeof(far_buf); + } + +done: + *_rsp = far_rsp; + *_rsp_count = far_count; + status = NT_STATUS_OK; +err_out: + talloc_free(tmp_ctx); + return status; +} + +static bool test_ioctl_sparse_qar(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + /* zero length file, shouldn't have any ranges */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 0, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + /* write into the (now) sparse file at 4k offset */ + ok = write_pattern(torture, tree, tmp_ctx, fh, + 4096, /* off */ + 1024, /* len */ + 4096); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* + * Query range before write off. Whether it's allocated or not is FS + * dependent. NTFS deallocates chunks in 64K increments, but others + * (e.g. XFS, Btrfs, etc.) may deallocate 4K chunks. + */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 0) { + torture_comment(torture, "FS deallocated 4K chunk\n"); + } else { + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, "far offset"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, "far len"); + } + + /* + * Query range before and past write, it should be allocated up to the + * end of the write. + */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 8192, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + /* FS dependent */ + if (far_rsp[0].file_off == 4096) { + /* 4K chunk unallocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 4096, "far offset"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, "far len"); + } else { + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, "far offset"); + torture_assert_u64_equal(torture, far_rsp[0].len, 5120, "far len"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_qar_malformed(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + struct file_alloced_range_buf far_buf; + NTSTATUS status; + enum ndr_err_code ndr_ret; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + size_t old_len; + + /* zero length file, shouldn't have any ranges */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* no allocated ranges, no space for range response, should pass */ + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_ALLOCATED_RANGES; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + far_buf.file_off = 0; + far_buf.len = 1024; + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &far_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + torture_assert_ndr_success(torture, ndr_ret, "push far ndr buf"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_QUERY_ALLOCATED_RANGES"); + + /* write into the file at 4k offset */ + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + 1024, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* allocated range, no space for range response, should fail */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_BUFFER_TOO_SMALL, "qar no space"); + + /* oversize (2x) file_alloced_range_buf in request, should pass */ + ioctl.smb2.in.max_output_response = 1024; + old_len = ioctl.smb2.in.out.length; + ok = data_blob_realloc(tmp_ctx, &ioctl.smb2.in.out, + (ioctl.smb2.in.out.length * 2)); + torture_assert(torture, ok, "2x data buffer"); + memcpy(ioctl.smb2.in.out.data + old_len, ioctl.smb2.in.out.data, + old_len); + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "qar too big"); + + /* no file_alloced_range_buf in request, should fail */ + data_blob_free(&ioctl.smb2.in.out); + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, "qar empty"); + + return true; +} + +bool test_ioctl_alternate_data_stream(struct torture_context *tctx) +{ + bool ret = false; + const char *fname = DNAME "\\test_stream_ioctl_dir"; + const char *sname = DNAME "\\test_stream_ioctl_dir:stream"; + NTSTATUS status; + struct smb2_create create = {}; + struct smb2_tree *tree = NULL; + struct smb2_handle h1 = {{0}}; + union smb_ioctl ioctl; + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_HIDDEN, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + h1 = create.out.file.handle; + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = sname, + }; + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = create.out.file.handle; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h1; + ioctl.smb2.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID, + ioctl.smb2.in.max_output_response = 64; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + status = smb2_ioctl(tree, tctx, &ioctl.smb2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_ioctl failed\n"); + ret = true; + +done: + + smb2_util_close(tree, h1); + smb2_deltree(tree, DNAME); + return ret; +} + +/* + * 2.3.57 FSCTL_SET_ZERO_DATA Request + * + * How an implementation zeros data within a file is implementation-dependent. + * A file system MAY choose to deallocate regions of disk space that have been + * zeroed.<50> + * <50> + * ... NTFS might deallocate disk space in the file if the file is stored on an + * NTFS volume, and the file is sparse or compressed. It will free any allocated + * space in chunks of 64 kilobytes that begin at an offset that is a multiple of + * 64 kilobytes. Other bytes in the file (prior to the first freed 64-kilobyte + * chunk and after the last freed 64-kilobyte chunk) will be zeroed but not + * deallocated. + */ +static NTSTATUS test_ioctl_zdata_req(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + int64_t off, + int64_t beyond_final_zero) +{ + union smb_ioctl ioctl; + NTSTATUS status; + enum ndr_err_code ndr_ret; + struct file_zero_data_info zdata_info; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_ZERO_DATA; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + zdata_info.file_off = off; + zdata_info.beyond_final_zero = beyond_final_zero; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &zdata_info, + (ndr_push_flags_fn_t)ndr_push_file_zero_data_info); + if (ndr_ret != NDR_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = NT_STATUS_OK; +err_out: + talloc_free(tmp_ctx); + return status; +} + +bool test_ioctl_zero_data(struct torture_context *tctx) +{ + bool ret = true; + int offset, beyond_final_zero; + const char *filename; + NTSTATUS status; + struct smb2_create create = { }; + struct smb2_tree *tree = NULL; + + offset = torture_setting_int(tctx, "offset", -1); + + if (offset < 0) { + torture_fail(tctx, "Need to provide non-negative offset " + "through --option=torture:offset=NNN\n"); + return false; + } + + beyond_final_zero = torture_setting_int(tctx, "beyond_final_zero", + -1); + if (beyond_final_zero < 0) { + torture_fail(tctx, "Need to provide non-negative " + "'beyond final zero' through " + "--option=torture:beyond_final_zero=NNN\n"); + return false; + } + filename = torture_setting_string(tctx, "filename", NULL); + if (filename == NULL) { + torture_fail(tctx, "Need to provide filename through " + "--option=torture:filename=testfile\n"); + return false; + } + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.fname = filename; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed.\n"); + + status = test_ioctl_zdata_req(tctx, tctx, tree, + create.out.file.handle, + offset, + beyond_final_zero); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "FSCTL_ZERO_DATA failed.\n"); + +done: + return ret; +} + +static bool test_ioctl_sparse_punch(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 4096, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + /* zero (hole-punch) the data, without sparse flag */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, + "unexpected far len"); + /* check that the data is now zeroed */ + ok = check_zero(torture, tree, tmp_ctx, fh, 0, 4096); + torture_assert(torture, ok, "non-sparse zeroed range"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* still fully allocated on NTFS, see note below for Samba */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + /* + * FS specific: Samba uses PUNCH_HOLE to zero the range, and + * subsequently uses fallocate() to allocate the punched range if the + * file is marked non-sparse and "strict allocate" is enabled. In both + * cases, the zeroed range will not be detected by SEEK_DATA, so the + * range won't be present in QAR responses until the file is marked + * non-sparse again. + */ + if (far_count == 0) { + torture_comment(torture, "non-sparse zeroed range disappeared " + "after marking sparse\n"); + } else { + /* NTFS: range remains fully allocated */ + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, + "unexpected far len"); + } + + /* zero (hole-punch) the data, _with_ sparse flag */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* the range should no longer be alloced */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + ok = check_zero(torture, tree, tmp_ctx, fh, 0, 4096); + torture_assert(torture, ok, "sparse zeroed range"); + + /* remove sparse flag, this should "unsparse" the zeroed range */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, + "unexpected far len"); + + ok = check_zero(torture, tree, tmp_ctx, fh, 0, 4096); + torture_assert(torture, ok, "sparse zeroed range"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + * Find the point at which a zeroed range in a sparse file is deallocated by the + * underlying filesystem. NTFS on Windows Server 2012 deallocates chunks in 64k + * increments. Also check whether zeroed neighbours are merged for deallocation. + */ +static bool test_ioctl_sparse_hole_dealloc(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t file_size; + uint64_t hlen; + uint64_t dealloc_chunk_len = 0; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file 1"); + + /* check for FS sparse file */ + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + file_size = 1024 * 1024; + + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + file_size, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* check allocated ranges, should be fully allocated */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, file_size, + "unexpected far len"); + + /* punch holes in sizes of 1k increments */ + for (hlen = 0; hlen <= file_size; hlen += 4096) { + + /* punch a hole from zero to the current increment */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + hlen); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* ensure hole is zeroed, and pattern is consistent */ + ok = check_zero(torture, tree, tmp_ctx, fh, 0, hlen); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, fh, hlen, + file_size - hlen, hlen); + torture_assert(torture, ok, "allocated pattern range"); + + /* Check allocated ranges, hole might have been deallocated */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES"); + if ((hlen == file_size) && (far_count == 0)) { + /* hole covered entire file, deallocation occurred */ + dealloc_chunk_len = file_size; + break; + } + + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off != 0) { + /* + * We now know the hole punch length needed to trigger a + * deallocation on this FS... + */ + dealloc_chunk_len = hlen; + torture_comment(torture, "hole punch %ju@0 resulted in " + "deallocation of %ju@0\n", + (uintmax_t)hlen, + (uintmax_t)far_rsp[0].file_off); + torture_assert_u64_equal(torture, + file_size - far_rsp[0].len, + far_rsp[0].file_off, + "invalid alloced range"); + break; + } + } + + if (dealloc_chunk_len == 0) { + torture_comment(torture, "strange, this FS never deallocates" + "zeroed ranges in sparse files\n"); + return true; /* FS specific, not a failure */ + } + + /* + * Check whether deallocation occurs when the (now known) + * deallocation chunk size is punched via two ZERO_DATA requests. + * I.e. Does the FS merge the two ranges and deallocate the chunk? + * NTFS on Windows Server 2012 does not. + */ + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + file_size, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* divide dealloc chunk size by two, to use as punch length */ + hlen = dealloc_chunk_len >> 1; + + /* + * /half of dealloc chunk size 1M\ + * | | + * /offset 0 | /dealloc chunk size | + * |------------------ |-------------------|-------------------| + * | zeroed, 1st punch | zeroed, 2nd punch | existing pattern | + */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + hlen); /* beyond final zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + hlen, /* off */ + dealloc_chunk_len); /* beyond final */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* ensure holes are zeroed, and pattern is consistent */ + ok = check_zero(torture, tree, tmp_ctx, fh, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, fh, dealloc_chunk_len, + file_size - dealloc_chunk_len, dealloc_chunk_len); + torture_assert(torture, ok, "allocated pattern range"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + + if ((far_count == 0) && (dealloc_chunk_len == file_size)) { + torture_comment(torture, "holes merged for deallocation of " + "full file\n"); + return true; + } + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off == dealloc_chunk_len) { + torture_comment(torture, "holes merged for deallocation of " + "%ju chunk\n", (uintmax_t)dealloc_chunk_len); + torture_assert_u64_equal(torture, + file_size - far_rsp[0].len, + far_rsp[0].file_off, + "invalid alloced range"); + } else { + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected deallocation"); + torture_comment(torture, "holes not merged for deallocation\n"); + } + + smb2_util_close(tree, fh); + + /* + * Check whether an unwritten range is allocated when a sparse file is + * written to at an offset past the dealloc chunk size: + * + * /dealloc chunk size + * /offset 0 | + * |------------------ |-------------------| + * | unwritten | pattern | + */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file 1"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + ok = write_pattern(torture, tree, tmp_ctx, fh, + dealloc_chunk_len, /* off */ + 1024, /* len */ + dealloc_chunk_len); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off == 0) { + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + torture_comment(torture, "unwritten range fully allocated\n"); + } else { + torture_assert_u64_equal(torture, far_rsp[0].file_off, dealloc_chunk_len, + "unexpected deallocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, + "unexpected far len"); + torture_comment(torture, "unwritten range not allocated\n"); + } + + ok = check_zero(torture, tree, tmp_ctx, fh, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, fh, dealloc_chunk_len, + 1024, dealloc_chunk_len); + torture_assert(torture, ok, "allocated pattern range"); + + /* unsparse, should now be fully allocated */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected deallocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* check whether a file with compression and sparse attrs can be deallocated */ +static bool test_ioctl_sparse_compressed(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t file_size = 1024 * 1024; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file 1"); + + /* check for FS sparse file and compression support */ + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + /* set compression and write some data */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + file_size, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* set sparse - now sparse and compressed */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* check allocated ranges, should be fully alloced */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, file_size, + "unexpected far len"); + + /* zero (hole-punch) all data, with sparse and compressed attrs */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* + * Windows Server 2012 still deallocates a zeroed range when a sparse + * file carries the compression attribute. + */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 0) { + torture_comment(torture, "sparse & compressed file " + "deallocated after hole-punch\n"); + } else { + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, file_size, + "unexpected far len"); + torture_comment(torture, "sparse & compressed file fully " + "allocated after hole-punch\n"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + * Create a sparse file, then attempt to copy unallocated and allocated ranges + * into a target file using FSCTL_SRV_COPYCHUNK. + */ +static bool test_ioctl_sparse_copy_chunk(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &src_h, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + /* check for FS sparse file support */ + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + smb2_util_close(tree, src_h); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + } + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 0, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert(torture, ok, "setup copy chunk error"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, src_h, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* start after dealloc_chunk_len, to create an unwritten sparse range */ + ok = write_pattern(torture, tree, tmp_ctx, src_h, + dealloc_chunk_len, /* off */ + 1024, /* len */ + dealloc_chunk_len); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* Skip test if 64k chunk is allocated - FS specific */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, src_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off == 0) { + torture_skip(torture, "unwritten range fully allocated\n"); + } + + torture_assert_u64_equal(torture, far_rsp[0].file_off, dealloc_chunk_len, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, + "unexpected far len"); + + /* copy-chunk unallocated + written ranges into non-sparse dest */ + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = dealloc_chunk_len + 1024; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + dealloc_chunk_len + 1024); /* bytes written */ + torture_assert(torture, ok, "bad copy chunk response data"); + + ok = check_zero(torture, tree, tmp_ctx, dest_h, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, dealloc_chunk_len, + 1024, dealloc_chunk_len); + torture_assert(torture, ok, "copychunked range"); + + /* copied range should be allocated in non-sparse dest */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + + /* set dest as sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, dest_h, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* zero (hole-punch) all data */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024); + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* zeroed range might be deallocated */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 0) { + /* FS specific (e.g. NTFS) */ + torture_comment(torture, "FS deallocates file on full-range " + "punch\n"); + } else { + /* FS specific (e.g. EXT4) */ + torture_comment(torture, "FS doesn't deallocate file on " + "full-range punch\n"); + } + ok = check_zero(torture, tree, tmp_ctx, dest_h, 0, + dealloc_chunk_len + 1024); + torture_assert(torture, ok, "punched zeroed range"); + + /* copy-chunk again, this time with sparse dest */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + dealloc_chunk_len + 1024); /* bytes written */ + torture_assert(torture, ok, "bad copy chunk response data"); + + ok = check_zero(torture, tree, tmp_ctx, dest_h, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, dealloc_chunk_len, + 1024, dealloc_chunk_len); + torture_assert(torture, ok, "copychunked range"); + + /* copied range may be allocated in sparse dest */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + /* + * FS specific: sparse region may be unallocated in dest if copy-chunk + * is handled in a sparse preserving way - E.g. vfs_btrfs + * with BTRFS_IOC_CLONE_RANGE. + */ + if (far_rsp[0].file_off == dealloc_chunk_len) { + torture_comment(torture, "copy-chunk sparse range preserved\n"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, + "unexpected far len"); + } else { + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_punch_invalid(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + int i; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 4096, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + /* loop twice, without and with sparse attrib */ + for (i = 0; i <= 1; i++) { + union smb_fileinfo io; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + /* get size before & after. zero data should never change it */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "getinfo"); + torture_assert_int_equal(torture, (int)io.all_info2.out.size, + 4096, "size after IO"); + + /* valid 8 byte zero data, but after EOF */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 4104); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* valid 8 byte zero data, but after EOF */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 8192, /* off */ + 8200); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "getinfo"); + torture_assert_int_equal(torture, (int)io.all_info2.out.size, + 4096, "size after IO"); + + /* valid 0 byte zero data, without sparse flag */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 4095, /* off */ + 4095); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* INVALID off is past beyond_final_zero */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 4095); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "invalid zero_data"); + + /* zero length QAR - valid */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 0, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + /* QAR after EOF - valid */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, + true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_perms(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + smb2_util_close(tree, fh); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + } + + /* set sparse without WRITE_ATTR permission should succeed */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & ~(SEC_FILE_WRITE_ATTRIBUTE + | SEC_STD_WRITE_DAC + | SEC_FILE_WRITE_EA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt get sparse without READ_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_READ & ~SEC_FILE_READ_DATA), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse set"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only WRITE_ATTR permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_WRITE_ATTRIBUTE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only WRITE_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_WRITE_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only APPEND_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_APPEND_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only WRITE_EA permission - should fail */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_WRITE_EA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SET_SPARSE permission"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt QAR with only READ_ATTR permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_READ_ATTRIBUTE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_QUERY_ALLOCATED_RANGES req passed"); + smb2_util_close(tree, fh); + + /* attempt QAR with only READ_DATA permission */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_READ_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + smb2_util_close(tree, fh); + + /* attempt QAR with only READ_EA permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_READ_EA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_QUERY_ALLOCATED_RANGES req passed"); + smb2_util_close(tree, fh); + + /* setup file for ZERO_DATA permissions tests */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 8192, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only WRITE_ATTR permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_WRITE_ATTRIBUTE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "zero_data permission"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only WRITE_DATA permission */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_WRITE_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only APPEND_DATA permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_APPEND_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "zero_data permission"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only WRITE_EA permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_WRITE_EA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "zero_data permission"); + smb2_util_close(tree, fh); + + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_lck(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + struct smb2_handle fh2; + NTSTATUS status; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, FNAME, &fh, + dealloc_chunk_len, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + smb2_util_close(tree, fh); + } + + /* open and lock via separate fh2 */ + status = torture_smb2_testfile(tree, FNAME, &fh2); + torture_assert_ntstatus_ok(torture, status, "2nd src open"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = fh2; + lck.in.locks = el; + el[0].offset = 0; + el[0].length = dealloc_chunk_len; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "lock"); + + /* set sparse while locked */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse attr after set"); + + /* zero data over locked range should fail */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_FILE_LOCK_CONFLICT, + "zero_data locked"); + + /* QAR over locked range should pass */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES locked"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + 4096, + "unexpected far len"); + + /* zero data over range past EOF should pass */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len, /* off */ + dealloc_chunk_len + 4096); + torture_assert_ntstatus_ok(torture, status, + "zero_data past EOF locked"); + + /* QAR over range past EOF should pass */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len, /* off */ + 4096, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES past EOF locked"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = fh2; + lck.in.locks = el; + el[0].offset = 0; + el[0].length = dealloc_chunk_len; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "unlock"); + + smb2_util_close(tree, fh2); + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* alleviate QAR off-by-one bug paranoia - help me ob1 */ +static bool test_ioctl_sparse_qar_ob1(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, dealloc_chunk_len * 2, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + smb2_util_close(tree, fh); + } + + /* non-sparse QAR with range one before EOF */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len * 2 - 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len * 2 - 1, + "unexpected far len"); + + /* non-sparse QAR with range one after EOF */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len * 2 + 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len * 2, + "unexpected far len"); + + /* non-sparse QAR with range one after EOF from off=1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 1, /* off */ + dealloc_chunk_len * 2, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 1, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len * 2 - 1, + "unexpected far len"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* punch out second chunk */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len, /* off */ + dealloc_chunk_len * 2); + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* sparse QAR with range one before hole */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len - 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len - 1, + "unexpected far len"); + + /* sparse QAR with range one after hole */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len + 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len, + "unexpected far len"); + + /* sparse QAR with range one after hole from off=1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 1, /* off */ + dealloc_chunk_len, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 1, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len - 1, + "unexpected far len"); + + /* sparse QAR with range one before EOF from off=chunk_len-1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len - 1, /* off */ + dealloc_chunk_len, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, + dealloc_chunk_len - 1, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + 1, "unexpected far len"); + + /* sparse QAR with range one after EOF from off=chunk_len+1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len + 1, /* off */ + dealloc_chunk_len, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* test QAR with multi-range responses */ +static bool test_ioctl_sparse_qar_multi(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + uint64_t this_off; + int i; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, dealloc_chunk_len * 2, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + smb2_util_close(tree, fh); + } + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* each loop, write out two chunks and punch the first out */ + for (i = 0; i < 10; i++) { + this_off = i * dealloc_chunk_len * 2; + + ok = write_pattern(torture, tree, tmp_ctx, fh, + this_off, /* off */ + dealloc_chunk_len * 2, /* len */ + this_off); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + this_off, /* off */ + this_off + dealloc_chunk_len); + torture_assert_ntstatus_ok(torture, status, "zero_data"); + } + + /* should now have one separate region for each iteration */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, + 10 * dealloc_chunk_len * 2, + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 1) { + torture_comment(torture, "this FS doesn't deallocate 64K" + "zeroed ranges in sparse files\n"); + return true; /* FS specific, not a failure */ + } + torture_assert_u64_equal(torture, far_count, 10, + "unexpected response len"); + for (i = 0; i < 10; i++) { + this_off = i * dealloc_chunk_len * 2; + + torture_assert_u64_equal(torture, far_rsp[i].file_off, + this_off + dealloc_chunk_len, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[i].len, + dealloc_chunk_len, + "unexpected far len"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_qar_overflow(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + struct file_alloced_range_buf far_buf; + NTSTATUS status; + enum ndr_err_code ndr_ret; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 1024, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* no allocated ranges, no space for range response, should pass */ + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_ALLOCATED_RANGES; + ioctl.smb2.in.max_output_response = 1024; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + /* off + length wraps around to 511 */ + far_buf.file_off = 512; + far_buf.len = 0xffffffffffffffffLL; + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &far_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + torture_assert_ndr_success(torture, ndr_ret, "push far ndr buf"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_ALLOCATED_RANGES overflow"); + + return true; +} + +static NTSTATUS test_ioctl_trim_supported(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *fh, + bool *trim_support) +{ + NTSTATUS status; + union smb_fsinfo info; + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_SECTOR_SIZE_INFORMATION; + info.generic.handle = *fh; + status = smb2_getinfo_fs(tree, tree, &info); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) { + /* + * Windows < Server 2012, 8 etc. don't support this info level + * or the trim ioctl. Ignore the error and let the caller skip. + */ + *trim_support = false; + return NT_STATUS_OK; + } else if (!NT_STATUS_IS_OK(status)) { + return status; + } + + torture_comment(torture, "sector size info: lb/s=%u, pb/sA=%u, " + "pb/sP=%u, fse/sA=%u, flags=0x%x, bosa=%u, bopa=%u\n", + (unsigned)info.sector_size_info.out.logical_bytes_per_sector, + (unsigned)info.sector_size_info.out.phys_bytes_per_sector_atomic, + (unsigned)info.sector_size_info.out.phys_bytes_per_sector_perf, + (unsigned)info.sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic, + (unsigned)info.sector_size_info.out.flags, + (unsigned)info.sector_size_info.out.byte_off_sector_align, + (unsigned)info.sector_size_info.out.byte_off_partition_align); + + if (info.sector_size_info.out.flags & QFS_SSINFO_FLAGS_TRIM_ENABLED) { + *trim_support = true; + } else { + *trim_support = false; + } + return NT_STATUS_OK; +} + +static bool test_setup_trim(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + uint32_t num_ranges, + struct smb2_handle *fh, + uint64_t file_size, + uint32_t desired_access, + struct fsctl_file_level_trim_req *trim_req, + union smb_ioctl *ioctl) +{ + bool ok; + + ok = test_setup_create_fill(torture, tree, mem_ctx, FNAME, + fh, file_size, desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "src file create fill"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *fh; + ioctl->smb2.in.function = FSCTL_FILE_LEVEL_TRIM; + ioctl->smb2.in.max_output_response + = sizeof(struct fsctl_file_level_trim_rsp); + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_STRUCTPN(trim_req); + /* leave key as zero for now. TODO test locking with differing keys */ + trim_req->num_ranges = num_ranges; + trim_req->ranges = talloc_zero_array(mem_ctx, + struct file_level_trim_range, + num_ranges); + torture_assert(torture, (trim_req->ranges != NULL), "no memory for ranges"); + + return true; +} + +static bool test_ioctl_trim_simple(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + union smb_ioctl ioctl; + bool trim_supported; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_file_level_trim_req trim_req; + struct fsctl_file_level_trim_rsp trim_rsp; + uint64_t trim_chunk_len = 64 * 1024; /* trim 64K chunks */ + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_trim(torture, tree, tmp_ctx, + 1, /* 1 range */ + &fh, 2 * trim_chunk_len, /* fill 128K file */ + SEC_RIGHTS_FILE_ALL, + &trim_req, + &ioctl); + if (!ok) { + torture_fail(torture, "setup trim error"); + } + + status = test_ioctl_trim_supported(torture, tree, tmp_ctx, &fh, + &trim_supported); + torture_assert_ntstatus_ok(torture, status, "fsinfo"); + if (!trim_supported) { + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + torture_skip(torture, "trim not supported\n"); + } + + /* trim first chunk, leave second */ + trim_req.ranges[0].off = 0; + trim_req.ranges[0].len = trim_chunk_len; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, &trim_req, + (ndr_push_flags_fn_t)ndr_push_fsctl_file_level_trim_req); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_fsctl_file_level_trim_req"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FILE_LEVEL_TRIM_RANGE"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &trim_rsp, + (ndr_pull_flags_fn_t)ndr_pull_fsctl_file_level_trim_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_fsctl_file_level_trim_rsp"); + + torture_assert_int_equal(torture, trim_rsp.num_ranges_processed, 1, ""); + + /* second half of the file should remain consitent */ + ok = check_pattern(torture, tree, tmp_ctx, fh, trim_chunk_len, + trim_chunk_len, trim_chunk_len); + torture_assert(torture, ok, "non-trimmed range inconsistent"); + + 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_output_response = 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); +#if 0 + /* + * 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"); +#endif + + /* 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"); + + /* + * src is sparse, but spec says: 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE + * Reply... STATUS_NOT_SUPPORTED: Target file is sparse, while source + * is a non-sparse 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_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"); + + /* + * dest is sparse, but spec says: 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE + * Reply... STATUS_NOT_SUPPORTED: Target file is sparse, while source + * is a non-sparse 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); + 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 = 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_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 regression test for BUG 14607 + https://bugzilla.samba.org/show_bug.cgi?id=14607 +*/ +static bool test_ioctl_bug14607(struct torture_context *torture, + struct smb2_tree *tree) +{ + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint32_t timeout_msec; + NTSTATUS status; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + status = smb2cli_ioctl(tree->session->transport->conn, + timeout_msec, + tree->session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + tmp_ctx, + &out_input_buffer, + &out_output_buffer); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) || + NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) || + NT_STATUS_EQUAL(status, NT_STATUS_FS_DRIVER_REQUIRED) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) + { + torture_comment(torture, + "FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8: %s\n", + nt_errstr(status)); + torture_skip(torture, "server doesn't support FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8\n"); + } + torture_assert_ntstatus_ok(torture, status, "FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8"); + + torture_assert_int_equal(torture, out_output_buffer.length, 1, + "output length"); + torture_assert_int_equal(torture, out_output_buffer.data[0], 8, + "output buffer byte should be 8"); + + talloc_free(tmp_ctx); + return true; +} + +/* + basic regression test for BUG 14769 + https://bugzilla.samba.org/show_bug.cgi?id=14769 +*/ +static bool test_ioctl_bug14769(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + const char *fname = "bug14769"; + bool ret = false; + struct smb2_handle h; + struct smb2_ioctl ioctl; + struct smb2_close cl; + struct smb2_request *smb2arr[2] = { 0 }; + uint8_t tosend_msec = 200; + DATA_BLOB send_buf = { &tosend_msec, 1 }; + + /* Create a test file. */ + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile(tree, fname, &h); + torture_assert_ntstatus_ok(torture, status, "create bug14769"); + + /* + * Send (not receive) the FSCTL. + * This should go async with a wait time of 200 msec. + */ + ZERO_STRUCT(ioctl); + ioctl.in.file.handle = h; + ioctl.in.function = FSCTL_SMBTORTURE_FSP_ASYNC_SLEEP; + ioctl.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + ioctl.in.out = send_buf; + + smb2arr[0] = smb2_ioctl_send(tree, &ioctl); + torture_assert_goto(torture, + smb2arr[0] != NULL, + ret, + done, + "smb2_ioctl_send failed\n"); + /* Immediately send the close. */ + ZERO_STRUCT(cl); + cl.in.file.handle = h; + cl.in.flags = 0; + smb2arr[1] = smb2_close_send(tree, &cl); + torture_assert_goto(torture, + smb2arr[1] != NULL, + ret, + done, + "smb2_close_send failed\n"); + + /* Now get the FSCTL reply. */ + /* + * If we suffer from bug #14769 this will fail as + * the ioctl will return with NT_STATUS_FILE_CLOSED, + * as the close will have closed the handle without + * waiting for the ioctl to complete. The server shouldn't + * complete the close until the ioctl finishes. + */ + status = smb2_ioctl_recv(smb2arr[0], tree, &ioctl); + torture_assert_ntstatus_ok_goto(torture, + status, + ret, + done, + "smb2_ioctl_recv failed\n"); + + /* Followed by the close reply. */ + status = smb2_close_recv(smb2arr[1], &cl); + torture_assert_ntstatus_ok_goto(torture, + status, + ret, + done, + "smb2_ioctl_close failed\n"); + ret = true; + + done: + smb2_util_unlink(tree, fname); + return ret; +} + +/* + basic regression test for BUG 14788, + with FSCTL_VALIDATE_NEGOTIATE_INFO + https://bugzilla.samba.org/show_bug.cgi?id=14788 +*/ +static bool test_ioctl_bug14788_VALIDATE_NEGOTIATE(struct torture_context *torture, + struct smb2_tree *tree0) +{ + const char *host = torture_setting_string(torture, "host", NULL); + const char *share = torture_setting_string(torture, "share", NULL); + const char *noperm_share = torture_setting_string(torture, "noperm_share", "noperm"); + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + struct smb2_session *session = NULL; + uint16_t noperm_flags = 0; + const char *noperm_unc = NULL; + struct smb2_tree *noperm_tree = NULL; + uint32_t timeout_msec; + struct tevent_req *subreq = NULL; + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(torture, "Can't test without SMB 3 support"); + } + + options = transport0->options; + options.client_guid = GUID_random(); + options.min_protocol = PROTOCOL_SMB3_00; + options.max_protocol = PROTOCOL_SMB3_02; + + status = smb2_connect(torture, + host, + lpcfg_smb_ports(torture->lp_ctx), + share, + lpcfg_resolve_context(torture->lp_ctx), + credentials, + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok(torture, status, "smb2_connect options failed"); + session = tree->session; + transport = session->transport; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + subreq = smb2cli_validate_negotiate_info_send(torture, + torture->ev, + transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli); + torture_assert(torture, + tevent_req_poll_ntstatus(subreq, torture->ev, &status), + "tevent_req_poll_ntstatus"); + status = smb2cli_validate_negotiate_info_recv(subreq); + torture_assert_ntstatus_ok(torture, status, "smb2cli_validate_negotiate_info"); + + noperm_unc = talloc_asprintf(torture, "\\\\%s\\%s", host, noperm_share); + torture_assert(torture, noperm_unc != NULL, "talloc_asprintf"); + + noperm_tree = smb2_tree_init(session, torture, false); + torture_assert(torture, noperm_tree != NULL, "smb2_tree_init"); + + status = smb2cli_raw_tcon(transport->conn, + SMB2_HDR_FLAG_SIGNED, + 0, /* clear_flags */ + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + noperm_flags, + noperm_unc); + if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_NETWORK_NAME)) { + torture_skip(torture, talloc_asprintf(torture, + "noperm_unc[%s] %s", + noperm_unc, nt_errstr(status))); + } + torture_assert_ntstatus_ok(torture, status, + talloc_asprintf(torture, + "smb2cli_tcon(%s)", + noperm_unc)); + + subreq = smb2cli_validate_negotiate_info_send(torture, + torture->ev, + transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli); + torture_assert(torture, + tevent_req_poll_ntstatus(subreq, torture->ev, &status), + "tevent_req_poll_ntstatus"); + status = smb2cli_validate_negotiate_info_recv(subreq); + torture_assert_ntstatus_ok(torture, status, "smb2cli_validate_negotiate_info noperm"); + + return true; +} + +/* + basic regression test for BUG 14788, + with FSCTL_QUERY_NETWORK_INTERFACE_INFO + https://bugzilla.samba.org/show_bug.cgi?id=14788 +*/ +static bool test_ioctl_bug14788_NETWORK_INTERFACE(struct torture_context *torture, + struct smb2_tree *tree0) +{ + const char *host = torture_setting_string(torture, "host", NULL); + const char *share = torture_setting_string(torture, "share", NULL); + const char *noperm_share = torture_setting_string(torture, "noperm_share", "noperm"); + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + struct smb2_session *session = NULL; + uint16_t noperm_flags = 0; + const char *noperm_unc = NULL; + struct smb2_tree *noperm_tree = NULL; + uint32_t timeout_msec; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(torture, "Can't test without SMB 3 support"); + } + + options = transport0->options; + options.client_guid = GUID_random(); + options.min_protocol = PROTOCOL_SMB3_00; + options.max_protocol = PROTOCOL_SMB3_02; + + status = smb2_connect(torture, + host, + lpcfg_smb_ports(torture->lp_ctx), + share, + lpcfg_resolve_context(torture->lp_ctx), + credentials, + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok(torture, status, "smb2_connect options failed"); + session = tree->session; + transport = session->transport; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + /* + * A valid FSCTL_QUERY_NETWORK_INTERFACE_INFO + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* is being UINT64_MAX and + * in_max_output_length = 1. + * + * This demonstrates NT_STATUS_BUFFER_TOO_SMALL + * if the server is not able to return the + * whole response buffer to the client. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_BUFFER_TOO_SMALL, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX. + * + * This gives INVALID_PARAMETER instead + * of FILE_CLOSED. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX and + * in_max_output_length = 1. + * + * This proves INVALID_PARAMETER instead + * of BUFFER_TOO_SMALL. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + noperm_unc = talloc_asprintf(torture, "\\\\%s\\%s", host, noperm_share); + torture_assert(torture, noperm_unc != NULL, "talloc_asprintf"); + + noperm_tree = smb2_tree_init(session, torture, false); + torture_assert(torture, noperm_tree != NULL, "smb2_tree_init"); + + status = smb2cli_raw_tcon(transport->conn, + SMB2_HDR_FLAG_SIGNED, + 0, /* clear_flags */ + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + noperm_flags, + noperm_unc); + if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_NETWORK_NAME)) { + torture_skip(torture, talloc_asprintf(torture, + "noperm_unc[%s] %s", + noperm_unc, nt_errstr(status))); + } + torture_assert_ntstatus_ok(torture, status, + talloc_asprintf(torture, + "smb2cli_tcon(%s)", + noperm_unc)); + + /* + * A valid FSCTL_QUERY_NETWORK_INTERFACE_INFO + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* is being UINT64_MAX and + * in_max_output_length = 1. + * + * This demonstrates NT_STATUS_BUFFER_TOO_SMALL + * if the server is not able to return the + * whole response buffer to the client. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_BUFFER_TOO_SMALL, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX. + * + * This gives INVALID_PARAMETER instead + * of FILE_CLOSED. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX and + * in_max_output_length = 1. + * + * This proves INVALID_PARAMETER instead + * of BUFFER_TOO_SMALL. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + return true; +} + +/* + * testing of SMB2 ioctls + */ +struct torture_suite *torture_smb2_ioctl_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "ioctl"); + struct torture_suite *bug14788 = torture_suite_create(ctx, "bug14788"); + + torture_suite_add_1smb2_test(suite, "shadow_copy", + test_ioctl_get_shadow_copy); + torture_suite_add_1smb2_test(suite, "req_resume_key", + test_ioctl_req_resume_key); + torture_suite_add_1smb2_test(suite, "req_two_resume_keys", + test_ioctl_req_two_resume_keys); + torture_suite_add_1smb2_test(suite, "copy_chunk_simple", + test_ioctl_copy_chunk_simple); + torture_suite_add_1smb2_test(suite, "copy_chunk_multi", + test_ioctl_copy_chunk_multi); + torture_suite_add_1smb2_test(suite, "copy_chunk_tiny", + test_ioctl_copy_chunk_tiny); + torture_suite_add_1smb2_test(suite, "copy_chunk_overwrite", + test_ioctl_copy_chunk_over); + torture_suite_add_1smb2_test(suite, "copy_chunk_append", + test_ioctl_copy_chunk_append); + torture_suite_add_1smb2_test(suite, "copy_chunk_limits", + test_ioctl_copy_chunk_limits); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_lock", + test_ioctl_copy_chunk_src_lck); + torture_suite_add_1smb2_test(suite, "copy_chunk_dest_lock", + test_ioctl_copy_chunk_dest_lck); + torture_suite_add_1smb2_test(suite, "copy_chunk_bad_key", + test_ioctl_copy_chunk_bad_key); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_is_dest", + test_ioctl_copy_chunk_src_is_dest); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_is_dest_overlap", + test_ioctl_copy_chunk_src_is_dest_overlap); + torture_suite_add_1smb2_test(suite, "copy_chunk_bad_access", + test_ioctl_copy_chunk_bad_access); + torture_suite_add_1smb2_test(suite, "copy_chunk_write_access", + test_ioctl_copy_chunk_write_access); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_exceed", + test_ioctl_copy_chunk_src_exceed); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_exceed_multi", + test_ioctl_copy_chunk_src_exceed_multi); + torture_suite_add_1smb2_test(suite, "copy_chunk_sparse_dest", + test_ioctl_copy_chunk_sparse_dest); + torture_suite_add_1smb2_test(suite, "copy_chunk_max_output_sz", + test_ioctl_copy_chunk_max_output_sz); + torture_suite_add_1smb2_test(suite, "copy_chunk_zero_length", + test_ioctl_copy_chunk_zero_length); + torture_suite_add_1smb2_test(suite, "copy-chunk streams", + test_copy_chunk_streams); + torture_suite_add_1smb2_test(suite, "copy_chunk_across_shares", + test_copy_chunk_across_shares); + torture_suite_add_1smb2_test(suite, "copy_chunk_across_shares2", + test_copy_chunk_across_shares2); + torture_suite_add_1smb2_test(suite, "copy_chunk_across_shares3", + test_copy_chunk_across_shares3); + torture_suite_add_1smb2_test(suite, "compress_file_flag", + test_ioctl_compress_file_flag); + torture_suite_add_1smb2_test(suite, "compress_dir_inherit", + test_ioctl_compress_dir_inherit); + torture_suite_add_1smb2_test(suite, "compress_invalid_format", + test_ioctl_compress_invalid_format); + torture_suite_add_1smb2_test(suite, "compress_invalid_buf", + test_ioctl_compress_invalid_buf); + torture_suite_add_1smb2_test(suite, "compress_query_file_attr", + test_ioctl_compress_query_file_attr); + torture_suite_add_1smb2_test(suite, "compress_create_with_attr", + test_ioctl_compress_create_with_attr); + torture_suite_add_1smb2_test(suite, "compress_inherit_disable", + test_ioctl_compress_inherit_disable); + torture_suite_add_1smb2_test(suite, "compress_set_file_attr", + test_ioctl_compress_set_file_attr); + torture_suite_add_1smb2_test(suite, "compress_perms", + test_ioctl_compress_perms); + torture_suite_add_1smb2_test(suite, "compress_notsup_get", + test_ioctl_compress_notsup_get); + torture_suite_add_1smb2_test(suite, "compress_notsup_set", + test_ioctl_compress_notsup_set); + torture_suite_add_1smb2_test(suite, "network_interface_info", + test_ioctl_network_interface_info); + torture_suite_add_1smb2_test(suite, "sparse_file_flag", + test_ioctl_sparse_file_flag); + torture_suite_add_1smb2_test(suite, "sparse_file_attr", + test_ioctl_sparse_file_attr); + torture_suite_add_1smb2_test(suite, "sparse_dir_flag", + test_ioctl_sparse_dir_flag); + torture_suite_add_1smb2_test(suite, "sparse_set_nobuf", + test_ioctl_sparse_set_nobuf); + torture_suite_add_1smb2_test(suite, "sparse_set_oversize", + test_ioctl_sparse_set_oversize); + torture_suite_add_1smb2_test(suite, "sparse_qar", + test_ioctl_sparse_qar); + torture_suite_add_1smb2_test(suite, "sparse_qar_malformed", + test_ioctl_sparse_qar_malformed); + torture_suite_add_1smb2_test(suite, "sparse_punch", + test_ioctl_sparse_punch); + torture_suite_add_1smb2_test(suite, "sparse_hole_dealloc", + test_ioctl_sparse_hole_dealloc); + torture_suite_add_1smb2_test(suite, "sparse_compressed", + test_ioctl_sparse_compressed); + torture_suite_add_1smb2_test(suite, "sparse_copy_chunk", + test_ioctl_sparse_copy_chunk); + torture_suite_add_1smb2_test(suite, "sparse_punch_invalid", + test_ioctl_sparse_punch_invalid); + torture_suite_add_1smb2_test(suite, "sparse_perms", + test_ioctl_sparse_perms); + torture_suite_add_1smb2_test(suite, "sparse_lock", + test_ioctl_sparse_lck); + torture_suite_add_1smb2_test(suite, "sparse_qar_ob1", + test_ioctl_sparse_qar_ob1); + torture_suite_add_1smb2_test(suite, "sparse_qar_multi", + test_ioctl_sparse_qar_multi); + torture_suite_add_1smb2_test(suite, "sparse_qar_overflow", + 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); + torture_suite_add_1smb2_test(suite, "bug14607", + test_ioctl_bug14607); + torture_suite_add_1smb2_test(suite, "bug14769", + test_ioctl_bug14769); + + torture_suite_add_1smb2_test(bug14788, "VALIDATE_NEGOTIATE", + test_ioctl_bug14788_VALIDATE_NEGOTIATE); + torture_suite_add_1smb2_test(bug14788, "NETWORK_INTERFACE", + test_ioctl_bug14788_NETWORK_INTERFACE); + torture_suite_add_suite(suite, bug14788); + + suite->description = talloc_strdup(suite, "SMB2-IOCTL tests"); + + return suite; +} + |