xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb2_fsctl_sparse.c (revision 252bc4b276969afed9d61561c1a53f4cad070a6b)
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 /*
1355f0a249SGordon Ross  * Copyright 2018 Nexenta Systems, 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
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
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
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
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 
30955f0a249SGordon Ross 		data = src_off;
31055f0a249SGordon Ross 		rc = smb_fsop_next_alloc_range(src_ofile->f_cr,
31155f0a249SGordon Ross 		    src_ofile->f_node, &data, &hole);
31255f0a249SGordon Ross 		switch (rc) {
31355f0a249SGordon Ross 		case 0:
31455f0a249SGordon Ross 			/* Found data, hole */
31555f0a249SGordon Ross 			break;
31655f0a249SGordon Ross 		case ENXIO:
31755f0a249SGordon Ross 			/* No data after here (will skip below). */
31855f0a249SGordon Ross 			data = hole = (src_off + *residp);
31955f0a249SGordon Ross 			break;
32055f0a249SGordon Ross 		default:
32155f0a249SGordon Ross 			cmn_err(CE_NOTE,
32255f0a249SGordon Ross 			    "smb_fsop_next_alloc_range: rc=%d", rc);
32355f0a249SGordon Ross 			/* FALLTHROUGH */
32455f0a249SGordon Ross 		case ENOSYS:	/* FS does not support VOP_IOCTL... */
32555f0a249SGordon Ross 		case ENOTTY:	/* ... or _FIO_SEEK_DATA, _HOLE */
32655f0a249SGordon Ross 			data = src_off;
32755f0a249SGordon Ross 			hole = src_off + *residp;
32855f0a249SGordon Ross 			break;
32955f0a249SGordon Ross 		}
33055f0a249SGordon Ross 
33155f0a249SGordon Ross 		/*
33255f0a249SGordon Ross 		 * Don't try to go past (src_off + *residp)
33355f0a249SGordon Ross 		 */
33455f0a249SGordon Ross 		if (hole > (src_off + *residp))
33555f0a249SGordon Ross 			hole = src_off + *residp;
33655f0a249SGordon Ross 		if (data > hole)
33755f0a249SGordon Ross 			data = hole;
33855f0a249SGordon Ross 
33955f0a249SGordon Ross 		/*
34055f0a249SGordon Ross 		 * If there's a gap (src_off .. data)
34155f0a249SGordon Ross 		 * skip in src_ofile, zero in dst_ofile
34255f0a249SGordon Ross 		 */
34355f0a249SGordon Ross 		if (src_off < data) {
34455f0a249SGordon Ross 			off64_t skip = data - src_off;
34555f0a249SGordon Ross 			rc = smb_fsop_freesp(sr, dst_ofile->f_cr,
34655f0a249SGordon Ross 			    dst_ofile, dst_off, skip);
34755f0a249SGordon Ross 			if (rc == 0) {
34855f0a249SGordon Ross 				src_off += skip;
34955f0a249SGordon Ross 				dst_off += skip;
35055f0a249SGordon Ross 				*residp -= (uint32_t)skip;
35155f0a249SGordon Ross 			} else {
35255f0a249SGordon Ross 				/* Fall back to regular copy */
35355f0a249SGordon Ross 				data = src_off;
35455f0a249SGordon Ross 			}
35555f0a249SGordon Ross 		}
35655f0a249SGordon Ross 		ASSERT(src_off == data);
35755f0a249SGordon Ross 
35855f0a249SGordon Ross 		/*
35955f0a249SGordon Ross 		 * Copy this segment: src_off .. hole
36055f0a249SGordon Ross 		 */
36155f0a249SGordon Ross 		while (src_off < hole) {
36255f0a249SGordon Ross 			ssize_t tsize = hole - src_off;
36355f0a249SGordon Ross 			if (tsize > bufsize)
36455f0a249SGordon Ross 				tsize = bufsize;
36555f0a249SGordon Ross 
36655f0a249SGordon Ross 			/*
36755f0a249SGordon Ross 			 * Read src_ofile into buffer
36855f0a249SGordon Ross 			 */
36955f0a249SGordon Ross 			iov.iov_base = buffer;
37055f0a249SGordon Ross 			iov.iov_len  = tsize;
37155f0a249SGordon Ross 			bzero(&uio, sizeof (uio));
37255f0a249SGordon Ross 			uio.uio_iov = &iov;
37355f0a249SGordon Ross 			uio.uio_iovcnt = 1;
37455f0a249SGordon Ross 			uio.uio_resid = tsize;
37555f0a249SGordon Ross 			uio.uio_loffset = src_off;
37655f0a249SGordon Ross 			uio.uio_segflg = UIO_SYSSPACE;
37755f0a249SGordon Ross 			uio.uio_extflg = UIO_COPY_DEFAULT;
37855f0a249SGordon Ross 
37955f0a249SGordon Ross 			rc = smb_fsop_read(sr, src_ofile->f_cr,
38055f0a249SGordon Ross 			    src_ofile->f_node, src_ofile, &uio);
38155f0a249SGordon Ross 			if (rc != 0) {
38255f0a249SGordon Ross 				status = smb_errno2status(rc);
38355f0a249SGordon Ross 				return (status);
38455f0a249SGordon Ross 			}
38555f0a249SGordon Ross 			/* Note: Could be partial read. */
38655f0a249SGordon Ross 			tsize -= uio.uio_resid;
38755f0a249SGordon Ross 			ASSERT(tsize > 0);
38855f0a249SGordon Ross 
38955f0a249SGordon Ross 			/*
39055f0a249SGordon Ross 			 * Write buffer to dst_ofile
39155f0a249SGordon Ross 			 */
39255f0a249SGordon Ross 			iov.iov_base = buffer;
39355f0a249SGordon Ross 			iov.iov_len  = tsize;
39455f0a249SGordon Ross 			bzero(&uio, sizeof (uio));
39555f0a249SGordon Ross 			uio.uio_iov = &iov;
39655f0a249SGordon Ross 			uio.uio_iovcnt = 1;
39755f0a249SGordon Ross 			uio.uio_resid = tsize;
39855f0a249SGordon Ross 			uio.uio_loffset = dst_off;
39955f0a249SGordon Ross 			uio.uio_segflg = UIO_SYSSPACE;
40055f0a249SGordon Ross 			uio.uio_extflg = UIO_COPY_DEFAULT;
40155f0a249SGordon Ross 
40255f0a249SGordon Ross 			rc = smb_fsop_write(sr, dst_ofile->f_cr,
40355f0a249SGordon Ross 			    dst_ofile->f_node, dst_ofile, &uio, &xfer, 0);
40455f0a249SGordon Ross 			if (rc != 0) {
40555f0a249SGordon Ross 				status = smb_errno2status(rc);
40655f0a249SGordon Ross 				return (status);
40755f0a249SGordon Ross 			}
40855f0a249SGordon Ross 			ASSERT(xfer <= tsize);
40955f0a249SGordon Ross 
41055f0a249SGordon Ross 			src_off += xfer;
41155f0a249SGordon Ross 			dst_off += xfer;
41255f0a249SGordon Ross 			*residp -= xfer;
41355f0a249SGordon Ross 		}
41455f0a249SGordon Ross 		ASSERT(src_off == hole);
41555f0a249SGordon Ross 	}
41655f0a249SGordon Ross 
41755f0a249SGordon Ross 	return (status);
41855f0a249SGordon Ross }
419*252bc4b2SGordon Ross 
420*252bc4b2SGordon Ross /*
421*252bc4b2SGordon Ross  * Not sure what header this might go in.
422*252bc4b2SGordon Ross  */
423*252bc4b2SGordon Ross #define	FILE_REGION_USAGE_VALID_CACHED_DATA	1
424*252bc4b2SGordon Ross #define	FILE_REGION_USAGE_VALID_NONCACHED_DATA	2
425*252bc4b2SGordon Ross #define	FILE_REGION_USAGE_VALID__MASK		3
426*252bc4b2SGordon Ross 
427*252bc4b2SGordon Ross typedef struct _FILE_REGION_INFO {
428*252bc4b2SGordon Ross 	uint64_t off;
429*252bc4b2SGordon Ross 	uint64_t len;
430*252bc4b2SGordon Ross 	uint32_t usage;
431*252bc4b2SGordon Ross 	uint32_t reserved;
432*252bc4b2SGordon Ross } FILE_REGION_INFO;
433*252bc4b2SGordon Ross 
434*252bc4b2SGordon Ross 
435*252bc4b2SGordon Ross /*
436*252bc4b2SGordon Ross  * FSCTL_QUERY_FILE_REGIONS
437*252bc4b2SGordon Ross  *
438*252bc4b2SGordon Ross  * [MS-FSCC] 2.3.39 FSCTL_QUERY_FILE_REGIONS Request
439*252bc4b2SGordon Ross  * [MS-FSCC] 2.3.40.1 FILE_REGION_INFO
440*252bc4b2SGordon Ross  *
441*252bc4b2SGordon Ross  * Looks like Hyper-V uses this to query the "valid data length",
442*252bc4b2SGordon Ross  * which to us is the beginning offset of the last "hole".
443*252bc4b2SGordon Ross  * Similar logic as smb2_sparse_copy()
444*252bc4b2SGordon Ross  */
445*252bc4b2SGordon Ross uint32_t
446*252bc4b2SGordon Ross smb2_fsctl_query_file_regions(smb_request_t *sr, smb_fsctl_t *fsctl)
447*252bc4b2SGordon Ross {
448*252bc4b2SGordon Ross 	smb_attr_t attr;
449*252bc4b2SGordon Ross 	cred_t *kcr;
450*252bc4b2SGordon Ross 	smb_ofile_t *ofile = sr->fid_ofile;
451*252bc4b2SGordon Ross 	FILE_REGION_INFO arg;
452*252bc4b2SGordon Ross 	off64_t cur_off, end_off, eof;
453*252bc4b2SGordon Ross 	off64_t data, hole;
454*252bc4b2SGordon Ross 	uint32_t tot_regions, put_regions;
455*252bc4b2SGordon Ross 	uint32_t status;
456*252bc4b2SGordon Ross 	int rc;
457*252bc4b2SGordon Ross 
458*252bc4b2SGordon Ross 	if (fsctl->InputCount == 0) {
459*252bc4b2SGordon Ross 		arg.off = 0;
460*252bc4b2SGordon Ross 		arg.len = INT64_MAX;
461*252bc4b2SGordon Ross 		arg.usage = FILE_REGION_USAGE_VALID_CACHED_DATA;
462*252bc4b2SGordon Ross 		arg.reserved = 0;
463*252bc4b2SGordon Ross 	} else {
464*252bc4b2SGordon Ross 		/* min size check: reserved is optional */
465*252bc4b2SGordon Ross 		rc = smb_mbc_decodef(fsctl->in_mbc, "qql",
466*252bc4b2SGordon Ross 		    &arg.off, &arg.len, &arg.usage);
467*252bc4b2SGordon Ross 		if (rc != 0)
468*252bc4b2SGordon Ross 			return (NT_STATUS_BUFFER_TOO_SMALL);
469*252bc4b2SGordon Ross 
470*252bc4b2SGordon Ross 		/*
471*252bc4b2SGordon Ross 		 * The given offset and length are int64_t (signed).
472*252bc4b2SGordon Ross 		 */
473*252bc4b2SGordon Ross 		if (arg.off > INT64_MAX || arg.len > INT64_MAX)
474*252bc4b2SGordon Ross 			return (NT_STATUS_INVALID_PARAMETER);
475*252bc4b2SGordon Ross 		if ((arg.off + arg.len) > INT64_MAX)
476*252bc4b2SGordon Ross 			return (NT_STATUS_INVALID_PARAMETER);
477*252bc4b2SGordon Ross 		if ((arg.usage & FILE_REGION_USAGE_VALID__MASK) == 0)
478*252bc4b2SGordon Ross 			return (NT_STATUS_INVALID_PARAMETER);
479*252bc4b2SGordon Ross 		arg.reserved = 0;
480*252bc4b2SGordon Ross 	}
481*252bc4b2SGordon Ross 
482*252bc4b2SGordon Ross 	if (fsctl->MaxOutputResp < (16 + sizeof (FILE_REGION_INFO)))
483*252bc4b2SGordon Ross 		return (NT_STATUS_BUFFER_TOO_SMALL);
484*252bc4b2SGordon Ross 
485*252bc4b2SGordon Ross 	if (!smb_node_is_file(ofile->f_node))
486*252bc4b2SGordon Ross 		return (NT_STATUS_INVALID_PARAMETER);
487*252bc4b2SGordon Ross 
488*252bc4b2SGordon Ross 	/*
489*252bc4b2SGordon Ross 	 * This operation is effectively a read
490*252bc4b2SGordon Ross 	 */
491*252bc4b2SGordon Ross 	status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
492*252bc4b2SGordon Ross 	if (status != NT_STATUS_SUCCESS)
493*252bc4b2SGordon Ross 		return (status);
494*252bc4b2SGordon Ross 
495*252bc4b2SGordon Ross 	/*
496*252bc4b2SGordon Ross 	 * Need the file size and dosattr
497*252bc4b2SGordon Ross 	 */
498*252bc4b2SGordon Ross 	bzero(&attr, sizeof (attr));
499*252bc4b2SGordon Ross 	attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
500*252bc4b2SGordon Ross 	kcr = zone_kcred();
501*252bc4b2SGordon Ross 	status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
502*252bc4b2SGordon Ross 	if (status != NT_STATUS_SUCCESS)
503*252bc4b2SGordon Ross 		return (status);
504*252bc4b2SGordon Ross 
505*252bc4b2SGordon Ross 	cur_off = arg.off;
506*252bc4b2SGordon Ross 	end_off = arg.off + arg.len;
507*252bc4b2SGordon Ross 	eof = attr.sa_vattr.va_size;
508*252bc4b2SGordon Ross 
509*252bc4b2SGordon Ross 	/*
510*252bc4b2SGordon Ross 	 * If (InputRegion.FileOffset > Eof) OR
511*252bc4b2SGordon Ross 	 * ((InputRegion.FileOffset == Eof) AND (Eof > 0)),
512*252bc4b2SGordon Ross 	 * the operation MUST return STATUS_SUCCESS, with
513*252bc4b2SGordon Ross 	 * BytesReturned set to 0 (empty data response)
514*252bc4b2SGordon Ross 	 */
515*252bc4b2SGordon Ross 	if ((arg.off > eof) || (arg.off == eof && eof > 0))
516*252bc4b2SGordon Ross 		return (NT_STATUS_SUCCESS);
517*252bc4b2SGordon Ross 	if (end_off > eof)
518*252bc4b2SGordon Ross 		end_off = eof;
519*252bc4b2SGordon Ross 
520*252bc4b2SGordon Ross 	/*
521*252bc4b2SGordon Ross 	 * We're going to return at least one region.  Put place-holder
522*252bc4b2SGordon Ross 	 * data for the fixed part of the response.  Will overwrite this
523*252bc4b2SGordon Ross 	 * later, when we know how many regions there are and how many
524*252bc4b2SGordon Ross 	 * of those fit in the allowed response buffer space.  These are:
525*252bc4b2SGordon Ross 	 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved
526*252bc4b2SGordon Ross 	 */
527*252bc4b2SGordon Ross 	rc = smb_mbc_encodef(fsctl->out_mbc, "llll", 0, 0, 0, 0);
528*252bc4b2SGordon Ross 	if (rc != 0)
529*252bc4b2SGordon Ross 		return (NT_STATUS_BUFFER_TOO_SMALL);
530*252bc4b2SGordon Ross 	tot_regions = put_regions = 0;
531*252bc4b2SGordon Ross 
532*252bc4b2SGordon Ross 	/*
533*252bc4b2SGordon Ross 	 * Get the first pair of (data, hole) offsets at or after
534*252bc4b2SGordon Ross 	 * the current offset (cur_off).
535*252bc4b2SGordon Ross 	 */
536*252bc4b2SGordon Ross 	data = hole = cur_off;
537*252bc4b2SGordon Ross 	rc = smb_fsop_next_alloc_range(ofile->f_cr,
538*252bc4b2SGordon Ross 	    ofile->f_node, &data, &hole);
539*252bc4b2SGordon Ross 	switch (rc) {
540*252bc4b2SGordon Ross 	case 0:
541*252bc4b2SGordon Ross 		/* Found (data, hole) */
542*252bc4b2SGordon Ross 		break;
543*252bc4b2SGordon Ross 	case ENXIO:
544*252bc4b2SGordon Ross 		/* No more data after cur_off. */
545*252bc4b2SGordon Ross 		break;
546*252bc4b2SGordon Ross 	default:
547*252bc4b2SGordon Ross 		cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", rc);
548*252bc4b2SGordon Ross 		/* FALLTHROUGH */
549*252bc4b2SGordon Ross 	case ENOSYS:	/* FS does not support VOP_IOCTL... */
550*252bc4b2SGordon Ross 	case ENOTTY:	/* ... or _FIO_SEEK_DATA, _HOLE */
551*252bc4b2SGordon Ross 		data = cur_off;
552*252bc4b2SGordon Ross 		hole = eof;
553*252bc4b2SGordon Ross 		break;
554*252bc4b2SGordon Ross 	}
555*252bc4b2SGordon Ross 	DTRACE_PROBE2(range0, uint64_t, data, uint64_t, hole);
556*252bc4b2SGordon Ross 
557*252bc4b2SGordon Ross 	/*
558*252bc4b2SGordon Ross 	 * Only sparse files should present un-allocated regions.
559*252bc4b2SGordon Ross 	 * If non-sparse, MS-FSA says to just return one region.
560*252bc4b2SGordon Ross 	 * There still can be a "hole" but only one, starting at
561*252bc4b2SGordon Ross 	 * "valid data length" (VDL) and ending at end of file.
562*252bc4b2SGordon Ross 	 * To determine VDL, find the last (data,hole) pair, then
563*252bc4b2SGordon Ross 	 * VDL is the last "hole" offset.  Note that the above
564*252bc4b2SGordon Ross 	 * smb_fsop_next_alloc_range may have set data somewhere
565*252bc4b2SGordon Ross 	 * above cur_off, so we we have to reset that here.
566*252bc4b2SGordon Ross 	 */
567*252bc4b2SGordon Ross 	if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
568*252bc4b2SGordon Ross 		/*
569*252bc4b2SGordon Ross 		 * This works, but it's rather inefficient, and
570*252bc4b2SGordon Ross 		 * usually just finds VDL==EOF.  Should look into
571*252bc4b2SGordon Ross 		 * whether there's a faster way to find the VDL.
572*252bc4b2SGordon Ross 		 */
573*252bc4b2SGordon Ross #if 0
574*252bc4b2SGordon Ross 		off64_t next_data, next_hole;
575*252bc4b2SGordon Ross 		data = cur_off;
576*252bc4b2SGordon Ross 		do {
577*252bc4b2SGordon Ross 			next_data = next_hole = hole;
578*252bc4b2SGordon Ross 			rc = smb_fsop_next_alloc_range(ofile->f_cr,
579*252bc4b2SGordon Ross 			    ofile->f_node, &next_data, &next_hole);
580*252bc4b2SGordon Ross 			if (rc == 0) {
581*252bc4b2SGordon Ross 				hole = next_hole;
582*252bc4b2SGordon Ross 			}
583*252bc4b2SGordon Ross 		} while (rc == 0);
584*252bc4b2SGordon Ross #else
585*252bc4b2SGordon Ross 		/* Assume no "holes" anywhere (VDL==EOF) */
586*252bc4b2SGordon Ross 		data = cur_off;
587*252bc4b2SGordon Ross 		hole = eof;
588*252bc4b2SGordon Ross #endif
589*252bc4b2SGordon Ross 		DTRACE_PROBE2(range1, uint64_t, data, uint64_t, hole);
590*252bc4b2SGordon Ross 	}
591*252bc4b2SGordon Ross 
592*252bc4b2SGordon Ross 	/*
593*252bc4b2SGordon Ross 	 * Loop terminates in the middle, continuing
594*252bc4b2SGordon Ross 	 * while (cur_off < end_off)
595*252bc4b2SGordon Ross 	 */
596*252bc4b2SGordon Ross 	for (;;) {
597*252bc4b2SGordon Ross 		/*
598*252bc4b2SGordon Ross 		 * We have a data region that covers (data, hole).
599*252bc4b2SGordon Ross 		 * It could be partially or entirely beyond the range
600*252bc4b2SGordon Ross 		 * the caller asked about (if so trim it).
601*252bc4b2SGordon Ross 		 */
602*252bc4b2SGordon Ross 		if (hole > end_off)
603*252bc4b2SGordon Ross 			hole = end_off;
604*252bc4b2SGordon Ross 		if (data > hole)
605*252bc4b2SGordon Ross 			data = hole;
606*252bc4b2SGordon Ross 
607*252bc4b2SGordon Ross 		/*
608*252bc4b2SGordon Ross 		 * If cur_off < data encode a "hole" region
609*252bc4b2SGordon Ross 		 * (cur_off,data) and advance cur_off.
610*252bc4b2SGordon Ross 		 */
611*252bc4b2SGordon Ross 		if (cur_off < data) {
612*252bc4b2SGordon Ross 			rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
613*252bc4b2SGordon Ross 			    cur_off,
614*252bc4b2SGordon Ross 			    (data - cur_off),
615*252bc4b2SGordon Ross 			    0, // usage (hole)
616*252bc4b2SGordon Ross 			    0); // reserved
617*252bc4b2SGordon Ross 			cur_off = data;
618*252bc4b2SGordon Ross 			if (rc == 0)
619*252bc4b2SGordon Ross 				put_regions++;
620*252bc4b2SGordon Ross 			tot_regions++;
621*252bc4b2SGordon Ross 		}
622*252bc4b2SGordon Ross 
623*252bc4b2SGordon Ross 		/*
624*252bc4b2SGordon Ross 		 * If cur_off < hole encode a "data" region
625*252bc4b2SGordon Ross 		 * (cur_off,hole) and advance cur_off.
626*252bc4b2SGordon Ross 		 */
627*252bc4b2SGordon Ross 		if (cur_off < hole) {
628*252bc4b2SGordon Ross 			rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
629*252bc4b2SGordon Ross 			    cur_off,
630*252bc4b2SGordon Ross 			    (hole - cur_off),
631*252bc4b2SGordon Ross 			    FILE_REGION_USAGE_VALID_CACHED_DATA,
632*252bc4b2SGordon Ross 			    0); // reserved
633*252bc4b2SGordon Ross 			cur_off = hole;
634*252bc4b2SGordon Ross 			if (rc == 0)
635*252bc4b2SGordon Ross 				put_regions++;
636*252bc4b2SGordon Ross 			tot_regions++;
637*252bc4b2SGordon Ross 		}
638*252bc4b2SGordon Ross 
639*252bc4b2SGordon Ross 		/*
640*252bc4b2SGordon Ross 		 * Normal loop termination
641*252bc4b2SGordon Ross 		 */
642*252bc4b2SGordon Ross 		if (cur_off >= end_off)
643*252bc4b2SGordon Ross 			break;
644*252bc4b2SGordon Ross 
645*252bc4b2SGordon Ross 		/*
646*252bc4b2SGordon Ross 		 * Get the next region (data, hole) starting on or after
647*252bc4b2SGordon Ross 		 * the current offset (cur_off).
648*252bc4b2SGordon Ross 		 */
649*252bc4b2SGordon Ross 		data = hole = cur_off;
650*252bc4b2SGordon Ross 		rc = smb_fsop_next_alloc_range(ofile->f_cr,
651*252bc4b2SGordon Ross 		    ofile->f_node, &data, &hole);
652*252bc4b2SGordon Ross 		switch (rc) {
653*252bc4b2SGordon Ross 		case 0:
654*252bc4b2SGordon Ross 			/* Found (data, hole) */
655*252bc4b2SGordon Ross 			break;
656*252bc4b2SGordon Ross 		case ENXIO:
657*252bc4b2SGordon Ross 			/*
658*252bc4b2SGordon Ross 			 * No more data after cur_off.
659*252bc4b2SGordon Ross 			 * Will encode one last hole.
660*252bc4b2SGordon Ross 			 */
661*252bc4b2SGordon Ross 			data = hole = eof;
662*252bc4b2SGordon Ross 			break;
663*252bc4b2SGordon Ross 		default:
664*252bc4b2SGordon Ross 			cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d",
665*252bc4b2SGordon Ross 			    rc);
666*252bc4b2SGordon Ross 			/* FALLTHROUGH */
667*252bc4b2SGordon Ross 		case ENOSYS:	/* FS does not support VOP_IOCTL... */
668*252bc4b2SGordon Ross 		case ENOTTY:	/* ... or _FIO_SEEK_DATA, _HOLE */
669*252bc4b2SGordon Ross 			data = cur_off;
670*252bc4b2SGordon Ross 			hole = eof;
671*252bc4b2SGordon Ross 			break;
672*252bc4b2SGordon Ross 		}
673*252bc4b2SGordon Ross 		DTRACE_PROBE2(range2, uint64_t, data, uint64_t, hole);
674*252bc4b2SGordon Ross 	}
675*252bc4b2SGordon Ross 
676*252bc4b2SGordon Ross 	/*
677*252bc4b2SGordon Ross 	 * Overwrite the fixed part of the response with the
678*252bc4b2SGordon Ross 	 * final numbers of regions etc.
679*252bc4b2SGordon Ross 	 * Flags, TotalRegionEntryCount, RegionEntryCount, Reserved
680*252bc4b2SGordon Ross 	 */
681*252bc4b2SGordon Ross 	(void) smb_mbc_poke(fsctl->out_mbc, 0, "llll",
682*252bc4b2SGordon Ross 	    0, // flags
683*252bc4b2SGordon Ross 	    tot_regions,
684*252bc4b2SGordon Ross 	    put_regions,
685*252bc4b2SGordon Ross 	    0); // reserved
686*252bc4b2SGordon Ross 
687*252bc4b2SGordon Ross 	if (put_regions < tot_regions)
688*252bc4b2SGordon Ross 		return (NT_STATUS_BUFFER_OVERFLOW);
689*252bc4b2SGordon Ross 
690*252bc4b2SGordon Ross 	return (NT_STATUS_SUCCESS);
691*252bc4b2SGordon Ross }
692