#include <smbsrv/smb2_kproto.h>
#define SMB2_LSN_SHIFT 4
#define SMB2_LSN_MASK 0xf
typedef struct SMB2_LOCK_ELEMENT {
uint64_t Offset;
uint64_t Length;
uint32_t Flags;
uint32_t reserved;
} lock_elem_t;
static uint32_t smb2_unlock(smb_request_t *);
static uint32_t smb2_locks(smb_request_t *);
static uint32_t smb2_lock_blocking(smb_request_t *);
static boolean_t smb2_lock_chk_lockseq(smb_ofile_t *, uint32_t);
static void smb2_lock_set_lockseq(smb_ofile_t *, uint32_t);
int smb2_lock_max_elem = 1024;
smb_sdrc_t
smb2_lock(smb_request_t *sr)
{
lock_elem_t *lvec, *lk;
smb2fid_t smb2fid;
uint32_t LockSequence;
uint32_t status;
uint16_t StructSize;
uint16_t LockCount;
uint16_t i;
int rc;
rc = smb_mbc_decodef(
&sr->smb_data, "wwlqq",
&StructSize,
&LockCount,
&LockSequence,
&smb2fid.persistent,
&smb2fid.temporal);
if (rc || StructSize != 48)
return (SDRC_ERROR);
status = smb2sr_lookup_fid(sr, &smb2fid);
DTRACE_SMB2_START(op__Lock, smb_request_t *, sr);
if (status)
goto errout;
if (sr->fid_ofile->f_node == NULL || LockCount == 0) {
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
if (LockCount > smb2_lock_max_elem) {
status = NT_STATUS_INSUFFICIENT_RESOURCES;
goto errout;
}
if (sr->session->dialect < SMB_VERS_2_1)
LockSequence = 0;
if ((sr->session->dialect == SMB_VERS_2_1) &&
sr->fid_ofile->dh_vers != SMB2_RESILIENT)
LockSequence = 0;
if (LockSequence != 0 &&
smb2_lock_chk_lockseq(sr->fid_ofile, LockSequence)) {
status = NT_STATUS_SUCCESS;
goto errout;
}
lvec = smb_srm_zalloc(sr, LockCount * sizeof (*lvec));
for (i = 0; i < LockCount; i++) {
lk = &lvec[i];
rc = smb_mbc_decodef(
&sr->smb_data, "qqll",
&lk->Offset,
&lk->Length,
&lk->Flags,
&lk->reserved);
if (rc) {
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
}
sr->arg.lock.lvec = lvec;
sr->arg.lock.lcnt = LockCount;
sr->arg.lock.lseq = LockSequence;
if (lvec[0].Flags & SMB2_LOCKFLAG_UNLOCK) {
status = smb2_unlock(sr);
} else {
status = smb2_locks(sr);
}
if (sr->fid_ofile->dh_persist) {
smb2_dh_update_locks(sr, sr->fid_ofile);
}
errout:
sr->smb2_status = status;
DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr);
if (status) {
smb2sr_put_error(sr, status);
return (SDRC_SUCCESS);
}
(void) smb_mbc_encodef(
&sr->reply, "w..",
4);
return (SDRC_SUCCESS);
}
static uint32_t
smb2_unlock(smb_request_t *sr)
{
lock_elem_t *lk;
lock_elem_t *lvec = sr->arg.lock.lvec;
uint32_t LockCount = sr->arg.lock.lcnt;
uint32_t LockSequence = sr->arg.lock.lseq;
uint32_t status = 0;
uint32_t pid = 0;
int i;
for (i = 0; i < LockCount; i++) {
lk = &lvec[i];
if (lk->Flags != SMB2_LOCKFLAG_UNLOCK) {
status = NT_STATUS_INVALID_PARAMETER;
break;
}
status = smb_unlock_range(sr, lk->Offset, lk->Length, pid);
if (status != 0)
break;
}
if (status == 0 && LockSequence != 0) {
smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
}
return (status);
}
static uint32_t
smb2_locks(smb_request_t *sr)
{
lock_elem_t *lk;
lock_elem_t *lvec = sr->arg.lock.lvec;
uint32_t LockCount = sr->arg.lock.lcnt;
uint32_t LockSequence = sr->arg.lock.lseq;
uint32_t i;
uint32_t ltype;
uint32_t pid = 0;
uint32_t timeout = 0;
uint32_t status = 0;
for (i = 0; i < LockCount; i++) {
lk = &lvec[i];
switch (lk->Flags) {
case SMB2_LOCKFLAG_SHARED_LOCK:
case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
if (i == 0 && LockCount == 1) {
status = smb2_lock_blocking(sr);
return (status);
}
case SMB2_LOCKFLAG_UNLOCK:
default:
status = NT_STATUS_INVALID_PARAMETER;
goto end_loop;
case SMB2_LOCKFLAG_SHARED_LOCK |
SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
ltype = SMB_LOCK_TYPE_READONLY;
break;
case SMB2_LOCKFLAG_EXCLUSIVE_LOCK |
SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
ltype = SMB_LOCK_TYPE_READWRITE;
break;
}
status = smb_lock_range(sr, lk->Offset, lk->Length, pid,
ltype, timeout);
if (status != 0) {
goto end_loop;
}
}
end_loop:
if (status != 0) {
while (i > 0) {
--i;
lk = &lvec[i];
(void) smb_unlock_range(sr,
lk->Offset, lk->Length, pid);
}
}
if (status == 0 && LockSequence != 0)
smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
return (status);
}
static uint32_t
smb2_lock_blocking(smb_request_t *sr)
{
lock_elem_t *lk = sr->arg.lock.lvec;
uint32_t LockCount = sr->arg.lock.lcnt;
uint32_t LockSequence = sr->arg.lock.lseq;
uint32_t status;
uint32_t ltype;
uint32_t pid = 0;
uint32_t timeout = UINT_MAX;
ASSERT(sr->fid_ofile->f_node != NULL);
ASSERT(LockCount == 1);
switch (lk->Flags) {
case SMB2_LOCKFLAG_SHARED_LOCK:
ltype = SMB_LOCK_TYPE_READONLY;
break;
case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
ltype = SMB_LOCK_TYPE_READWRITE;
break;
default:
ASSERT(0);
return (NT_STATUS_INTERNAL_ERROR);
}
status = smb_lock_range(sr, lk->Offset, lk->Length, pid, ltype, 0);
if (status == NT_STATUS_LOCK_NOT_GRANTED ||
status == NT_STATUS_FILE_LOCK_CONFLICT) {
status = smb2sr_go_async_indefinite(sr);
if (status != 0)
return (status);
status = smb_lock_range(sr, lk->Offset, lk->Length,
pid, ltype, timeout);
}
if (status == 0 && LockSequence != 0)
smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
return (status);
}
boolean_t
smb2_lock_chk_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
{
uint32_t lsi;
uint8_t lsn;
boolean_t rv;
lsn = lockseq & SMB2_LSN_MASK;
lsi = (lockseq >> SMB2_LSN_SHIFT);
if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX)
return (B_FALSE);
--lsi;
mutex_enter(&ofile->f_mutex);
if (ofile->f_lock_seq[lsi] == lsn) {
rv = B_TRUE;
} else {
ofile->f_lock_seq[lsi] = (uint8_t)-1;
rv = B_FALSE;
}
mutex_exit(&ofile->f_mutex);
return (rv);
}
static void
smb2_lock_set_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
{
uint32_t lsi;
uint8_t lsn;
lsn = lockseq & SMB2_LSN_MASK;
lsi = (lockseq >> SMB2_LSN_SHIFT);
if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX) {
cmn_err(CE_NOTE, "smb2_lock_set_lockseq, index=%u", lsi);
return;
}
--lsi;
mutex_enter(&ofile->f_mutex);
ofile->f_lock_seq[lsi] = lsn;
mutex_exit(&ofile->f_mutex);
}