Print this page
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-16943 network outages with thread stuck in smb2_scoreboard_cancel
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@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-5845 rework SMB immediate cancel
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-2975 SMB2 Cancel may fail (nits)
NEX-2975 SMB2 Cancel may fail
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Tony Nguyen <tony.nguyen@nexenta.com>
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)
        
*** 8,81 ****
   * source.  A copy of the CDDL is also available via the Internet at
   * http://www.illumos.org/license/CDDL.
   */
  
  /*
!  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
   * Dispatch function for SMB2_CANCEL
   */
  
  #include <smbsrv/smb2_kproto.h>
  
! static void smb2sr_cancel_async(smb_request_t *);
! static void smb2sr_cancel_sync(smb_request_t *);
  
  /*
   * This handles an SMB2_CANCEL request when seen in the reader.
   * (See smb2sr_newrq)  Handle this immediately, rather than
   * going through the normal taskq dispatch mechanism.
   * Note that Cancel does NOT get a response.
   */
  int
! smb2sr_newrq_cancel(smb_request_t *sr)
  {
-         int rc;
  
          /*
!          * Decode the header
           */
!         if ((rc = smb2_decode_header(sr)) != 0)
!                 return (rc);
  
          if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND)
!                 smb2sr_cancel_async(sr);
          else
!                 smb2sr_cancel_sync(sr);
  
          return (0);
  }
  
  static void
! smb2sr_cancel_sync(smb_request_t *sr)
  {
          struct smb_request *req;
          struct smb_session *session = sr->session;
          int cnt = 0;
  
          smb_slist_enter(&session->s_req_list);
!         req = smb_slist_head(&session->s_req_list);
!         while (req) {
!                 ASSERT(req->sr_magic == SMB_REQ_MAGIC);
!                 if ((req != sr) &&
!                     (req->smb2_messageid == sr->smb2_messageid)) {
                          smb_request_cancel(req);
                          cnt++;
                  }
-                 req = smb_slist_next(&session->s_req_list, req);
          }
          if (cnt != 1) {
                  DTRACE_PROBE2(smb2__cancel__error,
                      uint64_t, sr->smb2_messageid, int, cnt);
          }
-         smb_slist_exit(&session->s_req_list);
  }
  
  static void
! smb2sr_cancel_async(smb_request_t *sr)
  {
          struct smb_request *req;
          struct smb_session *session = sr->session;
          int cnt = 0;
  
--- 8,150 ----
   * source.  A copy of the CDDL is also available via the Internet at
   * http://www.illumos.org/license/CDDL.
   */
  
  /*
!  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
   * Dispatch function for SMB2_CANCEL
   */
  
  #include <smbsrv/smb2_kproto.h>
  
! static void smb2_cancel_async(smb_request_t *);
! static void smb2_cancel_sync(smb_request_t *);
  
  /*
   * This handles an SMB2_CANCEL request when seen in the reader.
   * (See smb2sr_newrq)  Handle this immediately, rather than
   * going through the normal taskq dispatch mechanism.
   * Note that Cancel does NOT get a response.
+  *
+  * Any non-zero return causes disconnect.
+  * SMB2 header is already decoded.
   */
  int
! smb2_newrq_cancel(smb_request_t *sr)
  {
  
          /*
!          * If we get SMB2 cancel as part of a compound,
!          * that's a protocol violation.  Drop 'em!
           */
!         if (sr->smb2_next_command != 0)
!                 return (EINVAL);
  
+         DTRACE_SMB2_START(op__Cancel, smb_request_t *, sr);
+ 
          if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND)
!                 smb2_cancel_async(sr);
          else
!                 smb2_cancel_sync(sr);
  
+         DTRACE_SMB2_DONE(op__Cancel, smb_request_t *, sr);
+ 
          return (0);
  }
  
+ /*
+  * Dispatch handler for SMB2_CANCEL.
+  * Note that Cancel does NOT get a response.
+  */
+ smb_sdrc_t
+ smb2_cancel(smb_request_t *sr)
+ {
+ 
+         /*
+          * If we get SMB2 cancel as part of a compound,
+          * that's a protocol violation.  Drop 'em!
+          */
+         if (sr->smb2_cmd_hdr != 0 || sr->smb2_next_command != 0)
+                 return (SDRC_DROP_VC);
+ 
+         DTRACE_SMB2_START(op__Cancel, smb_request_t *, sr);
+ 
+         if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) {
+                 smb2_cancel_async(sr);
+         } else {
+                 smb2_cancel_sync(sr);
+         }
+ 
+         DTRACE_SMB2_DONE(op__Cancel, smb_request_t *, sr);
+ 
+         return (SDRC_NO_REPLY);
+ }
+ 
+ /*
+  * SMB2 Cancel (sync) has an inherent race with the request being
+  * cancelled.  The request may have been received but not yet
+  * executed by a worker thread, in which case we'll mark the
+  * request state as cancelled, and when a worker thread starts
+  * on this request we'll cancel everything in the compound.
+  */
  static void
! smb2_cancel_sync(smb_request_t *sr)
  {
          struct smb_request *req;
          struct smb_session *session = sr->session;
          int cnt = 0;
  
+         if (sr->smb2_messageid == 0)
+                 goto failure;
+ 
          smb_slist_enter(&session->s_req_list);
!         for (req = smb_slist_head(&session->s_req_list); req != NULL;
!             req = smb_slist_next(&session->s_req_list, req)) {
! 
!                 /* never cancel self */
!                 if (req == sr)
!                         continue;
! 
!                 if (sr->smb2_messageid >= req->smb2_first_msgid &&
!                     sr->smb2_messageid < (req->smb2_first_msgid +
!                     req->smb2_total_credits)) {
                          smb_request_cancel(req);
                          cnt++;
                  }
          }
+         smb_slist_exit(&session->s_req_list);
+ 
          if (cnt != 1) {
+         failure:
                  DTRACE_PROBE2(smb2__cancel__error,
                      uint64_t, sr->smb2_messageid, int, cnt);
+ #ifdef  DEBUG
+                 /*
+                  * It's somewhat common that we may see a cancel for a
+                  * request that has already completed, so report that
+                  * only in debug builds.
+                  */
+                 cmn_err(CE_WARN, "SMB2 cancel failed, "
+                     "client=%s, MID=0x%llx",
+                     sr->session->ip_addr_str,
+                     (u_longlong_t)sr->smb2_messageid);
+ #endif
          }
  }
  
+ /*
+  * Note that cancelling an async request doesn't have a race
+  * because the client doesn't learn about the async ID until we
+  * send it to them in an interim reply, and by that point the
+  * request has progressed to the point where smb_cancel can find
+  * the request and cancel it.
+  */
  static void
! smb2_cancel_async(smb_request_t *sr)
  {
          struct smb_request *req;
          struct smb_session *session = sr->session;
          int cnt = 0;
  
*** 91,98 ****
--- 160,172 ----
                  req = smb_slist_next(&session->s_req_list, req);
          }
          if (cnt != 1) {
                  DTRACE_PROBE2(smb2__cancel__error,
                      uint64_t, sr->smb2_async_id, int, cnt);
+                 /*
+                  * Not logging here, as this is normal, i.e.
+                  * when both a cancel and a handle close
+                  * terminates an SMB2_notify request.
+                  */
          }
          smb_slist_exit(&session->s_req_list);
  }