Print this page
NEX-15578 SMB2 durable handle redesign
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5665 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-15578 SMB2 durable handle redesign
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5665 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-6041 Should pass the smbtorture lock tests
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-3553 SMB2/3 durable handles
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-5555 smb locks don't need l_uid or l_session_kid
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5537 Want reference counts for users, trees...
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5314 SMB server may wait for oplock break ack on disconnected session
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-4083 Upstream changes from illumos 5917 and 5995
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-2516 Need a way to disable exclusive oplocks
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
SMB-74 Process oplock breaks as session requests (part two)
SMB-74 Process oplock breaks as session requests
SMB-65 SMB server in non-global zones (kmem_caches)
common kmem_cache instances across zones
separate GZ-only init from NGZ init
SMB-64 smbsrv workers run at excessively high priority
SUP-599 smb_oplock_acquire thread deadlock
        
*** 18,792 ****
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
!  * 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().
!  *
!  * 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 <sys/nbmlock.h>
  
! #define SMB_OPLOCK_IS_EXCLUSIVE(level)          \
!         (((level) == SMB_OPLOCK_EXCLUSIVE) ||   \
!         ((level) == SMB_OPLOCK_BATCH))
  
- 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 void smb_oplock_sched_async_break(smb_oplock_grant_t *, uint8_t);
- static void smb_oplock_exec_async_break(void *);
- static void smb_oplock_break_levelII_locked(smb_node_t *);
- 
  /*
!  * 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) {
-                 smb_fem_oplock_uninstall(node);
-                 node->n_oplock.ol_fem = B_FALSE;
-         }
- }
- 
- /*
-  * This provides a way to fully disable oplocks, i.e. for testing.
-  * You _really_ do _not_ want to turn this off, because if you do,
-  * the clients send you very small read requests, and a _lot_ more
-  * of them.  The skc_oplock_enable parameter can be used to enable
-  * or disable exclusive oplocks.  Disabling that can be helpful
-  * when there are clients not responding to oplock breaks.
-  */
- int smb_oplocks_enabled = 1;
- 
- /*
-  * smb_oplock_acquire
-  *
-  * Attempt to acquire an oplock. Clients will request EXCLUSIVE or BATCH,
-  * but might only be granted LEVEL_II or NONE.
-  *
-  * 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 (SMB writers)
-  * Otherwise, grant LEVEL_II.
-  *
-  * 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_request_t *sr, smb_node_t *node, smb_ofile_t *ofile)
  {
!         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(ofile);
! 
!         ASSERT(node == SMB_OFILE_GET_NODE(ofile));
!         ASSERT(RW_LOCK_HELD(&node->n_lock));
! 
!         op = &sr->sr_open;
!         tree = SMB_OFILE_GET_TREE(ofile);
!         session = SMB_OFILE_GET_SESSION(ofile);
! 
!         if (smb_oplocks_enabled == 0 ||
!             (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;
!         grants = &ol->ol_grants;
! 
!         mutex_enter(&ol->ol_mutex);
!         smb_oplock_wait(node);
! 
!         /*
!          * Even if there are no other opens, we might want to
!          * grant only a Level II (shared) oplock so we avoid
!          * ever granting exclusive oplocks.
!          *
!          * Borrowing the SMB_TREE_OPLOCKS flag to enable/disable
!          * exclusive oplocks (for now).  See skc_oplock_enable,
!          * which can now be taken as "exclusive oplock enable".
!          * Should rename this parameter, and/or implement a new
!          * multi-valued parameter for oplock enables.
!          */
!         if ((node->n_open_count > 1) ||
!             (node->n_opening_count > 1) ||
!             !smb_tree_has_feature(tree, SMB_TREE_OPLOCKS) ||
!             smb_vop_other_opens(node->vp, ofile->f_mode)) {
!                 /*
!                  * There are other opens.
!                  */
!                 if ((!op->op_oplock_levelII) ||
!                     (!smb_session_levelII_oplocks(session)) ||
!                     (smb_oplock_exclusive_grant(grants) != NULL) ||
!                     (smb_lock_range_access(sr, node, 0, 0, B_FALSE))) {
!                         /*
!                          * LevelII (shared) oplock not allowed,
!                          * so reply with "none".
!                          */
                          op->op_oplock_level = SMB_OPLOCK_NONE;
-                         mutex_exit(&ol->ol_mutex);
                          return;
                  }
  
!                 op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
!         }
  
!         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;
!         }
  
!         ol->ol_xthread = curthread;
!         mutex_exit(&ol->ol_mutex);
! }
! 
! /*
!  * smb_oplock_break
!  *
!  * Break granted oplocks according to the following rules:
!  *
!  * 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)
!  *
!  * 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
!  *
!  * NB: sr == NULL when called by FEM framework.
   */
! int
! smb_oplock_break(smb_request_t *sr, smb_node_t *node, uint32_t flags)
! {
!         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;
!         grants = &ol->ol_grants;
! 
!         mutex_enter(&ol->ol_mutex);
!         smb_oplock_wait(node);
! 
!         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 {
!                 brk = SMB_OPLOCK_BREAK_TO_NONE;
!         }
! 
!         switch (ol->ol_break) {
!         case SMB_OPLOCK_NO_BREAK:
!                 ol->ol_break = brk;
!                 smb_oplock_sched_async_break(og, 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;
          }
  
!         if (flags & SMB_OPLOCK_BREAK_NOWAIT) {
!                 mutex_exit(&ol->ol_mutex);
!                 return (EAGAIN);
          }
  
!         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);
! }
! 
! /*
!  * smb_oplock_break_levelII
!  *
!  * This is called after a file is modified in some way.  If there are
!  * LevelII (shared) oplocks, break those to none.  If there is an
!  * exclusive oplock, there can be no LevelII oplocks, so do nothing.
!  *
!  * LevelII (shared) oplock breaks are processed asynchronously.
!  * Unlike exclusive oplock breaks, the thread initiating the break
!  * is NOT blocked while the request is processed.
!  *
!  * There may be a thread with exclusive rights to oplock state for
!  * this node (via ol_xthread in smb_oplock_wait) and if so, we must
!  * avoid breaking oplocks until that's out of the way.  However, we
!  * really don't want to block here, so when ol_xthread is set, we'll
!  * just mark that a "break level II to none" is pending, and let the
!  * exclusive thread do this work when it's done being exclusive.
   */
! void
! smb_oplock_break_levelII(smb_node_t *node)
! {
!         smb_oplock_t            *ol;
! 
!         ol = &node->n_oplock;
!         mutex_enter(&ol->ol_mutex);
! 
!         /* Instead of: smb_oplock_wait() ... */
!         if (ol->ol_xthread != NULL) {
!                 /* Defer the call to smb_oplock_broadcast(). */
!                 ol->ol_brk_pending = SMB_OPLOCK_BREAK_TO_NONE;
          } else {
!                 /* Equivalent of smb_oplock_wait() done. */
!                 smb_oplock_break_levelII_locked(node);
          }
  
-         mutex_exit(&ol->ol_mutex);
- }
- 
- /*
-  * smb_oplock_break_levelII_locked
-  * Internal helper for smb_oplock_break_levelII()
-  *
-  * Called with the oplock mutex already held, and _after_
-  * (the equivalent of) an smb_oplock_wait().
-  */
- static void
- smb_oplock_break_levelII_locked(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));
-         ASSERT(ol->ol_xthread == NULL);
- 
-         while ((og = list_head(grants)) != NULL) {
-                 SMB_OPLOCK_GRANT_VALID(og);
- 
                  /*
!                  * If there's an exclusive oplock, there are
!                  * no LevelII oplocks, so do nothing.
                   */
!                 if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
!                         break;
! 
!                 smb_oplock_sched_async_break(og, SMB_OPLOCK_BREAK_TO_NONE);
!                 smb_oplock_remove_grant(node, og);
!                 smb_oplock_clear_grant(og);
          }
- }
  
- /*
-  * Schedule a call to smb_session_oplock_break
-  * using an smb_request on the owning session.
-  */
- static void
- smb_oplock_sched_async_break(smb_oplock_grant_t *og, uint8_t brk)
- {
-         smb_request_t           *sr;
-         smb_ofile_t             *ofile;
- 
          /*
!          * Make sure we can get a hold on the ofile.  If we can't,
!          * the file is closing, and there's no point scheduling an
!          * oplock break on it.  (Also hold the tree and user.)
!          * These holds account for the pointers we copy into the
!          * smb_request fields: fid_ofile, tid_tree, uid_user.
!          * These holds are released via smb_request_free after
!          * the oplock break has been sent.
           */
!         ofile = og->og_ofile;
!         if (!smb_ofile_hold(ofile))
!                 return;
!         smb_tree_hold_internal(ofile->f_tree);
!         smb_user_hold_internal(ofile->f_user);
  
-         sr = smb_request_alloc(og->og_session, 0);
-         sr->sr_state = SMB_REQ_STATE_SUBMITTED;
-         sr->user_cr = zone_kcred();
-         sr->fid_ofile = ofile;
-         sr->tid_tree = ofile->f_tree;
-         sr->uid_user = ofile->f_user;
- 
-         sr->arg.olbrk = *og; /* struct copy */
-         sr->arg.olbrk.og_breaking = brk;
- 
-         (void) taskq_dispatch(
-             sr->sr_server->sv_worker_pool,
-             smb_oplock_exec_async_break, sr, TQ_SLEEP);
- }
- 
- /*
-  * smb_oplock_exec_async_break
-  *
-  * Called via the taskq to handle an asynchronous oplock break.
-  * We have a hold on the ofile, which keeps the FID here valid.
-  */
- static void
- smb_oplock_exec_async_break(void *arg)
- {
-         smb_request_t *sr = arg;
-         smb_oplock_grant_t *og = &sr->arg.olbrk;
- 
-         SMB_REQ_VALID(sr);
-         SMB_OPLOCK_GRANT_VALID(og);
- 
-         mutex_enter(&sr->sr_mutex);
-         sr->sr_worker = curthread;
-         sr->sr_time_active = gethrtime();
- 
-         switch (sr->sr_state) {
-         case SMB_REQ_STATE_SUBMITTED:
-                 sr->sr_state = SMB_REQ_STATE_ACTIVE;
-                 mutex_exit(&sr->sr_mutex);
- 
                  /*
!                  * This is where we actually do the deferred work
!                  * requested by smb_oplock_sched_async_break().
                   */
!                 smb_session_oplock_break(sr, og->og_breaking);
! 
!                 mutex_enter(&sr->sr_mutex);
!                 /* FALLTHROUGH */
! 
!         default: /* typically cancelled */
!                 sr->sr_state = SMB_REQ_STATE_COMPLETED;
!                 mutex_exit(&sr->sr_mutex);
!         }
! 
!         smb_request_free(sr);
! }
! 
! /*
!  * 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;
! 
!         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;
!                 }
!         }
!         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();
!                 }
!         }
!         ol->ol_break = SMB_OPLOCK_NO_BREAK;
! }
! 
! /*
!  * smb_oplock_release
!  *
!  * 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_grant_t      *og;
! 
!         ol = &node->n_oplock;
!         mutex_enter(&ol->ol_mutex);
!         smb_oplock_wait(node);
! 
!         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);
!                 }
!         }
! 
!         mutex_exit(&ol->ol_mutex);
! }
! 
! /*
!  * 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.
!  *
!  * 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.
!  */
! void
! smb_oplock_ack(smb_node_t *node, smb_ofile_t *of, uint8_t brk)
! {
!         smb_oplock_t            *ol;
!         smb_oplock_grant_t      *og;
! 
!         ol = &node->n_oplock;
!         mutex_enter(&ol->ol_mutex);
!         smb_oplock_wait(node);
! 
!         if ((ol->ol_break == SMB_OPLOCK_NO_BREAK) ||
!             ((og = smb_oplock_get_grant(ol, of)) == NULL)) {
!                 mutex_exit(&ol->ol_mutex);
                  return;
          }
  
!         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;
!                         smb_oplock_sched_async_break(og,
!                             SMB_OPLOCK_BREAK_TO_NONE);
                  }
-                 break;
-         default:
-                 SMB_PANIC();
-         }
- 
-         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);
- 
-         mutex_exit(&ol->ol_mutex);
- }
- 
- /*
-  * smb_oplock_broadcast
-  *
-  * Called when an open with oplock request completes.
-  *
-  * 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_xthread, wake any waiting threads.
-  */
- void
- smb_oplock_broadcast(smb_node_t *node)
- {
-         smb_oplock_t    *ol;
- 
-         SMB_NODE_VALID(node);
-         ol = &node->n_oplock;
- 
-         mutex_enter(&ol->ol_mutex);
-         if ((ol->ol_xthread != NULL) && (ol->ol_xthread == curthread)) {
-                 ol->ol_xthread = NULL;
-                 if (ol->ol_brk_pending) {
-                         ol->ol_brk_pending = 0;
-                         smb_oplock_break_levelII_locked(node);
-                 }
-                 cv_broadcast(&ol->ol_cv);
-         }
-         mutex_exit(&ol->ol_mutex);
- }
- 
- /*
-  * smb_oplock_wait
-  *
-  * 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;
- 
-         ol = &node->n_oplock;
-         ASSERT(MUTEX_HELD(&ol->ol_mutex));
- 
-         if ((ol->ol_xthread != NULL) && (ol->ol_xthread != curthread)) {
-                 while (ol->ol_xthread != NULL)
-                         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_breaking = 0;
-         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);
  }
--- 18,158 ----
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
!  * smb1 oplock support
   */
  
  #include <smbsrv/smb_kproto.h>
  
! #define BATCH_OR_EXCL   (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
  
  /*
!  * 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.
   */
  void
! smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
  {
!         smb_arg_open_t *op = &sr->arg.open;
!         smb_ofile_t *ofile = sr->fid_ofile;
!         uint32_t status;
  
!         /* Only disk trees get oplocks. */
!         if ((sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) {
                  op->op_oplock_level = SMB_OPLOCK_NONE;
                  return;
          }
  
!         if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
                  op->op_oplock_level = SMB_OPLOCK_NONE;
                  return;
          }
  
!         if (!smb_session_levelII_oplocks(sr->session))
!                 level2ok = B_FALSE;
  
!         /* Common code checks file type. */
  
!         /*
!          * SMB1: Convert to internal form.
           */
!         switch (op->op_oplock_level) {
!         case SMB_OPLOCK_BATCH:
!                 op->op_oplock_state = OPLOCK_LEVEL_BATCH;
                  break;
!         case SMB_OPLOCK_EXCLUSIVE:
!                 op->op_oplock_state = OPLOCK_LEVEL_ONE;
                  break;
!         case SMB_OPLOCK_LEVEL_II:
!                 op->op_oplock_state = OPLOCK_LEVEL_TWO;
                  break;
+         case SMB_OPLOCK_NONE:
+         default:
+                 op->op_oplock_level = SMB_OPLOCK_NONE;
+                 return;
          }
  
!         /*
!          * Tree options may force shared oplocks
!          */
!         if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
!                 op->op_oplock_state = OPLOCK_LEVEL_TWO;
          }
  
!         /*
!          * Try exclusive first, if requested
           */
!         if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) {
!                 status = smb_oplock_request(sr, ofile,
!                     &op->op_oplock_state);
          } else {
!                 status = NT_STATUS_OPLOCK_NOT_GRANTED;
          }
  
          /*
!          * If exclusive failed (or tree forced shared oplocks)
!          * and if the caller supports Level II, try shared.
           */
!         if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) {
!                 op->op_oplock_state = OPLOCK_LEVEL_TWO;
!                 status = smb_oplock_request(sr, ofile,
!                     &op->op_oplock_state);
          }
  
          /*
!          * 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(ofile->f_node, 0);
!                 status = 0;
!         }
  
          /*
!          * Keep track of what we got (in ofile->f_oplock.og_state)
!          * 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
!          * support "Level II" oplocks.  If we're talking to a
!          * 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:
!                 ofile->f_oplock.og_state = op->op_oplock_state;
                  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
!          * Convert internal oplock state to SMB1
!          */
!         if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
!                 op->op_oplock_level = SMB_OPLOCK_BATCH;
!         } else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
!                 op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
!         } else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
!                 op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
          } else {
!                 op->op_oplock_level = SMB_OPLOCK_NONE;
          }
  }