summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/smbsrv/smb_oplock.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/fs/smbsrv/smb_oplock.c')
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_oplock.c977
1 files changed, 665 insertions, 312 deletions
diff --git a/usr/src/uts/common/fs/smbsrv/smb_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_oplock.c
index 632293fc5a..6172b1726f 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c
@@ -21,441 +21,794 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*/
+
/*
- * SMB Locking library functions.
+ * smb_oplock_wait / smb_oplock_broadcast
+ * When an oplock is being acquired, we must ensure that the acquisition
+ * response is submitted to the network stack before any other operation
+ * is permitted on the oplock.
+ * In smb_oplock_acquire, oplock.ol_xthread is set to point to the worker
+ * thread processing the command that is granting the oplock.
+ * Other threads accessing the oplock will be suspended in smb_oplock_wait().
+ * They will be awakened when the worker thread referenced in 'ol_xthread'
+ * calls smb_oplock_broadcast().
*
- * You will notice that the functions in this file exit the lock of the session
- * and reenter it before returning. They even assume that the lock has been
- * entered in READER mode. The reason for that is a potential deadlock that may
- * occur when an oplock needs to be broken and the function
- * smb_session_break_oplock() is called. It should be noticed that the mutex of
- * the smb node, the oplock of which needs to be broken, is also exited before
- * calling smb_session_break_oplock(). The reason for that is the same: avoiding
- * a deadlock. That complexity is due to the fact that the lock of the session
- * is held during the treatment of a request. That complexity will go away when
- * that is not the case anymore.
+ * The purpose of this mechanism is to prevent another thread from
+ * triggering an oplock break before the response conveying the grant
+ * has been sent.
*/
#include <smbsrv/smb_kproto.h>
-#include <smbsrv/smb_fsops.h>
+#include <sys/nbmlock.h>
#include <inet/tcp.h>
+#define SMB_OPLOCK_IS_EXCLUSIVE(level) \
+ (((level) == SMB_OPLOCK_EXCLUSIVE) || \
+ ((level) == SMB_OPLOCK_BATCH))
+
+extern int smb_fem_oplock_install(smb_node_t *);
+extern int smb_fem_oplock_uninstall(smb_node_t *);
+
+static int smb_oplock_install_fem(smb_node_t *);
+static void smb_oplock_uninstall_fem(smb_node_t *);
+
static void smb_oplock_wait(smb_node_t *);
+static void smb_oplock_wait_ack(smb_node_t *, uint32_t);
+static void smb_oplock_timedout(smb_node_t *);
+
+static smb_oplock_grant_t *smb_oplock_set_grant(smb_ofile_t *, uint8_t);
+void smb_oplock_clear_grant(smb_oplock_grant_t *);
+static int smb_oplock_insert_grant(smb_node_t *, smb_oplock_grant_t *);
+static void smb_oplock_remove_grant(smb_node_t *, smb_oplock_grant_t *);
+static smb_oplock_grant_t *smb_oplock_exclusive_grant(list_t *);
+static smb_oplock_grant_t *smb_oplock_get_grant(smb_oplock_t *, smb_ofile_t *);
+
+static smb_oplock_break_t *smb_oplock_create_break(smb_node_t *);
+static smb_oplock_break_t *smb_oplock_get_break(void);
+static void smb_oplock_delete_break(smb_oplock_break_t *);
+static void smb_oplock_process_levelII_break(smb_node_t *);
+
+static void smb_oplock_break_thread();
+
+/* levelII oplock break requests (smb_oplock_break_t) */
+static boolean_t smb_oplock_initialized = B_FALSE;
+static kmem_cache_t *smb_oplock_break_cache = NULL;
+static smb_llist_t smb_oplock_breaks;
+static smb_thread_t smb_oplock_thread;
+
/*
- * Magic 0xFF 'S' 'M' 'B'
- * smb_com a byte, the "first" command
- * Error a 4-byte union, ignored in a request
- * smb_flg a one byte set of eight flags
- * smb_flg2 a two byte set of 16 flags
- * . twelve reserved bytes, have a role
- * in connectionless transports (IPX, UDP?)
- * smb_tid a 16-bit tree ID, a mount point sorta,
- * 0xFFFF is this command does not have
- * or require a tree context
- * smb_pid a 16-bit process ID
- * smb_uid a 16-bit user ID, specific to this "session"
- * and mapped to a system (bona-fide) UID
- * smb_mid a 16-bit multiplex ID, used to differentiate
- * multiple simultaneous requests from the same
- * process (pid) (ref RPC "xid")
- *
- * SMB_COM_LOCKING_ANDX allows both locking and/or unlocking of file range(s).
- *
- * Client Request Description
- * ================================== =================================
- *
- * UCHAR WordCount; Count of parameter words = 8
- * UCHAR AndXCommand; Secondary (X) command; 0xFF = none
- * UCHAR AndXReserved; Reserved (must be 0)
- * USHORT AndXOffset; Offset to next command WordCount
- * USHORT Fid; File handle
- * UCHAR LockType; See LockType table below
- * UCHAR OplockLevel; The new oplock level
- * ULONG Timeout; Milliseconds to wait for unlock
- * USHORT NumberOfUnlocks; Num. unlock range structs following
- * USHORT NumberOfLocks; Num. lock range structs following
- * USHORT ByteCount; Count of data bytes
- * LOCKING_ANDX_RANGE Unlocks[]; Unlock ranges
- * LOCKING_ANDX_RANGE Locks[]; Lock ranges
- *
- * LockType Flag Name Value Description
- * ============================ ===== ================================
- *
- * LOCKING_ANDX_SHARED_LOCK 0x01 Read-only lock
- * LOCKING_ANDX_OPLOCK_RELEASE 0x02 Oplock break notification
- * LOCKING_ANDX_CHANGE_LOCKTYPE 0x04 Change lock type
- * LOCKING_ANDX_CANCEL_LOCK 0x08 Cancel outstanding request
- * LOCKING_ANDX_LARGE_FILES 0x10 Large file locking format
- *
- * LOCKING_ANDX_RANGE Format
- * =====================================================================
- *
- * USHORT Pid; PID of process "owning" lock
- * ULONG Offset; Offset to bytes to [un]lock
- * ULONG Length; Number of bytes to [un]lock
- *
- * Large File LOCKING_ANDX_RANGE Format
- * =====================================================================
- *
- * USHORT Pid; PID of process "owning" lock
- * USHORT Pad; Pad to DWORD align (mbz)
- * ULONG OffsetHigh; Offset to bytes to [un]lock
- * (high)
- * ULONG OffsetLow; Offset to bytes to [un]lock (low)
- * ULONG LengthHigh; Number of bytes to [un]lock
- * (high)
- * ULONG LengthLow; Number of bytes to [un]lock (low)
- *
- * Server Response Description
- * ================================== =================================
- *
- * UCHAR WordCount; Count of parameter words = 2
- * UCHAR AndXCommand; Secondary (X) command; 0xFF =
- * none
- * UCHAR AndXReserved; Reserved (must be 0)
- * USHORT AndXOffset; Offset to next command WordCount
- * USHORT ByteCount; Count of data bytes = 0
+ * smb_oplock_init
*
+ * This function is not multi-thread safe. The caller must make sure only one
+ * thread makes the call.
*/
+int
+smb_oplock_init(void)
+{
+ int rc;
+
+ if (smb_oplock_initialized)
+ return (0);
+
+ smb_oplock_break_cache = kmem_cache_create("smb_oplock_break_cache",
+ sizeof (smb_oplock_break_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
+
+ smb_llist_constructor(&smb_oplock_breaks, sizeof (smb_oplock_break_t),
+ offsetof(smb_oplock_break_t, ob_lnd));
+
+ smb_thread_init(&smb_oplock_thread, "smb_thread_oplock_break",
+ smb_oplock_break_thread, NULL, NULL, NULL);
+
+ rc = smb_thread_start(&smb_oplock_thread);
+ if (rc != 0) {
+ smb_thread_destroy(&smb_oplock_thread);
+ smb_llist_destructor(&smb_oplock_breaks);
+ kmem_cache_destroy(smb_oplock_break_cache);
+ return (rc);
+ }
+
+ smb_oplock_initialized = B_TRUE;
+ return (0);
+}
+
+/*
+ * smb_oplock_fini
+ * This function is not multi-thread safe. The caller must make sure only one
+ * thread makes the call.
+ */
+void
+smb_oplock_fini(void)
+{
+ smb_oplock_break_t *ob;
+
+ if (!smb_oplock_initialized)
+ return;
+
+ smb_thread_stop(&smb_oplock_thread);
+ smb_thread_destroy(&smb_oplock_thread);
+
+ while ((ob = smb_llist_head(&smb_oplock_breaks)) != NULL) {
+ SMB_OPLOCK_BREAK_VALID(ob);
+ smb_llist_remove(&smb_oplock_breaks, ob);
+ smb_oplock_delete_break(ob);
+ }
+ smb_llist_destructor(&smb_oplock_breaks);
+
+ kmem_cache_destroy(smb_oplock_break_cache);
+}
+
+/*
+ * smb_oplock_install_fem
+ * Install fem monitor for cross protocol oplock breaking.
+ */
+static int
+smb_oplock_install_fem(smb_node_t *node)
+{
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+ if (node->n_oplock.ol_fem == B_FALSE) {
+ if (smb_fem_oplock_install(node) != 0) {
+ cmn_err(CE_NOTE, "No oplock granted: "
+ "failed to install fem monitor %s",
+ node->vp->v_path);
+ return (-1);
+ }
+ node->n_oplock.ol_fem = B_TRUE;
+ }
+ return (0);
+}
+
+/*
+ * smb_oplock_uninstall_fem
+ * Uninstall fem monitor for cross protocol oplock breaking.
+ */
+static void
+smb_oplock_uninstall_fem(smb_node_t *node)
+{
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+ if (node->n_oplock.ol_fem) {
+ if (smb_fem_oplock_uninstall(node) == 0) {
+ node->n_oplock.ol_fem = B_FALSE;
+ } else {
+ cmn_err(CE_NOTE,
+ "failed to uninstall fem monitor %s",
+ node->vp->v_path);
+ }
+ }
+}
/*
* smb_oplock_acquire
*
- * Attempt to acquire an oplock. Note that the oplock granted may be
- * none, i.e. the oplock was not granted. The result of the acquisition is
- * provided in ol->ol_level.
- *
- * Grant an oplock to the requestor if this session is the only one
- * that has the file open, regardless of the number of instances of
- * the file opened by this session.
+ * Attempt to acquire an oplock. Clients will request EXCLUSIVE or BATCH,
+ * but might only be granted LEVEL_II or NONE.
*
- * However, if there is no oplock on this file and there is already
- * at least one open, we will not grant an oplock, even if the only
- * existing opens are from the same client. This is "server discretion."
+ * If oplocks are not supported on the tree, or node, grant NONE.
+ * If nobody else has the file open, grant the requested level.
+ * If any of the following are true, grant NONE:
+ * - there is an exclusive oplock on the node
+ * - op->op_oplock_levelII is B_FALSE (LEVEL_II not supported by open cmd.
+ * - LEVEL_II oplocks are not supported for the session
+ * - a BATCH oplock is requested on a named stream
+ * - there are any range locks on the node
+ * Otherwise, grant LEVEL_II.
*
- * An oplock may need to be broken in order for one to be granted, and
- * depending on what action is taken by the other client (unlock or close),
- * an oplock may or may not be granted. (The breaking of an oplock is
- * done earlier in the calling path.)
+ * ol->ol_xthread is set to the current thread to lock the oplock against
+ * other operations until the acquire response is on the wire. When the
+ * acquire response is on the wire, smb_oplock_broadcast() is called to
+ * reset ol->ol_xthread and wake any waiting threads.
*/
void
-smb_oplock_acquire(smb_node_t *node, smb_ofile_t *of, smb_arg_open_t *op)
+smb_oplock_acquire(smb_request_t *sr, smb_node_t *node, smb_ofile_t *ofile)
{
- smb_session_t *session;
- smb_oplock_t *ol;
- clock_t time;
+ smb_oplock_t *ol;
+ smb_oplock_grant_t *og;
+ list_t *grants;
+ smb_arg_open_t *op;
+ smb_tree_t *tree;
+ smb_session_t *session;
SMB_NODE_VALID(node);
- SMB_OFILE_VALID(of);
+ SMB_OFILE_VALID(ofile);
- ASSERT(node == SMB_OFILE_GET_NODE(of));
+ ASSERT(node == SMB_OFILE_GET_NODE(ofile));
- session = SMB_OFILE_GET_SESSION(of);
+ op = &sr->sr_open;
+ tree = SMB_OFILE_GET_TREE(ofile);
+ session = SMB_OFILE_GET_SESSION(ofile);
- if (!smb_session_oplocks_enable(session) ||
- smb_tree_has_feature(SMB_OFILE_GET_TREE(of), SMB_TREE_NO_OPLOCKS)) {
+ if (!smb_tree_has_feature(tree, SMB_TREE_OPLOCKS) ||
+ (op->op_oplock_level == SMB_OPLOCK_NONE) ||
+ ((op->op_oplock_level == SMB_OPLOCK_BATCH) &&
+ SMB_IS_STREAM(node))) {
op->op_oplock_level = SMB_OPLOCK_NONE;
return;
}
ol = &node->n_oplock;
- time = MSEC_TO_TICK(smb_oplock_timeout) + ddi_get_lbolt();
+ grants = &ol->ol_grants;
- mutex_enter(&node->n_mutex);
+ mutex_enter(&ol->ol_mutex);
+ smb_oplock_wait(node);
- switch (node->n_state) {
- case SMB_NODE_STATE_OPLOCK_GRANTED:
- if (SMB_SESSION_GET_ID(session) == ol->ol_sess_id) {
- mutex_exit(&node->n_mutex);
- return;
- }
- break;
- case SMB_NODE_STATE_AVAILABLE:
- case SMB_NODE_STATE_OPLOCK_BREAKING:
- break;
- default:
- SMB_PANIC();
- }
+ nbl_start_crit(node->vp, RW_READER);
- for (;;) {
- int rc;
-
- smb_oplock_wait(node);
-
- if (node->n_state == SMB_NODE_STATE_AVAILABLE) {
- if ((op->op_oplock_level == SMB_OPLOCK_LEVEL_II) ||
- (op->op_oplock_level == SMB_OPLOCK_NONE) ||
- (node->n_open_count > 1)) {
- mutex_exit(&node->n_mutex);
- op->op_oplock_level = SMB_OPLOCK_NONE;
- return;
- }
- ol->ol_ofile = of;
- ol->ol_sess_id = SMB_SESSION_GET_ID(session);
- ol->ol_level = op->op_oplock_level;
- ol->ol_xthread = curthread;
- node->n_state = SMB_NODE_STATE_OPLOCK_GRANTED;
- mutex_exit(&node->n_mutex);
- if (smb_fsop_oplock_install(node, of->f_mode) == 0) {
- smb_ofile_set_oplock_granted(of);
- return;
- }
- mutex_enter(&node->n_mutex);
- ASSERT(node->n_state == SMB_NODE_STATE_OPLOCK_GRANTED);
- node->n_state = SMB_NODE_STATE_AVAILABLE;
- ol->ol_xthread = NULL;
+ if ((node->n_open_count > 1) ||
+ (node->n_opening_count > 1) ||
+ smb_vop_other_opens(node->vp, ofile->f_mode)) {
+ if ((!op->op_oplock_levelII) ||
+ (!smb_session_levelII_oplocks(session)) ||
+ (smb_oplock_exclusive_grant(grants) != NULL) ||
+ (smb_range_check(sr, node, 0, UINT64_MAX, B_TRUE) != 0)) {
op->op_oplock_level = SMB_OPLOCK_NONE;
- cv_broadcast(&ol->ol_cv);
- break;
+ nbl_end_crit(node->vp);
+ mutex_exit(&ol->ol_mutex);
+ return;
}
- if (node->n_state == SMB_NODE_STATE_OPLOCK_GRANTED) {
- if (SMB_SESSION_GET_ID(session) == ol->ol_sess_id)
- break;
- node->n_state = SMB_NODE_STATE_OPLOCK_BREAKING;
- mutex_exit(&node->n_mutex);
- smb_session_oplock_break(
- SMB_OFILE_GET_SESSION(ol->ol_ofile), ol->ol_ofile);
- mutex_enter(&node->n_mutex);
- continue;
- }
+ op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
+ }
- ASSERT(node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING);
-
- rc = cv_timedwait(&ol->ol_cv, &node->n_mutex, time);
-
- if (rc == -1) {
- /*
- * Oplock release timed out.
- */
- if (node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING) {
- node->n_state = SMB_NODE_STATE_AVAILABLE;
- ol->ol_xthread = curthread;
- mutex_exit(&node->n_mutex);
- smb_fsop_oplock_uninstall(node);
- mutex_enter(&node->n_mutex);
- ol->ol_xthread = NULL;
- cv_broadcast(&ol->ol_cv);
- }
- }
+ nbl_end_crit(node->vp);
+
+ og = smb_oplock_set_grant(ofile, op->op_oplock_level);
+ if (smb_oplock_insert_grant(node, og) != 0) {
+ smb_oplock_clear_grant(og);
+ op->op_oplock_level = SMB_OPLOCK_NONE;
+ mutex_exit(&ol->ol_mutex);
+ return;
}
- mutex_exit(&node->n_mutex);
+
+ ol->ol_xthread = curthread;
+ mutex_exit(&ol->ol_mutex);
}
/*
* smb_oplock_break
*
- * The oplock break may succeed for multiple reasons: file close, oplock
- * release, holder connection dropped, requesting client disconnect etc.
+ * Break granted oplocks according to the following rules:
*
- * Returns:
+ * If there's an exclusive oplock granted on the node
+ * - if the BREAK_BATCH flags is specified and the oplock is not
+ * a batch oplock, no break is required.
+ * - if the session doesn't support LEVEL II oplocks, and 'brk' is
+ * BREAK_TO_LEVEL_II, do a BREAK_TO_NONE.
+ * - if the oplock is already breaking update the break level (if
+ * the requested break is to a lesser level), otherwise send an
+ * oplock break.
+ * Wait for acknowledgement of the break (unless NOWAIT flag is set)
*
- * B_TRUE The oplock is broken.
- * B_FALSE The oplock is being broken. This is returned if nowait is set
- * to B_TRUE;
+ * Otherwise:
+ * If there are level II oplocks granted on the node, and the flags
+ * indicate that they should be broken (BREAK_TO_NONE specified,
+ * BREAK_EXCLUSIVE, BREAK_BATCH not specified) queue the levelII
+ * break request for asynchronous processing.
+ *
+ * Returns:
+ * 0 - oplock broken (or no break required)
+ * EAGAIN - oplock break request sent and would block
+ * awaiting the reponse but NOWAIT was specified
*/
-boolean_t
-smb_oplock_break(smb_node_t *node, smb_session_t *session, boolean_t nowait)
+int
+smb_oplock_break(smb_request_t *sr, smb_node_t *node, uint32_t flags)
{
- smb_oplock_t *ol;
- clock_t time;
+ smb_oplock_t *ol;
+ smb_oplock_grant_t *og;
+ list_t *grants;
+ uint32_t timeout;
+ uint8_t brk;
SMB_NODE_VALID(node);
ol = &node->n_oplock;
- time = MSEC_TO_TICK(smb_oplock_timeout) + ddi_get_lbolt();
+ grants = &ol->ol_grants;
+
+ mutex_enter(&ol->ol_mutex);
+ smb_oplock_wait(node);
- if (session != NULL) {
- mutex_enter(&node->n_mutex);
- if (SMB_SESSION_GET_ID(session) == ol->ol_sess_id) {
- mutex_exit(&node->n_mutex);
- return (B_TRUE);
+ og = list_head(grants);
+ if (og == NULL) {
+ mutex_exit(&ol->ol_mutex);
+ return (0);
+ }
+
+ SMB_OPLOCK_GRANT_VALID(og);
+
+ /* break levelII oplocks */
+ if (og->og_level == SMB_OPLOCK_LEVEL_II) {
+ mutex_exit(&ol->ol_mutex);
+
+ if ((flags & SMB_OPLOCK_BREAK_TO_NONE) &&
+ !(flags & SMB_OPLOCK_BREAK_EXCLUSIVE) &&
+ !(flags & SMB_OPLOCK_BREAK_BATCH)) {
+ smb_oplock_break_levelII(node);
}
+ return (0);
+ }
+
+ /* break exclusive oplock */
+ if ((flags & SMB_OPLOCK_BREAK_BATCH) &&
+ (og->og_level != SMB_OPLOCK_BATCH)) {
+ mutex_exit(&ol->ol_mutex);
+ return (0);
+ }
+
+ if ((flags & SMB_OPLOCK_BREAK_TO_LEVEL_II) &&
+ smb_session_levelII_oplocks(og->og_session)) {
+ brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
} else {
- mutex_enter(&node->n_mutex);
+ brk = SMB_OPLOCK_BREAK_TO_NONE;
}
- for (;;) {
- int rc;
+ switch (ol->ol_break) {
+ case SMB_OPLOCK_NO_BREAK:
+ ol->ol_break = brk;
+ smb_session_oplock_break(og->og_session,
+ og->og_tid, og->og_fid, brk);
+ break;
+ case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+ if (brk == SMB_OPLOCK_BREAK_TO_NONE)
+ ol->ol_break = SMB_OPLOCK_BREAK_TO_NONE;
+ break;
+ case SMB_OPLOCK_BREAK_TO_NONE:
+ default:
+ break;
+ }
- smb_oplock_wait(node);
+ if (flags & SMB_OPLOCK_BREAK_NOWAIT) {
+ mutex_exit(&ol->ol_mutex);
+ return (EAGAIN);
+ }
- if (node->n_state == SMB_NODE_STATE_AVAILABLE) {
- mutex_exit(&node->n_mutex);
- return (B_TRUE);
- }
+ if (sr && (sr->session == og->og_session) &&
+ (sr->smb_uid == og->og_uid)) {
+ timeout = smb_oplock_min_timeout;
+ } else {
+ timeout = smb_oplock_timeout;
+ }
+
+ mutex_exit(&ol->ol_mutex);
+ smb_oplock_wait_ack(node, timeout);
+ return (0);
+}
- if (node->n_state == SMB_NODE_STATE_OPLOCK_GRANTED) {
- node->n_state = SMB_NODE_STATE_OPLOCK_BREAKING;
- mutex_exit(&node->n_mutex);
- smb_session_oplock_break(
- SMB_OFILE_GET_SESSION(ol->ol_ofile), ol->ol_ofile);
- mutex_enter(&node->n_mutex);
- continue;
+/*
+ * smb_oplock_break_levelII
+ *
+ * LevelII (shared) oplock breaks are processed asynchronously.
+ * Unlike exclusive oplock breaks, the thread initiating the break
+ * is NOT blocked while the request is processed.
+ *
+ * Create an oplock_break_request and add it to the list for async
+ * processing.
+ */
+void
+smb_oplock_break_levelII(smb_node_t *node)
+{
+ smb_oplock_break_t *ob;
+
+ ob = smb_oplock_create_break(node);
+
+ smb_llist_enter(&smb_oplock_breaks, RW_WRITER);
+ smb_llist_insert_tail(&smb_oplock_breaks, ob);
+ smb_llist_exit(&smb_oplock_breaks);
+
+ smb_thread_signal(&smb_oplock_thread);
+}
+
+/*
+ * smb_oplock_break_thread
+ *
+ * The smb_oplock_thread is woken when an oplock break request is
+ * added to the list of pending levelII oplock break requests.
+ * Gets the oplock break request from the list, processes it and
+ * deletes it.
+ */
+/*ARGSUSED*/
+static void
+smb_oplock_break_thread(smb_thread_t *thread, void *arg)
+{
+ smb_oplock_break_t *ob;
+
+ while (smb_thread_continue(thread)) {
+ while ((ob = smb_oplock_get_break()) != NULL) {
+ smb_oplock_process_levelII_break(ob->ob_node);
+ smb_oplock_delete_break(ob);
}
+ }
+}
+
+/*
+ * smb_oplock_get_break
+ *
+ * Remove and return the next oplock break request from the list
+ */
+static smb_oplock_break_t *
+smb_oplock_get_break(void)
+{
+ smb_oplock_break_t *ob;
+
+ smb_llist_enter(&smb_oplock_breaks, RW_WRITER);
+ if ((ob = smb_llist_head(&smb_oplock_breaks)) != NULL) {
+ SMB_OPLOCK_BREAK_VALID(ob);
+ smb_llist_remove(&smb_oplock_breaks, ob);
+ }
+ smb_llist_exit(&smb_oplock_breaks);
+ return (ob);
+}
+
+/*
+ * smb_oplock_process_levelII_break
+ */
+void
+smb_oplock_process_levelII_break(smb_node_t *node)
+{
+ smb_oplock_t *ol;
+ smb_oplock_grant_t *og;
+ list_t *grants;
+
+ if (!smb_oplock_levelII)
+ return;
+
+ ol = &node->n_oplock;
+ mutex_enter(&ol->ol_mutex);
+ smb_oplock_wait(node);
+ grants = &node->n_oplock.ol_grants;
+
+ while ((og = list_head(grants)) != NULL) {
+ SMB_OPLOCK_GRANT_VALID(og);
+
+ if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
+ break;
+
+ smb_session_oplock_break(og->og_session,
+ og->og_tid, og->og_fid, SMB_OPLOCK_BREAK_TO_NONE);
+ smb_oplock_remove_grant(node, og);
+ smb_oplock_clear_grant(og);
+ }
+
+ mutex_exit(&ol->ol_mutex);
+}
+
+/*
+ * smb_oplock_wait_ack
+ *
+ * Timed wait for an oplock break acknowledgement (or oplock release).
+ */
+static void
+smb_oplock_wait_ack(smb_node_t *node, uint32_t timeout)
+{
+ smb_oplock_t *ol;
+ clock_t time;
- ASSERT(node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING);
- if (nowait) {
- mutex_exit(&node->n_mutex);
- return (B_FALSE);
+ ol = &node->n_oplock;
+ mutex_enter(&ol->ol_mutex);
+ time = MSEC_TO_TICK(timeout) + ddi_get_lbolt();
+
+ while (ol->ol_break != SMB_OPLOCK_NO_BREAK) {
+ if (cv_timedwait(&ol->ol_cv, &ol->ol_mutex, time) < 0) {
+ smb_oplock_timedout(node);
+ cv_broadcast(&ol->ol_cv);
+ break;
}
- rc = cv_timedwait(&ol->ol_cv, &node->n_mutex, time);
- if (rc == -1) {
- /*
- * Oplock release timed out.
- */
- if (node->n_state == SMB_NODE_STATE_OPLOCK_BREAKING) {
- node->n_state = SMB_NODE_STATE_AVAILABLE;
- ol->ol_xthread = curthread;
- mutex_exit(&node->n_mutex);
- smb_fsop_oplock_uninstall(node);
- mutex_enter(&node->n_mutex);
- ol->ol_xthread = NULL;
- cv_broadcast(&ol->ol_cv);
- break;
- }
+ }
+ mutex_exit(&ol->ol_mutex);
+}
+
+/*
+ * smb_oplock_timedout
+ *
+ * An oplock break has not been acknowledged within timeout
+ * 'smb_oplock_timeout'.
+ * Set oplock grant to the desired break level.
+ */
+static void
+smb_oplock_timedout(smb_node_t *node)
+{
+ smb_oplock_t *ol;
+ smb_oplock_grant_t *og;
+ list_t *grants;
+
+ ol = &node->n_oplock;
+ grants = &ol->ol_grants;
+
+ ASSERT(MUTEX_HELD(&ol->ol_mutex));
+
+ og = smb_oplock_exclusive_grant(grants);
+ if (og) {
+ switch (ol->ol_break) {
+ case SMB_OPLOCK_BREAK_TO_NONE:
+ og->og_level = SMB_OPLOCK_NONE;
+ smb_oplock_remove_grant(node, og);
+ smb_oplock_clear_grant(og);
+ break;
+ case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+ og->og_level = SMB_OPLOCK_LEVEL_II;
+ break;
+ default:
+ SMB_PANIC();
}
}
- mutex_exit(&node->n_mutex);
- return (B_TRUE);
+ ol->ol_break = SMB_OPLOCK_NO_BREAK;
}
/*
* smb_oplock_release
*
- * This function releases the oplock on the node passed in. If other threads
- * were waiting for the oplock to be released they are signaled.
+ * Release the oplock granted on ofile 'of'.
+ * Wake any threads waiting for an oplock break acknowledgement for
+ * this oplock.
+ * This is called when the ofile is being closed.
*/
void
smb_oplock_release(smb_node_t *node, smb_ofile_t *of)
{
- smb_oplock_t *ol;
+ smb_oplock_t *ol;
+ smb_oplock_grant_t *og;
- SMB_NODE_VALID(node);
ol = &node->n_oplock;
-
- mutex_enter(&node->n_mutex);
+ mutex_enter(&ol->ol_mutex);
smb_oplock_wait(node);
- switch (node->n_state) {
- case SMB_NODE_STATE_AVAILABLE:
- break;
- case SMB_NODE_STATE_OPLOCK_GRANTED:
- case SMB_NODE_STATE_OPLOCK_BREAKING:
- if (ol->ol_ofile == of) {
- node->n_state = SMB_NODE_STATE_AVAILABLE;
- ol->ol_xthread = curthread;
- mutex_exit(&node->n_mutex);
- smb_fsop_oplock_uninstall(node);
- mutex_enter(&node->n_mutex);
- ol->ol_xthread = NULL;
+ og = smb_oplock_get_grant(ol, of);
+ if (og) {
+ smb_oplock_remove_grant(node, og);
+ smb_oplock_clear_grant(og);
+
+ if (ol->ol_break != SMB_OPLOCK_NO_BREAK) {
+ ol->ol_break = SMB_OPLOCK_NO_BREAK;
cv_broadcast(&ol->ol_cv);
}
- break;
-
- default:
- SMB_PANIC();
}
- mutex_exit(&node->n_mutex);
+
+ mutex_exit(&ol->ol_mutex);
}
/*
- * smb_oplock_conflict
+ * smb_oplock_ack
+ *
+ * Process oplock acknowledgement received for ofile 'of'.
+ * - oplock.ol_break is the break level that was requested.
+ * - brk is the break level being acknowledged by the client.
*
- * The two checks on "session" and "op" are primarily for the open path.
- * Other SMB functions may call smb_oplock_conflict() with a session
- * pointer so as to do the session check.
+ * Update the oplock grant level to the lesser of ol_break and brk.
+ * If the grant is now SMB_OPLOCK_NONE, remove the grant from the
+ * oplock's grant list and delete it.
+ * If the requested break level (ol_break) was NONE and the brk is
+ * LEVEL_II, send another oplock break (NONE). Do not wait for an
+ * acknowledgement.
+ * Wake any threads waiting for the oplock break acknowledgement.
*/
-boolean_t
-smb_oplock_conflict(smb_node_t *node, smb_session_t *session,
- smb_arg_open_t *op)
+void
+smb_oplock_ack(smb_node_t *node, smb_ofile_t *of, uint8_t brk)
{
- boolean_t rb;
-
- SMB_NODE_VALID(node);
- SMB_SESSION_VALID(session);
+ smb_oplock_t *ol;
+ smb_oplock_grant_t *og;
+ boolean_t brk_to_none = B_FALSE;
- mutex_enter(&node->n_mutex);
+ ol = &node->n_oplock;
+ mutex_enter(&ol->ol_mutex);
smb_oplock_wait(node);
- switch (node->n_state) {
- case SMB_NODE_STATE_AVAILABLE:
- rb = B_FALSE;
- break;
- case SMB_NODE_STATE_OPLOCK_GRANTED:
- case SMB_NODE_STATE_OPLOCK_BREAKING:
- if (SMB_SESSION_GET_ID(session) == node->n_oplock.ol_sess_id) {
- rb = B_FALSE;
- break;
- }
+ if ((ol->ol_break == SMB_OPLOCK_NO_BREAK) ||
+ ((og = smb_oplock_get_grant(ol, of)) == NULL)) {
+ mutex_exit(&ol->ol_mutex);
+ return;
+ }
- if (op != NULL) {
- if (((op->desired_access & ~(FILE_READ_ATTRIBUTES |
- FILE_WRITE_ATTRIBUTES | SYNCHRONIZE)) == 0) &&
- (op->create_disposition != FILE_SUPERSEDE) &&
- (op->create_disposition != FILE_OVERWRITE)) {
- /* Attributs only */
- rb = B_FALSE;
- break;
- }
+ switch (brk) {
+ case SMB_OPLOCK_BREAK_TO_NONE:
+ og->og_level = SMB_OPLOCK_NONE;
+ break;
+ case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+ if (ol->ol_break == SMB_OPLOCK_BREAK_TO_LEVEL_II) {
+ og->og_level = SMB_OPLOCK_LEVEL_II;
+ } else {
+ /* SMB_OPLOCK_BREAK_TO_NONE */
+ og->og_level = SMB_OPLOCK_NONE;
+ brk_to_none = B_TRUE;
}
- rb = B_TRUE;
break;
-
default:
SMB_PANIC();
}
- mutex_exit(&node->n_mutex);
- return (rb);
+
+ if (og->og_level == SMB_OPLOCK_NONE) {
+ smb_oplock_remove_grant(node, og);
+ smb_oplock_clear_grant(og);
+ }
+
+ ol->ol_break = SMB_OPLOCK_NO_BREAK;
+ cv_broadcast(&ol->ol_cv);
+
+ if (brk_to_none) {
+ smb_session_oplock_break(of->f_session,
+ of->f_tree->t_tid, of->f_fid,
+ SMB_OPLOCK_BREAK_TO_NONE);
+ }
+
+ mutex_exit(&ol->ol_mutex);
}
/*
* smb_oplock_broadcast
*
- * The the calling thread has the pointer to its context stored in ol_thread
- * it resets that field. If any other thread is waiting for that field to
- * turn to NULL it is signaled.
- *
- * Returns:
- * B_TRUE Oplock unlocked
- * B_FALSE Oplock still locked
+ * ol->ol_xthread identifies the thread that was performing an oplock
+ * acquire. Other threads may be blocked awaiting completion of the
+ * acquire.
+ * If the calling thread is ol_ol_xthread, wake any waiting threads.
*/
-boolean_t
+void
smb_oplock_broadcast(smb_node_t *node)
{
smb_oplock_t *ol;
- boolean_t rb;
SMB_NODE_VALID(node);
ol = &node->n_oplock;
- rb = B_FALSE;
- mutex_enter(&node->n_mutex);
+ mutex_enter(&ol->ol_mutex);
if ((ol->ol_xthread != NULL) && (ol->ol_xthread == curthread)) {
ol->ol_xthread = NULL;
cv_broadcast(&ol->ol_cv);
- rb = B_TRUE;
}
- mutex_exit(&node->n_mutex);
- return (rb);
+ mutex_exit(&ol->ol_mutex);
}
/*
* smb_oplock_wait
*
- * The mutex of the node must have been entered before calling this function.
- * If the field ol_xthread is not NULL and doesn't contain the pointer to the
- * context of the calling thread, the caller will sleep until that field is
- * reset (set to NULL).
+ * Wait for the completion of an oplock acquire.
+ * If ol_xthread is not NULL and doesn't contain the pointer to the
+ * context of the calling thread, the caller will sleep until the
+ * ol_xthread is reset to NULL (via smb_oplock_broadcast()).
*/
static void
smb_oplock_wait(smb_node_t *node)
{
- smb_oplock_t *ol = &node->n_oplock;
+ smb_oplock_t *ol;
+
+ ol = &node->n_oplock;
+ ASSERT(MUTEX_HELD(&ol->ol_mutex));
if ((ol->ol_xthread != NULL) && (ol->ol_xthread != curthread)) {
- ASSERT(!MUTEX_HELD(&ol->ol_ofile->f_mutex));
while (ol->ol_xthread != NULL)
- cv_wait(&ol->ol_cv, &node->n_mutex);
+ cv_wait(&ol->ol_cv, &ol->ol_mutex);
+ }
+}
+
+/*
+ * smb_oplock_set_grant
+ */
+static smb_oplock_grant_t *
+smb_oplock_set_grant(smb_ofile_t *of, uint8_t level)
+{
+ smb_oplock_grant_t *og;
+
+ og = &of->f_oplock_grant;
+
+ og->og_magic = SMB_OPLOCK_GRANT_MAGIC;
+ og->og_level = level;
+ og->og_ofile = of;
+ og->og_fid = of->f_fid;
+ og->og_tid = of->f_tree->t_tid;
+ og->og_uid = of->f_user->u_uid;
+ og->og_session = of->f_session;
+ return (og);
+}
+
+/*
+ * smb_oplock_clear_grant
+ */
+void
+smb_oplock_clear_grant(smb_oplock_grant_t *og)
+{
+ bzero(og, sizeof (smb_oplock_grant_t));
+}
+
+/*
+ * smb_oplock_insert_grant
+ *
+ * If there are no grants in the oplock's list install the fem
+ * monitor.
+ * Insert the grant into the list and increment the grant count.
+ */
+static int
+smb_oplock_insert_grant(smb_node_t *node, smb_oplock_grant_t *og)
+{
+ smb_oplock_t *ol = &node->n_oplock;
+
+ ASSERT(MUTEX_HELD(&ol->ol_mutex));
+
+ if (ol->ol_count == 0) {
+ if (smb_oplock_install_fem(node) != 0)
+ return (-1);
+ }
+
+ list_insert_tail(&ol->ol_grants, og);
+ ++ol->ol_count;
+ return (0);
+}
+
+/*
+ * smb_oplock_remove_grant
+ *
+ * Remove the oplock grant from the list, decrement the grant count
+ * and, if there are no other grants in the list, uninstall the fem
+ * monitor.
+ */
+static void
+smb_oplock_remove_grant(smb_node_t *node, smb_oplock_grant_t *og)
+{
+ smb_oplock_t *ol = &node->n_oplock;
+
+ ASSERT(MUTEX_HELD(&ol->ol_mutex));
+ ASSERT(ol->ol_count > 0);
+
+ list_remove(&ol->ol_grants, og);
+ if (--ol->ol_count == 0)
+ smb_oplock_uninstall_fem(node);
+}
+
+/*
+ * smb_oplock_exclusive_grant
+ *
+ * If an exclusive (EXCLUSIVE or BATCH) oplock grant exists,
+ * return it. Otherwise return NULL.
+ */
+static smb_oplock_grant_t *
+smb_oplock_exclusive_grant(list_t *grants)
+{
+ smb_oplock_grant_t *og;
+
+ og = list_head(grants);
+ if (og) {
+ SMB_OPLOCK_GRANT_VALID(og);
+ if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
+ return (og);
}
+ return (NULL);
+}
+
+/*
+ * smb_oplock_get_grant
+ *
+ * Find oplock grant corresponding to the specified ofile.
+ */
+static smb_oplock_grant_t *
+smb_oplock_get_grant(smb_oplock_t *ol, smb_ofile_t *ofile)
+{
+ ASSERT(MUTEX_HELD(&ol->ol_mutex));
+
+ if (SMB_OFILE_OPLOCK_GRANTED(ofile))
+ return (&ofile->f_oplock_grant);
+ else
+ return (NULL);
+}
+
+/*
+ * smb_oplock_create_break
+ */
+static smb_oplock_break_t *
+smb_oplock_create_break(smb_node_t *node)
+{
+ smb_oplock_break_t *ob;
+
+ ob = kmem_cache_alloc(smb_oplock_break_cache, KM_SLEEP);
+
+ smb_node_ref(node);
+ ob->ob_magic = SMB_OPLOCK_BREAK_MAGIC;
+ ob->ob_node = node;
+
+ return (ob);
+}
+
+/*
+ * smb_oplock_delete_break
+ */
+static void
+smb_oplock_delete_break(smb_oplock_break_t *ob)
+{
+ smb_node_release(ob->ob_node);
+ kmem_cache_free(smb_oplock_break_cache, ob);
}