#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <smb/winioctl.h>
uint32_t
smb2_fsctl_set_sparse(smb_request_t *sr, smb_fsctl_t *fsctl)
{
smb_attr_t attr;
smb_ofile_t *ofile = sr->fid_ofile;
cred_t *kcr;
uint32_t amask;
uint32_t status;
uint8_t flag;
int rc;
rc = smb_mbc_decodef(fsctl->in_mbc, "b", &flag);
if (rc != 0)
flag = 0xff;
if (!smb_node_is_file(ofile->f_node))
return (NT_STATUS_INVALID_PARAMETER);
amask = FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_APPEND_DATA;
if ((ofile->f_granted_access & amask) == 0)
return (NT_STATUS_ACCESS_DENIED);
bzero(&attr, sizeof (attr));
attr.sa_mask = SMB_AT_DOSATTR;
kcr = zone_kcred();
status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
if (status != NT_STATUS_SUCCESS)
return (status);
if (flag != 0) {
if (attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE)
return (0);
attr.sa_dosattr |= FILE_ATTRIBUTE_SPARSE_FILE;
} else {
if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0)
return (0);
attr.sa_dosattr &= ~FILE_ATTRIBUTE_SPARSE_FILE;
}
attr.sa_mask = SMB_AT_DOSATTR;
status = smb_node_setattr(sr, ofile->f_node, kcr, ofile, &attr);
return (status);
}
uint32_t
smb2_fsctl_set_zero_data(smb_request_t *sr, smb_fsctl_t *fsctl)
{
smb_attr_t attr;
smb_ofile_t *ofile = sr->fid_ofile;
uint64_t start_off, end_off, zero_len;
uint32_t status;
int rc;
rc = smb_mbc_decodef(fsctl->in_mbc, "qq",
&start_off, &end_off);
if (rc != 0)
return (NT_STATUS_BUFFER_TOO_SMALL);
if (start_off > INT64_MAX ||
end_off > INT64_MAX ||
start_off > end_off)
return (NT_STATUS_INVALID_PARAMETER);
if (!smb_node_is_file(ofile->f_node))
return (NT_STATUS_INVALID_PARAMETER);
status = smb_ofile_access(ofile, ofile->f_cr, FILE_WRITE_DATA);
if (status != NT_STATUS_SUCCESS)
return (status);
bzero(&attr, sizeof (attr));
attr.sa_mask = SMB_AT_SIZE;
status = smb_node_getattr(sr, ofile->f_node, ofile->f_cr,
ofile, &attr);
if (status != NT_STATUS_SUCCESS)
return (status);
if (end_off > attr.sa_vattr.va_size)
end_off = attr.sa_vattr.va_size;
if (start_off >= end_off)
return (0);
zero_len = end_off - start_off;
status = smb_lock_range_access(sr, ofile->f_node,
start_off, zero_len, B_TRUE);
if (status != 0)
return (status);
rc = smb_fsop_freesp(sr, ofile->f_cr, ofile,
start_off, zero_len);
if (rc != 0)
status = smb_errno2status(rc);
return (status);
}
struct alloc_range {
off64_t off;
off64_t len;
};
uint32_t
smb2_fsctl_query_alloc_ranges(smb_request_t *sr, smb_fsctl_t *fsctl)
{
smb_attr_t attr;
cred_t *kcr;
smb_ofile_t *ofile = sr->fid_ofile;
struct alloc_range arg, res;
off64_t cur_off, end_off;
uint32_t status;
int err, rc;
rc = smb_mbc_decodef(fsctl->in_mbc, "qq", &arg.off, &arg.len);
if (rc != 0)
return (NT_STATUS_INVALID_PARAMETER);
end_off = arg.off + arg.len;
if (arg.off > INT64_MAX || arg.len < 0 ||
end_off > INT64_MAX || end_off < arg.off)
return (NT_STATUS_INVALID_PARAMETER);
if (!smb_node_is_file(ofile->f_node))
return (NT_STATUS_INVALID_PARAMETER);
status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
if (status != NT_STATUS_SUCCESS)
return (status);
if (arg.len == 0) {
return (0);
}
bzero(&attr, sizeof (attr));
attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
kcr = zone_kcred();
status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
if (status != NT_STATUS_SUCCESS)
return (status);
if (end_off > attr.sa_vattr.va_size)
end_off = attr.sa_vattr.va_size;
if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
if (arg.off < end_off) {
res.off = arg.off;
res.len = end_off - arg.off;
rc = smb_mbc_encodef(fsctl->out_mbc, "qq",
res.off, res.len);
if (rc != 0)
return (NT_STATUS_BUFFER_TOO_SMALL);
}
return (0);
}
cur_off = arg.off;
while (cur_off < end_off) {
off64_t data, hole;
data = cur_off;
err = smb_fsop_next_alloc_range(kcr, ofile->f_node,
&data, &hole);
if (err != 0)
break;
if (data < cur_off) {
ASSERT(0);
data = cur_off;
}
if (data >= end_off)
break;
if (hole <= data)
hole = end_off;
if (hole > end_off)
hole = end_off;
res.off = data;
res.len = hole - data;
if (res.len > 0) {
rc = smb_mbc_encodef(fsctl->out_mbc, "qq",
res.off, res.len);
if (rc != 0)
return (NT_STATUS_BUFFER_TOO_SMALL);
}
cur_off = hole;
}
return (0);
}
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)
{
iovec_t iov;
uio_t uio;
off64_t data, hole;
uint32_t xfer;
uint32_t status = 0;
int rc;
while (*residp > 0) {
if (sr->sr_state != SMB_REQ_STATE_ACTIVE)
break;
data = src_off;
rc = smb_fsop_next_alloc_range(src_ofile->f_cr,
src_ofile->f_node, &data, &hole);
switch (rc) {
case 0:
break;
case ENXIO:
data = hole = (src_off + *residp);
break;
default:
cmn_err(CE_NOTE,
"smb_fsop_next_alloc_range: rc=%d", rc);
case ENOSYS:
case ENOTTY:
data = src_off;
hole = src_off + *residp;
break;
}
if (hole > (src_off + *residp))
hole = src_off + *residp;
if (data > hole)
data = hole;
if (src_off < data) {
off64_t skip = data - src_off;
rc = smb_fsop_freesp(sr, dst_ofile->f_cr,
dst_ofile, dst_off, skip);
if (rc == 0) {
src_off += skip;
dst_off += skip;
*residp -= (uint32_t)skip;
} else {
data = src_off;
}
}
ASSERT(src_off == data);
while (src_off < hole) {
ssize_t tsize = hole - src_off;
if (tsize > bufsize)
tsize = bufsize;
iov.iov_base = buffer;
iov.iov_len = tsize;
bzero(&uio, sizeof (uio));
uio.uio_iov = &iov;
uio.uio_iovcnt = 1;
uio.uio_resid = tsize;
uio.uio_loffset = src_off;
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_extflg = UIO_COPY_DEFAULT;
rc = smb_fsop_read(sr, src_ofile->f_cr,
src_ofile->f_node, src_ofile, &uio, 0);
if (rc != 0) {
status = smb_errno2status(rc);
return (status);
}
tsize -= uio.uio_resid;
ASSERT(tsize > 0);
iov.iov_base = buffer;
iov.iov_len = tsize;
bzero(&uio, sizeof (uio));
uio.uio_iov = &iov;
uio.uio_iovcnt = 1;
uio.uio_resid = tsize;
uio.uio_loffset = dst_off;
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_extflg = UIO_COPY_DEFAULT;
rc = smb_fsop_write(sr, dst_ofile->f_cr,
dst_ofile->f_node, dst_ofile, &uio, &xfer, 0);
if (rc != 0) {
status = smb_errno2status(rc);
return (status);
}
ASSERT(xfer <= tsize);
src_off += xfer;
dst_off += xfer;
*residp -= xfer;
}
ASSERT(src_off == hole);
}
return (status);
}
#define FILE_REGION_USAGE_VALID_CACHED_DATA 1
#define FILE_REGION_USAGE_VALID_NONCACHED_DATA 2
#define FILE_REGION_USAGE_VALID__MASK 3
typedef struct _FILE_REGION_INFO {
uint64_t off;
uint64_t len;
uint32_t usage;
uint32_t reserved;
} FILE_REGION_INFO;
uint32_t
smb2_fsctl_query_file_regions(smb_request_t *sr, smb_fsctl_t *fsctl)
{
smb_attr_t attr;
cred_t *kcr;
smb_ofile_t *ofile = sr->fid_ofile;
FILE_REGION_INFO arg;
off64_t cur_off, end_off, eof;
off64_t data, hole;
uint32_t tot_regions, put_regions;
uint32_t status;
int rc;
if (fsctl->InputCount == 0) {
arg.off = 0;
arg.len = INT64_MAX;
arg.usage = FILE_REGION_USAGE_VALID_CACHED_DATA;
arg.reserved = 0;
} else {
rc = smb_mbc_decodef(fsctl->in_mbc, "qql",
&arg.off, &arg.len, &arg.usage);
if (rc != 0)
return (NT_STATUS_BUFFER_TOO_SMALL);
if (arg.off > INT64_MAX || arg.len > INT64_MAX)
return (NT_STATUS_INVALID_PARAMETER);
if ((arg.off + arg.len) > INT64_MAX)
return (NT_STATUS_INVALID_PARAMETER);
if ((arg.usage & FILE_REGION_USAGE_VALID__MASK) == 0)
return (NT_STATUS_INVALID_PARAMETER);
arg.reserved = 0;
}
if (fsctl->MaxOutputResp < (16 + sizeof (FILE_REGION_INFO)))
return (NT_STATUS_BUFFER_TOO_SMALL);
if (!smb_node_is_file(ofile->f_node))
return (NT_STATUS_INVALID_PARAMETER);
status = smb_ofile_access(ofile, ofile->f_cr, FILE_READ_DATA);
if (status != NT_STATUS_SUCCESS)
return (status);
bzero(&attr, sizeof (attr));
attr.sa_mask = SMB_AT_SIZE | SMB_AT_DOSATTR;
kcr = zone_kcred();
status = smb_node_getattr(sr, ofile->f_node, kcr, ofile, &attr);
if (status != NT_STATUS_SUCCESS)
return (status);
cur_off = arg.off;
end_off = arg.off + arg.len;
eof = attr.sa_vattr.va_size;
if ((arg.off > eof) || (arg.off == eof && eof > 0))
return (NT_STATUS_SUCCESS);
if (end_off > eof)
end_off = eof;
rc = smb_mbc_encodef(fsctl->out_mbc, "llll", 0, 0, 0, 0);
if (rc != 0)
return (NT_STATUS_BUFFER_TOO_SMALL);
tot_regions = put_regions = 0;
data = hole = cur_off;
rc = smb_fsop_next_alloc_range(ofile->f_cr,
ofile->f_node, &data, &hole);
switch (rc) {
case 0:
break;
case ENXIO:
break;
default:
cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d", rc);
case ENOSYS:
case ENOTTY:
data = cur_off;
hole = eof;
break;
}
DTRACE_PROBE2(range0, uint64_t, data, uint64_t, hole);
if ((attr.sa_dosattr & FILE_ATTRIBUTE_SPARSE_FILE) == 0) {
#if 0
off64_t next_data, next_hole;
data = cur_off;
do {
next_data = next_hole = hole;
rc = smb_fsop_next_alloc_range(ofile->f_cr,
ofile->f_node, &next_data, &next_hole);
if (rc == 0) {
hole = next_hole;
}
} while (rc == 0);
#else
data = cur_off;
hole = eof;
#endif
DTRACE_PROBE2(range1, uint64_t, data, uint64_t, hole);
}
for (;;) {
if (hole > end_off)
hole = end_off;
if (data > hole)
data = hole;
if (cur_off < data) {
rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
cur_off,
(data - cur_off),
0,
0);
cur_off = data;
if (rc == 0)
put_regions++;
tot_regions++;
}
if (cur_off < hole) {
rc = smb_mbc_encodef(fsctl->out_mbc, "qqll",
cur_off,
(hole - cur_off),
FILE_REGION_USAGE_VALID_CACHED_DATA,
0);
cur_off = hole;
if (rc == 0)
put_regions++;
tot_regions++;
}
if (cur_off >= end_off)
break;
data = hole = cur_off;
rc = smb_fsop_next_alloc_range(ofile->f_cr,
ofile->f_node, &data, &hole);
switch (rc) {
case 0:
break;
case ENXIO:
data = hole = eof;
break;
default:
cmn_err(CE_NOTE, "smb_fsop_next_alloc_range: rc=%d",
rc);
case ENOSYS:
case ENOTTY:
data = cur_off;
hole = eof;
break;
}
DTRACE_PROBE2(range2, uint64_t, data, uint64_t, hole);
}
(void) smb_mbc_poke(fsctl->out_mbc, 0, "llll",
0,
tot_regions,
put_regions,
0);
if (put_regions < tot_regions)
return (NT_STATUS_BUFFER_OVERFLOW);
return (NT_STATUS_SUCCESS);
}