#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>
extern boolean_t smb_allow_unbuffered;
int smb2_read_zcopy = 1;
typedef struct smb_xuio {
xuio_t su_xuio;
smb_node_t *su_node;
uint_t su_ref;
} smb_xuio_t;
smb_xuio_t *
smb_xuio_alloc(smb_node_t *node)
{
smb_xuio_t *su;
su = kmem_zalloc(sizeof (*su), KM_SLEEP);
su->su_node = node;
smb_node_ref(node);
su->su_ref = 1;
su->su_xuio.xu_type = UIOTYPE_ZEROCOPY;
return (su);
}
void
smb_xuio_free(void *varg)
{
uint_t ref;
smb_xuio_t *su = (smb_xuio_t *)varg;
xuio_t *xu = &su->su_xuio;
ref = atomic_dec_uint_nv(&su->su_ref);
if (ref != 0)
return;
if (xu->xu_uio.uio_extflg & UIO_XUIO) {
(void) smb_fsop_retzcbuf(su->su_node, xu, CRED());
}
smb_node_release(su->su_node);
kmem_free(su, sizeof (*su));
}
static void
smb_xuio_mbuf_free(mbuf_t *m)
{
ASSERT((m->m_flags & M_EXT) != 0);
smb_xuio_free(m->m_ext.ext_arg1);
}
static mbuf_t *
smb_xuio_to_mbuf(smb_xuio_t *su)
{
uio_t *uiop;
struct iovec *iovp;
mbuf_t *mp, *mp1;
int i;
uiop = &su->su_xuio.xu_uio;
if (uiop->uio_iovcnt == 0)
return (NULL);
iovp = uiop->uio_iov;
mp = smb_mbuf_alloc_ext(iovp->iov_base, iovp->iov_len,
smb_xuio_mbuf_free, su);
ASSERT(mp != NULL);
su->su_ref++;
mp1 = mp;
for (i = 1; i < uiop->uio_iovcnt; i++) {
iovp = (uiop->uio_iov + i);
mp1->m_next = smb_mbuf_alloc_ext(iovp->iov_base,
iovp->iov_len, smb_xuio_mbuf_free, su);
mp1 = mp1->m_next;
ASSERT(mp1 != NULL);
su->su_ref++;
}
return (mp);
}
smb_sdrc_t
smb2_read(smb_request_t *sr)
{
smb_rw_param_t *param = NULL;
smb_ofile_t *of = NULL;
smb_vdb_t *vdb = NULL;
struct mbuf *m = NULL;
smb_xuio_t *su = NULL;
uio_t *uio = NULL;
uint16_t StructSize;
uint8_t Padding;
uint8_t Flags;
uint8_t DataOff;
uint32_t Length;
uint64_t Offset;
smb2fid_t smb2fid;
uint32_t MinCount;
uint32_t Channel;
uint32_t Remaining;
uint16_t ChanInfoOffset;
uint16_t ChanInfoLength;
uint32_t XferCount = 0;
uint32_t status;
int rc = 0;
int ioflag = 0;
boolean_t unbuffered = B_FALSE;
boolean_t zcopy = B_FALSE;
rc = smb_mbc_decodef(
&sr->smb_data,
"wbblqqqlllww",
&StructSize,
&Padding,
&Flags,
&Length,
&Offset,
&smb2fid.persistent,
&smb2fid.temporal,
&MinCount,
&Channel,
&Remaining,
&ChanInfoOffset,
&ChanInfoLength);
if (rc)
return (SDRC_ERROR);
if (StructSize != 49)
return (SDRC_ERROR);
param = smb_srm_zalloc(sr, sizeof (*param));
param->rw_offset = Offset;
param->rw_count = Length;
sr->arg.rw = param;
status = smb2sr_lookup_fid(sr, &smb2fid);
of = sr->fid_ofile;
DTRACE_SMB2_START(op__Read, smb_request_t *, sr);
if (status != 0)
goto done;
if (Length == 0)
goto done;
if (Length > smb2_max_rwsize) {
status = NT_STATUS_INVALID_PARAMETER;
goto done;
}
if (MinCount > Length)
MinCount = Length;
vdb = ¶m->rw_vdb;
vdb->vdb_tag = 0;
vdb->vdb_uio.uio_iov = &vdb->vdb_iovec[0];
vdb->vdb_uio.uio_iovcnt = MAX_IOVEC;
vdb->vdb_uio.uio_resid = Length;
vdb->vdb_uio.uio_loffset = (offset_t)Offset;
vdb->vdb_uio.uio_segflg = UIO_SYSSPACE;
vdb->vdb_uio.uio_extflg = UIO_COPY_DEFAULT;
if (smb_allow_unbuffered &&
(Flags & SMB2_READFLAG_READ_UNBUFFERED) != 0) {
unbuffered = B_TRUE;
ioflag = FRSYNC;
}
switch (of->f_tree->t_res_type & STYPE_MASK) {
case STYPE_DISKTREE:
if (smb_node_is_dir(of->f_node)) {
rc = EISDIR;
break;
}
rc = smb_lock_range_access(sr, of->f_node,
Offset, Length, B_FALSE);
if (rc) {
rc = ERANGE;
break;
}
zcopy = (smb2_read_zcopy != 0);
if (zcopy) {
su = smb_xuio_alloc(of->f_node);
uio = &su->su_xuio.xu_uio;
uio->uio_segflg = UIO_SYSSPACE;
uio->uio_loffset = (offset_t)Offset;
uio->uio_resid = Length;
rc = smb_fsop_reqzcbuf(of->f_node, &su->su_xuio,
UIO_READ, of->f_cr);
if (rc == 0) {
ASSERT((uio->uio_extflg & UIO_XUIO) != 0);
} else {
ASSERT((uio->uio_extflg & UIO_XUIO) == 0);
smb_xuio_free(su);
su = NULL;
uio = NULL;
zcopy = B_FALSE;
}
}
if (!zcopy) {
sr->raw_data.max_bytes = Length;
m = smb_mbuf_allocate(&vdb->vdb_uio);
uio = &vdb->vdb_uio;
}
rc = smb_fsop_read(sr, of->f_cr, of->f_node, of, uio, ioflag);
if (rc != 0) {
if (zcopy) {
smb_xuio_free(su);
su = NULL;
uio = NULL;
}
m_freem(m);
m = NULL;
break;
}
XferCount = Length - uio->uio_resid;
if (zcopy) {
ASSERT(m == NULL);
m = smb_xuio_to_mbuf(su);
smb_xuio_free(su);
su = NULL;
uio = NULL;
}
sr->raw_data.max_bytes = XferCount;
smb_mbuf_trim(m, XferCount);
MBC_ATTACH_MBUF(&sr->raw_data, m);
break;
case STYPE_IPC:
if (unbuffered) {
rc = EINVAL;
break;
}
sr->raw_data.max_bytes = Length;
m = smb_mbuf_allocate(&vdb->vdb_uio);
rc = smb_opipe_read(sr, &vdb->vdb_uio);
XferCount = Length - vdb->vdb_uio.uio_resid;
sr->raw_data.max_bytes = XferCount;
smb_mbuf_trim(m, XferCount);
MBC_ATTACH_MBUF(&sr->raw_data, m);
break;
default:
case STYPE_PRINTQ:
rc = EACCES;
break;
}
status = smb_errno2status(rc);
if (status == 0 && XferCount < MinCount)
status = NT_STATUS_END_OF_FILE;
done:
sr->smb2_status = status;
DTRACE_SMB2_DONE(op__Read, smb_request_t *, sr);
if (status) {
smb2sr_put_error(sr, status);
return (SDRC_SUCCESS);
}
DataOff = SMB2_HDR_SIZE + 16;
rc = smb_mbc_encodef(
&sr->reply,
"wb.lllC",
17,
DataOff,
XferCount,
0,
0,
&sr->raw_data);
if (rc) {
sr->smb2_status = NT_STATUS_INTERNAL_ERROR;
return (SDRC_ERROR);
}
mutex_enter(&of->f_mutex);
of->f_seek_pos = Offset + XferCount;
mutex_exit(&of->f_mutex);
return (SDRC_SUCCESS);
}