summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGordon Ross <gwr@racktopsystems.com>2022-02-21 12:10:09 -0500
committerToomas Soome <tsoome@me.com>2022-12-14 00:03:26 +0200
commit72b35b0568511bf35ca88532dff910dc0f16847f (patch)
treed3d5016cf6d9f66dc78cefa1f521f8b74d3f5210
parent479710a24812632178acfabf12b47287101b7575 (diff)
downloadillumos-gate-72b35b0568511bf35ca88532dff910dc0f16847f.tar.gz
15188 smb oplock blocking access to folders
Reviewed by: Albert Lee <alee@racktopsystems.com> Reviewed by: Jerry Jelinek <gjelinek@racktopsystems.com> Reviewed by: Joyce McIntosh <jmcintosh@racktopsystems.com> Reviewed by: Matt Barden <mbarden@racktopsystems.com> Approved by: Dan McDonald <danmcd@mnx.io>
-rw-r--r--usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h23
-rw-r--r--usr/src/cmd/smbsrv/testoplock/tol_main.c28
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_dispatch.c4
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_durable.c3
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_lease.c590
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_oplock.c281
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c68
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_locking_andx.c74
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_ofile.c33
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_oplock.c225
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c482
-rw-r--r--usr/src/uts/common/smbsrv/smb_kproto.h25
-rw-r--r--usr/src/uts/common/smbsrv/smb_ktypes.h4
13 files changed, 1280 insertions, 560 deletions
diff --git a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h
index f743dcd179..5ecc17210a 100644
--- a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h
+++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2020 Tintri by DDN, Inc. All rights reserved.
+ * Copyright 2022 RackTop Systems, Inc.
*/
/*
@@ -70,6 +71,7 @@ int smb_account_connected(smb_user_t *user);
* Common oplock functions
*/
uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_request_LH(smb_request_t *, smb_ofile_t *, uint32_t *);
uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *);
uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *);
uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *,
@@ -83,22 +85,23 @@ uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *);
uint32_t smb_oplock_break_SETINFO(smb_node_t *,
smb_ofile_t *ofile, uint32_t InfoClass);
uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *);
+void smb_oplock_close(smb_ofile_t *);
+
+void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
+void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
+ uint32_t, boolean_t);
+void smb_oplock_send_break(smb_request_t *);
+
+uint32_t smb_oplock_wait_ack(smb_request_t *);
+uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int);
+uint32_t smb_oplock_wait_break_fem(smb_node_t *, int);
void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
/*
* Protocol-specific oplock functions
- * (and "server-level" functions)
+ * (not needed here)
*/
-void smb1_oplock_acquire(smb_request_t *, boolean_t);
-void smb1_oplock_break_notification(smb_request_t *, uint32_t);
-void smb2_oplock_break_notification(smb_request_t *, uint32_t);
-void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t);
-void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
-void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
- uint32_t, boolean_t);
-void smb_oplock_send_brk(smb_request_t *);
-uint32_t smb_oplock_wait_break(smb_node_t *, int);
int smb_lock_range_access(smb_request_t *, smb_node_t *,
uint64_t, uint64_t, boolean_t);
diff --git a/usr/src/cmd/smbsrv/testoplock/tol_main.c b/usr/src/cmd/smbsrv/testoplock/tol_main.c
index f78729a9d6..7691edbca3 100644
--- a/usr/src/cmd/smbsrv/testoplock/tol_main.c
+++ b/usr/src/cmd/smbsrv/testoplock/tol_main.c
@@ -234,6 +234,7 @@ do_req(int fid, char *arg2)
static void
do_ack(int fid, char *arg2)
{
+ smb_node_t *node = &test_node;
smb_ofile_t *ofile = &ofile_array[fid];
uint32_t oplock;
uint32_t status;
@@ -243,14 +244,20 @@ do_ack(int fid, char *arg2)
if (arg2 != NULL)
oplock = strtol(arg2, NULL, 16);
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
ofile->f_oplock.og_breaking = 0;
status = smb_oplock_ack_break(&test_sr, ofile, &oplock);
+ if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS)
+ ofile->f_oplock.og_state = oplock;
+
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
printf(" ack: break fid=%d, break-in-progress\n", fid);
- ofile->f_oplock.og_state = oplock;
}
- if (status == 0)
- ofile->f_oplock.og_state = oplock;
printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n",
fid, oplock, status, xlate_nt_status(status));
@@ -550,7 +557,9 @@ smb_lock_range_access(
}
/*
- * Test code replacement for: smb_oplock_send_brk()
+ * Test code replacement for combination of:
+ * smb_oplock_hdl_update()
+ * smb_oplock_send_brk()
*/
static void
test_oplock_send_brk(smb_ofile_t *ofile,
@@ -567,6 +576,8 @@ test_oplock_send_brk(smb_ofile_t *ofile,
* In a real server, we would send a break to the client,
* and keep track (at the SMB level) whether this oplock
* was obtained via a lease or an old-style oplock.
+ *
+ * This part like: smb_oplock_hdl_update()
*/
if (AckReq) {
uint32_t BreakTo;
@@ -576,6 +587,7 @@ test_oplock_send_brk(smb_ofile_t *ofile,
BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT;
if (BreakTo == 0)
BreakTo = BREAK_TO_NO_CACHING;
+ // ls_breaking = BreakTo;
} else {
if ((NewLevel & LEVEL_TWO_OPLOCK) != 0)
BreakTo = BREAK_TO_TWO;
@@ -583,12 +595,15 @@ test_oplock_send_brk(smb_ofile_t *ofile,
BreakTo = BREAK_TO_NONE;
}
og->og_breaking = BreakTo;
- last_ind_break_level = NewLevel;
/* Set og_state in do_ack */
} else {
og->og_state = NewLevel;
+ // If lease: ls_breaking = ...
/* Clear og_breaking in do_ack */
}
+
+ /* Next, smb_oplock_send_break() would send a break. */
+ last_ind_break_level = NewLevel;
}
/*
@@ -623,7 +638,6 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
break;
default:
- /* Checked by caller. */
ASSERT(0);
break;
}
@@ -638,7 +652,7 @@ smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile,
}
uint32_t
-smb_oplock_wait_break(smb_node_t *node, int timeout)
+smb_oplock_wait_break(smb_request_t *sr, smb_node_t *node, int timeout)
{
printf("*smb_oplock_wait_break (state=0x%x)\n",
node->n_oplock.ol_state);
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c
index f01f019dec..658cf2de49 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c
@@ -11,7 +11,7 @@
/*
* Copyright 2019 Nexenta Systems, Inc. All rights reserved.
- * Copyright 2020 RackTop Systems, Inc.
+ * Copyright 2022 RackTop Systems, Inc.
*/
@@ -1815,7 +1815,7 @@ smb2sr_run_postwork(smb_request_t *top_sr)
switch (post_sr->smb2_cmd_code) {
case SMB2_OPLOCK_BREAK:
- smb_oplock_send_brk(post_sr);
+ smb_oplock_send_break(post_sr);
break;
default:
ASSERT(0);
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_durable.c b/usr/src/uts/common/fs/smbsrv/smb2_durable.c
index 190a7ca6cc..094489c658 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_durable.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_durable.c
@@ -11,6 +11,7 @@
/*
* Copyright 2017-2022 Tintri by DDN, Inc. All rights reserved.
+ * Copyright 2022 RackTop Systems, Inc.
*/
/*
@@ -1426,7 +1427,7 @@ smb2_dh_reconnect(smb_request_t *sr)
/*
* The ofile is now in the caller's session & tree.
*
- * In case smb_ofile_hold or smb_oplock_send_brk() are
+ * In case smb_ofile_hold or smb_oplock_send_break() are
* waiting for state RECONNECT to complete, wakeup.
*/
mutex_enter(&of->f_mutex);
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_lease.c b/usr/src/uts/common/fs/smbsrv/smb2_lease.c
index 45f886e90a..81bf93d8fe 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_lease.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_lease.c
@@ -60,10 +60,19 @@ smb2_lease_hold(smb_lease_t *ls)
mutex_exit(&ls->ls_mutex);
}
+static void
+lease_destroy(smb_lease_t *ls)
+{
+ smb_node_release(ls->ls_node);
+ mutex_destroy(&ls->ls_mutex);
+ kmem_cache_free(smb_lease_cache, ls);
+}
+
void
smb2_lease_rele(smb_lease_t *ls)
{
smb_llist_t *bucket;
+ boolean_t destroy = B_FALSE;
mutex_enter(&ls->ls_mutex);
ls->ls_refcnt--;
@@ -81,16 +90,17 @@ smb2_lease_rele(smb_lease_t *ls)
smb_llist_enter(bucket, RW_WRITER);
mutex_enter(&ls->ls_mutex);
- if (ls->ls_refcnt == 0)
- smb_llist_remove(bucket, ls);
- mutex_exit(&ls->ls_mutex);
-
if (ls->ls_refcnt == 0) {
- mutex_destroy(&ls->ls_mutex);
- kmem_cache_free(smb_lease_cache, ls);
+ smb_llist_remove(bucket, ls);
+ destroy = B_TRUE;
}
+ mutex_exit(&ls->ls_mutex);
smb_llist_exit(bucket);
+
+ if (destroy) {
+ lease_destroy(ls);
+ }
}
/*
@@ -150,6 +160,7 @@ smb2_lease_create(smb_request_t *sr, uint8_t *clnt)
mutex_init(&newlease->ls_mutex, NULL, MUTEX_DEFAULT, NULL);
newlease->ls_bucket = bucket;
newlease->ls_node = of->f_node;
+ smb_node_ref(newlease->ls_node);
newlease->ls_refcnt = 1;
newlease->ls_epoch = op->lease_epoch;
newlease->ls_version = op->lease_version;
@@ -197,8 +208,7 @@ smb2_lease_create(smb_request_t *sr, uint8_t *clnt)
smb_llist_exit(bucket);
if (newlease != NULL) {
- mutex_destroy(&newlease->ls_mutex);
- kmem_cache_free(smb_lease_cache, newlease);
+ lease_destroy(newlease);
}
if (lease != NULL) {
@@ -213,9 +223,11 @@ smb2_lease_create(smb_request_t *sr, uint8_t *clnt)
* Find the lease for a given: client_uuid, lease_key
* Returns the lease with a new ref.
*/
-smb_lease_t *
-smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key)
+static smb_lease_t *
+lease_lookup(smb_request_t *sr, uint8_t *lease_key)
{
+ smb_server_t *sv = sr->sr_server;
+ uint8_t *clnt_uuid = sr->session->clnt_uuid;
smb_hash_t *ht = sv->sv_lease_ht;
smb_llist_t *bucket;
smb_lease_t *lease;
@@ -241,57 +253,36 @@ smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key)
}
/*
- * Find an smb_ofile_t in the current tree that shares the
- * specified lease and holds the oplock for the lease.
- * If lease not found, NT_STATUS_OBJECT_NAME_NOT_FOUND.
- * If no ofile (on the lease) holds the oplock, NT_STATUS_UNSUCCESSFUL.
+ * Find the oplock smb_ofile_t for the specified lease.
+ * If no such ofile, NT_STATUS_UNSUCCESSFUL.
* On success, ofile (held) in sr->fid_ofile.
*/
static uint32_t
-find_oplock_ofile(smb_request_t *sr, uint8_t *lease_key)
+lease_find_oplock(smb_request_t *sr, smb_lease_t *lease)
{
- smb_tree_t *tree = sr->tid_tree;
- smb_lease_t *lease;
- smb_llist_t *of_list;
+ smb_node_t *node = lease->ls_node;
smb_ofile_t *o;
- uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
-
- SMB_TREE_VALID(tree);
- of_list = &tree->t_ofile_list;
-
- smb_llist_enter(of_list, RW_READER);
- for (o = smb_llist_head(of_list); o != NULL;
- o = smb_llist_next(of_list, o)) {
+ uint32_t status = NT_STATUS_UNSUCCESSFUL;
- ASSERT(o->f_magic == SMB_OFILE_MAGIC);
- ASSERT(o->f_tree == tree);
-
- DTRACE_PROBE1(every_ofile, smb_ofile_t *, o);
- if ((lease = o->f_lease) == NULL)
- continue; // no lease
-
- if (bcmp(lease->ls_key, lease_key, UUID_LEN) != 0)
- continue; // wrong lease
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+ ASSERT(sr->fid_ofile == NULL);
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_lease != lease)
+ continue;
+ if (o != lease->ls_oplock_ofile)
+ continue;
/*
- * Now we know the lease exists, so if we don't
- * find the ofile that has the oplock, return:
+ * Found the ofile holding the oplock
+ * This hold released in smb_request_free
*/
- status = NT_STATUS_UNSUCCESSFUL;
-
- DTRACE_PROBE2(lease_ofile, smb_lease_t *, lease,
- smb_ofile_t *, o);
- if (lease->ls_oplock_ofile != o)
- continue; // not oplock holder
-
- /* Found the ofile holding the oplock */
- if (smb_ofile_hold(o)) {
+ if (smb_ofile_hold_olbrk(o)) {
sr->fid_ofile = o;
status = NT_STATUS_SUCCESS;
break;
}
}
- smb_llist_exit(of_list);
return (status);
}
@@ -300,15 +291,17 @@ find_oplock_ofile(smb_request_t *sr, uint8_t *lease_key)
* This is called by smb2_oplock_break_ack when the struct size
* indicates this is a lease break (SZ_LEASE). See:
* [MS-SMB2] 3.3.5.22.2 Processing a Lease Acknowledgment
+ * This is an "Ack" from the client.
*/
smb_sdrc_t
smb2_lease_break_ack(smb_request_t *sr)
{
+ smb_arg_olbrk_t *olbrk = &sr->arg.olbrk;
smb_lease_t *lease;
+ smb_node_t *node;
smb_ofile_t *ofile;
- uint8_t LeaseKey[UUID_LEN];
uint32_t LeaseState;
- uint32_t LeaseBreakTo;
+ uint32_t BreakTo;
uint32_t status;
int rc = 0;
@@ -324,56 +317,101 @@ smb2_lease_break_ack(smb_request_t *sr)
&sr->smb_data, "6.#cl8.",
/* reserved 6. */
UUID_LEN, /* # */
- LeaseKey, /* c */
- &LeaseState); /* l */
+ olbrk->LeaseKey, /* c */
+ &olbrk->NewLevel); /* l */
/* duration 8. */
if (rc != 0)
return (SDRC_ERROR);
+ LeaseState = olbrk->NewLevel;
- status = find_oplock_ofile(sr, LeaseKey);
+ /*
+ * Find the lease via the given key.
+ */
+ lease = lease_lookup(sr, olbrk->LeaseKey);
+ if (lease == NULL) {
+ /*
+ * It's unusual to skip the dtrace start/done
+ * probes like this, but trying to run them
+ * with no lease->node would be complex and
+ * would not show anything particularly useful.
+ * Do the start probe after we find the ofile.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ smb2sr_put_error(sr, status);
+ return (SDRC_SUCCESS);
+ }
+ // Note: lease ref; smb_lease_rele() below.
+ node = lease->ls_node;
+
+ /*
+ * Find the leased oplock. Hold locks so it can't move
+ * until we're done with ACK-break processing.
+ */
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ status = lease_find_oplock(sr, lease);
+ /* Normally have sr->fid_ofile now. */
DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
- if (status != 0)
+
+ if (status != 0) {
+ /* Leased oplock not found. Must have closed. */
goto errout;
+ }
- /* Success, so have sr->fid_ofile and lease */
+ /* Success, so have sr->fid_ofile */
ofile = sr->fid_ofile;
- lease = ofile->f_lease;
- /* Either this ACK is unsolicited, or we timed out waiting. */
if (lease->ls_breaking == 0) {
+ /*
+ * This ACK is either unsolicited or too late,
+ * eg. we timed out the ACK and did it locally.
+ */
status = NT_STATUS_UNSUCCESSFUL;
goto errout;
}
/*
- * Process the lease break ack.
- *
* If the new LeaseState has any bits in excess of
* the lease state we sent in the break, error...
*/
- LeaseBreakTo = (lease->ls_breaking >> BREAK_SHIFT) &
+ BreakTo = (lease->ls_breaking >> BREAK_SHIFT) &
OPLOCK_LEVEL_CACHE_MASK;
- if ((LeaseState & ~LeaseBreakTo) != 0) {
+ if ((LeaseState & ~BreakTo) != 0) {
status = NT_STATUS_REQUEST_NOT_ACCEPTED;
goto errout;
}
+ /*
+ * Process the lease break ack.
+ *
+ * Clear breaking flags before we ack,
+ * because ack might set those.
+ */
+ ofile->f_oplock.og_breaking = 0;
lease->ls_breaking = 0;
+ cv_broadcast(&lease->ls_ack_cv);
LeaseState |= OPLOCK_LEVEL_GRANULAR;
status = smb_oplock_ack_break(sr, ofile, &LeaseState);
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- (void) smb2sr_go_async(sr);
- (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
- status = NT_STATUS_SUCCESS;
- }
- lease->ls_state = LeaseState & OPLOCK_LEVEL_CACHE_MASK;
+ ofile->f_oplock.og_state = LeaseState;
+ lease->ls_state = LeaseState & CACHE_RWH;
+ /* ls_epoch does not change here */
+
+ if (ofile->dh_persist)
+ smb2_dh_update_oplock(sr, ofile);
errout:
sr->smb2_status = status;
DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
+
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
+ smb2_lease_rele(lease);
+
if (status) {
smb2sr_put_error(sr, status);
return (SDRC_SUCCESS);
@@ -389,7 +427,7 @@ errout:
SSZ_LEASE_ACK, /* w */
/* reserved 6. */
UUID_LEN, /* # */
- LeaseKey, /* c */
+ olbrk->LeaseKey, /* c */
LeaseState); /* l */
/* duration 8. */
@@ -404,25 +442,23 @@ errout:
*
* [MS-SMB2] 2.2.23.2 Lease Break Notification
*/
-void
-smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
- boolean_t AckReq)
+static void
+smb2_lease_break_notification(smb_request_t *sr,
+ uint32_t OldLevel, uint32_t NewLevel,
+ uint16_t Epoch, boolean_t AckReq)
{
smb_lease_t *ls = sr->fid_ofile->f_lease;
- uint32_t oldcache;
- uint32_t newcache;
- uint16_t Epoch;
- uint16_t Flags;
+ uint16_t Flags = 0;
/*
- * Convert internal level to SMB2
+ * Convert internal lease info to SMB2
*/
- oldcache = ls->ls_state & OPLOCK_LEVEL_CACHE_MASK;
- newcache = NewLevel & OPLOCK_LEVEL_CACHE_MASK;
+ if (AckReq)
+ Flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
if (ls->ls_version < 2)
Epoch = 0;
- else
- Epoch = ls->ls_epoch;
+ OldLevel &= OPLOCK_LEVEL_CACHE_MASK;
+ NewLevel &= OPLOCK_LEVEL_CACHE_MASK;
/*
* SMB2 Header
@@ -441,7 +477,6 @@ smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
* [MS-SMB2] says the current lease state preceeds the
* new lease state, but that looks like an error...
*/
- Flags = AckReq ? SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED : 0;
(void) smb_mbc_encodef(
&sr->reply, "wwl#cll4.4.4.",
SSZ_LEASE_BRK, /* w */
@@ -449,12 +484,266 @@ smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
Flags, /* l */
SMB_LEASE_KEY_SZ, /* # */
ls->ls_key, /* c */
- oldcache, /* cur.st l */
- newcache); /* new.st l */
+ OldLevel, /* cur.st l */
+ NewLevel); /* new.st l */
/* reserved (4.4.4.) */
}
/*
+ * Do our best to send a lease break message to the client.
+ * When we get to multi-channel, this is supposed to try
+ * every channel before giving up. For now, try every
+ * connected session with an ofile sharing this lease.
+ *
+ * If this ofile has a valid session, try that first.
+ * Otherwise look on the node list for other ofiles with
+ * the same lease and a connected session.
+ */
+static int
+lease_send_any_cn(smb_request_t *sr)
+{
+ smb_ofile_t *o;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_lease_t *lease = ofile->f_lease;
+ smb_node_t *node = ofile->f_node;
+ int rc = ENOTCONN;
+
+ /*
+ * If the passed oplock ofile has a session,
+ * this IF expression will be true.
+ */
+ if (sr->session == ofile->f_session) {
+ rc = smb_session_send(sr->session, 0, &sr->reply);
+ if (rc == 0)
+ return (rc);
+ }
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_lease != lease)
+ continue;
+ if (smb_ofile_hold(o)) {
+ /* Has a session. */
+ rc = smb_session_send(o->f_session, 0, &sr->reply);
+ smb_llist_post(&node->n_ofile_list, o,
+ smb_ofile_release_LL);
+ }
+ if (rc == 0)
+ break;
+ }
+ smb_llist_exit(&node->n_ofile_list);
+
+ return (rc);
+}
+
+/*
+ * See smb_llist_post on node->n_ofile_list below.
+ * Can't call smb_ofile_close with that list entered.
+ */
+static void
+lease_ofile_close_rele(void *arg)
+{
+ smb_ofile_t *of = (smb_ofile_t *)arg;
+
+ smb_ofile_close(of, 0);
+ smb_ofile_release(of);
+}
+
+/*
+ * [MS-SMB2] 3.3.4.7 Object Store Indicates a Lease Break
+ * If no connection, for each Open in Lease.LeaseOpens,
+ * the server MUST close the Open as specified in sec...
+ * for the following cases:
+ * - Open.IsDurable, Open.IsResilient, and
+ * Open.IsPersistent are all FALSE.
+ * - Open.IsDurable is TRUE and Lease.BreakToLeaseState
+ * does not contain SMB2_LEASE_HANDLE_CACHING and
+ */
+static void
+lease_close_notconn(smb_request_t *sr, uint32_t NewLevel)
+{
+ smb_ofile_t *o;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_lease_t *lease = ofile->f_lease;
+ smb_node_t *node = ofile->f_node;
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_lease != lease)
+ continue;
+ if (o->f_oplock_closing)
+ continue;
+ if (o->dh_persist)
+ continue;
+ if (o->dh_vers == SMB2_RESILIENT)
+ continue;
+ if (o->dh_vers == SMB2_NOT_DURABLE ||
+ (NewLevel & OPLOCK_LEVEL_CACHE_HANDLE) == 0) {
+ if (smb_ofile_hold_olbrk(o)) {
+ smb_llist_post(&node->n_ofile_list, o,
+ lease_ofile_close_rele);
+ }
+ }
+ }
+ smb_llist_exit(&node->n_ofile_list);
+}
+
+/*
+ * Send a lease break over the wire, or if we can't,
+ * then process the lease break locally.
+ *
+ * [MS-SMB2] 3.3.4.7 Object Store Indicates a Lease Break
+ *
+ * This is mostly similar to smb2_oplock_send_break()
+ * See top comment there about the design.
+ *
+ * Differences beween a lease break and oplock break:
+ *
+ * Leases are an SMB-level mechanism whereby multiple open
+ * SMB file handles can share an oplock. All SMB handles
+ * on the lease enjoy the same caching rights. Down at the
+ * file-system level, just one oplock holds the cache rights
+ * for a lease, but (this is the tricky part) that oplock can
+ * MOVE among the SMB file handles sharing the lease. Such
+ * oplock moves can happen when a handle is closed (if that
+ * handle is the one with the oplock) or when a new open on
+ * the lease causes an upgrade of the caching rights.
+ *
+ * We have to deal here with lease movement because this call
+ * happens asynchronously after the smb_oplock_ind_break call,
+ * meaning that the oplock for the lease may have moved by the
+ * time this runs. In addition, the ofile holding the oplock
+ * might not be the best one to use to send a lease break.
+ * If the oplock is held by a handle that's "orphaned" and
+ * there are other handles on the lease with active sessions,
+ * we want to send the lease break on an active session.
+ *
+ * Also note: NewLevel (as provided by smb_oplock_ind_break etc.)
+ * does NOT include the GRANULAR flag. This level is expected to
+ * keep track of how each oplock was acquired (by lease or not)
+ * and put the GRANULAR flag back in when appropriate.
+ */
+void
+smb2_lease_send_break(smb_request_t *sr)
+{
+ smb_ofile_t *old_ofile;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_node_t *node = ofile->f_node;
+ smb_lease_t *lease = ofile->f_lease;
+ smb_arg_olbrk_t *olbrk = &sr->arg.olbrk;
+ boolean_t AckReq = olbrk->AckRequired;
+ uint32_t OldLevel = olbrk->OldLevel;
+ uint32_t NewLevel = olbrk->NewLevel;
+ uint32_t status;
+ int rc;
+
+ NewLevel |= OPLOCK_LEVEL_GRANULAR;
+
+ /*
+ * Build the break message in sr->reply.
+ * It's free'd in smb_request_free().
+ * Always an SMB2 lease here.
+ */
+ sr->reply.max_bytes = MLEN;
+ smb2_lease_break_notification(sr,
+ OldLevel, NewLevel, lease->ls_epoch, AckReq);
+
+ /*
+ * Try to send the break message to the client,
+ * on any connection with this lease.
+ */
+ rc = lease_send_any_cn(sr);
+ if (rc != 0) {
+ /*
+ * We were unable to send the oplock break request,
+ * presumably because the connection is gone.
+ * Close uninteresting handles.
+ */
+ lease_close_notconn(sr, NewLevel);
+ /* Note: some handles may remain on the lease. */
+ if (!AckReq)
+ return;
+ /* Do local Ack below. */
+ } else {
+ /*
+ * OK, we were able to send the break message.
+ * If no ack. required, we're done.
+ */
+ if (!AckReq)
+ return;
+
+ /*
+ * We're expecting an ACK. Wait in this thread
+ * so we can log clients that don't respond.
+ */
+ status = smb_oplock_wait_ack(sr);
+ if (status == 0)
+ return;
+
+ cmn_err(CE_NOTE, "clnt %s oplock break timeout",
+ sr->session->ip_addr_str);
+ DTRACE_PROBE1(ack_timeout, smb_request_t *, sr);
+
+ /*
+ * Will do local ack below. Note, after timeout,
+ * do a break to none or "no caching" regardless
+ * of what the passed in cache level was.
+ * That means: clear all except GRANULAR.
+ */
+ NewLevel = OPLOCK_LEVEL_GRANULAR;
+ }
+
+ /*
+ * Do the ack locally.
+ *
+ * Find the ofile with the leased oplock
+ * (may have moved before we took locks)
+ */
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ old_ofile = ofile;
+ sr->fid_ofile = NULL;
+ status = lease_find_oplock(sr, lease);
+ if (status != 0) {
+ /* put back old_ofile */
+ sr->fid_ofile = old_ofile;
+ goto unlock_out;
+ }
+ smb_llist_post(&node->n_ofile_list, old_ofile,
+ smb_ofile_release_LL);
+
+ ofile = sr->fid_ofile;
+
+ /*
+ * Now continue like the non-lease code
+ */
+ ofile->f_oplock.og_breaking = 0;
+ lease->ls_breaking = 0;
+ cv_broadcast(&lease->ls_ack_cv);
+
+ status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+
+ ofile->f_oplock.og_state = NewLevel;
+ lease->ls_state = NewLevel & CACHE_RWH;
+ /* ls_epoch does not change here */
+
+ 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);
+
+#ifdef DEBUG
+ if (status != 0) {
+ cmn_err(CE_NOTE, "clnt %s local oplock ack, status=0x%x",
+ sr->session->ip_addr_str, status);
+ }
+#endif
+}
+
+/*
* Client has an open handle and requests a lease.
* Convert SMB2 lease request info in to internal form,
* call common oplock code, convert result to SMB2.
@@ -467,6 +756,7 @@ smb2_lease_acquire(smb_request_t *sr)
smb_arg_open_t *op = &sr->arg.open;
smb_ofile_t *ofile = sr->fid_ofile;
smb_lease_t *lease = ofile->f_lease;
+ smb_node_t *node = ofile->f_node;
uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
uint32_t have, want; /* lease flags */
boolean_t NewGrant = B_FALSE;
@@ -510,6 +800,13 @@ smb2_lease_acquire(smb_request_t *sr)
}
/*
+ * Using the "Locks Held" (LH) variant of smb_oplock_request
+ * below so things won't change underfoot.
+ */
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ /*
* Disallow downgrade
*
* Note that open with a lease is not allowed to turn off
@@ -547,7 +844,7 @@ smb2_lease_acquire(smb_request_t *sr)
if (have == want)
goto done;
- status = smb_oplock_request(sr, ofile,
+ status = smb_oplock_request_LH(sr, ofile,
&op->op_oplock_state);
if (status == NT_STATUS_SUCCESS ||
status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
@@ -587,7 +884,7 @@ smb2_lease_acquire(smb_request_t *sr)
if (have == want)
goto done;
- status = smb_oplock_request(sr, ofile,
+ status = smb_oplock_request_LH(sr, ofile,
&op->op_oplock_state);
if (status == NT_STATUS_SUCCESS ||
status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
@@ -599,6 +896,7 @@ smb2_lease_acquire(smb_request_t *sr)
* We did not get "RH", probably because
* ther were (old style) Level II oplocks.
* Continue, try for just read.
+ * Again, re-init op_oplock_state
*/
op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
(op->lease_state & CACHE_R);
@@ -612,7 +910,7 @@ smb2_lease_acquire(smb_request_t *sr)
if (have == want)
goto done;
- status = smb_oplock_request(sr, ofile,
+ status = smb_oplock_request_LH(sr, ofile,
&op->op_oplock_state);
if (status == NT_STATUS_SUCCESS ||
status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
@@ -634,26 +932,30 @@ smb2_lease_acquire(smb_request_t *sr)
op->op_oplock_state = OPLOCK_LEVEL_GRANULAR;
done:
+ /*
+ * Only success cases get here
+ */
+
+ /*
+ * Keep track of what we got (ofile->f_oplock.og_state etc)
+ * so we'll know what we had when sending a break later.
+ * Also keep a copy of some things in the lease.
+ *
+ * Not using og_dialect here, as ofile->f_lease tells us
+ * this has to be using granular oplocks.
+ */
if (NewGrant) {
- /*
- * After a new oplock grant, the status return
- * may indicate we need to wait for breaks.
- */
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- (void) smb2sr_go_async(sr);
- (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
- status = NT_STATUS_SUCCESS;
- }
- ASSERT(status == NT_STATUS_SUCCESS);
+ ofile->f_oplock.og_state = op->op_oplock_state;
+ ofile->f_oplock.og_breaking = 0;
- /*
- * Keep track of what we got (in lease->ls_state)
- * so we'll know what we last told the client.
- */
- mutex_enter(&lease->ls_mutex);
- lease->ls_state = op->op_oplock_state & CACHE_RWH;
+ lease->ls_oplock_ofile = ofile;
+ lease->ls_state = op->op_oplock_state;
+ lease->ls_breaking = B_FALSE;
lease->ls_epoch++;
- mutex_exit(&lease->ls_mutex);
+
+ if (ofile->dh_persist) {
+ smb2_dh_update_oplock(sr, ofile);
+ }
}
/*
@@ -665,6 +967,21 @@ done:
SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
op->lease_epoch = lease->ls_epoch;
op->lease_version = lease->ls_version;
+
+ /*
+ * End of lock-held region
+ */
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
+ /*
+ * After a new oplock grant, the status return
+ * may indicate we need to wait for breaks.
+ */
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
+ }
}
/*
@@ -675,6 +992,12 @@ done:
* If an ofile with a lease is closed and it's the one that
* owns the oplock, try to move the oplock to another ofile
* on the same lease.
+ *
+ * Would prefer that we could just use smb_ofile_hold_olbrk
+ * to select a suitable destination for the move, but this
+ * is called while holding the owning tree ofile list etc
+ * which can cause deadlock as described in illumos 13850
+ * when smb_ofile_hold_olbrk has to wait. XXX todo
*/
void
smb2_lease_ofile_close(smb_ofile_t *ofile)
@@ -695,7 +1018,7 @@ smb2_lease_ofile_close(smb_ofile_t *ofile)
/*
* Find another ofile to which we can move the oplock.
- * The ofile must be open and allow a new ref.
+ * First try for one that's open. Usually find one.
*/
FOREACH_NODE_OFILE(node, o) {
if (o == ofile)
@@ -704,17 +1027,58 @@ smb2_lease_ofile_close(smb_ofile_t *ofile)
continue;
if (o->f_oplock_closing)
continue;
- /* If we can get a hold, use this ofile. */
- if (smb_ofile_hold(o))
- break;
+
+ mutex_enter(&o->f_mutex);
+ if (o->f_state == SMB_OFILE_STATE_OPEN) {
+ smb_oplock_move(node, ofile, o);
+ lease->ls_oplock_ofile = o;
+ mutex_exit(&o->f_mutex);
+ return;
+ }
+ mutex_exit(&o->f_mutex);
}
- if (o == NULL) {
- /* Normal for last close on a lease. */
- lease->ls_oplock_ofile = NULL;
- return;
+
+ /*
+ * Now try for one that's orphaned etc.
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o == ofile)
+ continue;
+ if (o->f_lease != lease)
+ continue;
+ if (o->f_oplock_closing)
+ continue;
+
+ /*
+ * Allow most states as seen in smb_ofile_hold_olbrk
+ * without waiting for "_reconnect" or "_saving".
+ * Skip "_expired" because that's about to close.
+ * This is OK because just swapping the oplock state
+ * between two ofiles does not interfere with the
+ * dh_save or reconnect code paths.
+ */
+ mutex_enter(&o->f_mutex);
+ switch (o->f_state) {
+ case SMB_OFILE_STATE_OPEN:
+ case SMB_OFILE_STATE_SAVE_DH:
+ case SMB_OFILE_STATE_SAVING:
+ case SMB_OFILE_STATE_ORPHANED:
+ case SMB_OFILE_STATE_RECONNECT:
+ smb_oplock_move(node, ofile, o);
+ lease->ls_oplock_ofile = o;
+ mutex_exit(&o->f_mutex);
+ return;
+ }
+ mutex_exit(&o->f_mutex);
}
- smb_oplock_move(node, ofile, o);
- lease->ls_oplock_ofile = o;
- smb_ofile_release(o);
+ /*
+ * Normal for last close on a lease.
+ * Wakeup ACK waiters too.
+ */
+ lease->ls_state = 0;
+ lease->ls_breaking = 0;
+ cv_broadcast(&lease->ls_ack_cv);
+
+ lease->ls_oplock_ofile = NULL;
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c
index f8317b2e81..613b56dac9 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c
@@ -11,7 +11,7 @@
/*
* Copyright 2020 Tintri by DDN, Inc. All rights reserved.
- * Copyright 2019 RackTop Systems.
+ * Copyright 2022 RackTop Systems, Inc.
*/
/*
@@ -19,6 +19,7 @@
*/
#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_oplock.h>
#define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
@@ -30,11 +31,15 @@
* SMB2 Oplock Break Acknowledgement
* [MS-SMB2] 3.3.5.22.1 Processing an Oplock Acknowledgment
* Called via smb2_disp_table[]
+ * This is an "Ack" from the client.
*/
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;
@@ -72,16 +77,8 @@ smb2_oplock_break_ack(smb_request_t *sr)
if (rc != 0)
return (SDRC_ERROR);
- /* Find the ofile */
- status = smb2sr_lookup_fid(sr, &smb2fid);
- /* Success or NT_STATUS_FILE_CLOSED */
-
- DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
- if (status != 0)
- goto errout;
-
/*
- * Process an (old-style) oplock break ack.
+ * Convert SMB oplock level to internal form.
*/
switch (smbOplockLevel) {
case SMB2_OPLOCK_LEVEL_NONE: /* 0x00 */
@@ -96,50 +93,91 @@ smb2_oplock_break_ack(smb_request_t *sr)
case SMB2_OPLOCK_LEVEL_BATCH: /* 0x09 */
NewLevel = OPLOCK_LEVEL_BATCH;
break;
+
+ /* Note: _LEVEL_LEASE is not valid here. */
case SMB2_OPLOCK_LEVEL_LEASE: /* 0xFF */
- NewLevel = OPLOCK_LEVEL_NONE;
- break;
default:
+ /*
+ * Impossible NewLevel here, will cause
+ * NT_STATUS_INVALID_PARAMETER below.
+ */
+ NewLevel = OPLOCK_LEVEL_GRANULAR;
+ break;
+ }
+
+ /* for dtrace */
+ olbrk->NewLevel = NewLevel;
+
+ /* Find the ofile */
+ status = smb2sr_lookup_fid(sr, &smb2fid);
+ /* Success or NT_STATUS_FILE_CLOSED */
+
+ DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
+
+ if (status != 0) {
+ /* lookup fid failed */
+ goto errout;
+ }
+
+ if (NewLevel == OPLOCK_LEVEL_GRANULAR) {
+ /* Switch above got invalid smbOplockLevel */
status = NT_STATUS_INVALID_PARAMETER;
goto errout;
}
+ /* Success, so have sr->fid_ofile */
ofile = sr->fid_ofile;
- if (ofile->f_oplock.og_breaking == 0) {
+ 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 == 0) {
/*
* This is an unsolicited Ack. (There is no
* outstanding oplock break in progress now.)
* There are WPTS tests that care which error
* is returned. See [MS-SMB2] 3.3.5.22.1
*/
- if (smbOplockLevel == SMB2_OPLOCK_LEVEL_LEASE) {
- status = NT_STATUS_INVALID_PARAMETER;
- goto errout;
- }
- if (NewLevel >= (ofile->f_oplock.og_state &
- OPLOCK_LEVEL_TYPE_MASK)) {
+ if (NewLevel >= (og->og_state & OPLOCK_LEVEL_TYPE_MASK)) {
status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
- goto errout;
+ goto unlock_out;
}
status = NT_STATUS_INVALID_DEVICE_STATE;
- goto errout;
+ goto unlock_out;
}
+
+ /*
+ * Process the oplock break ack.
+ *
+ * Clear breaking flags before we ack,
+ * because ack might set those.
+ */
ofile->f_oplock.og_breaking = 0;
+ cv_broadcast(&ofile->f_oplock.og_ack_cv);
status = smb_oplock_ack_break(sr, ofile, &NewLevel);
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- status = smb2sr_go_async(sr);
- if (status != 0)
- goto errout;
- (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
- status = 0;
- }
- if (status != 0) {
- NewLevel = OPLOCK_LEVEL_NONE;
- goto errout;
- }
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);
+ }
+
+ /*
+ * Convert internal oplock state back to SMB form.
+ */
switch (NewLevel & OPLOCK_LEVEL_TYPE_MASK) {
case OPLOCK_LEVEL_NONE:
smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
@@ -159,14 +197,6 @@ smb2_oplock_break_ack(smb_request_t *sr)
break;
}
-errout:
- sr->smb2_status = status;
- DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
- if (status) {
- smb2sr_put_error(sr, status);
- return (SDRC_SUCCESS);
- }
-
/*
* Encode an SMB2 Oplock Break Ack response
* [MS-SMB2] 2.2.25.1
@@ -187,7 +217,7 @@ errout:
* the SMB2 header and everything, in sr->reply.
* The caller will send it and free the request.
*/
-void
+static void
smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
{
smb_ofile_t *ofile = sr->fid_ofile;
@@ -237,6 +267,137 @@ smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
}
/*
+ * Send an oplock break over the wire, or if we can't,
+ * then process the oplock break locally.
+ *
+ * [MS-SMB2] 3.3.4.6 Object Store Indicates an Oplock Break
+ *
+ * Note: When "AckRequired" is set, and we're for any reason
+ * unable to communicate with the client so that they do an
+ * "oplock break ACK", then we absolutely MUST do a local ACK
+ * for this break indication (or close the ofile).
+ *
+ * The file-system level oplock code (smb_cmn_oplock.c)
+ * requires these ACK calls to clear "breaking" flags.
+ *
+ * This is called either from smb_oplock_async_break via a
+ * taskq job scheduled in smb_oplock_ind_break, or from the
+ * smb2sr_append_postwork() mechanism when we're doing a
+ * "break in ack", via smb_oplock_ind_break_in_ack.
+ *
+ * This runs much like other smb_request_t handlers, in the
+ * context of a worker task that calls with no locks held.
+ *
+ * Note that we have sr->fid_ofile here but all the other
+ * normal sr members may be NULL: uid_user, tid_tree.
+ * Also sr->session may or may not be the same session as
+ * the ofile came from (ofile->f_session) depending on
+ * whether this is a "live" open or an orphaned DH,
+ * where ofile->f_session will be NULL.
+ */
+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;
+
+ /*
+ * Build the break message in sr->reply.
+ * It's free'd in smb_request_free().
+ * Always SMB2 oplock here (no lease)
+ */
+ sr->reply.max_bytes = MLEN;
+ smb2_oplock_break_notification(sr, NewLevel);
+
+ /*
+ * Try to send the break message to the client.
+ * If connected, this IF body will be true.
+ */
+ if (sr->session == ofile->f_session)
+ rc = smb_session_send(sr->session, 0, &sr->reply);
+ else
+ rc = ENOTCONN;
+
+ if (rc != 0) {
+ /*
+ * We were unable to send the oplock break request,
+ * presumably because the connection is gone.
+ *
+ * [MS-SMB2] 3.3.4.6 Object Store Indicates an Oplock Break
+ * If no connection is available, Open.IsResilient is FALSE,
+ * Open.IsDurable is FALSE, and Open.IsPersistent is FALSE,
+ * the server SHOULD close the Open as specified in...
+ */
+ 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;
+ }
+ /* Keep this (durable) open. */
+ if (!AckReq)
+ return;
+ /* Do local Ack below. */
+ } else {
+ /*
+ * OK, we were able to send the break message.
+ * If no ack. required, we're done.
+ */
+ if (!AckReq)
+ return;
+
+ /*
+ * We're expecting an ACK. Wait in this thread
+ * so we can log clients that don't respond.
+ */
+ status = smb_oplock_wait_ack(sr);
+ if (status == 0)
+ return;
+
+ cmn_err(CE_NOTE, "clnt %s oplock break timeout",
+ sr->session->ip_addr_str);
+ DTRACE_PROBE1(ack_timeout, smb_request_t *, sr);
+
+ /*
+ * Will do local ack below. Note, after timeout,
+ * do a break to none or "no caching" regardless
+ * of what the passed in cache level was.
+ */
+ NewLevel = OPLOCK_LEVEL_NONE;
+ }
+
+ /*
+ * Do the ack locally.
+ */
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ ofile->f_oplock.og_breaking = 0;
+ 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
+}
+
+/*
* Client has an open handle and requests an oplock.
* Convert SMB2 oplock request info in to internal form,
* call common oplock code, convert result to SMB2.
@@ -289,6 +450,8 @@ smb2_oplock_acquire(smb_request_t *sr)
/*
* Tree options may force shared oplocks,
* in which case we reduce the request.
+ * Can't get here with LEVEL_NONE, so
+ * this can only decrease the level.
*/
if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
op->op_oplock_state = OPLOCK_LEVEL_TWO;
@@ -315,41 +478,38 @@ smb2_oplock_acquire(smb_request_t *sr)
}
/*
- * Either of the above may have returned the
- * status code that says we should wait.
- */
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- (void) smb2sr_go_async(sr);
- (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
- status = 0;
- }
-
- /*
- * Keep track of what we got (in ofile->f_oplock.og_state)
+ * Keep track of what we got (ofile->f_oplock.og_state etc)
* so we'll know what we had when sending a break later.
* The og_dialect here is the oplock dialect, not the
* SMB dialect. No lease here, so SMB 2.0.
*/
- ofile->f_oplock.og_dialect = SMB_VERS_2_002;
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_breaking = 0;
+ if (ofile->dh_persist) {
+ smb2_dh_update_oplock(sr, ofile);
+ }
break;
+
case NT_STATUS_OPLOCK_NOT_GRANTED:
- ofile->f_oplock.og_state = 0;
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
+
default:
/* Caller did not check args sufficiently? */
cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
sr->session->ip_addr_str, status);
- ofile->f_oplock.og_state = 0;
+ DTRACE_PROBE2(other__error, smb_request_t *, sr,
+ uint32_t, status);
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
return;
}
/*
- * Have STATUS_SUCCESS
+ * Only success cases get here
* Convert internal oplock state to SMB2
*/
if (op->op_oplock_state & OPLOCK_LEVEL_GRANULAR) {
@@ -364,6 +524,15 @@ smb2_oplock_acquire(smb_request_t *sr)
} else {
op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
}
+
+ /*
+ * An smb_oplock_reqest call may have returned the
+ * status code that says we should wait.
+ */
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
+ }
}
/*
diff --git a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c
index bee55e2e60..441dc59a6e 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c
@@ -315,10 +315,30 @@ uint32_t
smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep)
{
smb_node_t *node = ofile->f_node;
+ uint32_t status;
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ status = smb_oplock_request_LH(sr, ofile, statep);
+
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
+ return (status);
+}
+
+uint32_t
+smb_oplock_request_LH(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep)
+{
+ smb_node_t *node = ofile->f_node;
uint32_t type = *statep & OPLOCK_LEVEL_TYPE_MASK;
uint32_t level = *statep & OPLOCK_LEVEL_CACHE_MASK;
uint32_t status;
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
*statep = LEVEL_NONE;
/*
@@ -343,9 +363,6 @@ smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep)
return (NT_STATUS_OPLOCK_NOT_GRANTED);
}
- smb_llist_enter(&node->n_ofile_list, RW_READER);
- mutex_enter(&node->n_oplock.ol_mutex);
-
/*
* If Type is LEVEL_ONE or LEVEL_BATCH:
* The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED
@@ -495,25 +512,15 @@ smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep)
break;
}
- /* Give caller back the "Granular" bit. */
- if (status == NT_STATUS_SUCCESS) {
+ /*
+ * Give caller back the "Granular" bit, eg. when
+ * NT_STATUS_SUCCESS or NT_STATUS_OPLOCK_BREAK_IN_PROGRESS
+ */
+ if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) {
*statep |= LEVEL_GRANULAR;
-
- /*
- * The oplock lease may have moved to this ofile. Update.
- * Minor violation of layering here (leases vs oplocks)
- * but we want this update coverd by the oplock mutex.
- */
-#ifndef TESTJIG
- if (ofile->f_lease != NULL)
- ofile->f_lease->ls_oplock_ofile = ofile;
-#endif
}
out:
- mutex_exit(&node->n_oplock.ol_mutex);
- smb_llist_exit(&node->n_ofile_list);
-
return (status);
}
@@ -1481,8 +1488,8 @@ smb_oplock_ack_break(
boolean_t FoundMatchingRHOplock = B_FALSE;
int other_keys;
- smb_llist_enter(&node->n_ofile_list, RW_READER);
- mutex_enter(&node->n_oplock.ol_mutex);
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
/*
* If Open.Stream.Oplock is empty, the operation MUST be
@@ -2069,6 +2076,7 @@ smb_oplock_ack_break(
* the spec for this bit of code. Therefore, this will
* return SUCCESS instead of OPLOCK_BREAK_IN_PROGRESS.
*/
+ ASSERT(node->n_oplock.excl_open == ofile);
node->n_oplock.ol_state = level | EXCLUSIVE;
status = NT_STATUS_SUCCESS;
break; /* case (READ_CACHING|WRITE_CACHING|...) */
@@ -2088,11 +2096,6 @@ out:
type == LEVEL_GRANULAR &&
*rop != LEVEL_NONE) {
*rop |= LEVEL_GRANULAR;
- /* As above, leased oplock may have moved. */
-#ifndef TESTJIG
- if (ofile->f_lease != NULL)
- ofile->f_lease->ls_oplock_ofile = ofile;
-#endif
}
/*
@@ -2111,9 +2114,14 @@ out:
* The spec. describes waiting for a break here,
* but we let the caller do that (when needed) if
* status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS
+ *
+ * After some research, smb_oplock_ack_break()
+ * never returns that status. Paranoid check.
*/
- mutex_exit(&node->n_oplock.ol_mutex);
- smb_llist_exit(&node->n_ofile_list);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ ASSERT(!"Unexpected OPLOCK_BREAK_IN_PROGRESS");
+ status = NT_STATUS_SUCCESS;
+ }
return (status);
}
@@ -2751,6 +2759,8 @@ smb_oplock_break_cmn(smb_node_t *node,
* completes some earlier call to
* 2.1.5.17.1.)
*/
+ o = nol->excl_open;
+ ASSERT(o != NULL);
smb_oplock_ind_break(o,
LEVEL_TWO, B_TRUE,
NT_STATUS_SUCCESS);
@@ -2829,6 +2839,8 @@ smb_oplock_break_cmn(smb_node_t *node,
* completes some earlier call to
* 2.1.5.17.1.)
*/
+ o = nol->excl_open;
+ ASSERT(o != NULL);
smb_oplock_ind_break(o,
LEVEL_NONE, B_TRUE,
NT_STATUS_SUCCESS);
@@ -2904,7 +2916,7 @@ smb_oplock_break_cmn(smb_node_t *node,
* equals Open.TargetOplockKey,
* go to the LeaveBreakToNone label.
*/
- if (o != NULL &&
+ if ((o = nol->excl_open) != NULL &&
CompareOplockKeys(ofile, o, CmpFlags))
goto LeaveBreakToNone;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
index b58d3f9f0f..a09d5f80de 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2020 Tintri by DDN, Inc. All rights reserved.
+ * Copyright 2022 RackTop Systems, Inc.
*/
/*
@@ -250,7 +251,6 @@ smb_com_locking_andx(smb_request_t *sr)
DWORD result;
int rc;
uint32_t ltype;
- uint32_t status;
smb_ofile_t *ofile;
uint16_t tmp_pid; /* locking uses 16-bit pids */
uint32_t lrv_tot;
@@ -288,18 +288,16 @@ smb_com_locking_andx(smb_request_t *sr)
ltype = SMB_LOCK_TYPE_READWRITE;
if (lock_type & LOCKING_ANDX_OPLOCK_RELEASE) {
- uint32_t NewLevel;
- if (oplock_level == 0)
- NewLevel = OPLOCK_LEVEL_NONE;
- else
- NewLevel = OPLOCK_LEVEL_TWO;
- status = smb_oplock_ack_break(sr, ofile, &NewLevel);
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
- status = 0;
- }
+ smb1_oplock_ack_break(sr, oplock_level);
if (unlock_num == 0 && lock_num == 0)
return (SDRC_NO_REPLY);
+ /*
+ * Don't allow combining other lock/unlock actions
+ * with an oplock ACK (normally don't get here).
+ */
+ smbsr_error(sr, NT_STATUS_INVALID_PARAMETER,
+ ERRDOS, ERROR_INVALID_PARAMETER);
+ return (SDRC_ERROR);
}
/*
@@ -413,57 +411,3 @@ out:
return (SDRC_ERROR);
return (SDRC_SUCCESS);
}
-
-/*
- * Compose an SMB1 Oplock Break Notification packet, including
- * the SMB1 header and everything, in sr->reply.
- * The caller will send it and free the request.
- */
-void
-smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
-{
- smb_ofile_t *ofile = sr->fid_ofile;
- uint16_t fid;
- uint8_t lock_type;
- uint8_t oplock_level;
-
- /*
- * Convert internal level to SMB1
- */
- switch (NewLevel) {
- default:
- ASSERT(0);
- /* FALLTHROUGH */
- case OPLOCK_LEVEL_NONE:
- oplock_level = 0;
- break;
-
- case OPLOCK_LEVEL_TWO:
- oplock_level = 1;
- break;
- }
-
- sr->smb_com = SMB_COM_LOCKING_ANDX;
- sr->smb_tid = ofile->f_tree->t_tid;
- sr->smb_pid = 0xFFFF;
- sr->smb_uid = 0;
- sr->smb_mid = 0xFFFF;
- fid = ofile->f_fid;
- lock_type = LOCKING_ANDX_OPLOCK_RELEASE;
-
- (void) smb_mbc_encodef(
- &sr->reply, "Mb19.wwwwbb3.wbb10.",
- /* "\xffSMB" M */
- sr->smb_com, /* b */
- /* status, flags, signature 19. */
- sr->smb_tid, /* w */
- sr->smb_pid, /* w */
- sr->smb_uid, /* w */
- sr->smb_mid, /* w */
- 8, /* word count b */
- 0xFF, /* AndX cmd b */
- /* AndX reserved, offset 3. */
- fid,
- lock_type,
- oplock_level);
-}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_ofile.c b/usr/src/uts/common/fs/smbsrv/smb_ofile.c
index 9279b71981..379006a5e8 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_ofile.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_ofile.c
@@ -227,7 +227,7 @@
* Transition T7
*
* This transition occurs in smb_session_durable_timers() and
- * smb_oplock_send_brk(). The ofile will soon be closed.
+ * smb_oplock_send_break(). The ofile will soon be closed.
* In the former case, f_timeout_offset nanoseconds have passed since
* the ofile was orphaned. In the latter, an oplock break occured
* on the ofile while it was orphaned.
@@ -444,6 +444,10 @@ smb_ofile_open(
* SMB_OFILE_STATE_OPEN protocol close, smb_ofile_drop
* SMB_OFILE_STATE_EXPIRED called via smb2_dh_expire
* SMB_OFILE_STATE_ORPHANED smb2_dh_shutdown
+ *
+ * Not that this enters the of->node->n_ofile_list rwlock as reader,
+ * (via smb_oplock_close) so this must not be called while holding
+ * that rwlock.
*/
void
smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec)
@@ -453,21 +457,7 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec)
SMB_OFILE_VALID(of);
if (of->f_ftype == SMB_FTYPE_DISK) {
- smb_node_t *node = of->f_node;
-
- smb_llist_enter(&node->n_ofile_list, RW_READER);
- mutex_enter(&node->n_oplock.ol_mutex);
-
- if (of->f_oplock_closing == B_FALSE) {
- of->f_oplock_closing = B_TRUE;
-
- if (of->f_lease != NULL)
- smb2_lease_ofile_close(of);
- smb_oplock_break_CLOSE(node, of);
- }
-
- mutex_exit(&node->n_oplock.ol_mutex);
- smb_llist_exit(&node->n_ofile_list);
+ smb_oplock_close(of);
}
mutex_enter(&of->f_mutex);
@@ -743,7 +733,7 @@ smb_ofile_enum(smb_ofile_t *of, smb_svcenum_t *svcenum)
/*
* Take a reference on an open file, in any of the states:
- * RECONNECT, SAVE_DH, OPEN, ORPHANED.
+ * RECONNECT, SAVE_DH, OPEN, ORPHANED, EXPIRED.
* Return TRUE if ref taken. Used for oplock breaks.
*
* Note: When the oplock break code calls this, it holds the
@@ -774,13 +764,20 @@ again:
goto again;
case SMB_OFILE_STATE_OPEN:
- case SMB_OFILE_STATE_ORPHANED:
case SMB_OFILE_STATE_SAVE_DH:
+ case SMB_OFILE_STATE_ORPHANED:
+ case SMB_OFILE_STATE_EXPIRED:
of->f_refcnt++;
ret = B_TRUE;
break;
+ /*
+ * This is called only when an ofile has an oplock,
+ * so if we come across states that should not have
+ * an oplock, let's debug how that happened.
+ */
default:
+ ASSERT(0);
break;
}
mutex_exit(&of->f_mutex);
diff --git a/usr/src/uts/common/fs/smbsrv/smb_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_oplock.c
index 5215da8693..a3fd103fa1 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c
@@ -21,6 +21,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2020 Tintri by DDN, Inc. All rights reserved.
+ * Copyright 2022 RackTop Systems, Inc.
*/
/*
@@ -32,6 +33,199 @@
#define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
/*
+ * This is called by the SMB1 "Locking_andX" handler,
+ * for SMB1 oplock break acknowledgement.
+ * This is an "Ack" from the client.
+ */
+void
+smb1_oplock_ack_break(smb_request_t *sr, uchar_t oplock_level)
+{
+ smb_ofile_t *ofile;
+ smb_node_t *node;
+ uint32_t NewLevel;
+
+ ofile = sr->fid_ofile;
+ node = ofile->f_node;
+
+ if (oplock_level == 0)
+ NewLevel = OPLOCK_LEVEL_NONE;
+ else
+ NewLevel = OPLOCK_LEVEL_TWO;
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ ofile->f_oplock.og_breaking = 0;
+ cv_broadcast(&ofile->f_oplock.og_ack_cv);
+
+ (void) smb_oplock_ack_break(sr, ofile, &NewLevel);
+
+ ofile->f_oplock.og_state = NewLevel;
+
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+}
+
+/*
+ * Compose an SMB1 Oplock Break Notification packet, including
+ * the SMB1 header and everything, in sr->reply.
+ * The caller will send it and free the request.
+ */
+static void
+smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
+{
+ smb_ofile_t *ofile = sr->fid_ofile;
+ uint16_t fid;
+ uint8_t lock_type;
+ uint8_t oplock_level;
+
+ /*
+ * Convert internal level to SMB1
+ */
+ switch (NewLevel) {
+ default:
+ ASSERT(0);
+ /* FALLTHROUGH */
+ case OPLOCK_LEVEL_NONE:
+ oplock_level = 0;
+ break;
+
+ case OPLOCK_LEVEL_TWO:
+ oplock_level = 1;
+ break;
+ }
+
+ sr->smb_com = SMB_COM_LOCKING_ANDX;
+ sr->smb_tid = ofile->f_tree->t_tid;
+ sr->smb_pid = 0xFFFF;
+ sr->smb_uid = 0;
+ sr->smb_mid = 0xFFFF;
+ fid = ofile->f_fid;
+ lock_type = LOCKING_ANDX_OPLOCK_RELEASE;
+
+ (void) smb_mbc_encodef(
+ &sr->reply, "Mb19.wwwwbb3.wbb10.",
+ /* "\xffSMB" M */
+ sr->smb_com, /* b */
+ /* status, flags, signature 19. */
+ sr->smb_tid, /* w */
+ sr->smb_pid, /* w */
+ sr->smb_uid, /* w */
+ sr->smb_mid, /* w */
+ 8, /* word count b */
+ 0xFF, /* AndX cmd b */
+ /* AndX reserved, offset 3. */
+ fid,
+ lock_type,
+ oplock_level);
+}
+
+/*
+ * Send an oplock break over the wire, or if we can't,
+ * then process the oplock break locally.
+ *
+ * [MS-CIFS] 3.3.4.2 Object Store Indicates an OpLock Break
+ *
+ * This is mostly similar to smb2_oplock_send_break()
+ * See top comment there about the design.
+ * Called from smb_oplock_async_break.
+ *
+ * This handles only SMB1, which has no durable handles,
+ * and never has GRANULAR oplocks.
+ */
+void
+smb1_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;
+
+ /*
+ * SMB1 clients should only get Level II oplocks if they
+ * set the capability indicating they know about them.
+ */
+ if (NewLevel == OPLOCK_LEVEL_TWO &&
+ ofile->f_oplock.og_dialect < NT_LM_0_12)
+ NewLevel = OPLOCK_LEVEL_NONE;
+
+ /*
+ * Build the break message in sr->reply.
+ * It's free'd in smb_request_free().
+ * Always SMB1 here.
+ */
+ sr->reply.max_bytes = MLEN;
+ smb1_oplock_break_notification(sr, NewLevel);
+
+ /*
+ * Try to send the break message to the client.
+ * If connected, this IF body will be true.
+ */
+ if (sr->session == ofile->f_session)
+ rc = smb_session_send(sr->session, 0, &sr->reply);
+ else
+ rc = ENOTCONN;
+
+ if (rc != 0) {
+ /*
+ * We were unable to send the oplock break request,
+ * presumably because the connection is gone.
+ * Just close the handle.
+ */
+ smb_ofile_close(ofile, 0);
+ return;
+ }
+
+ /*
+ * OK, we were able to send the break message.
+ * If no ack. required, we're done.
+ */
+ if (!AckReq)
+ return;
+
+ /*
+ * We're expecting an ACK. Wait in this thread
+ * so we can log clients that don't respond.
+ */
+ status = smb_oplock_wait_ack(sr);
+ if (status == 0)
+ return;
+
+ cmn_err(CE_NOTE, "clnt %s oplock break timeout",
+ sr->session->ip_addr_str);
+ DTRACE_PROBE1(ack_timeout, smb_request_t *, sr);
+
+ /*
+ * Did not get an ACK, so do the ACK locally.
+ * Note: always break to none here, regardless
+ * of what the passed in cache level was.
+ */
+ 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 = 0;
+ cv_broadcast(&ofile->f_oplock.og_ack_cv);
+
+ status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+
+ ofile->f_oplock.og_state = NewLevel;
+
+ 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
+}
+
+/*
* Client has an open handle and requests an oplock.
* Convert SMB1 oplock request info in to internal form,
* call common oplock code, convert result to SMB1.
@@ -96,7 +290,7 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
}
/*
- * If exclusive failed (or tree forced shared oplocks)
+ * If exclusive failed (or the tree forced shared oplocks)
* and if the caller supports Level II, try shared.
*/
if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) {
@@ -106,16 +300,7 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
}
/*
- * Either of the above may have returned the
- * status code that says we should wait.
- */
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
- status = 0;
- }
-
- /*
- * Keep track of what we got (in ofile->f_oplock.og_state)
+ * Keep track of what we got (ofile->f_oplock.og_state etc)
* so we'll know what we had when sending a break later.
* The og_dialect here is the oplock dialect, which may be
* different than SMB dialect. Pre-NT clients did not
@@ -123,27 +308,27 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
* client that didn't set the CAP_LEVEL_II_OPLOCKS in
* its capabilities, let og_dialect = LANMAN2_1.
*/
- ofile->f_oplock.og_dialect = (level2ok) ?
- NT_LM_0_12 : LANMAN2_1;
switch (status) {
case NT_STATUS_SUCCESS:
+ case NT_STATUS_OPLOCK_BREAK_IN_PROGRESS:
+ ofile->f_oplock.og_dialect = (level2ok) ?
+ NT_LM_0_12 : LANMAN2_1;
ofile->f_oplock.og_state = op->op_oplock_state;
+ ofile->f_oplock.og_breaking = 0;
break;
case NT_STATUS_OPLOCK_NOT_GRANTED:
- ofile->f_oplock.og_state = 0;
op->op_oplock_level = SMB_OPLOCK_NONE;
return;
default:
/* Caller did not check args sufficiently? */
cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
sr->session->ip_addr_str, status);
- ofile->f_oplock.og_state = 0;
op->op_oplock_level = SMB_OPLOCK_NONE;
return;
}
/*
- * Have STATUS_SUCCESS
+ * Only succes cases get here.
* Convert internal oplock state to SMB1
*/
if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
@@ -155,4 +340,12 @@ smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
} else {
op->op_oplock_level = SMB_OPLOCK_NONE;
}
+
+ /*
+ * An smb_oplock_reqest call may have returned the
+ * status code that says we should wait.
+ */
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb_oplock_wait_break(sr, ofile->f_node, 0);
+ }
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c
index 76560f20a5..df917c3d0c 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c
@@ -73,7 +73,9 @@ int smb_oplock_timeout_ack = 30000; /* mSec. */
int smb_oplock_timeout_def = 45000; /* mSec. */
static void smb_oplock_async_break(void *);
-static void smb_oplock_hdl_clear(smb_ofile_t *);
+static void smb_oplock_hdl_update(smb_request_t *sr);
+static void smb_oplock_hdl_moved(smb_ofile_t *);
+static void smb_oplock_hdl_closed(smb_ofile_t *);
static void smb_oplock_wait_break_cancel(smb_request_t *sr);
@@ -181,8 +183,7 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile,
* We're going to schedule a request that will have a
* reference to this ofile. Get the hold first.
*/
- if (ofile->f_oplock_closing ||
- !smb_ofile_hold_olbrk(ofile)) {
+ if (!smb_ofile_hold_olbrk(ofile)) {
/* It's closing (or whatever). Nothing to do. */
return;
}
@@ -229,6 +230,8 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile,
sr->arg.olbrk.NewLevel = NewLevel;
sr->arg.olbrk.AckRequired = AckRequired;
+ smb_oplock_hdl_update(sr);
+
if (use_postwork) {
/*
* Using smb2_cmd_code to indicate what to call.
@@ -253,6 +256,8 @@ smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile,
* Schedule a request & taskq job to do oplock break work
* as requested by the FS-level code (smb_cmn_oplock.c).
*
+ * See also: smb_oplock_ind_break_in_ack
+ *
* Note called with the node ofile list rwlock held and
* the oplock mutex entered.
*/
@@ -281,11 +286,11 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
break;
case STATUS_NEW_HANDLE:
- /* nothing to do (keep for observability) */
+ smb_oplock_hdl_moved(ofile);
return;
case NT_STATUS_OPLOCK_HANDLE_CLOSED:
- smb_oplock_hdl_clear(ofile);
+ smb_oplock_hdl_closed(ofile);
return;
default:
@@ -297,8 +302,7 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
* We're going to schedule a request that will have a
* reference to this ofile. Get the hold first.
*/
- if (ofile->f_oplock_closing ||
- !smb_ofile_hold_olbrk(ofile)) {
+ if (!smb_ofile_hold_olbrk(ofile)) {
/* It's closing (or whatever). Nothing to do. */
return;
}
@@ -350,6 +354,8 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
sr->arg.olbrk.AckRequired = AckRequired;
sr->smb2_status = CompletionStatus;
+ smb_oplock_hdl_update(sr);
+
(void) taskq_dispatch(
sv->sv_worker_pool,
smb_oplock_async_break, sr, TQ_SLEEP);
@@ -362,16 +368,7 @@ smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
* We have a hold on the ofile, which will be released in
* smb_request_free (via sr->fid_ofile)
*
- * Note we have: sr->uid_user == NULL, sr->tid_tree == NULL.
- * Nothing called here needs those.
- *
- * Note that NewLevel as provided by the FS up-call does NOT
- * include the GRANULAR flag. The SMB level is expected to
- * keep track of how each oplock was acquired (by lease or
- * traditional oplock request) and put the GRANULAR flag
- * back into the oplock state when calling down to the
- * FS-level code. Also note that the lease break message
- * carries only the cache flags, not the GRANULAR flag.
+ * Note we may have: sr->uid_user == NULL, sr->tid_tree == NULL.
*/
static void
smb_oplock_async_break(void *arg)
@@ -398,7 +395,7 @@ smb_oplock_async_break(void *arg)
case STATUS_CANT_GRANT:
case NT_STATUS_SUCCESS:
- smb_oplock_send_brk(sr);
+ smb_oplock_send_break(sr);
break;
default:
@@ -416,94 +413,115 @@ smb_oplock_async_break(void *arg)
smb_request_free(sr);
}
-static void
-smb_oplock_update(smb_request_t *sr, smb_ofile_t *ofile, uint32_t NewLevel)
+/*
+ * Send an oplock (or lease) break to the client.
+ * If we can't, then do a local break.
+ *
+ * This is called either from smb_oplock_async_break via a
+ * taskq job scheduled in smb_oplock_ind_break, or from the
+ * smb2sr_append_postwork() mechanism when we're doing a
+ * "break in ack", via smb_oplock_ind_break_in_ack.
+ *
+ * We don't always have an sr->session here, so
+ * determine the oplock type (lease etc) from
+ * f_lease and f_oplock.og_dialect etc.
+ */
+void
+smb_oplock_send_break(smb_request_t *sr)
{
+ smb_ofile_t *ofile = sr->fid_ofile;
+
if (ofile->f_lease != NULL)
- ofile->f_lease->ls_state = NewLevel & CACHE_RWH;
+ smb2_lease_send_break(sr);
+ else if (ofile->f_oplock.og_dialect >= SMB_VERS_2_BASE)
+ smb2_oplock_send_break(sr);
else
- ofile->f_oplock.og_state = NewLevel;
+ smb1_oplock_send_break(sr);
+}
- if (ofile->dh_persist) {
- smb2_dh_update_oplock(sr, ofile);
- }
+/*
+ * Called by smb_oplock_ind_break for the case STATUS_NEW_HANDLE,
+ * which is an alias for NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+ *
+ * The FS-level oplock layer calls this to update the SMB-level state
+ * when the oplock for some lease is about to move to a different
+ * ofile on the lease.
+ *
+ * To avoid later confusion, clear og_state on this ofile now.
+ * Without this, smb_oplock_move() may issue debug complaints
+ * about moving oplock state onto a non-empty oplock.
+ */
+static const smb_ofile_t invalid_ofile;
+static void
+smb_oplock_hdl_moved(smb_ofile_t *ofile)
+{
+ smb_lease_t *ls = ofile->f_lease;
+
+ ASSERT(ls != NULL);
+ if (ls != NULL && ls->ls_oplock_ofile == ofile)
+ ls->ls_oplock_ofile = (smb_ofile_t *)&invalid_ofile;
+
+ ofile->f_oplock.og_state = 0;
+ ofile->f_oplock.og_breaking = 0;
}
-#ifdef DEBUG
-int smb_oplock_debug_wait = 0;
-#endif
+/*
+ * See: NT_STATUS_OPLOCK_HANDLE_CLOSED above and
+ * smb_ofile_close, smb_oplock_break_CLOSE.
+ *
+ * The FS-level oplock layer calls this to update the
+ * SMB-level state when a handle loses its oplock.
+ */
+static void
+smb_oplock_hdl_closed(smb_ofile_t *ofile)
+{
+ smb_lease_t *lease = ofile->f_lease;
+
+ if (lease != NULL) {
+ if (lease->ls_oplock_ofile == ofile) {
+ /*
+ * smb2_lease_ofile_close should have
+ * moved the oplock to another ofile.
+ */
+ ASSERT(0);
+ lease->ls_oplock_ofile = NULL;
+ }
+ }
+ ofile->f_oplock.og_state = 0;
+ ofile->f_oplock.og_breaking = 0;
+}
/*
- * Send an oplock break over the wire, or if we can't,
- * then process the oplock break locally.
- *
- * Note that we have sr->fid_ofile here but all the other
- * normal sr members may be NULL: uid_user, tid_tree.
- * Also sr->session may or may not be the same session as
- * the ofile came from (ofile->f_session) depending on
- * whether this is a "live" open or an orphaned DH,
- * where ofile->f_session will be NULL.
- *
- * Given that we don't always have a session, we determine
- * the oplock type (lease etc) from f_oplock.og_dialect.
+ * smb_oplock_hdl_update
+ *
+ * Called by smb_oplock_ind_break (and ...in_ack) just before we
+ * schedule smb_oplock_async_break / mb_oplock_send_break taskq job,
+ * so we can make any state changes that should happen immediately.
+ *
+ * Here, keep track of what we will send to the client.
+ * More importantly, if a break ack is expected, set
+ * the og_breaking flags to note that fact.
*/
-void
-smb_oplock_send_brk(smb_request_t *sr)
+static void
+smb_oplock_hdl_update(smb_request_t *sr)
{
- smb_ofile_t *ofile;
- smb_lease_t *lease;
- uint32_t NewLevel;
- boolean_t AckReq;
- uint32_t status;
- int rc;
-
- ofile = sr->fid_ofile;
- NewLevel = sr->arg.olbrk.NewLevel;
- AckReq = sr->arg.olbrk.AckRequired;
- lease = ofile->f_lease;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_lease_t *lease = ofile->f_lease;
+ uint32_t NewLevel = sr->arg.olbrk.NewLevel;
+ boolean_t AckReq = sr->arg.olbrk.AckRequired;
+
+#ifdef DEBUG
+ smb_node_t *node = ofile->f_node;
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+#endif
- /*
- * Build the break message in sr->reply.
- * It's free'd in smb_request_free().
- * Also updates the lease and NewLevel.
- */
- sr->reply.max_bytes = MLEN;
if (lease != NULL) {
- /*
- * The ofile has as lease. Must be SMB2+
- * Oplock state has changed, so update the epoch.
- */
- mutex_enter(&lease->ls_mutex);
+ sr->arg.olbrk.OldLevel = lease->ls_state;
lease->ls_epoch++;
- mutex_exit(&lease->ls_mutex);
-
- /* Note, needs "old" state in ls_state */
- smb2_lease_break_notification(sr,
- (NewLevel & CACHE_RWH), AckReq);
NewLevel |= OPLOCK_LEVEL_GRANULAR;
- } else if (ofile->f_oplock.og_dialect >= SMB_VERS_2_BASE) {
- /*
- * SMB2 using old-style oplock (no lease)
- */
- smb2_oplock_break_notification(sr, NewLevel);
- } else {
- /*
- * SMB1 clients should only get Level II oplocks if they
- * set the capability indicating they know about them.
- */
- if (NewLevel == OPLOCK_LEVEL_TWO &&
- ofile->f_oplock.og_dialect < NT_LM_0_12)
- NewLevel = OPLOCK_LEVEL_NONE;
- smb1_oplock_break_notification(sr, NewLevel);
}
- /*
- * Keep track of what we last sent to the client,
- * preserving the GRANULAR flag (if a lease).
- * If we're expecting an ACK, set og_breaking
- * (or maybe lease->ls_breaking) so we can
- * filter unsolicited ACKs.
- */
if (AckReq) {
uint32_t BreakTo;
@@ -517,174 +535,166 @@ smb_oplock_send_brk(smb_request_t *sr)
BreakTo = BREAK_TO_TWO;
else
BreakTo = BREAK_TO_NONE;
- ofile->f_oplock.og_breaking = BreakTo;
}
+ ofile->f_oplock.og_breaking = BreakTo;
/* Will update ls/og_state in ack. */
} else {
- smb_oplock_update(sr, ofile, NewLevel);
- }
-
- /*
- * Try to send the break message to the client.
- * When we get to multi-channel, this is supposed to
- * try to send on every channel before giving up.
- */
- if (sr->session == ofile->f_session)
- rc = smb_session_send(sr->session, 0, &sr->reply);
- else
- rc = ENOTCONN;
-
- if (rc == 0) {
- /*
- * OK, we were able to send the break message.
- * If no ack. required, we're done.
- */
- if (!AckReq)
- return;
-
/*
- * We're expecting an ACK. Wait in this thread
- * so we can log clients that don't respond.
- *
- * If debugging, may want to break after a
- * short wait to look into why we might be
- * holding up progress. (i.e. locks?)
+ * Not expecting an Ack from the client.
+ * Update state immediately.
*/
-#ifdef DEBUG
- if (smb_oplock_debug_wait > 0) {
- status = smb_oplock_wait_break(sr, ofile->f_node,
- smb_oplock_debug_wait);
- if (status == 0)
- return;
- cmn_err(CE_NOTE, "clnt %s oplock break wait debug",
- sr->session->ip_addr_str);
- debug_enter("oplock_wait");
+ ofile->f_oplock.og_state = NewLevel;
+ if (lease != NULL) {
+ lease->ls_state = NewLevel & CACHE_RWH;
}
-#endif
- status = smb_oplock_wait_break(sr, ofile->f_node,
- smb_oplock_timeout_ack);
- if (status == 0)
- return;
+ if (ofile->dh_persist) {
+ smb2_dh_update_oplock(sr, ofile);
+ }
+ }
+}
- cmn_err(CE_NOTE, "clnt %s oplock break timeout",
- sr->session->ip_addr_str);
- DTRACE_PROBE1(break_timeout, smb_ofile_t *, ofile);
+/*
+ * Helper for smb_ofile_close
+ *
+ * Note that a client may close an ofile in response to an
+ * oplock break or lease break intead of doing an Ack break,
+ * so this must wake anything that might be waiting on an ack.
+ */
+void
+smb_oplock_close(smb_ofile_t *ofile)
+{
+ smb_node_t *node = ofile->f_node;
- /*
- * Will do local ack below. Note, after timeout,
- * do a break to none or "no caching" regardless
- * of what the passed in cache level was.
- * That means: clear all except GRANULAR.
- */
- NewLevel &= OPLOCK_LEVEL_GRANULAR;
- } else {
- /*
- * We were unable to send the oplock break request.
- * Generally, that means we have no connection to this
- * client right now, and this ofile will have state
- * SMB_OFILE_STATE_ORPHANED. We either close the handle
- * or break the oplock locally, in which case the client
- * gets the updated oplock state when they reconnect.
- * Decide whether to keep or close.
- *
- * Relevant [MS-SMB2] sections:
- *
- * 3.3.4.6 Object Store Indicates an Oplock Break
- * If Open.Connection is NULL, Open.IsResilient is FALSE,
- * Open.IsDurable is FALSE and Open.IsPersistent is FALSE,
- * the server SHOULD close the Open as specified in...
- *
- * 3.3.4.7 Object Store Indicates a Lease Break
- * If Open.Connection is NULL, the server MUST close the
- * Open as specified in ... for the following cases:
- * - Open.IsResilient is FALSE, Open.IsDurable is FALSE,
- * and Open.IsPersistent is FALSE.
- * - Lease.BreakToLeaseState does not contain
- * ...HANDLE_CACHING and Open.IsDurable is TRUE.
- * If Lease.LeaseOpens is empty, (... local ack to "none").
- */
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
- /*
- * See similar logic in smb_dh_should_save
- */
- switch (ofile->dh_vers) {
- case SMB2_RESILIENT:
- break; /* keep DH */
-
- case SMB2_DURABLE_V2:
- if (ofile->dh_persist)
- break; /* keep DH */
- /* FALLTHROUGH */
- case SMB2_DURABLE_V1:
- /* IS durable (v1 or v2) */
- if ((NewLevel & (OPLOCK_LEVEL_BATCH |
- OPLOCK_LEVEL_CACHE_HANDLE)) != 0)
- break; /* keep DH */
- /* FALLTHROUGH */
- case SMB2_NOT_DURABLE:
- default:
- smb_ofile_close(ofile, 0);
- return;
- }
- /* Keep this ofile (durable handle). */
+ if (ofile->f_oplock_closing == B_FALSE) {
+ ofile->f_oplock_closing = B_TRUE;
- if (!AckReq) {
- /* Nothing more to do. */
- return;
- }
- }
+ if (ofile->f_lease != NULL)
+ smb2_lease_ofile_close(ofile);
- /*
- * We get here after either an oplock break ack timeout,
- * or a send failure for a durable handle type that we
- * preserve rather than just close. Do local ack.
- */
- if (lease != NULL)
- lease->ls_breaking = 0;
- else
- ofile->f_oplock.og_breaking = 0;
+ smb_oplock_break_CLOSE(node, ofile);
- status = smb_oplock_ack_break(sr, ofile, &NewLevel);
- if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
- /* Not expecting this status return. */
- cmn_err(CE_NOTE, "clnt local oplock ack wait?");
- (void) smb_oplock_wait_break(sr, ofile->f_node,
- smb_oplock_timeout_ack);
- status = 0;
- }
- if (status != 0) {
- cmn_err(CE_NOTE, "clnt local oplock ack, "
- "status=0x%x", status);
+ ofile->f_oplock.og_state = 0;
+ ofile->f_oplock.og_breaking = 0;
+ cv_broadcast(&ofile->f_oplock.og_ack_cv);
}
- /* Update ls/og_state as if we heard from the client. */
- smb_oplock_update(sr, ofile, NewLevel);
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
}
/*
- * See: NT_STATUS_OPLOCK_HANDLE_CLOSED above and
- * smb_ofile_close, smb_oplock_break_CLOSE.
- *
- * The FS-level oplock layer calls this to update the
- * SMB-level state when a handle loses its oplock.
+ * Called by smb_request_cancel() via sr->cancel_method
+ * Arg is the smb_node_t with the breaking oplock.
*/
static void
-smb_oplock_hdl_clear(smb_ofile_t *ofile)
+smb_oplock_wait_ack_cancel(smb_request_t *sr)
{
- smb_lease_t *lease = ofile->f_lease;
+ kcondvar_t *cvp = sr->cancel_arg2;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_node_t *node = ofile->f_node;
+
+ mutex_enter(&node->n_oplock.ol_mutex);
+ cv_broadcast(cvp);
+ mutex_exit(&node->n_oplock.ol_mutex);
+}
+
+/*
+ * Wait for an oplock break ACK to arrive. This is called after
+ * we've sent an oplock break or lease break to the client where
+ * an "Ack break" is expected back. If we get an Ack, that will
+ * wake us up via smb2_oplock_break_ack or smb2_lease_break_ack,
+ * which signal the CV on which we wait here.
+ */
+uint32_t
+smb_oplock_wait_ack(smb_request_t *sr)
+{
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_lease_t *lease = ofile->f_lease;
+ smb_node_t *node = ofile->f_node;
+ smb_oplock_t *ol = &node->n_oplock;
+ uint32_t *brp;
+ kcondvar_t *cvp;
+ clock_t time, rv;
+ uint32_t status = 0;
+ smb_req_state_t srstate;
+
+ time = ddi_get_lbolt() +
+ MSEC_TO_TICK(smb_oplock_timeout_ack);
if (lease != NULL) {
- if (lease->ls_oplock_ofile == ofile) {
- /*
- * smb2_lease_ofile_close should have
- * moved the oplock to another ofile.
- */
- ASSERT(0);
- lease->ls_oplock_ofile = NULL;
+ brp = &lease->ls_breaking;
+ cvp = &lease->ls_ack_cv;
+ } else {
+ brp = &ofile->f_oplock.og_breaking;
+ cvp = &ofile->f_oplock.og_ack_cv;
+ }
+
+ /*
+ * Setup cancellation callback
+ */
+ mutex_enter(&sr->sr_mutex);
+ if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
+ mutex_exit(&sr->sr_mutex);
+ return (NT_STATUS_CANCELLED);
+ }
+ sr->sr_state = SMB_REQ_STATE_WAITING_OLBRK;
+ sr->cancel_method = smb_oplock_wait_ack_cancel;
+ sr->cancel_arg2 = cvp;
+ mutex_exit(&sr->sr_mutex);
+
+ /*
+ * Enter the wait loop
+ */
+ mutex_enter(&ol->ol_mutex);
+ while (*brp != 0) {
+ rv = cv_timedwait(cvp, &ol->ol_mutex, time);
+ if (rv < 0) {
+ /* cv_timewait timeout */
+ status = NT_STATUS_CANNOT_BREAK_OPLOCK;
+ break;
+ }
+
+ /*
+ * Check if we were woken by smb_request_cancel,
+ * which sets state SMB_REQ_STATE_CANCEL_PENDING
+ * and signals the CV. The mutex enter/exit is
+ * just to ensure cache visibility of sr_state
+ * that was updated in smb_request_cancel.
+ */
+ mutex_enter(&sr->sr_mutex);
+ srstate = sr->sr_state;
+ mutex_exit(&sr->sr_mutex);
+ if (srstate != SMB_REQ_STATE_WAITING_OLBRK) {
+ break;
}
}
- ofile->f_oplock.og_state = 0;
- ofile->f_oplock.og_breaking = 0;
+ mutex_exit(&ol->ol_mutex);
+
+ /*
+ * Clear cancellation callback and see if it fired.
+ */
+ mutex_enter(&sr->sr_mutex);
+ sr->cancel_method = NULL;
+ sr->cancel_arg2 = NULL;
+ switch (sr->sr_state) {
+ case SMB_REQ_STATE_WAITING_OLBRK:
+ sr->sr_state = SMB_REQ_STATE_ACTIVE;
+ /* status from above */
+ break;
+ case SMB_REQ_STATE_CANCEL_PENDING:
+ sr->sr_state = SMB_REQ_STATE_CANCELLED;
+ status = NT_STATUS_CANCELLED;
+ break;
+ default:
+ status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+ mutex_exit(&sr->sr_mutex);
+
+ return (status);
}
/*
@@ -760,7 +770,9 @@ smb_oplock_wait_break(smb_request_t *sr, smb_node_t *node, int timeout)
/*
* Check if we were woken by smb_request_cancel,
* which sets state SMB_REQ_STATE_CANCEL_PENDING
- * and signals WaitingOpenCV.
+ * and signals the CV. The mutex enter/exit is
+ * just to ensure cache visibility of sr_state
+ * that was updated in smb_request_cancel.
*/
mutex_enter(&sr->sr_mutex);
srstate = sr->sr_state;
diff --git a/usr/src/uts/common/smbsrv/smb_kproto.h b/usr/src/uts/common/smbsrv/smb_kproto.h
index 3ffc10f15e..c93ecb353e 100644
--- a/usr/src/uts/common/smbsrv/smb_kproto.h
+++ b/usr/src/uts/common/smbsrv/smb_kproto.h
@@ -267,6 +267,7 @@ int smb_net_id(uint32_t);
* Common oplock functions
*/
uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_request_LH(smb_request_t *, smb_ofile_t *, uint32_t *);
uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *);
uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *);
uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *,
@@ -280,6 +281,16 @@ uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *);
uint32_t smb_oplock_break_SETINFO(smb_node_t *,
smb_ofile_t *ofile, uint32_t InfoClass);
uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *);
+void smb_oplock_close(smb_ofile_t *);
+
+void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
+void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
+ uint32_t, boolean_t);
+void smb_oplock_send_break(smb_request_t *);
+
+uint32_t smb_oplock_wait_ack(smb_request_t *);
+uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int);
+uint32_t smb_oplock_wait_break_fem(smb_node_t *, int);
void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
@@ -287,16 +298,12 @@ void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
* Protocol-specific oplock functions
* (and "server-level" functions)
*/
+void smb1_oplock_ack_break(smb_request_t *, uchar_t);
void smb1_oplock_acquire(smb_request_t *, boolean_t);
-void smb1_oplock_break_notification(smb_request_t *, uint32_t);
-void smb2_oplock_break_notification(smb_request_t *, uint32_t);
-void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t);
-void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
-void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
- uint32_t, boolean_t);
-void smb_oplock_send_brk(smb_request_t *);
-uint32_t smb_oplock_wait_break(smb_request_t *, smb_node_t *, int);
-uint32_t smb_oplock_wait_break_fem(smb_node_t *, int);
+void smb1_oplock_send_break(smb_request_t *);
+void smb2_oplock_send_break(smb_request_t *);
+void smb2_lease_ofile_close(smb_ofile_t *);
+void smb2_lease_send_break(smb_request_t *);
/*
* range lock functions - node operations
diff --git a/usr/src/uts/common/smbsrv/smb_ktypes.h b/usr/src/uts/common/smbsrv/smb_ktypes.h
index f29a5b24c0..76def803ab 100644
--- a/usr/src/uts/common/smbsrv/smb_ktypes.h
+++ b/usr/src/uts/common/smbsrv/smb_ktypes.h
@@ -601,6 +601,7 @@ typedef struct smb_oplock_grant {
uint32_t og_state; /* latest sent to client */
uint32_t og_breaking; /* BREAK_TO... flags */
uint16_t og_dialect; /* how to send breaks */
+ kcondvar_t og_ack_cv; /* Wait for ACK */
/* File-system level state */
uint8_t onlist_II;
uint8_t onlist_R;
@@ -626,6 +627,7 @@ typedef struct smb_lease {
uint32_t ls_breaking; /* BREAK_TO... flags */
uint16_t ls_epoch;
uint16_t ls_version;
+ kcondvar_t ls_ack_cv; /* Wait for ACK */
uint8_t ls_key[SMB_LEASE_KEY_SZ];
uint8_t ls_clnt[SMB_LEASE_KEY_SZ];
} smb_lease_t;
@@ -1657,7 +1659,9 @@ typedef struct smb_arg_lock {
typedef struct smb_arg_olbrk {
uint32_t NewLevel;
+ uint32_t OldLevel;
boolean_t AckRequired;
+ uint8_t LeaseKey[SMB_LEASE_KEY_SZ];
} smb_arg_olbrk_t;
/*