/*
   Unix SMB/CIFS implementation.

   test suite for SMB2 ioctl operations

   Copyright (C) David Disseldorp 2011-2024

   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
 * specific 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;
}

static bool test_ioctl_sparse_qar_truncated(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;
	struct file_alloced_range_buf far_rsp;

	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_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true);
	torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE");

	/*
	 * Write 0 and 1M offsets as (hopefully) two separate extents.
	 * XXX this test assumes that these ranges will be recorded as separate
	 * FSCTL_QUERY_ALLOCATED_RANGES extents, which isn't strictly required:
	 * the spec basically says the FS can do what it wants as long as
	 * non-zeroed data ranges aren't reported as sparse holes.
	 */
	ok = write_pattern(torture, tree, tmp_ctx, fh,
			   0,		/* off */
			   1024,	/* len */
			   0);		/* pattern offset */
	torture_assert(torture, ok, "write pattern");
	ok = write_pattern(torture, tree, tmp_ctx, fh,
			   1024 * 1024,	/* off */
			   1024,	/* len */
			   0);		/* pattern offset */
	torture_assert(torture, ok, "write pattern");

	/* qar max output enough to carry one range, should be truncated */
	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 = sizeof(struct file_alloced_range_buf);
	ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;

	far_buf.file_off = 0;
	far_buf.len = 2048 * 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_equal(torture, status,
				      STATUS_BUFFER_OVERFLOW, "qar truncated");
	torture_assert_size_equal(torture,
				  ioctl.smb2.out.out.length, sizeof(far_buf),
				  "qar outlen");
	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
				       &far_rsp,
			(ndr_pull_flags_fn_t)ndr_pull_file_alloced_range_buf);
	torture_assert_ndr_success(torture, ndr_ret, "pull far range");
	torture_assert_u64_equal(torture, far_rsp.file_off, 0, "far offset");
	/* length depends on allocation behaviour of FS, so allow range */
	torture_assert(torture, far_rsp.len >= 1024, "far len");

	/* qar max output for just under 2 ranges, should be truncated */
	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 = 31;
	ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;

	far_buf.file_off = 0;
	far_buf.len = 2048 * 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_equal(torture, status,
				      STATUS_BUFFER_OVERFLOW, "qar truncated");
	torture_assert_size_equal(torture,
				  ioctl.smb2.out.out.length, sizeof(far_buf),
				  "qar outlen");
	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
				       &far_rsp,
			(ndr_pull_flags_fn_t)ndr_pull_file_alloced_range_buf);
	torture_assert_ndr_success(torture, ndr_ret, "pull far range");
	torture_assert_u64_equal(torture, far_rsp.file_off, 0, "far offset");
	torture_assert(torture, far_rsp.len >= 1024, "far len");

	/* qar max output for 2 ranges, 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 = 32;
	ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;

	far_buf.file_off = 0;
	far_buf.len = 2048 * 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, "qar non-truncated");
	torture_assert_size_equal(torture,
				  ioctl.smb2.out.out.length,
				  2 * sizeof(far_buf), "qar outlen");
	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
				       &far_rsp,
			(ndr_pull_flags_fn_t)ndr_pull_file_alloced_range_buf);
	torture_assert_ndr_success(torture, ndr_ret, "pull far range");
	torture_assert_u64_equal(torture, far_rsp.file_off, 0, "far offset");
	torture_assert(torture, far_rsp.len >= 1024, "far len");
	/* move to next buffer */
	ioctl.smb2.out.out.data += sizeof(far_buf);
	ioctl.smb2.out.out.length -= sizeof(far_buf);
	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
				       &far_rsp,
			(ndr_pull_flags_fn_t)ndr_pull_file_alloced_range_buf);
	torture_assert_ndr_success(torture, ndr_ret, "pull far range");
	torture_assert_u64_equal(torture, far_rsp.file_off, 1024 * 1024,
				 "far offset");
	torture_assert(torture, far_rsp.len >= 1024, "far len");

	smb2_util_close(tree, fh);
	talloc_free(tmp_ctx);
	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_SET_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 consistent */
	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,
			      share,
			      torture->lp_ctx,
			      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,
			      share,
			      torture->lp_ctx,
			      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;
}

/*
 * basic regression test for BUG 15664
 * https://bugzilla.samba.org/show_bug.cgi?id=15664
 */
static bool test_ioctl_copy_chunk_bug15644(struct torture_context *torture,
					   struct smb2_tree *tree)
{
	struct smb2_handle dest_h;
	NTSTATUS status;
	union smb_ioctl ioctl;
	TALLOC_CTX *tmp_ctx = talloc_new(tree);
	struct srv_copychunk chunk;
	struct srv_copychunk_copy cc_copy;
	enum ndr_err_code ndr_ret;
	bool ok;

	ok = test_setup_create_fill(torture,
				    tree,
				    tmp_ctx,
				    FNAME2,
				    &dest_h,
				    0,
				    SEC_RIGHTS_FILE_ALL,
				    FILE_ATTRIBUTE_NORMAL);
	torture_assert(torture, ok, "dest file create fill");

	ZERO_STRUCT(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_STRUCT(chunk);
	ZERO_STRUCT(cc_copy);
	/* overwrite the resume key with a bogus value */
	memcpy(cc_copy.source_key, "deadbeefdeadbeefdeadbeef", 24);
	cc_copy.chunk_count = 1;
	cc_copy.chunks = &chunk;
	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");

	status = smb2_util_close(tree, dest_h);
	torture_assert_ntstatus_ok(torture, status, "close");

	talloc_free(tmp_ctx);
	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_bug15644",
				     test_ioctl_copy_chunk_bug15644);
	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_qar_truncated",
				     test_ioctl_sparse_qar_truncated);
	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;
}

