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-1643 dtrace provider for smbsrv
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-5586 SMB2 ofiles need real Persistent IDs
NEX-5313 SMB2 oplock break notification should use TID=0
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5560 smb2 should use 64-bit server-global uids
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-1892 30 sec. delay responding to smb2_create
NEX-1734 SMB2 oplock break request missing a flag
SMB-122 smbd core dumps in smbd_dc_update / smb_log
SMB-117 Win7 fails to open security properties
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,152 ****
   * 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_OPLOCK_BREAK
   */
  
  #include <smbsrv/smb2_kproto.h>
  
  /*
   * SMB2 Oplock Break Acknowledgement
!  * [MS-SMB2 2.2.24]
   */
  smb_sdrc_t
  smb2_oplock_break_ack(smb_request_t *sr)
  {
!         smb_node_t *node;
          smb2fid_t smb2fid;
          uint32_t status;
!         uint16_t StructSize;
!         uint8_t OplockLevel;
!         uint8_t brk;
          int rc = 0;
  
          /*
!          * Decode the SMB2 Oplock Break Ack.
           */
          rc = smb_mbc_decodef(
!             &sr->smb_data, "wb5.qq",
!             &StructSize,                /* w */
!             &OplockLevel,               /* b */
              /* reserved                   5. */
              &smb2fid.persistent,        /* q */
              &smb2fid.temporal);         /* q */
!         if (rc || StructSize != 24)
                  return (SDRC_ERROR);
  
          status = smb2sr_lookup_fid(sr, &smb2fid);
!         if (status)
                  goto errout;
-         if ((node = sr->fid_ofile->f_node) == NULL) {
-                 /* Not a regular file */
-                 status = NT_STATUS_INVALID_PARAMETER;
-                 goto errout;
-         }
  
          /*
!          * Process the oplock break ack.  We only expect levels
!          * at or below the hightest break levels we send, which is
!          * currently SMB2_OPLOCK_LEVEL_II.
           */
!         switch (OplockLevel) {
          case SMB2_OPLOCK_LEVEL_NONE:    /* 0x00 */
!                 brk = SMB_OPLOCK_BREAK_TO_NONE;
                  break;
- 
          case SMB2_OPLOCK_LEVEL_II:      /* 0x01 */
!                 brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
                  break;
- 
-         /* We don't break to these levels (yet). */
          case SMB2_OPLOCK_LEVEL_EXCLUSIVE: /* 0x08 */
          case SMB2_OPLOCK_LEVEL_BATCH:   /* 0x09 */
          case SMB2_OPLOCK_LEVEL_LEASE:   /* 0xFF */
!         default: /* gcc -Wuninitialized */
!                 status = NT_STATUS_INVALID_PARAMETER;
                  goto errout;
          }
  
!         smb_oplock_ack(node, sr->fid_ofile, brk);
  
          /*
!          * Generate SMB2 Oplock Break response
!          * [MS-SMB2] 2.2.25
           */
-         StructSize = 24;
          (void) smb_mbc_encodef(
              &sr->reply, "wb5.qq",
!             StructSize,                 /* w */
!             OplockLevel,                /* b */
              /* reserved                   5. */
              smb2fid.persistent,         /* q */
              smb2fid.temporal);          /* q */
-         return (SDRC_SUCCESS);
  
- errout:
-         smb2sr_put_error(sr, status);
          return (SDRC_SUCCESS);
  }
  
  /*
   * Compose an SMB2 Oplock Break Notification packet, including
   * the SMB2 header and everything, in sr->reply.
   * The caller will send it and free the request.
   */
  void
! smb2_oplock_break_notification(smb_request_t *sr, uint8_t brk)
  {
          smb_ofile_t *ofile = sr->fid_ofile;
          smb2fid_t smb2fid;
          uint16_t StructSize;
          uint8_t OplockLevel;
  
!         switch (brk) {
          default:
                  ASSERT(0);
                  /* FALLTHROUGH */
!         case SMB_OPLOCK_BREAK_TO_NONE:
                  OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
                  break;
!         case SMB_OPLOCK_BREAK_TO_LEVEL_II:
                  OplockLevel = SMB2_OPLOCK_LEVEL_II;
                  break;
          }
  
          /*
           * SMB2 Header
           */
          sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
          sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
!         sr->smb_tid = ofile->f_tree->t_tid;
          sr->smb_pid = 0;
!         sr->smb_uid = 0;
          sr->smb2_messageid = UINT64_MAX;
          (void) smb2_encode_header(sr, B_FALSE);
  
          /*
           * SMB2 Oplock Break, variable part
           */
          StructSize = 24;
!         smb2fid.persistent = 0;
          smb2fid.temporal = ofile->f_fid;
          (void) smb_mbc_encodef(
              &sr->reply, "wb5.qq",
              StructSize,         /* w */
              OplockLevel,        /* b */
              /* reserved           5. */
              smb2fid.persistent, /* q */
              smb2fid.temporal);  /* q */
  }
--- 8,344 ----
   * source.  A copy of the CDDL is also available via the Internet at
   * http://www.illumos.org/license/CDDL.
   */
  
  /*
!  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
   * Dispatch function for SMB2_OPLOCK_BREAK
   */
  
  #include <smbsrv/smb2_kproto.h>
  
+ #define BATCH_OR_EXCL   (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
+ 
+ /* StructSize for the two "break" message formats. */
+ #define SSZ_OPLOCK      24
+ #define SSZ_LEASE       36
+ 
  /*
   * SMB2 Oplock Break Acknowledgement
!  * [MS-SMB2] 3.3.5.22.1 Processing an Oplock Acknowledgment
!  * Called via smb2_disp_table[]
   */
  smb_sdrc_t
  smb2_oplock_break_ack(smb_request_t *sr)
  {
!         smb_ofile_t *ofile;
          smb2fid_t smb2fid;
          uint32_t status;
!         uint32_t NewLevel;
!         uint8_t smbOplockLevel;
          int rc = 0;
+         uint16_t StructSize;
  
          /*
!          * Decode the SMB2 Oplock Break Ack (24 bytes) or
!          * Lease Break Ack (36 bytes), starting with just
!          * the StructSize, which tells us what this is.
           */
+         rc = smb_mbc_decodef(&sr->smb_data, "w", &StructSize);
+         if (rc != 0)
+                 return (SDRC_ERROR);
+ 
+         if (StructSize == SSZ_LEASE) {
+                 /* See smb2_lease.c */
+                 return (smb2_lease_break_ack(sr));
+         }
+         if (StructSize != SSZ_OPLOCK)
+                 return (SDRC_ERROR);
+ 
+         /*
+          * Decode an SMB2 Oplock Break Ack.
+          * [MS-SMB2] 2.2.24.1
+          * Note: Struct size decoded above.
+          */
          rc = smb_mbc_decodef(
!             &sr->smb_data, "b5.qq",
!             &smbOplockLevel,            /* b */
              /* reserved                   5. */
              &smb2fid.persistent,        /* q */
              &smb2fid.temporal);         /* q */
!         if (rc != 0)
                  return (SDRC_ERROR);
  
+         /* Find the ofile */
          status = smb2sr_lookup_fid(sr, &smb2fid);
!         /* Success or NT_STATUS_FILE_CLOSED */
! 
!         DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
!         if (status != 0)
                  goto errout;
  
          /*
!          * Process an (old-style) oplock break ack.
           */
!         switch (smbOplockLevel) {
          case SMB2_OPLOCK_LEVEL_NONE:    /* 0x00 */
!                 NewLevel = OPLOCK_LEVEL_NONE;
                  break;
          case SMB2_OPLOCK_LEVEL_II:      /* 0x01 */
!                 NewLevel = OPLOCK_LEVEL_TWO;
                  break;
          case SMB2_OPLOCK_LEVEL_EXCLUSIVE: /* 0x08 */
+                 NewLevel = OPLOCK_LEVEL_ONE;
+                 break;
          case SMB2_OPLOCK_LEVEL_BATCH:   /* 0x09 */
+                 NewLevel = OPLOCK_LEVEL_BATCH;
+                 break;
          case SMB2_OPLOCK_LEVEL_LEASE:   /* 0xFF */
!         default:
!                 NewLevel = OPLOCK_LEVEL_NONE;
!                 break;
!         }
! 
!         ofile = sr->fid_ofile;
!         ofile->f_oplock.og_breaking = 0;
!         status = smb_oplock_ack_break(sr, ofile, &NewLevel);
!         if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
!                 status = smb2sr_go_async(sr);
!                 if (status != 0)
                          goto errout;
+                 (void) smb_oplock_wait_break(ofile->f_node, 0);
+                 status = 0;
          }
+         if (status != 0) {
+                 NewLevel = OPLOCK_LEVEL_NONE;
+                 goto errout;
+         }
  
!         ofile->f_oplock.og_state = NewLevel;
!         switch (NewLevel & OPLOCK_LEVEL_TYPE_MASK) {
!         case OPLOCK_LEVEL_NONE:
!                 smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
!                 break;
!         case OPLOCK_LEVEL_TWO:
!                 smbOplockLevel = SMB2_OPLOCK_LEVEL_II;
!                 break;
!         case OPLOCK_LEVEL_ONE:
!                 smbOplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
!                 break;
!         case OPLOCK_LEVEL_BATCH:
!                 smbOplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
!                 break;
!         case OPLOCK_LEVEL_GRANULAR:
!         default:
!                 smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
!                 break;
!         }
  
+ errout:
+         sr->smb2_status = status;
+         DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
+         if (status) {
+                 smb2sr_put_error(sr, status);
+                 return (SDRC_SUCCESS);
+         }
+ 
          /*
!          * Encode an SMB2 Oplock Break Ack response
!          * [MS-SMB2] 2.2.25.1
           */
          (void) smb_mbc_encodef(
              &sr->reply, "wb5.qq",
!             SSZ_OPLOCK,                 /* w */
!             smbOplockLevel,             /* b */
              /* reserved                   5. */
              smb2fid.persistent,         /* q */
              smb2fid.temporal);          /* q */
  
          return (SDRC_SUCCESS);
  }
  
  /*
   * Compose an SMB2 Oplock Break Notification packet, including
   * the SMB2 header and everything, in sr->reply.
   * The caller will send it and free the request.
   */
  void
! smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
  {
          smb_ofile_t *ofile = sr->fid_ofile;
          smb2fid_t smb2fid;
          uint16_t StructSize;
          uint8_t OplockLevel;
  
!         /*
!          * Convert internal level to SMB2
!          */
!         switch (NewLevel) {
          default:
                  ASSERT(0);
                  /* FALLTHROUGH */
!         case OPLOCK_LEVEL_NONE:
                  OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
                  break;
!         case OPLOCK_LEVEL_TWO:
                  OplockLevel = SMB2_OPLOCK_LEVEL_II;
                  break;
          }
  
          /*
           * SMB2 Header
           */
          sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
          sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
!         sr->smb_tid = 0;
          sr->smb_pid = 0;
!         sr->smb2_ssnid = 0;
          sr->smb2_messageid = UINT64_MAX;
          (void) smb2_encode_header(sr, B_FALSE);
  
          /*
           * SMB2 Oplock Break, variable part
           */
          StructSize = 24;
!         smb2fid.persistent = ofile->f_persistid;
          smb2fid.temporal = ofile->f_fid;
          (void) smb_mbc_encodef(
              &sr->reply, "wb5.qq",
              StructSize,         /* w */
              OplockLevel,        /* b */
              /* reserved           5. */
              smb2fid.persistent, /* q */
              smb2fid.temporal);  /* q */
+ }
+ 
+ /*
+  * Client has an open handle and requests an oplock.
+  * Convert SMB2 oplock request info in to internal form,
+  * call common oplock code, convert result to SMB2.
+  *
+  * If necessary, "go async" here.
+  */
+ void
+ smb2_oplock_acquire(smb_request_t *sr)
+ {
+         smb_arg_open_t *op = &sr->arg.open;
+         smb_ofile_t *ofile = sr->fid_ofile;
+         uint32_t status;
+ 
+         /* Only disk trees get oplocks. */
+         ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
+ 
+         /* Only plain files... */
+         if (!smb_node_is_file(ofile->f_node)) {
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+                 return;
+         }
+ 
+         if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+                 return;
+         }
+ 
+         /*
+          * SMB2: Convert to internal form.
+          */
+         switch (op->op_oplock_level) {
+         case SMB2_OPLOCK_LEVEL_BATCH:
+                 op->op_oplock_state = OPLOCK_LEVEL_BATCH;
+                 break;
+         case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+                 op->op_oplock_state = OPLOCK_LEVEL_ONE;
+                 break;
+         case SMB2_OPLOCK_LEVEL_II:
+                 op->op_oplock_state = OPLOCK_LEVEL_TWO;
+                 break;
+         case SMB2_OPLOCK_LEVEL_LEASE:
+                 ASSERT(0); /* Handled elsewhere */
+                 /* FALLTHROUGH */
+         case SMB2_OPLOCK_LEVEL_NONE:
+         default:
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+                 return;
+         }
+ 
+         /*
+          * Tree options may force shared oplocks,
+          * in which case we reduce the request.
+          */
+         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 the tree forced shared oplocks)
+          * try for a shared oplock (Level II)
+          */
+         if (status == NT_STATUS_OPLOCK_NOT_GRANTED) {
+                 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) smb2sr_go_async(sr);
+                 (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, not the
+          * SMB dialect.  No lease here, so SMB 2.0.
+          */
+         ofile->f_oplock.og_dialect = SMB_VERS_2_002;
+         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 = SMB2_OPLOCK_LEVEL_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 = SMB2_OPLOCK_LEVEL_NONE;
+                 return;
+         }
+ 
+         /*
+          * Have STATUS_SUCCESS
+          * Convert internal oplock state to SMB2
+          */
+         if (op->op_oplock_state & OPLOCK_LEVEL_GRANULAR) {
+                 ASSERT(0);
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+         } else if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
+         } else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+         } else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
+         } else {
+                 op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+         }
  }