#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_oplock.h>
#define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
#define SSZ_OPLOCK 24
#define SSZ_LEASE 36
smb_sdrc_t
smb2_oplock_break_ack(smb_request_t *sr)
{
smb_arg_olbrk_t *olbrk = &sr->arg.olbrk;
smb_node_t *node;
smb_ofile_t *ofile;
smb_oplock_grant_t *og;
smb2fid_t smb2fid;
uint32_t status;
uint32_t NewLevel;
uint8_t smbOplockLevel;
int rc = 0;
uint16_t StructSize;
rc = smb_mbc_decodef(&sr->smb_data, "w", &StructSize);
if (rc != 0)
return (SDRC_ERROR);
if (StructSize == SSZ_LEASE) {
return (smb2_lease_break_ack(sr));
}
if (StructSize != SSZ_OPLOCK)
return (SDRC_ERROR);
rc = smb_mbc_decodef(
&sr->smb_data, "b5.qq",
&smbOplockLevel,
&smb2fid.persistent,
&smb2fid.temporal);
if (rc != 0)
return (SDRC_ERROR);
switch (smbOplockLevel) {
case SMB2_OPLOCK_LEVEL_NONE:
NewLevel = OPLOCK_LEVEL_NONE;
break;
case SMB2_OPLOCK_LEVEL_II:
NewLevel = OPLOCK_LEVEL_TWO;
break;
case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
NewLevel = OPLOCK_LEVEL_ONE;
break;
case SMB2_OPLOCK_LEVEL_BATCH:
NewLevel = OPLOCK_LEVEL_BATCH;
break;
case SMB2_OPLOCK_LEVEL_LEASE:
default:
NewLevel = OPLOCK_LEVEL_GRANULAR;
break;
}
olbrk->NewLevel = NewLevel;
status = smb2sr_lookup_fid(sr, &smb2fid);
DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
if (status != 0) {
goto errout;
}
if (NewLevel == OPLOCK_LEVEL_GRANULAR) {
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
ofile = sr->fid_ofile;
og = &ofile->f_oplock;
node = ofile->f_node;
smb_llist_enter(&node->n_ofile_list, RW_READER);
mutex_enter(&node->n_oplock.ol_mutex);
if (og->og_breaking == B_FALSE) {
if (NewLevel >= (og->og_state & OPLOCK_LEVEL_TYPE_MASK)) {
status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
goto unlock_out;
}
status = NT_STATUS_INVALID_DEVICE_STATE;
goto unlock_out;
}
ofile->f_oplock.og_breaking = B_FALSE;
cv_broadcast(&ofile->f_oplock.og_ack_cv);
status = smb_oplock_ack_break(sr, ofile, &NewLevel);
ofile->f_oplock.og_state = NewLevel;
if (ofile->dh_persist)
smb2_dh_update_oplock(sr, ofile);
unlock_out:
mutex_exit(&node->n_oplock.ol_mutex);
smb_llist_exit(&node->n_ofile_list);
errout:
sr->smb2_status = status;
DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
if (status) {
smb2sr_put_error(sr, status);
return (SDRC_SUCCESS);
}
switch (NewLevel & OPLOCK_LEVEL_TYPE_MASK) {
case OPLOCK_LEVEL_NONE:
smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
break;
case OPLOCK_LEVEL_TWO:
smbOplockLevel = SMB2_OPLOCK_LEVEL_II;
break;
case OPLOCK_LEVEL_ONE:
smbOplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
break;
case OPLOCK_LEVEL_BATCH:
smbOplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
break;
case OPLOCK_LEVEL_GRANULAR:
default:
smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
break;
}
(void) smb_mbc_encodef(
&sr->reply, "wb5.qq",
SSZ_OPLOCK,
smbOplockLevel,
smb2fid.persistent,
smb2fid.temporal);
return (SDRC_SUCCESS);
}
static void
smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
{
smb_ofile_t *ofile = sr->fid_ofile;
smb2fid_t smb2fid;
uint16_t StructSize;
uint8_t OplockLevel;
switch (NewLevel) {
default:
ASSERT(0);
case OPLOCK_LEVEL_NONE:
OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
break;
case OPLOCK_LEVEL_TWO:
OplockLevel = SMB2_OPLOCK_LEVEL_II;
break;
}
sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
sr->smb_tid = 0;
sr->smb_pid = 0;
sr->smb2_ssnid = 0;
sr->smb2_messageid = UINT64_MAX;
(void) smb2_encode_header(sr, B_FALSE);
StructSize = 24;
smb2fid.persistent = ofile->f_persistid;
smb2fid.temporal = ofile->f_fid;
(void) smb_mbc_encodef(
&sr->reply, "wb5.qq",
StructSize,
OplockLevel,
smb2fid.persistent,
smb2fid.temporal);
}
void
smb2_oplock_send_break(smb_request_t *sr)
{
smb_ofile_t *ofile = sr->fid_ofile;
smb_node_t *node = ofile->f_node;
uint32_t NewLevel = sr->arg.olbrk.NewLevel;
boolean_t AckReq = sr->arg.olbrk.AckRequired;
uint32_t status;
int rc;
sr->reply.max_bytes = MLEN;
smb2_oplock_break_notification(sr, NewLevel);
if (sr->session == ofile->f_session)
rc = smb_session_send(sr->session, 0, &sr->reply);
else
rc = ENOTCONN;
if (rc != 0) {
if (ofile->dh_persist == B_FALSE &&
ofile->dh_vers != SMB2_RESILIENT &&
(ofile->dh_vers == SMB2_NOT_DURABLE ||
(NewLevel & OPLOCK_LEVEL_BATCH) == 0)) {
smb_ofile_close(ofile, 0);
return;
}
if (!AckReq)
return;
} else {
if (!AckReq)
return;
status = smb_oplock_wait_ack(sr, NewLevel);
if (status == 0)
return;
DTRACE_PROBE2(wait__ack__failed, smb_request_t *, sr,
uint32_t, status);
NewLevel = OPLOCK_LEVEL_NONE;
}
smb_llist_enter(&node->n_ofile_list, RW_READER);
mutex_enter(&node->n_oplock.ol_mutex);
ofile->f_oplock.og_breaking = B_FALSE;
cv_broadcast(&ofile->f_oplock.og_ack_cv);
status = smb_oplock_ack_break(sr, ofile, &NewLevel);
ofile->f_oplock.og_state = NewLevel;
if (ofile->dh_persist)
smb2_dh_update_oplock(sr, ofile);
mutex_exit(&node->n_oplock.ol_mutex);
smb_llist_exit(&node->n_ofile_list);
#ifdef DEBUG
if (status != 0) {
cmn_err(CE_NOTE, "clnt %s local oplock ack, status=0x%x",
sr->session->ip_addr_str, status);
}
#endif
}
void
smb2_oplock_acquire(smb_request_t *sr)
{
smb_arg_open_t *op = &sr->arg.open;
smb_ofile_t *ofile = sr->fid_ofile;
uint32_t status;
ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
if (!smb_node_is_file(ofile->f_node)) {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
}
if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
}
switch (op->op_oplock_level) {
case SMB2_OPLOCK_LEVEL_BATCH:
op->op_oplock_state = OPLOCK_LEVEL_BATCH;
break;
case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
op->op_oplock_state = OPLOCK_LEVEL_ONE;
break;
case SMB2_OPLOCK_LEVEL_II:
op->op_oplock_state = OPLOCK_LEVEL_TWO;
break;
case SMB2_OPLOCK_LEVEL_LEASE:
ASSERT(0);
case SMB2_OPLOCK_LEVEL_NONE:
default:
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
}
if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
op->op_oplock_state = OPLOCK_LEVEL_TWO;
}
if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) {
status = smb_oplock_request(sr, ofile,
&op->op_oplock_state);
} else {
status = NT_STATUS_OPLOCK_NOT_GRANTED;
}
if (status == NT_STATUS_OPLOCK_NOT_GRANTED) {
op->op_oplock_state = OPLOCK_LEVEL_TWO;
status = smb_oplock_request(sr, ofile,
&op->op_oplock_state);
}
switch (status) {
case NT_STATUS_SUCCESS:
case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS:
ofile->f_oplock.og_dialect = SMB_VERS_2_002;
ofile->f_oplock.og_state = op->op_oplock_state;
ofile->f_oplock.og_breakto = op->op_oplock_state;
ofile->f_oplock.og_breaking = B_FALSE;
if (ofile->dh_persist) {
smb2_dh_update_oplock(sr, ofile);
}
break;
case NT_STATUS_OPLOCK_NOT_GRANTED:
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
default:
cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
sr->session->ip_addr_str, status);
DTRACE_PROBE2(other__error, smb_request_t *, sr,
uint32_t, status);
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
}
if (op->op_oplock_state & OPLOCK_LEVEL_GRANULAR) {
ASSERT(0);
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
} else if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
} else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
} else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
} else {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
}
if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
(void) smb2sr_go_async(sr);
(void) smb_oplock_wait_break(sr, ofile->f_node, 0);
}
}
void
smb2_oplock_reconnect(smb_request_t *sr)
{
smb_arg_open_t *op = &sr->arg.open;
smb_ofile_t *ofile = sr->fid_ofile;
op->op_oplock_state = ofile->f_oplock.og_state;
if (ofile->f_lease != NULL) {
smb_lease_t *ls = ofile->f_lease;
op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
op->lease_state = ls->ls_state &
OPLOCK_LEVEL_CACHE_MASK;
op->lease_flags = (ls->ls_breaking != 0) ?
SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
op->lease_epoch = ls->ls_epoch;
op->lease_version = ls->ls_version;
} else {
switch (op->op_oplock_state & OPLOCK_LEVEL_TYPE_MASK) {
default:
case OPLOCK_LEVEL_NONE:
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
break;
case OPLOCK_LEVEL_TWO:
op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
break;
case OPLOCK_LEVEL_ONE:
op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
break;
case OPLOCK_LEVEL_BATCH:
op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
break;
}
}
}