#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <smb/winioctl.h>
typedef struct chunk {
uint64_t src_off;
uint64_t dst_off;
uint32_t length;
uint32_t _reserved;
} chunk_t;
struct copychunk_resp {
uint32_t ChunksWritten;
uint32_t ChunkBytesWritten;
uint32_t TotalBytesWritten;
};
typedef struct copychunk_args {
smb_attr_t src_attr;
void *buffer;
size_t bufsize;
uint32_t ccnt;
chunk_t cvec[1];
} copychunk_args_t;
uint32_t smb2_copychunk_max_cnt = 256;
uint32_t smb2_copychunk_max_seg = (1<<20);
uint32_t smb2_copychunk_max_total = (1<<24);
static uint32_t smb2_fsctl_copychunk_decode(smb_request_t *, mbuf_chain_t *);
static uint32_t smb2_fsctl_copychunk_array(smb_request_t *, smb_ofile_t *,
struct copychunk_resp *);
static uint32_t smb2_fsctl_copychunk_aapl(smb_request_t *, smb_ofile_t *,
struct copychunk_resp *);
static uint32_t smb2_fsctl_copychunk_1(smb_request_t *, smb_ofile_t *,
struct chunk *);
static int smb2_fsctl_copychunk_meta(smb_request_t *, smb_ofile_t *);
uint32_t
smb2_fsctl_copychunk(smb_request_t *sr, smb_fsctl_t *fsctl)
{
struct copychunk_resp ccr;
smb_ofile_t *dst_of = sr->fid_ofile;
smb_ofile_t *src_of = NULL;
copychunk_args_t *args = NULL;
smb2fid_t smb2fid;
uint32_t status = NT_STATUS_INVALID_PARAMETER;
uint32_t desired_access;
uint32_t chunk_cnt;
int rc;
boolean_t aapl_copyfile = B_FALSE;
bzero(&ccr, sizeof (ccr));
if (fsctl->MaxOutputResp < sizeof (ccr)) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
if (!smb_node_is_file(dst_of->f_node)) {
status = NT_STATUS_ACCESS_DENIED;
goto out;
}
desired_access = FILE_WRITE_DATA;
if (fsctl->CtlCode == FSCTL_SRV_COPYCHUNK)
desired_access |= FILE_READ_DATA;
status = smb_ofile_access(dst_of, dst_of->f_cr, desired_access);
if (status != NT_STATUS_SUCCESS)
goto out;
rc = smb_mbc_decodef(
fsctl->in_mbc, "qq8.l4.",
&smb2fid.persistent,
&smb2fid.temporal,
&chunk_cnt);
if (rc != 0) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
src_of = smb_ofile_lookup_by_fid(sr, (uint16_t)smb2fid.temporal);
if (src_of == NULL ||
src_of->f_persistid != smb2fid.persistent) {
status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto out;
}
if (!smb_node_is_file(src_of->f_node)) {
status = NT_STATUS_ACCESS_DENIED;
goto out;
}
status = smb_ofile_access(src_of, src_of->f_cr, FILE_READ_DATA);
if (status != NT_STATUS_SUCCESS)
goto out;
if (chunk_cnt == 0) {
if ((sr->session->s_flags & SMB_SSN_AAPL_CCEXT) != 0) {
aapl_copyfile = B_TRUE;
} else {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
}
if (chunk_cnt > smb2_copychunk_max_cnt) {
status = NT_STATUS_INVALID_PARAMETER;
goto out;
}
args = smb_srm_zalloc(sr, sizeof (*args) +
(chunk_cnt * sizeof (args->cvec)));
args->ccnt = chunk_cnt;
sr->arg.other = args;
if (chunk_cnt > 0) {
status = smb2_fsctl_copychunk_decode(sr, fsctl->in_mbc);
if (status != 0)
goto out;
}
if (aapl_copyfile)
args->src_attr.sa_mask = SMB_AT_ALL;
else
args->src_attr.sa_mask = SMB_AT_STANDARD;
status = smb2_ofile_getattr(sr, src_of, &args->src_attr);
if (status != 0)
goto out;
args->bufsize = smb2_copychunk_max_seg;
args->buffer = kmem_alloc(args->bufsize, KM_NOSLEEP_LAZY);
if (args->buffer == NULL) {
status = NT_STATUS_INSUFF_SERVER_RESOURCES;
goto out;
}
if (aapl_copyfile) {
status = smb2_fsctl_copychunk_aapl(sr, src_of, &ccr);
} else {
status = smb2_fsctl_copychunk_array(sr, src_of, &ccr);
}
out:
if (args != NULL) {
if (args->buffer != NULL) {
kmem_free(args->buffer, args->bufsize);
}
}
if (src_of != NULL)
smb_ofile_release(src_of);
if (status == NT_STATUS_INVALID_PARAMETER) {
ccr.ChunksWritten = smb2_copychunk_max_cnt;
ccr.ChunkBytesWritten = smb2_copychunk_max_seg;
ccr.TotalBytesWritten = smb2_copychunk_max_total;
}
(void) smb_mbc_encodef(
fsctl->out_mbc, "lll",
ccr.ChunksWritten,
ccr.ChunkBytesWritten,
ccr.TotalBytesWritten);
sr->arg.other = NULL;
return (status);
}
static uint32_t
smb2_fsctl_copychunk_decode(smb_request_t *sr, mbuf_chain_t *mbc)
{
copychunk_args_t *args = sr->arg.other;
chunk_t *cc;
uint32_t status = NT_STATUS_INVALID_PARAMETER;
uint32_t total_len = 0;
int i, rc;
for (i = 0; i < args->ccnt; i++) {
cc = &args->cvec[i];
rc = smb_mbc_decodef(
mbc, "qqll",
&cc->src_off,
&cc->dst_off,
&cc->length,
&cc->_reserved);
if (rc != 0 || cc->length == 0 ||
cc->length > smb2_copychunk_max_seg)
goto out;
total_len += cc->length;
}
if (total_len > smb2_copychunk_max_total)
goto out;
status = 0;
out:
return (status);
}
static uint32_t
smb2_fsctl_copychunk_array(smb_request_t *sr, smb_ofile_t *src_of,
struct copychunk_resp *ccr)
{
copychunk_args_t *args = sr->arg.other;
chunk_t *cc;
uint64_t src_size = args->src_attr.sa_vattr.va_size;
uint32_t save_len;
uint32_t copied;
uint32_t status = 0;
int i;
for (i = 0; i < args->ccnt; i++) {
cc = &args->cvec[i];
if (cc->src_off > src_size ||
(cc->src_off + cc->length) < cc->src_off ||
(cc->src_off + cc->length) > src_size) {
status = NT_STATUS_INVALID_VIEW_SIZE;
goto out;
}
save_len = cc->length;
status = smb2_fsctl_copychunk_1(sr, src_of, cc);
if (status != 0) {
break;
}
copied = save_len - cc->length;
ccr->TotalBytesWritten += copied;
if (cc->length != 0) {
ccr->ChunkBytesWritten = copied;
break;
}
ccr->ChunksWritten++;
}
if (ccr->ChunksWritten > 0)
status = NT_STATUS_SUCCESS;
out:
return (status);
}
int smb2_fsctl_copychunk_aapl_timeout = 10;
static uint32_t
smb2_fsctl_copychunk_aapl(smb_request_t *sr, smb_ofile_t *src_of,
struct copychunk_resp *ccr)
{
copychunk_args_t *args = sr->arg.other;
chunk_t *cc = args->cvec;
uint64_t src_size = args->src_attr.sa_vattr.va_size;
uint64_t off;
uint32_t xfer;
uint32_t status = 0;
hrtime_t end_time = sr->sr_time_active +
(smb2_fsctl_copychunk_aapl_timeout * NANOSEC);
off = 0;
while (off < src_size) {
if (sr->sr_state != SMB_REQ_STATE_ACTIVE)
return (NT_STATUS_CANCELLED);
if (gethrtime() > end_time)
return (NT_STATUS_IO_TIMEOUT);
xfer = smb2_copychunk_max_seg;
if (off + xfer > src_size)
xfer = (uint32_t)(src_size - off);
cc->src_off = off;
cc->dst_off = off;
cc->length = xfer;
status = smb2_fsctl_copychunk_1(sr, src_of, cc);
if (status != 0)
break;
if (cc->length != 0) {
status = NT_STATUS_PARTIAL_COPY;
break;
}
ccr->TotalBytesWritten += xfer;
ccr->ChunksWritten++;
off += xfer;
}
if (status == 0) {
int rc = smb2_fsctl_copychunk_meta(sr, src_of);
if (rc != 0) {
cmn_err(CE_NOTE, "smb2 copychunk meta, rc=%d", rc);
}
}
return (status);
}
static int
smb2_fsctl_copychunk_meta(smb_request_t *sr, smb_ofile_t *src_of)
{
smb_fssd_t fs_sd;
copychunk_args_t *args = sr->arg.other;
smb_ofile_t *dst_of = sr->fid_ofile;
uint32_t sd_flags = 0;
uint32_t secinfo = SMB_DACL_SECINFO;
int error;
args->src_attr.sa_mask = SMB_AT_MODE | SMB_AT_SIZE |
SMB_AT_ATIME | SMB_AT_MTIME | SMB_AT_CTIME |
SMB_AT_DOSATTR | SMB_AT_ALLOCSZ;
error = smb_node_setattr(sr, dst_of->f_node, sr->user_cr,
dst_of, &args->src_attr);
if (error != 0)
return (error);
smb_fssd_init(&fs_sd, secinfo, sd_flags);
sr->fid_ofile = NULL;
error = smb_fsop_sdread(sr, sr->user_cr, src_of->f_node, &fs_sd);
if (error == 0) {
error = smb_fsop_sdwrite(sr, sr->user_cr, dst_of->f_node,
&fs_sd, 1);
}
sr->fid_ofile = dst_of;
smb_fssd_term(&fs_sd);
return (error);
}
static uint32_t
smb2_fsctl_copychunk_1(smb_request_t *sr, smb_ofile_t *src_ofile,
struct chunk *cc)
{
copychunk_args_t *args = sr->arg.other;
smb_ofile_t *dst_ofile = sr->fid_ofile;
uint32_t status;
if (cc->length > args->bufsize)
return (NT_STATUS_INTERNAL_ERROR);
status = smb_lock_range_access(sr, src_ofile->f_node,
cc->src_off, cc->length, B_FALSE);
if (status != 0)
return (status);
status = smb_lock_range_access(sr, dst_ofile->f_node,
cc->dst_off, cc->length, B_TRUE);
if (status != 0)
return (status);
status = smb2_sparse_copy(sr, src_ofile, dst_ofile,
cc->src_off, cc->dst_off, &cc->length,
args->buffer, args->bufsize);
return (status);
}