diff options
Diffstat (limited to 'usr/src/uts/common/fs/smbsrv/smb_oplock.c')
| -rw-r--r-- | usr/src/uts/common/fs/smbsrv/smb_oplock.c | 977 |
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); } |
