Print this page
NEX-20459 smb/server service restart fails, stuck in offlining state
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-4856 SMB2 kstats don't correctly count compound requests
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-18748 (Hyper-V 2016) VM goes to poweroff state when smbd is restarted
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
NEX-17589 Get "too high" smbd error when copy big file to cifs share (redo)
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-16519 Panic while running IOmeter to a pool through an SMB share
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@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-15581 SMB keep-alive feature is just noise
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-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@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-15581 SMB keep-alive feature is just noise
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-1643 dtrace provider for smbsrv
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-9864 Some SMB cancel races remain after NEX-5845
Revert (part of) "NEX-5845 rework SMB immediate cancel"
reverts (part of) commit 7a5da69f6d42b17ebcc95ca3d02925d07a01343e.
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5273 SMB 3 Encryption
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-8120 smbstat -C requires IPv4 addresses to be prefixed with ::ffff: if ipv6_enable is true
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Jean McCormack <jean.mccormack@nexenta.com>
NEX-6308 namespace collision for per-share kstats when changing sharesmb property
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@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-5845 rework SMB immediate cancel
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-5560 smb2 should use 64-bit server-global uids
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5537 Want reference counts for users, trees...
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5330 SMB server should combine TCP+NBT session lists
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-3906 Prefer that SMB change notify not tie up a worker thread
NEX-5278 SMB notify should buffer per file handle
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-5175 want SMB statistics separately for reads, writes, other
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4313 want iops, bandwidth, and latency kstats for smb
Portions contributed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
NEX-4714 smbstat -t tbytes/s always zero
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-2522 svcadm disable network/smb/server may hang
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4391 improve smb cancel support
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4147 Cancelled SMB2 requests sometimes have no response (missed things)
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>
SUP-672 Zero-padded IP address strings returned by SMB server...
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-3738 Should support SMB2_CAP_LARGE_MTU
Reviewed by: Alek Pinchuk <alek@nexenta.com>
Reviewed by: Bayard Bell <bayard.bell@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-3610 CLONE NEX-3591 SMB3 signing
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
NEX-3004 Keep track of remote TCP port
Reviewed by: Dan Fields <Dan.Fields@nexenta.com>
Reviewed by: Josef Sipek <Josef.Sipek@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
Reviewed by: Kevin Crowe <Kevin.Crowe@nexenta.com>
Reviewed by: Alek Pinchuk <Alek.Pinchuk@nexenta.com>
Reviewed by: Rick McNeal <Rick.McNeal@nexenta.com>
NEX-2798 SMB 1 disconnects after large write attempt
NEX-2781 SMB2 credit handling needs work
NEX-2353 Codenomicon: SMB2 TC # 448950 - PANIC in SMB2.Compounded-commands...
NEX-1991 SMB2 never grants level II oplocks
SMB-137 assertion failed: (sr->reply.chain_offset & 7) == 0, file: ../../common/fs/smbsrv/smb2_dispatch.c, line: 727
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-50 User-mode SMB server (fix elfchk noise)
SMB-56 extended security NTLMSSP, inbound (fix a leak)
SMB-39 Use AF_UNIX pipes for RPC (fix a leak)
SMB-75 smb_session_timers way too frequent
SMB-69 read-raw, write-raw are dead code
SMB-56 extended security NTLMSSP, inbound
SMB-50 User-mode SMB server
 Includes work by these authors:
 Thomas Keiser <thomas.keiser@nexenta.com>
 Albert Lee <trisk@nexenta.com>
SMB-65 SMB server in non-global zones (kmem_caches)
common kmem_cache instances across zones
separate GZ-only init from NGZ init
SMB-63 taskq_create_proc ... TQ_DYNAMIC puts tasks in p0
re #11974 CIFS Share - Tree connect fails from Windows 7 Clients
Fix up some merges where we wanted the upstream version.
SMB-48 Panic with smbtorture raw.scan-eamax
re #7126 rb4153 smbd panic with missing negotiate challenge
re #11215 rb3676 sesctl to SGI JBOD hangs in biowait() with a command stuck in mptsas driver
re #10734 NT Trans. Notify returning too quickly
re #6813 rb1757 port 2976 Child folder visibility through shares
        
*** 18,28 ****
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
   */
  
  #include <sys/atomic.h>
  #include <sys/synch.h>
  #include <sys/types.h>
--- 18,28 ----
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
   */
  
  #include <sys/atomic.h>
  #include <sys/synch.h>
  #include <sys/types.h>
*** 44,70 ****
   * We track the keepalive in minutes, but this constant
   * specifies it in seconds, so convert to minutes.
   */
  uint32_t smb_keep_alive = SMB_PI_KEEP_ALIVE_MIN / 60;
  
  static int  smbsr_newrq_initial(smb_request_t *);
  
  static void smb_session_cancel(smb_session_t *);
  static int smb_session_reader(smb_session_t *);
  static int smb_session_xprt_puthdr(smb_session_t *,
      uint8_t msg_type, uint32_t msg_len,
      uint8_t *dst, size_t dstlen);
! static smb_tree_t *smb_session_get_tree(smb_session_t *, smb_tree_t *);
! static void smb_session_logoff(smb_session_t *);
  static void smb_request_init_command_mbuf(smb_request_t *sr);
  static void smb_session_genkey(smb_session_t *);
  
  void
! smb_session_timers(smb_llist_t *ll)
  {
          smb_session_t   *session;
  
          smb_llist_enter(ll, RW_READER);
          session = smb_llist_head(ll);
          while (session != NULL) {
                  /*
                   * Walk through the table and decrement each keep_alive
--- 44,105 ----
   * We track the keepalive in minutes, but this constant
   * specifies it in seconds, so convert to minutes.
   */
  uint32_t smb_keep_alive = SMB_PI_KEEP_ALIVE_MIN / 60;
  
+ /*
+  * There are many smbtorture test cases that send
+  * racing requests, and where the tests fail if we
+  * don't execute them in exactly the order sent.
+  * These are test bugs.  The protocol makes no
+  * guarantees about execution order of requests
+  * that are concurrently active.
+  *
+  * Nonetheless, smbtorture has many useful tests,
+  * so we have this work-around we can enable to
+  * basically force sequential execution.  When
+  * enabled, insert a delay after each request is
+  * issued a taskq job.  Enable this with mdb by
+  * setting smb_reader_delay to 10.  Don't make it
+  * more than 500 or so or the server will appear
+  * to be so slow that tests may time out.
+  */
+ int smb_reader_delay = 0;  /* mSec. */
+ 
  static int  smbsr_newrq_initial(smb_request_t *);
  
  static void smb_session_cancel(smb_session_t *);
  static int smb_session_reader(smb_session_t *);
  static int smb_session_xprt_puthdr(smb_session_t *,
      uint8_t msg_type, uint32_t msg_len,
      uint8_t *dst, size_t dstlen);
! static void smb_session_disconnect_trees(smb_session_t  *);
  static void smb_request_init_command_mbuf(smb_request_t *sr);
  static void smb_session_genkey(smb_session_t *);
+ static int smb_session_kstat_update(kstat_t *, int);
+ void session_stats_init(smb_server_t *, smb_session_t *);
+ void session_stats_fini(smb_session_t *);
  
+ /*
+  * This (legacy) code is in support of an "idle timeout" feature,
+  * which is apparently incomplete.  To complete it, we should:
+  * when the keep_alive timer expires, check whether the client
+  * has any open files, and if not then kill their session.
+  * Right now the timers are there, but nothing happens when
+  * a timer expires.
+  *
+  * Todo: complete logic to kill idle sessions.
+  *
+  * Only called when sv_cfg.skc_keepalive != 0
+  */
  void
! smb_session_timers(smb_server_t *sv)
  {
          smb_session_t   *session;
+         smb_llist_t     *ll;
  
+         ll = &sv->sv_session_list;
          smb_llist_enter(ll, RW_READER);
          session = smb_llist_head(ll);
          while (session != NULL) {
                  /*
                   * Walk through the table and decrement each keep_alive
*** 72,119 ****
                   */
                  SMB_SESSION_VALID(session);
                  if (session->keep_alive &&
                      (session->keep_alive != (uint32_t)-1))
                          session->keep_alive--;
                  session = smb_llist_next(ll, session);
          }
          smb_llist_exit(ll);
  }
  
- void
- smb_session_correct_keep_alive_values(smb_llist_t *ll, uint32_t new_keep_alive)
- {
-         smb_session_t           *sn;
- 
-         /*
-          * Caller specifies seconds, but we track in minutes, so
-          * convert to minutes (rounded up).
-          */
-         new_keep_alive = (new_keep_alive + 59) / 60;
- 
-         if (new_keep_alive == smb_keep_alive)
-                 return;
-         /*
-          * keep alive == 0 means do not drop connection if it's idle
-          */
-         smb_keep_alive = (new_keep_alive) ? new_keep_alive : -1;
- 
-         /*
-          * Walk through the table and set each session to the new keep_alive
-          * value if they have not already timed out.  Block clock interrupts.
-          */
-         smb_llist_enter(ll, RW_READER);
-         sn = smb_llist_head(ll);
-         while (sn != NULL) {
-                 SMB_SESSION_VALID(sn);
-                 if (sn->keep_alive != 0)
-                         sn->keep_alive = new_keep_alive;
-                 sn = smb_llist_next(ll, sn);
-         }
-         smb_llist_exit(ll);
- }
- 
  /*
   * Send a session message - supports SMB-over-NBT and SMB-over-TCP.
   * If an mbuf chain is provided (optional), it will be freed and
   * set to NULL -- unconditionally!  (error or not)
   *
--- 107,122 ----
                   */
                  SMB_SESSION_VALID(session);
                  if (session->keep_alive &&
                      (session->keep_alive != (uint32_t)-1))
                          session->keep_alive--;
+ 
                  session = smb_llist_next(ll, session);
          }
          smb_llist_exit(ll);
  }
  
  /*
   * Send a session message - supports SMB-over-NBT and SMB-over-TCP.
   * If an mbuf chain is provided (optional), it will be freed and
   * set to NULL -- unconditionally!  (error or not)
   *
*** 415,461 ****
   * state.
   */
  void
  smb_request_cancel(smb_request_t *sr)
  {
          mutex_enter(&sr->sr_mutex);
          switch (sr->sr_state) {
  
          case SMB_REQ_STATE_INITIALIZING:
          case SMB_REQ_STATE_SUBMITTED:
          case SMB_REQ_STATE_ACTIVE:
          case SMB_REQ_STATE_CLEANED_UP:
!                 sr->sr_state = SMB_REQ_STATE_CANCELED;
                  break;
  
          case SMB_REQ_STATE_WAITING_LOCK:
                  /*
!                  * This request is waiting on a lock.  Wakeup everything
!                  * waiting on the lock so that the relevant thread regains
!                  * control and notices that is has been canceled.  The
!                  * other lock request threads waiting on this lock will go
!                  * back to sleep when they discover they are still blocked.
                   */
!                 sr->sr_state = SMB_REQ_STATE_CANCELED;
! 
!                 ASSERT(sr->sr_awaiting != NULL);
!                 mutex_enter(&sr->sr_awaiting->l_mutex);
!                 cv_broadcast(&sr->sr_awaiting->l_cv);
!                 mutex_exit(&sr->sr_awaiting->l_mutex);
                  break;
  
!         case SMB_REQ_STATE_WAITING_EVENT:
!                 /*
!                  * This request is waiting in change notify.
!                  */
!                 sr->sr_state = SMB_REQ_STATE_CANCELED;
!                 cv_signal(&sr->sr_ncr.nc_cv);
!                 break;
! 
!         case SMB_REQ_STATE_EVENT_OCCURRED:
          case SMB_REQ_STATE_COMPLETED:
!         case SMB_REQ_STATE_CANCELED:
                  /*
                   * No action required for these states since the request
                   * is completing.
                   */
                  break;
--- 418,461 ----
   * state.
   */
  void
  smb_request_cancel(smb_request_t *sr)
  {
+         void (*cancel_method)(smb_request_t *) = NULL;
+ 
          mutex_enter(&sr->sr_mutex);
          switch (sr->sr_state) {
  
          case SMB_REQ_STATE_INITIALIZING:
          case SMB_REQ_STATE_SUBMITTED:
          case SMB_REQ_STATE_ACTIVE:
          case SMB_REQ_STATE_CLEANED_UP:
!                 sr->sr_state = SMB_REQ_STATE_CANCELLED;
                  break;
  
+         case SMB_REQ_STATE_WAITING_AUTH:
+         case SMB_REQ_STATE_WAITING_FCN1:
          case SMB_REQ_STATE_WAITING_LOCK:
+         case SMB_REQ_STATE_WAITING_PIPE:
                  /*
!                  * These are states that have a cancel_method.
!                  * Make the state change now, to ensure that
!                  * we call cancel_method exactly once.  Do the
!                  * method call below, after we drop sr_mutex.
!                  * When the cancelled request thread resumes,
!                  * it should re-take sr_mutex and set sr_state
!                  * to CANCELLED, then return STATUS_CANCELLED.
                   */
!                 sr->sr_state = SMB_REQ_STATE_CANCEL_PENDING;
!                 cancel_method = sr->cancel_method;
!                 VERIFY(cancel_method != NULL);
                  break;
  
!         case SMB_REQ_STATE_WAITING_FCN2:
          case SMB_REQ_STATE_COMPLETED:
!         case SMB_REQ_STATE_CANCEL_PENDING:
!         case SMB_REQ_STATE_CANCELLED:
                  /*
                   * No action required for these states since the request
                   * is completing.
                   */
                  break;
*** 463,478 ****
--- 463,487 ----
          case SMB_REQ_STATE_FREE:
          default:
                  SMB_PANIC();
          }
          mutex_exit(&sr->sr_mutex);
+ 
+         if (cancel_method != NULL) {
+                 cancel_method(sr);
+         }
  }
  
  /*
   * smb_session_receiver
   *
   * Receives request from the network and dispatches them to a worker.
+  *
+  * When we receive a disconnect here, it _could_ be due to the server
+  * having initiated disconnect, in which case the session state will be
+  * SMB_SESSION_STATE_TERMINATED and we want to keep that state so later
+  * tear-down logic will know which side initiated.
   */
  void
  smb_session_receiver(smb_session_t *session)
  {
          int     rc = 0;
*** 483,493 ****
  
          if (session->s_local_port == IPPORT_NETBIOS_SSN) {
                  rc = smb_netbios_session_request(session);
                  if (rc != 0) {
                          smb_rwx_rwenter(&session->s_lock, RW_WRITER);
!                         session->s_state = SMB_SESSION_STATE_DISCONNECTED;
                          smb_rwx_rwexit(&session->s_lock);
                          return;
                  }
          }
  
--- 492,504 ----
  
          if (session->s_local_port == IPPORT_NETBIOS_SSN) {
                  rc = smb_netbios_session_request(session);
                  if (rc != 0) {
                          smb_rwx_rwenter(&session->s_lock, RW_WRITER);
!                         if (session->s_state != SMB_SESSION_STATE_TERMINATED)
!                                 session->s_state =
!                                     SMB_SESSION_STATE_DISCONNECTED;
                          smb_rwx_rwexit(&session->s_lock);
                          return;
                  }
          }
  
*** 496,505 ****
--- 507,517 ----
          smb_rwx_rwexit(&session->s_lock);
  
          (void) smb_session_reader(session);
  
          smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+         if (session->s_state != SMB_SESSION_STATE_TERMINATED)
                  session->s_state = SMB_SESSION_STATE_DISCONNECTED;
          smb_rwx_rwexit(&session->s_lock);
  
          smb_soshutdown(session->sock);
  
*** 514,524 ****
  }
  
  /*
   * smb_session_disconnect
   *
!  * Disconnects the session passed in.
   */
  void
  smb_session_disconnect(smb_session_t *session)
  {
          SMB_SESSION_VALID(session);
--- 526,536 ----
  }
  
  /*
   * smb_session_disconnect
   *
!  * Server-initiated disconnect (i.e. server shutdown)
   */
  void
  smb_session_disconnect(smb_session_t *session)
  {
          SMB_SESSION_VALID(session);
*** 528,539 ****
          case SMB_SESSION_STATE_INITIALIZED:
          case SMB_SESSION_STATE_CONNECTED:
          case SMB_SESSION_STATE_ESTABLISHED:
          case SMB_SESSION_STATE_NEGOTIATED:
                  smb_soshutdown(session->sock);
!                 session->s_state = SMB_SESSION_STATE_DISCONNECTED;
!                 _NOTE(FALLTHRU)
          case SMB_SESSION_STATE_DISCONNECTED:
          case SMB_SESSION_STATE_TERMINATED:
                  break;
          }
          smb_rwx_rwexit(&session->s_lock);
--- 540,551 ----
          case SMB_SESSION_STATE_INITIALIZED:
          case SMB_SESSION_STATE_CONNECTED:
          case SMB_SESSION_STATE_ESTABLISHED:
          case SMB_SESSION_STATE_NEGOTIATED:
                  smb_soshutdown(session->sock);
!                 session->s_state = SMB_SESSION_STATE_TERMINATED;
!                 break;
          case SMB_SESSION_STATE_DISCONNECTED:
          case SMB_SESSION_STATE_TERMINATED:
                  break;
          }
          smb_rwx_rwexit(&session->s_lock);
*** 600,611 ****
  
                  session->keep_alive = smb_keep_alive;
  
                  /*
                   * Allocate a request context, read the whole message.
                   */
!                 sr = smb_request_alloc(session, hdr.xh_length);
  
                  req_buf = (uint8_t *)sr->sr_request_buf;
                  resid = hdr.xh_length;
  
                  rc = smb_sorecv(session->sock, req_buf, resid);
--- 612,626 ----
  
                  session->keep_alive = smb_keep_alive;
  
                  /*
                   * Allocate a request context, read the whole message.
+                  * If the request alloc fails, we've disconnected
+                  * and won't be able to send the reply anyway, so bail now.
                   */
!                 if ((sr = smb_request_alloc(session, hdr.xh_length)) == NULL)
!                         break;
  
                  req_buf = (uint8_t *)sr->sr_request_buf;
                  resid = hdr.xh_length;
  
                  rc = smb_sorecv(session->sock, req_buf, resid);
*** 612,623 ****
                  if (rc) {
                          smb_request_free(sr);
                          break;
                  }
  
!                 /* accounting: requests, received bytes */
!                 smb_server_inc_req(sv);
                  smb_server_add_rxb(sv,
                      (int64_t)(hdr.xh_length + NETBIOS_HDR_SZ));
  
                  /*
                   * Initialize command MBC to represent the received data.
--- 627,637 ----
                  if (rc) {
                          smb_request_free(sr);
                          break;
                  }
  
!                 /* accounting: received bytes */
                  smb_server_add_rxb(sv,
                      (int64_t)(hdr.xh_length + NETBIOS_HDR_SZ));
  
                  /*
                   * Initialize command MBC to represent the received data.
*** 628,638 ****
--- 642,657 ----
  
                  rc = session->newrq_func(sr);
                  sr = NULL;      /* enqueued or freed */
                  if (rc != 0)
                          break;
+ 
+                 /* See notes where this is defined (above). */
+                 if (smb_reader_delay) {
+                         delay(MSEC_TO_TICK(smb_reader_delay));
                  }
+         }
          return (rc);
  }
  
  /*
   * This is the initial handler for new smb requests, called from
*** 647,656 ****
--- 666,678 ----
   *
   * This and other "post a request" handlers must either enqueue
   * the new request for the session taskq, or smb_request_free it
   * (in case we've decided to drop this connection).  In this
   * (special) new request handler, we always free the request.
+  *
+  * Return value is 0 for success, and anything else will
+  * terminate the reader thread (drop the connection).
   */
  static int
  smbsr_newrq_initial(smb_request_t *sr)
  {
          uint32_t magic;
*** 701,717 ****
                  return (NULL);
          }
  
          now = ddi_get_lbolt64();
  
          session->s_kid = SMB_NEW_KID();
          session->s_state = SMB_SESSION_STATE_INITIALIZED;
          session->native_os = NATIVE_OS_UNKNOWN;
          session->opentime = now;
          session->keep_alive = smb_keep_alive;
          session->activity_timestamp = now;
- 
          smb_session_genkey(session);
  
          mutex_init(&session->s_credits_mutex, NULL, MUTEX_DEFAULT, NULL);
  
          smb_slist_constructor(&session->s_req_list, sizeof (smb_request_t),
--- 723,739 ----
                  return (NULL);
          }
  
          now = ddi_get_lbolt64();
  
+         session->s_server = sv;
          session->s_kid = SMB_NEW_KID();
          session->s_state = SMB_SESSION_STATE_INITIALIZED;
          session->native_os = NATIVE_OS_UNKNOWN;
          session->opentime = now;
          session->keep_alive = smb_keep_alive;
          session->activity_timestamp = now;
          smb_session_genkey(session);
  
          mutex_init(&session->s_credits_mutex, NULL, MUTEX_DEFAULT, NULL);
  
          smb_slist_constructor(&session->s_req_list, sizeof (smb_request_t),
*** 728,738 ****
  
          smb_net_txl_constructor(&session->s_txlst);
  
          smb_rwx_init(&session->s_lock);
  
!         if (new_so != NULL) {
                  if (family == AF_INET) {
                          slen = sizeof (sin);
                          (void) ksocket_getsockname(new_so,
                              (struct sockaddr *)&sin, &slen, CRED());
                          bcopy(&sin.sin_addr,
--- 750,775 ----
  
          smb_net_txl_constructor(&session->s_txlst);
  
          smb_rwx_init(&session->s_lock);
  
!         session->s_srqueue = &sv->sv_srqueue;
!         smb_server_get_cfg(sv, &session->s_cfg);
! 
!         if (new_so == NULL) {
!                 /*
!                  * This call is creating the special "server" session,
!                  * used for kshare export, oplock breaks, CA import.
!                  * CA import creates temporary trees on this session
!                  * and those should never get map/unmap up-calls, so
!                  * force the map/unmap flags zero on this session.
!                  * Set a "modern" dialect for CA import too, so
!                  * pathname parse doesn't do OS/2 stuff, etc.
!                  */
!                 session->s_cfg.skc_execflags = 0;
!                 session->dialect = session->s_cfg.skc_max_protocol;
!         } else {
                  if (family == AF_INET) {
                          slen = sizeof (sin);
                          (void) ksocket_getsockname(new_so,
                              (struct sockaddr *)&sin, &slen, CRED());
                          bcopy(&sin.sin_addr,
*** 770,782 ****
                  if (port == IPPORT_NETBIOS_SSN)
                          smb_server_inc_nbt_sess(sv);
                  else
                          smb_server_inc_tcp_sess(sv);
          }
-         session->s_server = sv;
-         smb_server_get_cfg(sv, &session->s_cfg);
-         session->s_srqueue = &sv->sv_srqueue;
  
          /*
           * The initial new request handler is special,
           * and only accepts negotiation requests.
           */
--- 807,816 ----
*** 784,803 ****
--- 818,950 ----
  
          /* These may increase in SMB2 negotiate. */
          session->cmd_max_bytes = SMB_REQ_MAX_SIZE;
          session->reply_max_bytes = SMB_REQ_MAX_SIZE;
  
+         session_stats_init(sv, session);
+ 
          session->s_magic = SMB_SESSION_MAGIC;
+ 
          return (session);
  }
  
  void
+ session_stats_init(smb_server_t *sv, smb_session_t *ss)
+ {
+         static const char       *kr_names[] = SMBSRV_CLSH__NAMES;
+         char                    ks_name[KSTAT_STRLEN];
+         char                    *ipaddr_str = ss->ip_addr_str;
+         smbsrv_clsh_kstats_t    *ksr;
+         int                     idx;
+ 
+         /* Don't include the special internal session (sv->sv_session). */
+         if (ss->sock == NULL)
+                 return;
+ 
+         /*
+          * If ipv6_enable is set to true, IPv4 addresses will be prefixed
+          * with ::ffff: (IPv4-mapped IPv6 address), strip it.
+          */
+         if (strncasecmp(ipaddr_str, "::ffff:", 7) == 0)
+                 ipaddr_str += 7;
+ 
+         /*
+          * Create raw kstats for sessions with a name composed as:
+          * cl/$IPADDR  and instance ss->s_kid
+          * These will look like: smbsrv:0:cl/10.10.0.5
+          */
+         (void) snprintf(ks_name, sizeof (ks_name), "cl/%s", ipaddr_str);
+ 
+         ss->s_ksp = kstat_create_zone(SMBSRV_KSTAT_MODULE, ss->s_kid,
+             ks_name, SMBSRV_KSTAT_CLASS, KSTAT_TYPE_RAW,
+             sizeof (smbsrv_clsh_kstats_t), 0, sv->sv_zid);
+ 
+         if (ss->s_ksp == NULL)
+                 return;
+ 
+         ss->s_ksp->ks_update = smb_session_kstat_update;
+         ss->s_ksp->ks_private = ss;
+ 
+         /*
+          * In-line equivalent of smb_dispatch_stats_init
+          */
+         ksr = (smbsrv_clsh_kstats_t *)ss->s_ksp->ks_data;
+         for (idx = 0; idx < SMBSRV_CLSH__NREQ; idx++) {
+                 smb_latency_init(&ss->s_stats[idx].sdt_lat);
+                 (void) strlcpy(ksr->ks_clsh[idx].kr_name, kr_names[idx],
+                     KSTAT_STRLEN);
+         }
+ 
+         kstat_install(ss->s_ksp);
+ }
+ 
+ void
+ session_stats_fini(smb_session_t *ss)
+ {
+         int     idx;
+ 
+         for (idx = 0; idx < SMBSRV_CLSH__NREQ; idx++)
+                 smb_latency_destroy(&ss->s_stats[idx].sdt_lat);
+ }
+ 
+ /*
+  * Update the kstat data from our private stats.
+  */
+ static int
+ smb_session_kstat_update(kstat_t *ksp, int rw)
+ {
+         smb_session_t *session;
+         smb_disp_stats_t *sds;
+         smbsrv_clsh_kstats_t    *clsh;
+         smb_kstat_req_t *ksr;
+         int i;
+ 
+         if (rw == KSTAT_WRITE)
+                 return (EACCES);
+ 
+         session = ksp->ks_private;
+         SMB_SESSION_VALID(session);
+         sds = session->s_stats;
+ 
+         clsh = (smbsrv_clsh_kstats_t *)ksp->ks_data;
+         ksr = clsh->ks_clsh;
+ 
+         for (i = 0; i < SMBSRV_CLSH__NREQ; i++, ksr++, sds++) {
+                 ksr->kr_rxb = sds->sdt_rxb;
+                 ksr->kr_txb = sds->sdt_txb;
+                 mutex_enter(&sds->sdt_lat.ly_mutex);
+                 ksr->kr_nreq = sds->sdt_lat.ly_a_nreq;
+                 ksr->kr_sum = sds->sdt_lat.ly_a_sum;
+                 ksr->kr_a_mean = sds->sdt_lat.ly_a_mean;
+                 ksr->kr_a_stddev = sds->sdt_lat.ly_a_stddev;
+                 ksr->kr_d_mean = sds->sdt_lat.ly_d_mean;
+                 ksr->kr_d_stddev = sds->sdt_lat.ly_d_stddev;
+                 sds->sdt_lat.ly_d_mean = 0;
+                 sds->sdt_lat.ly_d_nreq = 0;
+                 sds->sdt_lat.ly_d_stddev = 0;
+                 sds->sdt_lat.ly_d_sum = 0;
+                 mutex_exit(&sds->sdt_lat.ly_mutex);
+         }
+ 
+         return (0);
+ }
+ 
+ void
  smb_session_delete(smb_session_t *session)
  {
  
          ASSERT(session->s_magic == SMB_SESSION_MAGIC);
  
+         if (session->s_ksp != NULL) {
+                 kstat_delete(session->s_ksp);
+                 session->s_ksp = NULL;
+                 session_stats_fini(session);
+         }
+ 
+         if (session->enc_mech != NULL)
+                 smb3_encrypt_fini(session);
+ 
          if (session->sign_fini != NULL)
                  session->sign_fini(session);
  
          if (session->signing.mackey != NULL) {
                  kmem_free(session->signing.mackey,
*** 845,865 ****
           * this session.
           */
          smb_slist_wait_for_empty(&session->s_req_list);
  
          /*
!          * At this point the reference count of the users, trees, files,
!          * directories should be zero. It should be possible to destroy them
!          * without any problem.
           */
          xa = smb_llist_head(&session->s_xa_list);
          while (xa) {
                  nextxa = smb_llist_next(&session->s_xa_list, xa);
                  smb_xa_close(xa);
                  xa = nextxa;
          }
  
          smb_session_logoff(session);
  }
  
  /*
   * Cancel requests.  If a non-null tree is specified, only requests specific
--- 992,1015 ----
           * this session.
           */
          smb_slist_wait_for_empty(&session->s_req_list);
  
          /*
!          * Cleanup transact state objects
           */
          xa = smb_llist_head(&session->s_xa_list);
          while (xa) {
                  nextxa = smb_llist_next(&session->s_xa_list, xa);
                  smb_xa_close(xa);
                  xa = nextxa;
          }
  
+         /*
+          * At this point the reference count of the files and directories
+          * should be zero. It should be possible to destroy them without
+          * any problem, which should trigger the destruction of other objects.
+          */
          smb_session_logoff(session);
  }
  
  /*
   * Cancel requests.  If a non-null tree is specified, only requests specific
*** 893,954 ****
   * Find a user on the specified session by SMB UID.
   */
  smb_user_t *
  smb_session_lookup_uid(smb_session_t *session, uint16_t uid)
  {
!         return (smb_session_lookup_uid_st(session, uid,
              SMB_USER_STATE_LOGGED_ON));
  }
  
  smb_user_t *
! smb_session_lookup_uid_st(smb_session_t *session, uint16_t uid,
!     smb_user_state_t st)
  {
          smb_user_t      *user;
          smb_llist_t     *user_list;
  
          SMB_SESSION_VALID(session);
  
          user_list = &session->s_user_list;
          smb_llist_enter(user_list, RW_READER);
  
!         user = smb_llist_head(user_list);
!         while (user) {
                  SMB_USER_VALID(user);
                  ASSERT(user->u_session == session);
  
!                 if (user->u_uid == uid && user->u_state == st) {
!                         smb_user_hold_internal(user);
                          break;
                  }
! 
!                 user = smb_llist_next(user_list, user);
          }
  
          smb_llist_exit(user_list);
          return (user);
  }
  
- void
- smb_session_post_user(smb_session_t *session, smb_user_t *user)
- {
-         SMB_USER_VALID(user);
-         ASSERT(user->u_refcnt == 0);
-         ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF);
-         ASSERT(user->u_session == session);
- 
-         smb_llist_post(&session->s_user_list, user, smb_user_delete);
- }
- 
  /*
   * Find a tree by tree-id.
   */
  smb_tree_t *
  smb_session_lookup_tree(
      smb_session_t       *session,
      uint16_t            tid)
- 
  {
          smb_tree_t      *tree;
  
          SMB_SESSION_VALID(session);
  
--- 1043,1109 ----
   * Find a user on the specified session by SMB UID.
   */
  smb_user_t *
  smb_session_lookup_uid(smb_session_t *session, uint16_t uid)
  {
!         return (smb_session_lookup_uid_st(session, 0, uid,
              SMB_USER_STATE_LOGGED_ON));
  }
  
+ /*
+  * Find a user on the specified session by SMB2 SSNID.
+  */
  smb_user_t *
! smb_session_lookup_ssnid(smb_session_t *session, uint64_t ssnid)
  {
+         return (smb_session_lookup_uid_st(session, ssnid, 0,
+             SMB_USER_STATE_LOGGED_ON));
+ }
+ 
+ smb_user_t *
+ smb_session_lookup_uid_st(smb_session_t *session, uint64_t ssnid,
+     uint16_t uid, smb_user_state_t st)
+ {
          smb_user_t      *user;
          smb_llist_t     *user_list;
  
          SMB_SESSION_VALID(session);
  
          user_list = &session->s_user_list;
          smb_llist_enter(user_list, RW_READER);
  
!         for (user = smb_llist_head(user_list);
!             user != NULL;
!             user = smb_llist_next(user_list, user)) {
! 
                  SMB_USER_VALID(user);
                  ASSERT(user->u_session == session);
  
!                 if (user->u_ssnid != ssnid && user->u_uid != uid)
!                         continue;
! 
!                 mutex_enter(&user->u_mutex);
!                 if (user->u_state == st) {
!                         // smb_user_hold_internal(user);
!                         user->u_refcnt++;
!                         mutex_exit(&user->u_mutex);
                          break;
                  }
!                 mutex_exit(&user->u_mutex);
          }
  
          smb_llist_exit(user_list);
          return (user);
  }
  
  /*
   * Find a tree by tree-id.
   */
  smb_tree_t *
  smb_session_lookup_tree(
      smb_session_t       *session,
      uint16_t            tid)
  {
          smb_tree_t      *tree;
  
          SMB_SESSION_VALID(session);
  
*** 975,1095 ****
          smb_llist_exit(&session->s_tree_list);
          return (NULL);
  }
  
  /*
-  * Find the first connected tree that matches the specified sharename.
-  * If the specified tree is NULL the search starts from the beginning of
-  * the user's tree list.  If a tree is provided the search starts just
-  * after that tree.
-  */
- smb_tree_t *
- smb_session_lookup_share(
-     smb_session_t       *session,
-     const char          *sharename,
-     smb_tree_t          *tree)
- {
-         SMB_SESSION_VALID(session);
-         ASSERT(sharename);
- 
-         smb_llist_enter(&session->s_tree_list, RW_READER);
- 
-         if (tree) {
-                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                 ASSERT(tree->t_session == session);
-                 tree = smb_llist_next(&session->s_tree_list, tree);
-         } else {
-                 tree = smb_llist_head(&session->s_tree_list);
-         }
- 
-         while (tree) {
-                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                 ASSERT(tree->t_session == session);
-                 if (smb_strcasecmp(tree->t_sharename, sharename, 0) == 0) {
-                         if (smb_tree_hold(tree)) {
-                                 smb_llist_exit(&session->s_tree_list);
-                                 return (tree);
-                         }
-                 }
-                 tree = smb_llist_next(&session->s_tree_list, tree);
-         }
- 
-         smb_llist_exit(&session->s_tree_list);
-         return (NULL);
- }
- 
- /*
-  * Find the first connected tree that matches the specified volume name.
-  * If the specified tree is NULL the search starts from the beginning of
-  * the user's tree list.  If a tree is provided the search starts just
-  * after that tree.
-  */
- smb_tree_t *
- smb_session_lookup_volume(
-     smb_session_t       *session,
-     const char          *name,
-     smb_tree_t          *tree)
- {
-         SMB_SESSION_VALID(session);
-         ASSERT(name);
- 
-         smb_llist_enter(&session->s_tree_list, RW_READER);
- 
-         if (tree) {
-                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                 ASSERT(tree->t_session == session);
-                 tree = smb_llist_next(&session->s_tree_list, tree);
-         } else {
-                 tree = smb_llist_head(&session->s_tree_list);
-         }
- 
-         while (tree) {
-                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                 ASSERT(tree->t_session == session);
- 
-                 if (smb_strcasecmp(tree->t_volume, name, 0) == 0) {
-                         if (smb_tree_hold(tree)) {
-                                 smb_llist_exit(&session->s_tree_list);
-                                 return (tree);
-                         }
-                 }
- 
-                 tree = smb_llist_next(&session->s_tree_list, tree);
-         }
- 
-         smb_llist_exit(&session->s_tree_list);
-         return (NULL);
- }
- 
- /*
   * Disconnect all trees that match the specified client process-id.
   */
  void
  smb_session_close_pid(
      smb_session_t       *session,
      uint32_t            pid)
  {
          smb_tree_t      *tree;
  
!         SMB_SESSION_VALID(session);
  
!         tree = smb_session_get_tree(session, NULL);
          while (tree) {
!                 smb_tree_t *next;
!                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
!                 ASSERT(tree->t_session == session);
                  smb_tree_close_pid(tree, pid);
-                 next = smb_session_get_tree(session, tree);
                  smb_tree_release(tree);
-                 tree = next;
          }
  }
  
  static void
! smb_session_tree_dtor(void *t)
  {
!         smb_tree_t      *tree = (smb_tree_t *)t;
  
          smb_tree_disconnect(tree, B_TRUE);
          /* release the ref acquired during the traversal loop */
          smb_tree_release(tree);
  }
--- 1130,1168 ----
          smb_llist_exit(&session->s_tree_list);
          return (NULL);
  }
  
  /*
   * Disconnect all trees that match the specified client process-id.
+  * Used by the SMB1 "process exit" request.
   */
  void
  smb_session_close_pid(
      smb_session_t       *session,
      uint32_t            pid)
  {
+         smb_llist_t     *tree_list = &session->s_tree_list;
          smb_tree_t      *tree;
  
!         smb_llist_enter(tree_list, RW_READER);
  
!         tree = smb_llist_head(tree_list);
          while (tree) {
!                 if (smb_tree_hold(tree)) {
                          smb_tree_close_pid(tree, pid);
                          smb_tree_release(tree);
                  }
+                 tree = smb_llist_next(tree_list, tree);
+         }
+ 
+         smb_llist_exit(tree_list);
  }
  
  static void
! smb_session_tree_dtor(void *arg)
  {
!         smb_tree_t      *tree = arg;
  
          smb_tree_disconnect(tree, B_TRUE);
          /* release the ref acquired during the traversal loop */
          smb_tree_release(tree);
  }
*** 1116,1127 ****
                  if ((tree->t_owner == owner) &&
                      smb_tree_hold(tree)) {
                          /*
                           * smb_tree_hold() succeeded, hence we are in state
                           * SMB_TREE_STATE_CONNECTED; schedule this tree
!                          * for asynchronous disconnect, which will fire
!                          * after we drop the llist traversal lock.
                           */
                          smb_llist_post(tree_list, tree, smb_session_tree_dtor);
                  }
                  tree = smb_llist_next(tree_list, tree);
          }
--- 1189,1201 ----
                  if ((tree->t_owner == owner) &&
                      smb_tree_hold(tree)) {
                          /*
                           * smb_tree_hold() succeeded, hence we are in state
                           * SMB_TREE_STATE_CONNECTED; schedule this tree
!                          * for disconnect after smb_llist_exit because
!                          * the "unmap exec" up-call can block, and we'd
!                          * rather not block with the tree list locked.
                           */
                          smb_llist_post(tree_list, tree, smb_session_tree_dtor);
                  }
                  tree = smb_llist_next(tree_list, tree);
          }
*** 1131,1275 ****
  }
  
  /*
   * Disconnect all trees that this user has connected.
   */
! void
  smb_session_disconnect_trees(
      smb_session_t       *session)
  {
          smb_tree_t      *tree;
  
!         SMB_SESSION_VALID(session);
  
!         tree = smb_session_get_tree(session, NULL);
          while (tree) {
!                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
!                 ASSERT(tree->t_session == session);
!                 smb_tree_disconnect(tree, B_TRUE);
!                 smb_tree_release(tree);
!                 tree = smb_session_get_tree(session, NULL);
          }
  }
  
  /*
!  * Disconnect all trees that match the specified share name.
   */
! void
! smb_session_disconnect_share(
!     smb_session_t       *session,
!     const char          *sharename)
  {
!         smb_tree_t      *tree;
!         smb_tree_t      *next;
  
!         SMB_SESSION_VALID(session);
  
-         tree = smb_session_lookup_share(session, sharename, NULL);
-         while (tree) {
-                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                 ASSERT(tree->t_session == session);
-                 smb_session_cancel_requests(session, tree, NULL);
                  smb_tree_disconnect(tree, B_TRUE);
!                 next = smb_session_lookup_share(session, sharename, tree);
                  smb_tree_release(tree);
-                 tree = next;
-         }
  }
  
- void
- smb_session_post_tree(smb_session_t *session, smb_tree_t *tree)
- {
-         SMB_SESSION_VALID(session);
-         SMB_TREE_VALID(tree);
-         ASSERT0(tree->t_refcnt);
-         ASSERT(tree->t_state == SMB_TREE_STATE_DISCONNECTED);
-         ASSERT(tree->t_session == session);
- 
-         smb_llist_post(&session->s_tree_list, tree, smb_tree_dealloc);
- }
- 
  /*
!  * Get the next connected tree in the list.  A reference is taken on
!  * the tree, which can be released later with smb_tree_release().
!  *
!  * If the specified tree is NULL the search starts from the beginning of
!  * the tree list.  If a tree is provided the search starts just after
!  * that tree.
!  *
!  * Returns NULL if there are no connected trees in the list.
   */
! static smb_tree_t *
! smb_session_get_tree(
      smb_session_t       *session,
!     smb_tree_t          *tree)
  {
!         smb_llist_t     *tree_list;
  
          SMB_SESSION_VALID(session);
-         tree_list = &session->s_tree_list;
  
!         smb_llist_enter(tree_list, RW_READER);
  
!         if (tree) {
!                 ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
!                 tree = smb_llist_next(tree_list, tree);
!         } else {
!                 tree = smb_llist_head(tree_list);
!         }
  
!         while (tree) {
!                 if (smb_tree_hold(tree))
!                         break;
  
!                 tree = smb_llist_next(tree_list, tree);
          }
  
!         smb_llist_exit(tree_list);
!         return (tree);
  }
  
  /*
   * Logoff all users associated with the specified session.
   */
! static void
  smb_session_logoff(smb_session_t *session)
  {
          smb_user_t      *user;
  
          SMB_SESSION_VALID(session);
  
!         smb_session_disconnect_trees(session);
  
!         smb_llist_enter(&session->s_user_list, RW_READER);
! 
!         user = smb_llist_head(&session->s_user_list);
          while (user) {
                  SMB_USER_VALID(user);
                  ASSERT(user->u_session == session);
  
                  switch (user->u_state) {
                  case SMB_USER_STATE_LOGGING_ON:
                  case SMB_USER_STATE_LOGGED_ON:
!                         smb_user_hold_internal(user);
                          smb_user_logoff(user);
                          smb_user_release(user);
                          break;
  
                  case SMB_USER_STATE_LOGGED_OFF:
                  case SMB_USER_STATE_LOGGING_OFF:
                          break;
  
                  default:
                          ASSERT(0);
                          break;
                  }
  
!                 user = smb_llist_next(&session->s_user_list, user);
          }
  
!         smb_llist_exit(&session->s_user_list);
  }
  
  /*
   * Copy the session workstation/client name to buf.  If the workstation
   * is an empty string (which it will be on TCP connections), use the
--- 1205,1393 ----
  }
  
  /*
   * Disconnect all trees that this user has connected.
   */
! static void
  smb_session_disconnect_trees(
      smb_session_t       *session)
  {
+         smb_llist_t     *tree_list = &session->s_tree_list;
          smb_tree_t      *tree;
  
!         smb_llist_enter(tree_list, RW_READER);
  
!         tree = smb_llist_head(tree_list);
          while (tree) {
!                 if (smb_tree_hold(tree)) {
!                         smb_llist_post(tree_list, tree,
!                             smb_session_tree_dtor);
                  }
+                 tree = smb_llist_next(tree_list, tree);
+         }
+ 
+         /* drop the lock and flush the dtor queue */
+         smb_llist_exit(tree_list);
  }
  
  /*
!  * Variant of smb_session_tree_dtor that also
!  * cancels requests using this tree.
   */
! static void
! smb_session_tree_kill(void *arg)
  {
!         smb_tree_t      *tree = arg;
  
!         SMB_TREE_VALID(tree);
  
          smb_tree_disconnect(tree, B_TRUE);
!         smb_session_cancel_requests(tree->t_session, tree, NULL);
! 
!         /* release the ref acquired during the traversal loop */
          smb_tree_release(tree);
  }
  
  /*
!  * Disconnect all trees that match the specified share name,
!  * and kill requests using those trees.
   */
! void
! smb_session_disconnect_share(
      smb_session_t       *session,
!     const char          *sharename)
  {
!         smb_llist_t     *ll;
!         smb_tree_t      *tree;
  
          SMB_SESSION_VALID(session);
  
!         ll = &session->s_tree_list;
!         smb_llist_enter(ll, RW_READER);
  
!         for (tree = smb_llist_head(ll);
!             tree != NULL;
!             tree = smb_llist_next(ll, tree)) {
  
!                 SMB_TREE_VALID(tree);
!                 ASSERT(tree->t_session == session);
  
!                 if (smb_strcasecmp(tree->t_sharename, sharename, 0) != 0)
!                         continue;
! 
!                 if (smb_tree_hold(tree)) {
!                         smb_llist_post(ll, tree,
!                             smb_session_tree_kill);
                  }
+         }
  
!         smb_llist_exit(ll);
  }
  
  /*
   * Logoff all users associated with the specified session.
+  *
+  * This is called for both server-initiated disconnect
+  * (SMB_SESSION_STATE_TERMINATED) and client-initiated
+  * disconnect (SMB_SESSION_STATE_DISCONNECTED).
+  * If client-initiated, save durable handles.
   */
! void
  smb_session_logoff(smb_session_t *session)
  {
+         smb_llist_t     *ulist;
          smb_user_t      *user;
  
          SMB_SESSION_VALID(session);
  
! top:
!         ulist = &session->s_user_list;
!         smb_llist_enter(ulist, RW_READER);
  
!         user = smb_llist_head(ulist);
          while (user) {
                  SMB_USER_VALID(user);
                  ASSERT(user->u_session == session);
  
+                 mutex_enter(&user->u_mutex);
                  switch (user->u_state) {
                  case SMB_USER_STATE_LOGGING_ON:
                  case SMB_USER_STATE_LOGGED_ON:
!                         // smb_user_hold_internal(user);
!                         user->u_refcnt++;
!                         mutex_exit(&user->u_mutex);
                          smb_user_logoff(user);
                          smb_user_release(user);
                          break;
  
                  case SMB_USER_STATE_LOGGED_OFF:
                  case SMB_USER_STATE_LOGGING_OFF:
+                         mutex_exit(&user->u_mutex);
                          break;
  
                  default:
+                         mutex_exit(&user->u_mutex);
                          ASSERT(0);
                          break;
                  }
  
!                 user = smb_llist_next(ulist, user);
          }
  
!         /* Needed below (Was the list empty?) */
!         user = smb_llist_head(ulist);
! 
!         smb_llist_exit(ulist);
! 
!         /*
!          * It's possible for user objects to remain due to references
!          * obtained via smb_server_lookup_ssnid(), when an SMB2
!          * session setup is destroying a previous session.
!          *
!          * Wait for user objects to clear out (last refs. go away,
!          * then smb_user_delete takes them out of the list).  When
!          * the last user object is removed, the session state is
!          * set to SHUTDOWN and s_lock is signaled.
!          *
!          * Not all places that call smb_user_release necessarily
!          * flush the delete queue, so after we wait for the list
!          * to empty out, go back to the top and recheck the list
!          * delete queue to make sure smb_user_delete happens.
!          */
!         if (user == NULL) {
!                 /* User list is empty. */
!                 smb_rwx_rwenter(&session->s_lock, RW_WRITER);
!                 session->s_state = SMB_SESSION_STATE_SHUTDOWN;
!                 smb_rwx_rwexit(&session->s_lock);
!         } else {
!                 smb_rwx_rwenter(&session->s_lock, RW_READER);
!                 if (session->s_state != SMB_SESSION_STATE_SHUTDOWN) {
!                         (void) smb_rwx_cvwait(&session->s_lock,
!                             MSEC_TO_TICK(200));
!                         smb_rwx_rwexit(&session->s_lock);
!                         goto top;
!                 }
!                 smb_rwx_rwexit(&session->s_lock);
!         }
!         ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN);
! 
!         /*
!          * User list should be empty now.
!          */
! #ifdef  DEBUG
!         if (ulist->ll_count != 0) {
!                 cmn_err(CE_WARN, "user list not empty?");
!                 debug_enter("s_user_list");
!         }
! #endif
! 
!         /*
!          * User logoff happens first so we'll set preserve_opens
!          * for client-initiated disconnect.  When that's done
!          * there should be no trees left, but check anyway.
!          */
!         smb_session_disconnect_trees(session);
  }
  
  /*
   * Copy the session workstation/client name to buf.  If the workstation
   * is an empty string (which it will be on TCP connections), use the
*** 1318,1328 ****
   * smb_request_alloc
   *
   * Allocate an smb_request_t structure from the kmem_cache.  Partially
   * initialize the found/new request.
   *
!  * Returns pointer to a request
   */
  smb_request_t *
  smb_request_alloc(smb_session_t *session, int req_length)
  {
          smb_request_t   *sr;
--- 1436,1447 ----
   * smb_request_alloc
   *
   * Allocate an smb_request_t structure from the kmem_cache.  Partially
   * initialize the found/new request.
   *
!  * Returns pointer to a request, or NULL if the session state is
!  * one in which new requests are no longer allowed.
   */
  smb_request_t *
  smb_request_alloc(smb_session_t *session, int req_length)
  {
          smb_request_t   *sr;
*** 1338,1348 ****
           * whole thing and start over.
           */
          bzero(sr, sizeof (smb_request_t));
  
          mutex_init(&sr->sr_mutex, NULL, MUTEX_DEFAULT, NULL);
-         cv_init(&sr->sr_ncr.nc_cv, NULL, CV_DEFAULT, NULL);
          smb_srm_init(sr);
          sr->session = session;
          sr->sr_server = session->s_server;
          sr->sr_gmtoff = session->s_server->si_gmtoff;
          sr->sr_cfg = &session->s_cfg;
--- 1457,1466 ----
*** 1351,1361 ****
--- 1469,1509 ----
          sr->sr_req_length = req_length;
          if (req_length)
                  sr->sr_request_buf = kmem_alloc(req_length, KM_SLEEP);
          sr->sr_magic = SMB_REQ_MAGIC;
          sr->sr_state = SMB_REQ_STATE_INITIALIZING;
+ 
+         /*
+          * Only allow new SMB requests in some states.
+          */
+         smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+         switch (session->s_state) {
+         case SMB_SESSION_STATE_CONNECTED:
+         case SMB_SESSION_STATE_INITIALIZED:
+         case SMB_SESSION_STATE_ESTABLISHED:
+         case SMB_SESSION_STATE_NEGOTIATED:
                  smb_slist_insert_tail(&session->s_req_list, sr);
+                 break;
+ 
+         default:
+                 ASSERT(0);
+                 /* FALLTHROUGH */
+         case SMB_SESSION_STATE_DISCONNECTED:
+         case SMB_SESSION_STATE_SHUTDOWN:
+         case SMB_SESSION_STATE_TERMINATED:
+                 /* Disallow new requests in these states. */
+                 if (sr->sr_request_buf)
+                         kmem_free(sr->sr_request_buf, sr->sr_req_length);
+                 sr->session = NULL;
+                 sr->sr_magic = 0;
+                 mutex_destroy(&sr->sr_mutex);
+                 kmem_cache_free(smb_cache_request, sr);
+                 sr = NULL;
+                 break;
+         }
+         smb_rwx_rwexit(&session->s_lock);
+ 
          return (sr);
  }
  
  /*
   * smb_request_free
*** 1366,1388 ****
  smb_request_free(smb_request_t *sr)
  {
          ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
          ASSERT(sr->session);
          ASSERT(sr->r_xa == NULL);
-         ASSERT(sr->sr_ncr.nc_fname == NULL);
  
          if (sr->fid_ofile != NULL) {
-                 smb_ofile_request_complete(sr->fid_ofile);
                  smb_ofile_release(sr->fid_ofile);
          }
  
          if (sr->tid_tree != NULL)
                  smb_tree_release(sr->tid_tree);
  
          if (sr->uid_user != NULL)
                  smb_user_release(sr->uid_user);
  
          smb_slist_remove(&sr->session->s_req_list, sr);
  
          sr->session = NULL;
  
          smb_srm_fini(sr);
--- 1514,1543 ----
  smb_request_free(smb_request_t *sr)
  {
          ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
          ASSERT(sr->session);
          ASSERT(sr->r_xa == NULL);
  
          if (sr->fid_ofile != NULL) {
                  smb_ofile_release(sr->fid_ofile);
          }
  
          if (sr->tid_tree != NULL)
                  smb_tree_release(sr->tid_tree);
  
          if (sr->uid_user != NULL)
                  smb_user_release(sr->uid_user);
  
+         if (sr->tform_ssn != NULL)
+                 smb_user_release(sr->tform_ssn);
+ 
+         /*
+          * The above may have left work on the delete queues
+          */
+         smb_llist_flush(&sr->session->s_tree_list);
+         smb_llist_flush(&sr->session->s_user_list);
+ 
          smb_slist_remove(&sr->session->s_req_list, sr);
  
          sr->session = NULL;
  
          smb_srm_fini(sr);
*** 1395,1405 ****
                  m_freem(sr->reply.chain);
          if (sr->raw_data.chain)
                  m_freem(sr->raw_data.chain);
  
          sr->sr_magic = 0;
-         cv_destroy(&sr->sr_ncr.nc_cv);
          mutex_destroy(&sr->sr_mutex);
          kmem_cache_free(smb_cache_request, sr);
  }
  
  boolean_t
--- 1550,1559 ----
*** 1415,1463 ****
  boolean_t
  smb_session_levelII_oplocks(smb_session_t *session)
  {
          SMB_SESSION_VALID(session);
  
-         /* Clients using SMB2 and later always know about oplocks. */
-         if (session->dialect > NT_LM_0_12)
-                 return (B_TRUE);
- 
          /* Older clients only do Level II oplocks if negotiated. */
          if ((session->capabilities & CAP_LEVEL_II_OPLOCKS) != 0)
                  return (B_TRUE);
  
          return (B_FALSE);
  }
  
- /*
-  * smb_session_oplock_break
-  *
-  * Send an oplock break request to the client,
-  * recalling some cache delegation.
-  */
- void
- smb_session_oplock_break(smb_request_t *sr, uint8_t brk)
- {
-         smb_session_t   *session = sr->session;
-         mbuf_chain_t    *mbc = &sr->reply;
- 
-         SMB_SESSION_VALID(session);
- 
-         /*
-          * Build the break message in sr->reply and then send it.
-          * The mbc is free'd later, in smb_request_free().
-          */
-         mbc->max_bytes = MLEN;
-         if (session->dialect <= NT_LM_0_12) {
-                 smb1_oplock_break_notification(sr, brk);
-         } else {
-                 smb2_oplock_break_notification(sr, brk);
-         }
- 
-         (void) smb_session_send(session, 0, mbc);
- }
- 
  static void
  smb_session_genkey(smb_session_t *session)
  {
          uint8_t         tmp_key[SMB_CHALLENGE_SZ];
  
--- 1569,1585 ----