155f0a249SGordon Ross /*
255f0a249SGordon Ross  * This file and its contents are supplied under the terms of the
355f0a249SGordon Ross  * Common Development and Distribution License ("CDDL"), version 1.0.
455f0a249SGordon Ross  * You may only use this file in accordance with the terms of version
555f0a249SGordon Ross  * 1.0 of the CDDL.
655f0a249SGordon Ross  *
755f0a249SGordon Ross  * A full copy of the text of the CDDL should have accompanied this
855f0a249SGordon Ross  * source.  A copy of the CDDL is also available via the Internet at
955f0a249SGordon Ross  * http://www.illumos.org/license/CDDL.
1055f0a249SGordon Ross  */
1155f0a249SGordon Ross 
1255f0a249SGordon Ross /*
13*1baeef30SPrashanth Badari  * Copyright 2020 Tintri by DDN, Inc.  All rights reserved.
1455f0a249SGordon Ross  */
1555f0a249SGordon Ross 
1655f0a249SGordon Ross /*
1755f0a249SGordon Ross  * Support functions for smb2_ioctl/fsctl codes:
1855f0a249SGordon Ross  * FSCTL_SET_SPARSE
1955f0a249SGordon Ross  * FSCTL_SET_ZERO_DATA
2055f0a249SGordon Ross  * FSCTL_QUERY_ALLOCATED_RANGES
2155f0a249SGordon Ross  */
2255f0a249SGordon Ross 
2355f0a249SGordon Ross #include <smbsrv/smb2_kproto.h>
2455f0a249SGordon Ross #include <smbsrv/smb_fsops.h>
2555f0a249SGordon Ross #include <smb/winioctl.h>
2655f0a249SGordon Ross 
2755f0a249SGordon Ross /*
2855f0a249SGordon Ross  * FSCTL_SET_SPARSE
2955f0a249SGordon Ross  *
3055f0a249SGordon Ross  * In args: one byte flag (optional: default TRUE)
3155f0a249SGordon Ross  */
3255f0a249SGordon Ross uint32_t
smb2_fsctl_set_sparse(smb_request_t * sr,smb_fsctl_t * fsctl)3355f0a249SGordon Ross smb2_fsctl_set_sparse(smb_request_t *sr, smb_fsctl_t *fsctl)
3455f0a249SGordon Ross {
3555f0a249SGordon Ross 	smb_attr_t attr;
3655f0a249SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
3755f0a249SGordon Ross 	cred_t *kcr;
3855f0a249SGordon Ross 	uint32_t amask;
3955f0a249SGordon Ross 	uint32_t status;
4055f0a249SGordon Ross 	uint8_t flag;
4155f0a249SGordon Ross 	int rc;
4255f0a249SGordon Ross 
4355f0a249SGordon Ross 	rc = smb_mbc_decodef(fsctl->in_mbc, "b", &flag);
4455f0a249SGordon Ross 	if (rc != 0)
4555f0a249SGordon Ross 		flag = 0xff;
4655f0a249SGordon Ross 
4755f0a249SGordon Ross 	if (!smb_node_is_file(ofile->f_node))
4855f0a249SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
4955f0a249SGordon Ross 
5055f0a249SGordon Ross 	/*
5155f0a249SGordon Ross 	 * Allow if we have any of FILE_WRITE_ATTRIBUTES,
5255f0a249SGordon Ross 	 * FILE_WRITE_DATA, FILE_APPEND_DATA
5355f0a249SGordon Ross 	 */
5455f0a249SGordon Ross 	amask = FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_APPEND_DATA;
5555f0a249SGordon Ross 	if ((ofile->f_granted_access & amask) == 0)
5655f0a249SGordon Ross 		return (NT_STATUS_ACCESS_DENIED);
5755f0a249SGordon Ross 
5855f0a249SGordon Ross 	/*
5955f0a249SGordon Ross 	 * Need the current DOS attributes
6055f0a249SGordon Ross 	 */
6155f0a249SGordon Ross 	bzero(&attr, sizeof (attr));
6255f0a249SGordon Ross 	attr.sa_mask = SMB_AT_DOSATTR;
6355f0a249SGordon Ross 	kcr = zone_kcred();
6455f0a249SGordon Ross 	status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
6555f0a249SGordon Ross 	if (status != NT_STATUS_SUCCESS)
6655f0a249SGordon Ross 		return (status);
6755f0a249SGordon Ross 
6855f0a249SGordon Ross 	if (flag != 0) {
6955f0a249SGordon Ross 		/* Set "sparse" */
7055f0a249SGordon Ross 		if (attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE)
7155f0a249SGordon Ross 			return (0);
7255f0a249SGordon Ross 		attr.sa_dosattr |= FILE_ATTRIBUTE_SPARSE_FILE;
7355f0a249SGordon Ross 	} else {
7455f0a249SGordon Ross 		/* Clear "sparse" */
7555f0a249SGordon Ross 		if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0)
7655f0a249SGordon Ross 			return (0);
7755f0a249SGordon Ross 		attr.sa_dosattr &= ~FILE_ATTRIBUTE_SPARSE_FILE;
7855f0a249SGordon Ross 	}
7955f0a249SGordon Ross 
8055f0a249SGordon Ross 	attr.sa_mask = SMB_AT_DOSATTR;
8155f0a249SGordon Ross 	status = smb_node_setattr(sr, ofile->f_node, kcr, ofile, &attr);
8255f0a249SGordon Ross 	return (status);
8355f0a249SGordon Ross }
8455f0a249SGordon Ross 
8555f0a249SGordon Ross /*
8655f0a249SGordon Ross  * FSCTL_SET_ZERO_DATA
8755f0a249SGordon Ross  *
8855f0a249SGordon Ross  * In args: uint64_t start_off, end_off
8955f0a249SGordon Ross  */
9055f0a249SGordon Ross uint32_t
smb2_fsctl_set_zero_data(smb_request_t * sr,smb_fsctl_t * fsctl)9155f0a249SGordon Ross smb2_fsctl_set_zero_data(smb_request_t *sr, smb_fsctl_t *fsctl)
9255f0a249SGordon Ross {
9355f0a249SGordon Ross 	smb_attr_t attr;
9455f0a249SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
9555f0a249SGordon Ross 	uint64_t start_off, end_off, zero_len;
9655f0a249SGordon Ross 	uint32_t status;
9755f0a249SGordon Ross 	int rc;
9855f0a249SGordon Ross 
9955f0a249SGordon Ross 	rc = smb_mbc_decodef(fsctl->in_mbc, "qq",
10055f0a249SGordon Ross 	    &start_off, &end_off);
10155f0a249SGordon Ross 	if (rc != 0)
10255f0a249SGordon Ross 		return (NT_STATUS_BUFFER_TOO_SMALL);
10355f0a249SGordon Ross 
10455f0a249SGordon Ross 	/*
10555f0a249SGordon Ross 	 * The given offsets are actually int64_t (signed).
10655f0a249SGordon Ross 	 */
10755f0a249SGordon Ross 	if (start_off > INT64_MAX ||
10855f0a249SGordon Ross 	    end_off > INT64_MAX ||
10955f0a249SGordon Ross 	    start_off > end_off)
11055f0a249SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
11155f0a249SGordon Ross 
11255f0a249SGordon Ross 	if (!smb_node_is_file(ofile->f_node))
11355f0a249SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
11455f0a249SGordon Ross 
11555f0a249SGordon Ross 	/*
11655f0a249SGordon Ross 	 * This operation is effectively a write (of zeros)
11755f0a249SGordon Ross 	 */
11855f0a249SGordon Ross 	status = smb_ofile_access(ofile, ofile->f_cr, FILE_WRITE_DATA);
11955f0a249SGordon Ross 	if (status != NT_STATUS_SUCCESS)
12055f0a249SGordon Ross 		return (status);
12155f0a249SGordon Ross 
12255f0a249SGordon Ross 	/*
12355f0a249SGordon Ross 	 * Need the file size
12455f0a249SGordon Ross 	 */
12555f0a249SGordon Ross 	bzero(&attr, sizeof (attr));
12655f0a249SGordon Ross 	attr.sa_mask = SMB_AT_SIZE;
12755f0a249SGordon Ross 	status = smb_node_getattr(sr, ofile->f_node, ofile->f_cr,
12855f0a249SGordon Ross 	    ofile, &attr);
12955f0a249SGordon Ross 	if (status != NT_STATUS_SUCCESS)
13055f0a249SGordon Ross 		return (status);
13155f0a249SGordon Ross 
13255f0a249SGordon Ross 	/*
13355f0a249SGordon Ross 	 * Ignore any zero-ing beyond EOF
13455f0a249SGordon Ross 	 */
13555f0a249SGordon Ross 	if (end_off > attr.sa_vattr.va_size)
13655f0a249SGordon Ross 		end_off = attr.sa_vattr.va_size;
13755f0a249SGordon Ross 	if (start_off >= end_off)
13855f0a249SGordon Ross 		return (0);
13955f0a249SGordon Ross 	zero_len = end_off - start_off;
14055f0a249SGordon Ross 
14155f0a249SGordon Ross 	/*
14255f0a249SGordon Ross 	 * Check for lock conflicting with the write.
14355f0a249SGordon Ross 	 */
14455f0a249SGordon Ross 	status = smb_lock_range_access(sr, ofile->f_node,
14555f0a249SGordon Ross 	    start_off, zero_len, B_TRUE);
14655f0a249SGordon Ross 	if (status != 0)
14755f0a249SGordon Ross 		return (status); /* == FILE_LOCK_CONFLICT */
14855f0a249SGordon Ross 
14955f0a249SGordon Ross 	rc = smb_fsop_freesp(sr, ofile->f_cr, ofile,
15055f0a249SGordon Ross 	    start_off, zero_len);
15155f0a249SGordon Ross 	if (rc != 0)
15255f0a249SGordon Ross 		status = smb_errno2status(rc);
15355f0a249SGordon Ross 
15455f0a249SGordon Ross 	return (status);
15555f0a249SGordon Ross }
15655f0a249SGordon Ross 
15755f0a249SGordon Ross /*
15855f0a249SGordon Ross  * FSCTL_QUERY_ALLOCATED_RANGES
15955f0a249SGordon Ross  *
16055f0a249SGordon Ross  * Incoming args: uint64_t start_off, end_off
16155f0a249SGordon Ross  */
16255f0a249SGordon Ross struct alloc_range {
16355f0a249SGordon Ross 	off64_t off;
16455f0a249SGordon Ross 	off64_t len;
16555f0a249SGordon Ross };
16655f0a249SGordon Ross uint32_t
smb2_fsctl_query_alloc_ranges(smb_request_t * sr,smb_fsctl_t * fsctl)16755f0a249SGordon Ross smb2_fsctl_query_alloc_ranges(smb_request_t *sr, smb_fsctl_t *fsctl)
16855f0a249SGordon Ross {
16955f0a249SGordon Ross 	smb_attr_t attr;
17055f0a249SGordon Ross 	cred_t *kcr;
17155f0a249SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
17255f0a249SGordon Ross 	struct alloc_range arg, res;
17355f0a249SGordon Ross 	off64_t cur_off, end_off;
17455f0a249SGordon Ross 	uint32_t status;
17555f0a249SGordon Ross 	int err, rc;
17655f0a249SGordon Ross 
17755f0a249SGordon Ross 	/*
17855f0a249SGordon Ross 	 * Most ioctls return NT_STATUS_BUFFER_TOO_SMALL for
17955f0a249SGordon Ross 	 * short in/out buffers, but for this one, MS-FSA
18055f0a249SGordon Ross 	 * says short input returns invalid parameter.
18155f0a249SGordon Ross 	 */
18255f0a249SGordon Ross 	rc = smb_mbc_decodef(fsctl->in_mbc, "qq", &arg.off, &arg.len);
18355f0a249SGordon Ross 	if (rc != 0)
18455f0a249SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
18555f0a249SGordon Ross 
18655f0a249SGordon Ross 	/*
18755f0a249SGordon Ross 	 * The given offsets are actually int64_t (signed).
18855f0a249SGordon Ross 	 */
18955f0a249SGordon Ross 	end_off = arg.off + arg.len;
19055f0a249SGordon Ross 	if (arg.off > INT64_MAX || arg.len < 0 ||
19155f0a249SGordon Ross 	    end_off > INT64_MAX || end_off < arg.off)
19255f0a249SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
19355f0a249SGordon Ross 
19455f0a249SGordon Ross 	if (!smb_node_is_file(ofile->f_node))
19555f0a249SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
19655f0a249SGordon Ross 
19755f0a249SGordon Ross 	/*
19855f0a249SGordon Ross 	 * This operation is effectively a read
19955f0a249SGordon Ross 	 */
20055f0a249SGordon Ross 	status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
20155f0a249SGordon Ross 	if (status != NT_STATUS_SUCCESS)
20255f0a249SGordon Ross 		return (status);
20355f0a249SGordon Ross 
20455f0a249SGordon Ross 	if (arg.len == 0) {
20555f0a249SGordon Ross 		/* MS-FSA says empty result for this. */
20655f0a249SGordon Ross 		return (0);
20755f0a249SGordon Ross 	}
20855f0a249SGordon Ross 
20955f0a249SGordon Ross 	/*
21055f0a249SGordon Ross 	 * Need the file size and dosattr
21155f0a249SGordon Ross 	 */
21255f0a249SGordon Ross 	bzero(&attr, sizeof (attr));
21355f0a249SGordon Ross 	attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
21455f0a249SGordon Ross 	kcr = zone_kcred();
21555f0a249SGordon Ross 	status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
21655f0a249SGordon Ross 	if (status != NT_STATUS_SUCCESS)
21755f0a249SGordon Ross 		return (status);
21855f0a249SGordon Ross 	if (end_off > attr.sa_vattr.va_size)
21955f0a249SGordon Ross 		end_off = attr.sa_vattr.va_size;
22055f0a249SGordon Ross 
22155f0a249SGordon Ross 	/*
22255f0a249SGordon Ross 	 * Only sparse files should present un-allocated ranges.
22355f0a249SGordon Ross 	 * If non-sparse, MS-FSA says (just return one range).
22455f0a249SGordon Ross 	 */
22555f0a249SGordon Ross 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
22655f0a249SGordon Ross 		if (arg.off < end_off) {
22755f0a249SGordon Ross 			res.off = arg.off;
22855f0a249SGordon Ross 			res.len = end_off - arg.off;
22955f0a249SGordon Ross 			rc = smb_mbc_encodef(fsctl->out_mbc, "qq",
23055f0a249SGordon Ross 			    res.off, res.len);
23155f0a249SGordon Ross 			if (rc != 0)
23255f0a249SGordon Ross 				return (NT_STATUS_BUFFER_TOO_SMALL);
23355f0a249SGordon Ross 		}
23455f0a249SGordon Ross 		return (0);
23555f0a249SGordon Ross 	}
23655f0a249SGordon Ross 
23755f0a249SGordon Ross 	cur_off = arg.off;
23855f0a249SGordon Ross 	while (cur_off < end_off) {
23955f0a249SGordon Ross 		off64_t data, hole;
24055f0a249SGordon Ross 
24155f0a249SGordon Ross 		data = cur_off;
24255f0a249SGordon Ross 		err = smb_fsop_next_alloc_range(kcr, ofile->f_node,
24355f0a249SGordon Ross 		    &data, &hole);
24455f0a249SGordon Ross 		if (err != 0)
24555f0a249SGordon Ross 			break;
24655f0a249SGordon Ross 
24755f0a249SGordon Ross 		/* sanity check data (ensure progress) */
24855f0a249SGordon Ross 		if (data < cur_off) {
24955f0a249SGordon Ross 			ASSERT(0);
25055f0a249SGordon Ross 			data = cur_off;
25155f0a249SGordon Ross 		}
25255f0a249SGordon Ross 
25355f0a249SGordon Ross 		/* Normal termination */
25455f0a249SGordon Ross 		if (data >= end_off)
25555f0a249SGordon Ross 			break;
25655f0a249SGordon Ross 
25755f0a249SGordon Ross 		/* sanity check hole (ensure progress) */
25855f0a249SGordon Ross 		if (hole <= data)
25955f0a249SGordon Ross 			hole = end_off;
26055f0a249SGordon Ross 
26155f0a249SGordon Ross 		/* Trim this range as needed. */
26255f0a249SGordon Ross 		if (hole > end_off)
26355f0a249SGordon Ross 			hole = end_off;
26455f0a249SGordon Ross 
26555f0a249SGordon Ross 		res.off = data;
26655f0a249SGordon Ross 		res.len = hole - data;
26755f0a249SGordon Ross 
26855f0a249SGordon Ross 		if (res.len > 0) {
26955f0a249SGordon Ross 			rc = smb_mbc_encodef(fsctl->out_mbc, "qq",
27055f0a249SGordon Ross 			    res.off, res.len);
27155f0a249SGordon Ross 			if (rc != 0)
27255f0a249SGordon Ross 				return (NT_STATUS_BUFFER_TOO_SMALL);
27355f0a249SGordon Ross 		}
27455f0a249SGordon Ross 
27555f0a249SGordon Ross 		cur_off = hole;
27655f0a249SGordon Ross 	}
27755f0a249SGordon Ross 
27855f0a249SGordon Ross 	return (0);
27955f0a249SGordon Ross }
28055f0a249SGordon Ross 
28155f0a249SGordon Ross /*
28255f0a249SGordon Ross  * Copy a segment of a file, preserving sparseness.
28355f0a249SGordon Ross  * Uses a caller-provided buffer for read/write.
28455f0a249SGordon Ross  * Caller should already have checked for locks.
28555f0a249SGordon Ross  *
28655f0a249SGordon Ross  * On entry, *residp is the length to copy.
28755f0a249SGordon Ross  * On return, it's the "resid" (amount not copied)
28855f0a249SGordon Ross  *
28955f0a249SGordon Ross  * If this gets an error from any I/O, return it, even if some data
29055f0a249SGordon Ross  * have already been copied.  The caller should normally ignore an
29155f0a249SGordon Ross  * error when some data have been copied.
29255f0a249SGordon Ross  */
29355f0a249SGordon Ross uint32_t
smb2_sparse_copy(smb_request_t * sr,smb_ofile_t * src_ofile,smb_ofile_t * dst_ofile,off64_t src_off,off64_t dst_off,uint32_t * residp,void * buffer,size_t bufsize)29455f0a249SGordon Ross smb2_sparse_copy(
29555f0a249SGordon Ross 	smb_request_t *sr,
29655f0a249SGordon Ross 	smb_ofile_t *src_ofile, smb_ofile_t *dst_ofile,
29755f0a249SGordon Ross 	off64_t src_off, off64_t dst_off, uint32_t *residp,
29855f0a249SGordon Ross 	void *buffer, size_t bufsize)
29955f0a249SGordon Ross {
30055f0a249SGordon Ross 	iovec_t iov;
30155f0a249SGordon Ross 	uio_t uio;
30255f0a249SGordon Ross 	off64_t data, hole;
30355f0a249SGordon Ross 	uint32_t xfer;
30455f0a249SGordon Ross 	uint32_t status = 0;
30555f0a249SGordon Ross 	int rc;
30655f0a249SGordon Ross 
30755f0a249SGordon Ross 	while (*residp > 0) {
30855f0a249SGordon Ross 
309*1baeef30SPrashanth Badari 		if (sr->sr_state != SMB_REQ_STATE_ACTIVE)
310*1baeef30SPrashanth Badari 			break;
311*1baeef30SPrashanth Badari 
31255f0a249SGordon Ross 		data = src_off;
31355f0a249SGordon Ross 		rc = smb_fsop_next_alloc_range(src_ofile->f_cr,
31455f0a249SGordon Ross 		    src_ofile->f_node, &data, &hole);
31555f0a249SGordon Ross 		switch (rc) {
31655f0a249SGordon Ross 		case 0:
31755f0a249SGordon Ross 			/* Found data, hole */
31855f0a249SGordon Ross 			break;
31955f0a249SGordon Ross 		case ENXIO:
32055f0a249SGordon Ross 			/* No data after here (will skip below). */
32155f0a249SGordon Ross 			data = hole = (src_off + *residp);
32255f0a249SGordon Ross 			break;
32355f0a249SGordon Ross 		default:
32455f0a249SGordon Ross 			cmn_err(CE_NOTE,
32555f0a249SGordon Ross 			    "smb_fsop_next_alloc_range: rc=%d", rc);
32655f0a249SGordon Ross 			/* FALLTHROUGH */
32755f0a249SGordon Ross 		case ENOSYS:	/* FS does not support VOP_IOCTL... */
32855f0a249SGordon Ross 		case ENOTTY:	/* ... or _FIO_SEEK_DATA, _HOLE */
32955f0a249SGordon Ross 			data = src_off;
33055f0a249SGordon Ross 			hole = src_off + *residp;
33155f0a249SGordon Ross 			break;
33255f0a249SGordon Ross 		}
33355f0a249SGordon Ross 
33455f0a249SGordon Ross 		/*
33555f0a249SGordon Ross 		 * Don't try to go past (src_off + *residp)
33655f0a249SGordon Ross 		 */
33755f0a249SGordon Ross 		if (hole > (src_off + *residp))
33855f0a249SGordon Ross 			hole = src_off + *residp;
33955f0a249SGordon Ross 		if (data > hole)
34055f0a249SGordon Ross 			data = hole;
34155f0a249SGordon Ross 
34255f0a249SGordon Ross 		/*
34355f0a249SGordon Ross 		 * If there's a gap (src_off .. data)
34455f0a249SGordon Ross 		 * skip in src_ofile, zero in dst_ofile
34555f0a249SGordon Ross 		 */
34655f0a249SGordon Ross 		if (src_off < data) {
34755f0a249SGordon Ross 			off64_t skip = data - src_off;
34855f0a249SGordon Ross 			rc = smb_fsop_freesp(sr, dst_ofile->f_cr,
34955f0a249SGordon Ross 			    dst_ofile, dst_off, skip);
35055f0a249SGordon Ross 			if (rc == 0) {
35155f0a249SGordon Ross 				src_off += skip;
35255f0a249SGordon Ross 				dst_off += skip;
35355f0a249SGordon Ross 				*residp -= (uint32_t)skip;
35455f0a249SGordon Ross 			} else {
35555f0a249SGordon Ross 				/* Fall back to regular copy */
35655f0a249SGordon Ross 				data = src_off;
35755f0a249SGordon Ross 			}
35855f0a249SGordon Ross 		}
35955f0a249SGordon Ross 		ASSERT(src_off == data);
36055f0a249SGordon Ross 
36155f0a249SGordon Ross 		/*
36255f0a249SGordon Ross 		 * Copy this segment: src_off .. hole
36355f0a249SGordon Ross 		 */
36455f0a249SGordon Ross 		while (src_off < hole) {
36555f0a249SGordon Ross 			ssize_t tsize = hole - src_off;
36655f0a249SGordon Ross 			if (tsize > bufsize)
36755f0a249SGordon Ross 				tsize = bufsize;
36855f0a249SGordon Ross 
36955f0a249SGordon Ross 			/*
37055f0a249SGordon Ross 			 * Read src_ofile into buffer
37155f0a249SGordon Ross 			 */
37255f0a249SGordon Ross 			iov.iov_base = buffer;
37355f0a249SGordon Ross 			iov.iov_len  = tsize;
37455f0a249SGordon Ross 			bzero(&uio, sizeof (uio));
37555f0a249SGordon Ross 			uio.uio_iov = &iov;
37655f0a249SGordon Ross 			uio.uio_iovcnt = 1;
37755f0a249SGordon Ross 			uio.uio_resid = tsize;
37855f0a249SGordon Ross 			uio.uio_loffset = src_off;
37955f0a249SGordon Ross 			uio.uio_segflg = UIO_SYSSPACE;
38055f0a249SGordon Ross 			uio.uio_extflg = UIO_COPY_DEFAULT;
38155f0a249SGordon Ross 
38255f0a249SGordon Ross 			rc = smb_fsop_read(sr, src_ofile->f_cr,
383dfa42fabSMatt Barden 			    src_ofile->f_node, src_ofile, &uio, 0);
38455f0a249SGordon Ross 			if (rc != 0) {
38555f0a249SGordon Ross 				status = smb_errno2status(rc);
38655f0a249SGordon Ross 				return (status);
38755f0a249SGordon Ross 			}
38855f0a249SGordon Ross 			/* Note: Could be partial read. */
38955f0a249SGordon Ross 			tsize -= uio.uio_resid;
39055f0a249SGordon Ross 			ASSERT(tsize > 0);
39155f0a249SGordon Ross 
39255f0a249SGordon Ross 			/*
39355f0a249SGordon Ross 			 * Write buffer to dst_ofile
39455f0a249SGordon Ross 			 */
39555f0a249SGordon Ross 			iov.iov_base = buffer;
39655f0a249SGordon Ross 			iov.iov_len  = tsize;
39755f0a249SGordon Ross 			bzero(&uio, sizeof (uio));
39855f0a249SGordon Ross 			uio.uio_iov = &iov;
39955f0a249SGordon Ross 			uio.uio_iovcnt = 1;
40055f0a249SGordon Ross 			uio.uio_resid = tsize;
40155f0a249SGordon Ross 			uio.uio_loffset = dst_off;
40255f0a249SGordon Ross 			uio.uio_segflg = UIO_SYSSPACE;
40355f0a249SGordon Ross 			uio.uio_extflg = UIO_COPY_DEFAULT;
40455f0a249SGordon Ross 
40555f0a249SGordon Ross 			rc = smb_fsop_write(sr, dst_ofile->f_cr,
40655f0a249SGordon Ross 			    dst_ofile->f_node, dst_ofile, &uio, &xfer, 0);
40755f0a249SGordon Ross 			if (rc != 0) {
40855f0a249SGordon Ross 				status = smb_errno2status(rc);
40955f0a249SGordon Ross 				return (status);
41055f0a249SGordon Ross 			}
41155f0a249SGordon Ross 			ASSERT(xfer <= tsize);
41255f0a249SGordon Ross 
41355f0a249SGordon Ross 			src_off += xfer;
41455f0a249SGordon Ross 			dst_off += xfer;
41555f0a249SGordon Ross 			*residp -= xfer;
41655f0a249SGordon Ross 		}
41755f0a249SGordon Ross 		ASSERT(src_off == hole);
41855f0a249SGordon Ross 	}
41955f0a249SGordon Ross 
42055f0a249SGordon Ross 	return (status);
42155f0a249SGordon Ross }
422252bc4b2SGordon Ross 
423252bc4b2SGordon Ross /*
424252bc4b2SGordon Ross  * Not sure what header this might go in.
425252bc4b2SGordon Ross  */
426252bc4b2SGordon Ross #define	FILE_REGION_USAGE_VALID_CACHED_DATA	1
427252bc4b2SGordon Ross #define	FILE_REGION_USAGE_VALID_NONCACHED_DATA	2
428252bc4b2SGordon Ross #define	FILE_REGION_USAGE_VALID__MASK		3
429252bc4b2SGordon Ross 
430252bc4b2SGordon Ross typedef struct _FILE_REGION_INFO {
431252bc4b2SGordon Ross 	uint64_t off;
432252bc4b2SGordon Ross 	uint64_t len;
433252bc4b2SGordon Ross 	uint32_t usage;
434252bc4b2SGordon Ross 	uint32_t reserved;
435252bc4b2SGordon Ross } FILE_REGION_INFO;
436252bc4b2SGordon Ross 
437252bc4b2SGordon Ross 
438252bc4b2SGordon Ross /*
439252bc4b2SGordon Ross  * FSCTL_QUERY_FILE_REGIONS
440252bc4b2SGordon Ross  *
441252bc4b2SGordon Ross  * [MS-FSCC] 2.3.39 FSCTL_QUERY_FILE_REGIONS Request
442252bc4b2SGordon Ross  * [MS-FSCC] 2.3.40.1 FILE_REGION_INFO
443252bc4b2SGordon Ross  *
444252bc4b2SGordon Ross  * Looks like Hyper-V uses this to query the "valid data length",
445252bc4b2SGordon Ross  * which to us is the beginning offset of the last "hole".
446252bc4b2SGordon Ross  * Similar logic as smb2_sparse_copy()
447252bc4b2SGordon Ross  */
448252bc4b2SGordon Ross uint32_t
smb2_fsctl_query_file_regions(smb_request_t * sr,smb_fsctl_t * fsctl)449252bc4b2SGordon Ross smb2_fsctl_query_file_regions(smb_request_t *sr, smb_fsctl_t *fsctl)
450252bc4b2SGordon Ross {
451252bc4b2SGordon Ross 	smb_attr_t attr;
452252bc4b2SGordon Ross 	cred_t *kcr;
453252bc4b2SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
454252bc4b2SGordon Ross 	FILE_REGION_INFO arg;
455252bc4b2SGordon Ross 	off64_t cur_off, end_off, eof;
456252bc4b2SGordon Ross 	off64_t data, hole;
457252bc4b2SGordon Ross 	uint32_t tot_regions, put_regions;
458252bc4b2SGordon Ross 	uint32_t status;
459252bc4b2SGordon Ross 	int rc;
460252bc4b2SGordon Ross 
461252bc4b2SGordon Ross 	if (fsctl->InputCount == 0) {
462252bc4b2SGordon Ross 		arg.off = 0;
463252bc4b2SGordon Ross 		arg.len = INT64_MAX;
464252bc4b2SGordon Ross 		arg.usage = FILE_REGION_USAGE_VALID_CACHED_DATA;
465252bc4b2SGordon Ross 		arg.reserved = 0;
466252bc4b2SGordon Ross 	} else {
467252bc4b2SGordon Ross 		/* min size check: reserved is optional */
468252bc4b2SGordon Ross 		rc = smb_mbc_decodef(fsctl->in_mbc, "qql",
469252bc4b2SGordon Ross 		    &arg.off, &arg.len, &arg.usage);
470252bc4b2SGordon Ross 		if (rc != 0)
471252bc4b2SGordon Ross 			return (NT_STATUS_BUFFER_TOO_SMALL);
472252bc4b2SGordon Ross 
473252bc4b2SGordon Ross 		/*
474252bc4b2SGordon Ross 		 * The given offset and length are int64_t (signed).
475252bc4b2SGordon Ross 		 */
476252bc4b2SGordon Ross 		if (arg.off > INT64_MAX || arg.len > INT64_MAX)
477252bc4b2SGordon Ross 			return (NT_STATUS_INVALID_PARAMETER);
478252bc4b2SGordon Ross 		if ((arg.off + arg.len) > INT64_MAX)
479252bc4b2SGordon Ross 			return (NT_STATUS_INVALID_PARAMETER);
480252bc4b2SGordon Ross 		if ((arg.usage & FILE_REGION_USAGE_VALID__MASK) == 0)
481252bc4b2SGordon Ross 			return (NT_STATUS_INVALID_PARAMETER);
482252bc4b2SGordon Ross 		arg.reserved = 0;
483252bc4b2SGordon Ross 	}
484252bc4b2SGordon Ross 
485252bc4b2SGordon Ross 	if (fsctl->MaxOutputResp < (16 + sizeof (FILE_REGION_INFO)))
486252bc4b2SGordon Ross 		return (NT_STATUS_BUFFER_TOO_SMALL);
487252bc4b2SGordon Ross 
488252bc4b2SGordon Ross 	if (!smb_node_is_file(ofile->f_node))
489252bc4b2SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
490252bc4b2SGordon Ross 
491252bc4b2SGordon Ross 	/*
492252bc4b2SGordon Ross 	 * This operation is effectively a read
493252bc4b2SGordon Ross 	 */
494252bc4b2SGordon Ross 	status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
495252bc4b2SGordon Ross 	if (status != NT_STATUS_SUCCESS)
496252bc4b2SGordon Ross 		return (status);
497252bc4b2SGordon Ross 
498252bc4b2SGordon Ross 	/*
499252bc4b2SGordon Ross 	 * Need the file size and dosattr
500252bc4b2SGordon Ross 	 */
501252bc4b2SGordon Ross 	bzero(&attr, sizeof (attr));
502252bc4b2SGordon Ross 	attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
503252bc4b2SGordon Ross 	kcr = zone_kcred();
504252bc4b2SGordon Ross 	status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
505252bc4b2SGordon Ross 	if (status != NT_STATUS_SUCCESS)
506252bc4b2SGordon Ross 		return (status);
507252bc4b2SGordon Ross 
508252bc4b2SGordon Ross 	cur_off = arg.off;
509252bc4b2SGordon Ross 	end_off = arg.off + arg.len;
510252bc4b2SGordon Ross 	eof = attr.sa_vattr.va_size;
511252bc4b2SGordon Ross 
512252bc4b2SGordon Ross 	/*
513252bc4b2SGordon Ross 	 * If (InputRegion.FileOffset > Eof) OR
514252bc4b2SGordon Ross 	 * ((InputRegion.FileOffset == Eof) AND (Eof > 0)),
515252bc4b2SGordon Ross 	 * the operation MUST return STATUS_SUCCESS, with
516252bc4b2SGordon Ross 	 * BytesReturned set to 0 (empty data response)
517252bc4b2SGordon Ross 	 */
518252bc4b2SGordon Ross 	if ((arg.off > eof) || (arg.off == eof && eof > 0))
519252bc4b2SGordon Ross 		return (NT_STATUS_SUCCESS);
520252bc4b2SGordon Ross 	if (end_off > eof)
521252bc4b2SGordon Ross 		end_off = eof;
522252bc4b2SGordon Ross 
523252bc4b2SGordon Ross 	/*
524252bc4b2SGordon Ross 	 * We're going to return at least one region.  Put place-holder
525252bc4b2SGordon Ross 	 * data for the fixed part of the response.  Will overwrite this
526252bc4b2SGordon Ross 	 * later, when we know how many regions there are and how many
527252bc4b2SGordon Ross 	 * of those fit in the allowed response buffer space.  These are:
528252bc4b2SGordon Ross 	 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved
529252bc4b2SGordon Ross 	 */
530252bc4b2SGordon Ross 	rc = smb_mbc_encodef(fsctl->out_mbc, "llll", 0, 0, 0, 0);
531252bc4b2SGordon Ross 	if (rc != 0)
532252bc4b2SGordon Ross 		return (NT_STATUS_BUFFER_TOO_SMALL);
533252bc4b2SGordon Ross 	tot_regions = put_regions = 0;
534252bc4b2SGordon Ross 
535252bc4b2SGordon Ross 	/*
536252bc4b2SGordon Ross 	 * Get the first pair of (data, hole) offsets at or after
537252bc4b2SGordon Ross 	 * the current offset (cur_off).
538252bc4b2SGordon Ross 	 */
539252bc4b2SGordon Ross 	data = hole = cur_off;
540252bc4b2SGordon Ross 	rc = smb_fsop_next_alloc_range(ofile->f_cr,
541252bc4b2SGordon Ross 	    ofile->f_node, &data, &hole);
542252bc4b2SGordon Ross 	switch (rc) {
543252bc4b2SGordon Ross 	case 0:
544252bc4b2SGordon Ross 		/* Found (data, hole) */
545252bc4b2SGordon Ross 		break;
546252bc4b2SGordon Ross 	case ENXIO:
547252bc4b2SGordon Ross 		/* No more data after cur_off. */
548252bc4b2SGordon Ross 		break;
549252bc4b2SGordon Ross 	default:
550252bc4b2SGordon Ross 		cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", rc);
551252bc4b2SGordon Ross 		/* FALLTHROUGH */
552252bc4b2SGordon Ross 	case ENOSYS:	/* FS does not support VOP_IOCTL... */
553252bc4b2SGordon Ross 	case ENOTTY:	/* ... or _FIO_SEEK_DATA, _HOLE */
554252bc4b2SGordon Ross 		data = cur_off;
555252bc4b2SGordon Ross 		hole = eof;
556252bc4b2SGordon Ross 		break;
557252bc4b2SGordon Ross 	}
558252bc4b2SGordon Ross 	DTRACE_PROBE2(range0, uint64_t, data, uint64_t, hole);
559252bc4b2SGordon Ross 
560252bc4b2SGordon Ross 	/*
561252bc4b2SGordon Ross 	 * Only sparse files should present un-allocated regions.
562252bc4b2SGordon Ross 	 * If non-sparse, MS-FSA says to just return one region.
563252bc4b2SGordon Ross 	 * There still can be a "hole" but only one, starting at
564252bc4b2SGordon Ross 	 * "valid data length" (VDL) and ending at end of file.
565252bc4b2SGordon Ross 	 * To determine VDL, find the last (data,hole) pair, then
566252bc4b2SGordon Ross 	 * VDL is the last "hole" offset.  Note that the above
567252bc4b2SGordon Ross 	 * smb_fsop_next_alloc_range may have set data somewhere
568252bc4b2SGordon Ross 	 * above cur_off, so we we have to reset that here.
569252bc4b2SGordon Ross 	 */
570252bc4b2SGordon Ross 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
571252bc4b2SGordon Ross 		/*
572252bc4b2SGordon Ross 		 * This works, but it's rather inefficient, and
573252bc4b2SGordon Ross 		 * usually just finds VDL==EOF.  Should look into
574252bc4b2SGordon Ross 		 * whether there's a faster way to find the VDL.
575252bc4b2SGordon Ross 		 */
576252bc4b2SGordon Ross #if 0
577252bc4b2SGordon Ross 		off64_t next_data, next_hole;
578252bc4b2SGordon Ross 		data = cur_off;
579252bc4b2SGordon Ross 		do {
580252bc4b2SGordon Ross 			next_data = next_hole = hole;
581252bc4b2SGordon Ross 			rc = smb_fsop_next_alloc_range(ofile->f_cr,
582252bc4b2SGordon Ross 			    ofile->f_node, &next_data, &next_hole);
583252bc4b2SGordon Ross 			if (rc == 0) {
584252bc4b2SGordon Ross 				hole = next_hole;
585252bc4b2SGordon Ross 			}
586252bc4b2SGordon Ross 		} while (rc == 0);
587252bc4b2SGordon Ross #else
588252bc4b2SGordon Ross 		/* Assume no "holes" anywhere (VDL==EOF) */
589252bc4b2SGordon Ross 		data = cur_off;
590252bc4b2SGordon Ross 		hole = eof;
591252bc4b2SGordon Ross #endif
592252bc4b2SGordon Ross 		DTRACE_PROBE2(range1, uint64_t, data, uint64_t, hole);
593252bc4b2SGordon Ross 	}
594252bc4b2SGordon Ross 
595252bc4b2SGordon Ross 	/*
596252bc4b2SGordon Ross 	 * Loop terminates in the middle, continuing
597252bc4b2SGordon Ross 	 * while (cur_off < end_off)
598252bc4b2SGordon Ross 	 */
599252bc4b2SGordon Ross 	for (;;) {
600252bc4b2SGordon Ross 		/*
601252bc4b2SGordon Ross 		 * We have a data region that covers (data, hole).
602252bc4b2SGordon Ross 		 * It could be partially or entirely beyond the range
603252bc4b2SGordon Ross 		 * the caller asked about (if so trim it).
604252bc4b2SGordon Ross 		 */
605252bc4b2SGordon Ross 		if (hole > end_off)
606252bc4b2SGordon Ross 			hole = end_off;
607252bc4b2SGordon Ross 		if (data > hole)
608252bc4b2SGordon Ross 			data = hole;
609252bc4b2SGordon Ross 
610252bc4b2SGordon Ross 		/*
611252bc4b2SGordon Ross 		 * If cur_off < data encode a "hole" region
612252bc4b2SGordon Ross 		 * (cur_off,data) and advance cur_off.
613252bc4b2SGordon Ross 		 */
614252bc4b2SGordon Ross 		if (cur_off < data) {
615252bc4b2SGordon Ross 			rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
616252bc4b2SGordon Ross 			    cur_off,
617252bc4b2SGordon Ross 			    (data - cur_off),
618252bc4b2SGordon Ross 			    0, // usage (hole)
619252bc4b2SGordon Ross 			    0); // reserved
620252bc4b2SGordon Ross 			cur_off = data;
621252bc4b2SGordon Ross 			if (rc == 0)
622252bc4b2SGordon Ross 				put_regions++;
623252bc4b2SGordon Ross 			tot_regions++;
624252bc4b2SGordon Ross 		}
625252bc4b2SGordon Ross 
626252bc4b2SGordon Ross 		/*
627252bc4b2SGordon Ross 		 * If cur_off < hole encode a "data" region
628252bc4b2SGordon Ross 		 * (cur_off,hole) and advance cur_off.
629252bc4b2SGordon Ross 		 */
630252bc4b2SGordon Ross 		if (cur_off < hole) {
631252bc4b2SGordon Ross 			rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
632252bc4b2SGordon Ross 			    cur_off,
633252bc4b2SGordon Ross 			    (hole - cur_off),
634252bc4b2SGordon Ross 			    FILE_REGION_USAGE_VALID_CACHED_DATA,
635252bc4b2SGordon Ross 			    0); // reserved
636252bc4b2SGordon Ross 			cur_off = hole;
637252bc4b2SGordon Ross 			if (rc == 0)
638252bc4b2SGordon Ross 				put_regions++;
639252bc4b2SGordon Ross 			tot_regions++;
640252bc4b2SGordon Ross 		}
641252bc4b2SGordon Ross 
642252bc4b2SGordon Ross 		/*
643252bc4b2SGordon Ross 		 * Normal loop termination
644252bc4b2SGordon Ross 		 */
645252bc4b2SGordon Ross 		if (cur_off >= end_off)
646252bc4b2SGordon Ross 			break;
647252bc4b2SGordon Ross 
648252bc4b2SGordon Ross 		/*
649252bc4b2SGordon Ross 		 * Get the next region (data, hole) starting on or after
650252bc4b2SGordon Ross 		 * the current offset (cur_off).
651252bc4b2SGordon Ross 		 */
652252bc4b2SGordon Ross 		data = hole = cur_off;
653252bc4b2SGordon Ross 		rc = smb_fsop_next_alloc_range(ofile->f_cr,
654252bc4b2SGordon Ross 		    ofile->f_node, &data, &hole);
655252bc4b2SGordon Ross 		switch (rc) {
656252bc4b2SGordon Ross 		case 0:
657252bc4b2SGordon Ross 			/* Found (data, hole) */
658252bc4b2SGordon Ross 			break;
659252bc4b2SGordon Ross 		case ENXIO:
660252bc4b2SGordon Ross 			/*
661252bc4b2SGordon Ross 			 * No more data after cur_off.
662252bc4b2SGordon Ross 			 * Will encode one last hole.
663252bc4b2SGordon Ross 			 */
664252bc4b2SGordon Ross 			data = hole = eof;
665252bc4b2SGordon Ross 			break;
666252bc4b2SGordon Ross 		default:
667252bc4b2SGordon Ross 			cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d",
668252bc4b2SGordon Ross 			    rc);
669252bc4b2SGordon Ross 			/* FALLTHROUGH */
670252bc4b2SGordon Ross 		case ENOSYS:	/* FS does not support VOP_IOCTL... */
671252bc4b2SGordon Ross 		case ENOTTY:	/* ... or _FIO_SEEK_DATA, _HOLE */
672252bc4b2SGordon Ross 			data = cur_off;
673252bc4b2SGordon Ross 			hole = eof;
674252bc4b2SGordon Ross 			break;
675252bc4b2SGordon Ross 		}
676252bc4b2SGordon Ross 		DTRACE_PROBE2(range2, uint64_t, data, uint64_t, hole);
677252bc4b2SGordon Ross 	}
678252bc4b2SGordon Ross 
679252bc4b2SGordon Ross 	/*
680252bc4b2SGordon Ross 	 * Overwrite the fixed part of the response with the
681252bc4b2SGordon Ross 	 * final numbers of regions etc.
682252bc4b2SGordon Ross 	 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved
683252bc4b2SGordon Ross 	 */
684252bc4b2SGordon Ross 	(void) smb_mbc_poke(fsctl->out_mbc, 0, "llll",
685252bc4b2SGordon Ross 	    0, // flags
686252bc4b2SGordon Ross 	    tot_regions,
687252bc4b2SGordon Ross 	    put_regions,
688252bc4b2SGordon Ross 	    0); // reserved
689252bc4b2SGordon Ross 
690252bc4b2SGordon Ross 	if (put_regions < tot_regions)
691252bc4b2SGordon Ross 		return (NT_STATUS_BUFFER_OVERFLOW);
692252bc4b2SGordon Ross 
693252bc4b2SGordon Ross 	return (NT_STATUS_SUCCESS);
694252bc4b2SGordon Ross }
695