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