Print this page
NEX-13644 File access audit logging
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Reviewed by: Saso Kiselkov <saso.kiselkov@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-17779 Creating named streams on existing files is not quite right
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-2807 Restoring previous versions from snapshots doesn't work with nested folders.
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15931 Panic removing files in SMB3 CA share
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Include in backports of:
  NEX-9808 SMB3 persistent handles
NEX-15931 Panic removing files in SMB3 CA share
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Include in backports of:
  NEX-9808 SMB3 persistent handles
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-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-15069 smtorture smb2.create.blob is failed
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@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-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-15069 smtorture smb2.create.blob is failed
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-13653 Obsolete SMB server work-around for ZFS read-only
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-9604 SMB: smb2 does not delete a read-only file, where smb1 does
Reviewed by: Matt Barden <matt.barden@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-5312 delete_on_close should be acted on earlier
Reviewed by: Gordon Ross <gwr@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-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-3620 need upstream cleanups for smbsrv
Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com>
SMB-142 Deadlock in SMB2
SMB-131 Don't allow setting delete-on-close on non empty dirs
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
 Includes work by these authors:
 Thomas Keiser <thomas.keiser@nexenta.com>
 Albert Lee <trisk@nexenta.com>
SMB-65 SMB server in non-global zones (use zone_kcred())
SMB-65 SMB server in non-global zones (kmem_caches)
common kmem_cache instances across zones
separate GZ-only init from NGZ init
SUP-599 smb_oplock_acquire thread deadlock
re #7815 SMB server delivers old modification time... (fix allocsz)
re #13470 rb4432 Sync some SMB differences from illumos
re #7815 SMB server delivers old modification time...
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
        
*** 18,28 ****
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
   */
  /*
   * SMB Node State Machine
   * ----------------------
   *
--- 18,28 ----
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
   */
  /*
   * SMB Node State Machine
   * ----------------------
   *
*** 86,96 ****
   *    releasing the mutex. That way, even if smb_node_lookup() finds it, the
   *    state will indicate that the node should be treated as non existent (of
   *    course the state of the node should be tested/updated under the
   *    protection of the mutex).
   */
! #include <smbsrv/smb_kproto.h>
  #include <smbsrv/smb_fsops.h>
  #include <smbsrv/smb_kstat.h>
  #include <sys/ddi.h>
  #include <sys/extdirent.h>
  #include <sys/pathname.h>
--- 86,96 ----
   *    releasing the mutex. That way, even if smb_node_lookup() finds it, the
   *    state will indicate that the node should be treated as non existent (of
   *    course the state of the node should be tested/updated under the
   *    protection of the mutex).
   */
! #include <smbsrv/smb2_kproto.h>
  #include <smbsrv/smb_fsops.h>
  #include <smbsrv/smb_kstat.h>
  #include <sys/ddi.h>
  #include <sys/extdirent.h>
  #include <sys/pathname.h>
*** 97,107 ****
  #include <sys/sdt.h>
  #include <sys/nbmlock.h>
  #include <fs/fs_reparse.h>
  
  uint32_t smb_is_executable(char *);
- static void smb_node_delete_on_close(smb_node_t *);
  static void smb_node_create_audit_buf(smb_node_t *, int);
  static void smb_node_destroy_audit_buf(smb_node_t *);
  static void smb_node_audit(smb_node_t *);
  static smb_node_t *smb_node_alloc(char *, vnode_t *, smb_llist_t *, uint32_t);
  static void smb_node_free(smb_node_t *);
--- 97,106 ----
*** 188,197 ****
--- 187,197 ----
          if (smb_node_cache == NULL)
                  return;
  
  #ifdef DEBUG
          for (i = 0; i <= SMBND_HASH_MASK; i++) {
+                 smb_llist_t     *bucket;
                  smb_node_t      *node;
  
                  /*
                   * The following sequence is just intended for sanity check.
                   * This will have to be modified when the code goes into
*** 203,215 ****
                   * The reason why SMB nodes are still remaining in the hash
                   * table is problably due to a mismatch between calls to
                   * smb_node_lookup() and smb_node_release(). You must track that
                   * down.
                   */
!                 node = smb_llist_head(&smb_node_hash_table[i]);
!                 ASSERT(node == NULL);
          }
  #endif
  
          for (i = 0; i <= SMBND_HASH_MASK; i++) {
                  smb_llist_destructor(&smb_node_hash_table[i]);
          }
--- 203,220 ----
                   * The reason why SMB nodes are still remaining in the hash
                   * table is problably due to a mismatch between calls to
                   * smb_node_lookup() and smb_node_release(). You must track that
                   * down.
                   */
!                 bucket = &smb_node_hash_table[i];
!                 node = smb_llist_head(bucket);
!                 while (node != NULL) {
!                         cmn_err(CE_NOTE, "leaked node: 0x%p %s",
!                             (void *)node, node->od_name);
!                         node = smb_llist_next(bucket, node);
                  }
+         }
  #endif
  
          for (i = 0; i <= SMBND_HASH_MASK; i++) {
                  smb_llist_destructor(&smb_node_hash_table[i]);
          }
*** 480,490 ****
--- 485,497 ----
                          smb_llist_exit(node->n_hash_bucket);
  
                          /*
                           * Check if the file was deleted
                           */
+                         if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
                                  smb_node_delete_on_close(node);
+                         }
  
                          if (node->n_dnode) {
                                  ASSERT(node->n_dnode->n_magic ==
                                      SMB_NODE_MAGIC);
                                  smb_node_release(node->n_dnode);
*** 505,524 ****
          }
          smb_node_audit(node);
          mutex_exit(&node->n_mutex);
  }
  
! static void
  smb_node_delete_on_close(smb_node_t *node)
  {
          smb_node_t      *d_snode;
          int             rc = 0;
          uint32_t        flags = 0;
  
          d_snode = node->n_dnode;
!         if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
                  node->flags &= ~NODE_FLAGS_DELETE_ON_CLOSE;
                  flags = node->n_delete_on_close_flags;
                  ASSERT(node->od_name != NULL);
  
                  if (smb_node_is_dir(node))
                          rc = smb_fsop_rmdir(0, node->delete_on_close_cred,
--- 512,534 ----
          }
          smb_node_audit(node);
          mutex_exit(&node->n_mutex);
  }
  
! void
  smb_node_delete_on_close(smb_node_t *node)
  {
          smb_node_t      *d_snode;
          int             rc = 0;
          uint32_t        flags = 0;
  
          d_snode = node->n_dnode;
! 
!         ASSERT((node->flags & NODE_FLAGS_DELETE_ON_CLOSE) != 0);
! 
          node->flags &= ~NODE_FLAGS_DELETE_ON_CLOSE;
+         node->flags |= NODE_FLAGS_DELETE_COMMITTED;
          flags = node->n_delete_on_close_flags;
          ASSERT(node->od_name != NULL);
  
          if (smb_node_is_dir(node))
                  rc = smb_fsop_rmdir(0, node->delete_on_close_cred,
*** 525,535 ****
                              d_snode, node->od_name, flags);
                  else
                          rc = smb_fsop_remove(0, node->delete_on_close_cred,
                              d_snode, node->od_name, flags);
                  crfree(node->delete_on_close_cred);
!         }
          if (rc != 0)
                  cmn_err(CE_WARN, "File %s could not be removed, rc=%d\n",
                      node->od_name, rc);
          DTRACE_PROBE2(smb_node_delete_on_close, int, rc, smb_node_t *, node);
  }
--- 535,546 ----
                      d_snode, node->od_name, flags);
          else
                  rc = smb_fsop_remove(0, node->delete_on_close_cred,
                      d_snode, node->od_name, flags);
          crfree(node->delete_on_close_cred);
!         node->delete_on_close_cred = NULL;
! 
          if (rc != 0)
                  cmn_err(CE_WARN, "File %s could not be removed, rc=%d\n",
                      node->od_name, rc);
          DTRACE_PROBE2(smb_node_delete_on_close, int, rc, smb_node_t *, node);
  }
*** 586,641 ****
          /*
           * We're getting smb nodes below the zone root here,
           * so need to use kcred, not zone_kcred().
           */
          error = smb_pathname(NULL, zone->zone_rootpath, 0,
!             smb_root_node, smb_root_node, NULL, svrootp, kcred);
  
          return (error);
  }
  
  /*
   * Helper function for smb_node_set_delete_on_close(). Assumes node is a dir.
   * Return 0 if this is an empty dir. Otherwise return a NT_STATUS code.
!  * We distinguish between readdir failure and non-empty dir by returning
!  * different values.
   */
  static uint32_t
! smb_rmdir_possible(smb_node_t *n, uint32_t flags)
  {
          ASSERT(n->vp->v_type == VDIR);
!         char buf[512]; /* Only large enough to see if the dir is empty. */
!         int eof, bsize = sizeof (buf), reclen = 0;
!         char *name;
!         boolean_t edp = vfs_has_feature(n->vp->v_vfsp, VFSFT_DIRENTFLAGS);
  
!         union {
!                 char            *u_bufptr;
!                 struct edirent  *u_edp;
!                 struct dirent64 *u_dp;
!         } u;
! #define bufptr  u.u_bufptr
! #define extdp   u.u_edp
! #define dp      u.u_dp
  
!         if (smb_vop_readdir(n->vp, 0, buf, &bsize, &eof, flags, zone_kcred()))
!                 return (NT_STATUS_CANNOT_DELETE);
!         if (bsize == 0)
!                 return (NT_STATUS_CANNOT_DELETE);
          bufptr = buf;
!         while ((bufptr += reclen) < buf + bsize) {
!                 if (edp) {
!                         reclen = extdp->ed_reclen;
!                         name = extdp->ed_name;
!                 } else {
!                         reclen = dp->d_reclen;
!                         name = dp->d_name;
                  }
!                 if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
!                         return (NT_STATUS_DIRECTORY_NOT_EMPTY);
          }
!         return (0);
  }
  
  /*
   * When DeleteOnClose is set on an smb_node, the common open code will
   * reject subsequent open requests for the file. Observation of Windows
--- 597,659 ----
          /*
           * We're getting smb nodes below the zone root here,
           * so need to use kcred, not zone_kcred().
           */
          error = smb_pathname(NULL, zone->zone_rootpath, 0,
!             smb_root_node, smb_root_node, NULL, svrootp, kcred, NULL);
  
          return (error);
  }
  
  /*
   * Helper function for smb_node_set_delete_on_close(). Assumes node is a dir.
   * Return 0 if this is an empty dir. Otherwise return a NT_STATUS code.
!  * Unfortunately, to find out if a directory is empty, we have to read it
!  * and check for anything other than "." or ".." in the readdir buf.
   */
  static uint32_t
! smb_rmdir_possible(smb_node_t *n)
  {
          ASSERT(n->vp->v_type == VDIR);
!         char *buf;
!         char *bufptr;
!         struct dirent64 *dp;
!         uint32_t status = NT_STATUS_SUCCESS;
!         int bsize = SMB_ODIR_BUFSIZE;
!         int eof = 0;
  
!         buf = kmem_alloc(SMB_ODIR_BUFSIZE, KM_SLEEP);
  
!         /* Flags zero: no edirent, no ABE wanted here */
!         if (smb_vop_readdir(n->vp, 0, buf, &bsize, &eof, 0, zone_kcred())) {
!                 status = NT_STATUS_INTERNAL_ERROR;
!                 goto out;
!         }
! 
          bufptr = buf;
!         while (bsize > 0) {
!                 /* LINTED pointer alignment */
!                 dp = (struct dirent64 *)bufptr;
! 
!                 bufptr += dp->d_reclen;
!                 bsize  -= dp->d_reclen;
!                 if (bsize < 0) {
!                         /* partial record */
!                         status = NT_STATUS_DIRECTORY_NOT_EMPTY;
!                         break;
                  }
! 
!                 if (strcmp(dp->d_name, ".") != 0 &&
!                     strcmp(dp->d_name, "..") != 0) {
!                         status = NT_STATUS_DIRECTORY_NOT_EMPTY;
!                         break;
                  }
!         }
! 
! out:
!         kmem_free(buf, SMB_ODIR_BUFSIZE);
!         return (status);
  }
  
  /*
   * When DeleteOnClose is set on an smb_node, the common open code will
   * reject subsequent open requests for the file. Observation of Windows
*** 645,688 ****
   *
   * If there are multiple opens with delete-on-close create options,
   * whichever the first file handle is closed will trigger the node to be
   * marked as delete-on-close. The credentials of that ofile will be used
   * as the delete-on-close credentials of the node.
   */
  uint32_t
  smb_node_set_delete_on_close(smb_node_t *node, cred_t *cr, uint32_t flags)
  {
-         int rc = 0;
          uint32_t status;
-         smb_attr_t attr;
  
-         if (node->n_pending_dosattr & FILE_ATTRIBUTE_READONLY)
-                 return (NT_STATUS_CANNOT_DELETE);
- 
-         bzero(&attr, sizeof (smb_attr_t));
-         attr.sa_mask = SMB_AT_DOSATTR;
-         rc = smb_fsop_getattr(NULL, zone_kcred(), node, &attr);
-         if ((rc != 0) || (attr.sa_dosattr & FILE_ATTRIBUTE_READONLY)) {
-                 return (NT_STATUS_CANNOT_DELETE);
-         }
- 
          /*
           * If the directory is not empty we should fail setting del-on-close
           * with STATUS_DIRECTORY_NOT_EMPTY. see MS's
           * "File System Behavior Overview" doc section 4.3.2
           */
          if (smb_node_is_dir(node)) {
!                 status = smb_rmdir_possible(node, flags);
                  if (status != 0) {
                          return (status);
                  }
          }
  
          mutex_enter(&node->n_mutex);
          if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
                  mutex_exit(&node->n_mutex);
!                 return (NT_STATUS_CANNOT_DELETE);
          }
  
          crhold(cr);
          node->delete_on_close_cred = cr;
          node->n_delete_on_close_flags = flags;
--- 663,697 ----
   *
   * If there are multiple opens with delete-on-close create options,
   * whichever the first file handle is closed will trigger the node to be
   * marked as delete-on-close. The credentials of that ofile will be used
   * as the delete-on-close credentials of the node.
+  *
+  * Note that "read-only" tests have already happened before this call.
   */
  uint32_t
  smb_node_set_delete_on_close(smb_node_t *node, cred_t *cr, uint32_t flags)
  {
          uint32_t status;
  
          /*
           * If the directory is not empty we should fail setting del-on-close
           * with STATUS_DIRECTORY_NOT_EMPTY. see MS's
           * "File System Behavior Overview" doc section 4.3.2
           */
          if (smb_node_is_dir(node)) {
!                 status = smb_rmdir_possible(node);
                  if (status != 0) {
                          return (status);
                  }
          }
  
          mutex_enter(&node->n_mutex);
          if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
+                 /* It was already marked.  We're done. */
                  mutex_exit(&node->n_mutex);
!                 return (NT_STATUS_SUCCESS);
          }
  
          crhold(cr);
          node->delete_on_close_cred = cr;
          node->n_delete_on_close_flags = flags;
*** 692,702 ****
          /*
           * Tell any change notify calls to close their handles
           * and get out of the way.  FILE_ACTION_DELETE_PENDING
           * is a special, internal-only action for this purpose.
           */
!         smb_notify_event(node, FILE_ACTION_DELETE_PENDING, NULL);
  
          return (NT_STATUS_SUCCESS);
  }
  
  void
--- 701,711 ----
          /*
           * Tell any change notify calls to close their handles
           * and get out of the way.  FILE_ACTION_DELETE_PENDING
           * is a special, internal-only action for this purpose.
           */
!         smb_node_notify_change(node, FILE_ACTION_DELETE_PENDING, NULL);
  
          return (NT_STATUS_SUCCESS);
  }
  
  void
*** 740,749 ****
--- 749,762 ----
                  case NT_STATUS_SUCCESS:
                          of = smb_llist_next(&node->n_ofile_list, of);
                          break;
                  default:
                          ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+                         DTRACE_PROBE3(conflict3,
+                             smb_ofile_t, of,
+                             uint32_t, desired_access,
+                             uint32_t, share_access);
                          smb_llist_exit(&node->n_ofile_list);
                          return (status);
                  }
          }
  
*** 772,781 ****
--- 785,795 ----
                  case NT_STATUS_SUCCESS:
                          of = smb_llist_next(&node->n_ofile_list, of);
                          break;
                  default:
                          ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+                         DTRACE_PROBE1(conflict1, smb_ofile_t, of);
                          smb_llist_exit(&node->n_ofile_list);
                          return (status);
                  }
          }
          smb_llist_exit(&node->n_ofile_list);
*** 809,818 ****
--- 823,833 ----
                  case NT_STATUS_SUCCESS:
                          of = smb_llist_next(&node->n_ofile_list, of);
                          break;
                  default:
                          ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+                         DTRACE_PROBE1(conflict1, smb_ofile_t, of);
                          smb_llist_exit(&node->n_ofile_list);
                          return (status);
                  }
          }
          smb_llist_exit(&node->n_ofile_list);
*** 845,921 ****
  /*
   * SMB Change Notification
   */
  
  void
! smb_node_fcn_subscribe(smb_node_t *node, smb_request_t *sr)
  {
-         smb_node_fcn_t          *fcn = &node->n_fcn;
  
!         mutex_enter(&fcn->fcn_mutex);
!         if (fcn->fcn_count == 0)
                  (void) smb_fem_fcn_install(node);
!         fcn->fcn_count++;
!         list_insert_tail(&fcn->fcn_watchers, sr);
!         mutex_exit(&fcn->fcn_mutex);
  }
  
  void
! smb_node_fcn_unsubscribe(smb_node_t *node, smb_request_t *sr)
  {
-         smb_node_fcn_t          *fcn = &node->n_fcn;
  
!         mutex_enter(&fcn->fcn_mutex);
!         list_remove(&fcn->fcn_watchers, sr);
!         fcn->fcn_count--;
!         if (fcn->fcn_count == 0)
                  smb_fem_fcn_uninstall(node);
!         mutex_exit(&fcn->fcn_mutex);
  }
  
  void
  smb_node_notify_change(smb_node_t *node, uint_t action, const char *name)
  {
          SMB_NODE_VALID(node);
  
!         smb_notify_event(node, action, name);
! 
          /*
!          * These two events come as a pair:
!          *   FILE_ACTION_RENAMED_OLD_NAME
!          *   FILE_ACTION_RENAMED_NEW_NAME
!          * Only do the parent notify for "new".
           */
!         if (action == FILE_ACTION_RENAMED_OLD_NAME)
!                 return;
  
!         smb_node_notify_parents(node);
! }
! 
! /*
!  * smb_node_notify_parents
!  *
!  * Iterate up the directory tree notifying any parent
!  * directories that are being watched for changes in
!  * their sub directories.
!  * Stop at the root node, which has a NULL parent node.
   */
! void
! smb_node_notify_parents(smb_node_t *dnode)
! {
!         smb_node_t *pnode;      /* parent */
! 
!         SMB_NODE_VALID(dnode);
!         pnode = dnode->n_dnode;
! 
!         while (pnode != NULL) {
!                 SMB_NODE_VALID(pnode);
!                 smb_notify_event(pnode, FILE_ACTION_SUBDIR_CHANGED, NULL);
!                 /* cd .. */
!                 dnode = pnode;
!                 pnode = dnode->n_dnode;
          }
  }
  
  /*
   * smb_node_start_crit()
   *
--- 860,949 ----
  /*
   * SMB Change Notification
   */
  
  void
! smb_node_fcn_subscribe(smb_node_t *node)
  {
  
!         mutex_enter(&node->n_mutex);
!         if (node->n_fcn_count == 0)
                  (void) smb_fem_fcn_install(node);
!         node->n_fcn_count++;
!         mutex_exit(&node->n_mutex);
  }
  
  void
! smb_node_fcn_unsubscribe(smb_node_t *node)
  {
  
!         mutex_enter(&node->n_mutex);
!         node->n_fcn_count--;
!         if (node->n_fcn_count == 0)
                  smb_fem_fcn_uninstall(node);
!         mutex_exit(&node->n_mutex);
  }
  
  void
  smb_node_notify_change(smb_node_t *node, uint_t action, const char *name)
  {
+         smb_ofile_t     *of;
+ 
          SMB_NODE_VALID(node);
  
!         smb_llist_enter(&node->n_ofile_list, RW_READER);
!         of = smb_llist_head(&node->n_ofile_list);
!         while (of) {
                  /*
!                  * We'd rather deliver events only to ofiles that have
!                  * subscribed.  There's no explicit synchronization with
!                  * where this flag is set, but other actions cause this
!                  * value to reach visibility soon enough for events to
!                  * start arriving by the time we need them to start.
!                  * Once nc_subscribed is set, it stays set for the
!                  * life of the ofile.
                   */
!                 if (of->f_notify.nc_subscribed)
!                         smb_notify_ofile(of, action, name);
!                 of = smb_llist_next(&node->n_ofile_list, of);
!         }
!         smb_llist_exit(&node->n_ofile_list);
  
!         /*
!          * After changes that add or remove a name,
!          * we know the directory attributes changed,
!          * and we can tell the immediate parent.
           */
!         switch (action) {
!         case FILE_ACTION_ADDED:
!         case FILE_ACTION_REMOVED:
!         case FILE_ACTION_RENAMED_NEW_NAME:
!                 /*
!                  * Note: FILE_ACTION_RENAMED_OLD_NAME is intentionally
!                  * omitted, because it's always followed by another
!                  * event with FILE_ACTION_RENAMED_NEW_NAME posted to
!                  * the same directory, and we only need/want one.
!                  */
!                 if (node->n_dnode != NULL) {
!                         smb_node_notify_change(node->n_dnode,
!                             FILE_ACTION_MODIFIED, node->od_name);
                  }
+                 break;
+         }
+ 
+         /*
+          * If we wanted to support recursive notify events
+          * (where a notify call on some directory receives
+          * events from all objects below that directory),
+          * we might deliver _SUBDIR_CHANGED to all our
+          * parents, grandparents etc, here.  However, we
+          * don't currently subscribe to changes on all the
+          * child (and grandchild) objects that would be
+          * needed to make that work. It's prohibitively
+          * expensive to do that, and support for recursive
+          * notify is optional anyway, so don't bother.
+          */
  }
  
  /*
   * smb_node_start_crit()
   *
*** 1131,1140 ****
--- 1159,1303 ----
  
          return (rc);
  }
  
  /*
+  * smb_node_getpath_nofail
+  *
+  * Same as smb_node_getpath, but try to reconstruct on failure,
+  * and truncate from the beginning if we can't.
+  */
+ void
+ smb_node_getpath_nofail(smb_node_t *node, vnode_t *rootvp, char *buf,
+     uint32_t buflen)
+ {
+         int rc, len, addlen;
+         vnode_t *vp;
+         smb_node_t *unode, *dnode;
+         cred_t *kcr = zone_kcred();
+         boolean_t is_dir, is_stream;
+ 
+         is_stream = (SMB_IS_STREAM(node) != NULL);
+         unode = (is_stream) ? node->n_unode : node;
+         is_dir = smb_node_is_dir(unode);
+         dnode = (is_dir) ? unode : unode->n_dnode;
+ 
+         /* find path to directory node */
+         vp = dnode->vp;
+         VN_HOLD(vp);
+         if (rootvp) {
+                 VN_HOLD(rootvp);
+                 rc = vnodetopath(rootvp, vp, buf, buflen, kcr);
+                 VN_RELE(rootvp);
+         } else {
+                 rc = vnodetopath(NULL, vp, buf, buflen, kcr);
+         }
+         VN_RELE(vp);
+ 
+         /* On failure, reconstruct the path from the node_t's */
+         if (rc != 0) {
+                 smb_node_t *nodep = unode;
+                 char *p = buf + buflen;
+ 
+                 /* append named stream name if necessary */
+                 if (is_stream) {
+                         len = strlen(node->od_name) + 1;
+                         ASSERT3U(buflen, >=, len);
+                         p -= len;
+                         (void) strcpy(p, node->od_name);
+                 }
+ 
+                 len = strlen(nodep->od_name) + 1;
+                 p -= len;
+                 while (nodep->n_dnode != NULL && nodep->vp != rootvp &&
+                     p >= buf) {
+                         (void) strcpy(p, nodep->od_name);
+                         p[len - 1] = '/';
+                         nodep = nodep->n_dnode;
+                         len = strlen(nodep->od_name) + 1;
+                         p -= len;
+                 }
+                 if (nodep->n_dnode != NULL && nodep->vp != rootvp) {
+                         /* something went horribly wrong... */
+ #ifdef DEBUG
+                         cmn_err(CE_WARN,
+                             "smb_node_getpath_nofail: buffer too small: "
+                             "size %d", buflen);
+ #else
+                         cmn_err(CE_WARN,
+                             "smb_node_getpath_nofail: couldn't get full path");
+ #endif
+                         p = buf;
+                         *p = '*';
+                 } else {
+                         p += len - 1;
+                         if (p >= buf)
+                                 *p = '/';
+                 }
+ 
+                 buf[buflen - 1] = '\0';
+                 (void) memmove(buf, p, strlen(p) + 1);
+                 cmn_err(CE_NOTE,
+                     "smb_node_getpath_nofail: vnodetopath failed, rc=%d", rc);
+                 return;
+         }
+ 
+         len = strlen(buf) + 1;
+ 
+         /* append filename if necessary */
+         if (!is_dir) {
+                 if (buf[len - 2] != '/' && strlcat(buf, "/", buflen) >= buflen)
+                         goto trunc;
+                 if (strlcat(buf, unode->od_name, buflen) >= buflen)
+                         goto trunc;
+         }
+ 
+         /* append named stream name if necessary */
+         if (!is_stream || strlcat(buf, node->od_name, buflen) < buflen)
+                 return;
+ 
+ trunc:
+         buf[len - 1] = '\0';
+         addlen = 0;
+         /* append filename if necessary */
+         if (!is_dir) {
+                 if (buf[len - 2] != '/')
+                         addlen++;
+                 addlen += strlen(unode->od_name);
+         }
+ 
+         /* append named stream name if necessary */
+         if (is_stream)
+                 addlen += strlen(node->od_name);
+ 
+         if ((buflen - len) < addlen) {
+ #ifdef DEBUG
+                 cmn_err(CE_WARN,
+                     "smb_node_getpath_nofail: vnodetopath succeeded, "
+                     "but buffer too small for filename");
+ #else
+                 cmn_err(CE_WARN,
+                     "smb_node_getpath_nofail: couldn't get full path");
+ #endif
+                 addlen = addlen - (buflen - len);
+                 (void) memmove(buf, buf + addlen, len - addlen);
+                 buf[0] = '*';
+         }
+ 
+         /* append filename if necessary */
+         if (!is_dir) {
+                 if (buf[len - 2] != '/')
+                         (void) strlcat(buf, "/", buflen);
+                 (void) strlcat(buf, unode->od_name, buflen);
+         }
+ 
+         /* append named stream name if necessary */
+         if (is_stream)
+                 (void) strlcat(buf, node->od_name, buflen);
+ }
+ 
+ /*
   * smb_node_alloc
   */
  static smb_node_t *
  smb_node_alloc(
      char        *od_name,
*** 1154,1174 ****
          VN_HOLD(vp);
          node->vp = vp;
          node->n_refcnt = 1;
          node->n_hash_bucket = bucket;
          node->n_hashkey = hashkey;
-         node->n_pending_dosattr = 0;
          node->n_open_count = 0;
          node->n_allocsz = 0;
          node->n_dnode = NULL;
          node->n_unode = NULL;
          node->delete_on_close_cred = NULL;
          node->n_delete_on_close_flags = 0;
          node->n_oplock.ol_fem = B_FALSE;
-         node->n_oplock.ol_xthread = NULL;
-         node->n_oplock.ol_count = 0;
-         node->n_oplock.ol_break = SMB_OPLOCK_NO_BREAK;
  
          (void) strlcpy(node->od_name, od_name, sizeof (node->od_name));
          if (strcmp(od_name, XATTR_DIR) == 0)
                  node->flags |= NODE_XATTR_DIR;
  
--- 1317,1333 ----
*** 1193,1205 ****
          SMB_NODE_VALID(node);
  
          node->n_magic = 0;
          VERIFY(!list_link_active(&node->n_lnd));
          VERIFY(node->n_lock_list.ll_count == 0);
          VERIFY(node->n_ofile_list.ll_count == 0);
-         VERIFY(node->n_oplock.ol_count == 0);
-         VERIFY(node->n_oplock.ol_xthread == NULL);
          VERIFY(node->n_oplock.ol_fem == B_FALSE);
          VERIFY(MUTEX_NOT_HELD(&node->n_mutex));
          VERIFY(!RW_LOCK_HELD(&node->n_lock));
          VN_RELE(node->vp);
          kmem_cache_free(smb_node_cache, node);
--- 1352,1363 ----
          SMB_NODE_VALID(node);
  
          node->n_magic = 0;
          VERIFY(!list_link_active(&node->n_lnd));
          VERIFY(node->n_lock_list.ll_count == 0);
+         VERIFY(node->n_wlock_list.ll_count == 0);
          VERIFY(node->n_ofile_list.ll_count == 0);
          VERIFY(node->n_oplock.ol_fem == B_FALSE);
          VERIFY(MUTEX_NOT_HELD(&node->n_mutex));
          VERIFY(!RW_LOCK_HELD(&node->n_lock));
          VN_RELE(node->vp);
          kmem_cache_free(smb_node_cache, node);
*** 1216,1235 ****
          smb_node_t      *node = (smb_node_t *)buf;
  
          bzero(node, sizeof (smb_node_t));
  
          smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t),
!             offsetof(smb_ofile_t, f_nnd));
          smb_llist_constructor(&node->n_lock_list, sizeof (smb_lock_t),
              offsetof(smb_lock_t, l_lnd));
!         mutex_init(&node->n_fcn.fcn_mutex, NULL, MUTEX_DEFAULT, NULL);
!         list_create(&node->n_fcn.fcn_watchers, sizeof (smb_request_t),
!             offsetof(smb_request_t, sr_ncr.nc_lnd));
!         cv_init(&node->n_oplock.ol_cv, NULL, CV_DEFAULT, NULL);
          mutex_init(&node->n_oplock.ol_mutex, NULL, MUTEX_DEFAULT, NULL);
!         list_create(&node->n_oplock.ol_grants, sizeof (smb_oplock_grant_t),
!             offsetof(smb_oplock_grant_t, og_lnd));
          rw_init(&node->n_lock, NULL, RW_DEFAULT, NULL);
          mutex_init(&node->n_mutex, NULL, MUTEX_DEFAULT, NULL);
          smb_node_create_audit_buf(node, kmflags);
          return (0);
  }
--- 1374,1390 ----
          smb_node_t      *node = (smb_node_t *)buf;
  
          bzero(node, sizeof (smb_node_t));
  
          smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t),
!             offsetof(smb_ofile_t, f_node_lnd));
          smb_llist_constructor(&node->n_lock_list, sizeof (smb_lock_t),
              offsetof(smb_lock_t, l_lnd));
!         smb_llist_constructor(&node->n_wlock_list, sizeof (smb_lock_t),
!             offsetof(smb_lock_t, l_lnd));
          mutex_init(&node->n_oplock.ol_mutex, NULL, MUTEX_DEFAULT, NULL);
!         cv_init(&node->n_oplock.WaitingOpenCV, NULL, CV_DEFAULT, NULL);
          rw_init(&node->n_lock, NULL, RW_DEFAULT, NULL);
          mutex_init(&node->n_mutex, NULL, MUTEX_DEFAULT, NULL);
          smb_node_create_audit_buf(node, kmflags);
          return (0);
  }
*** 1245,1261 ****
          smb_node_t      *node = (smb_node_t *)buf;
  
          smb_node_destroy_audit_buf(node);
          mutex_destroy(&node->n_mutex);
          rw_destroy(&node->n_lock);
!         cv_destroy(&node->n_oplock.ol_cv);
          mutex_destroy(&node->n_oplock.ol_mutex);
-         list_destroy(&node->n_fcn.fcn_watchers);
-         mutex_destroy(&node->n_fcn.fcn_mutex);
          smb_llist_destructor(&node->n_lock_list);
          smb_llist_destructor(&node->n_ofile_list);
-         list_destroy(&node->n_oplock.ol_grants);
  }
  
  /*
   * smb_node_create_audit_buf
   */
--- 1400,1414 ----
          smb_node_t      *node = (smb_node_t *)buf;
  
          smb_node_destroy_audit_buf(node);
          mutex_destroy(&node->n_mutex);
          rw_destroy(&node->n_lock);
!         cv_destroy(&node->n_oplock.WaitingOpenCV);
          mutex_destroy(&node->n_oplock.ol_mutex);
          smb_llist_destructor(&node->n_lock_list);
+         smb_llist_destructor(&node->n_wlock_list);
          smb_llist_destructor(&node->n_ofile_list);
  }
  
  /*
   * smb_node_create_audit_buf
   */
*** 1377,1401 ****
  
  /*
   * smb_node_file_is_readonly
   *
   * Checks if the file (which node represents) is marked readonly
!  * in the filesystem. No account is taken of any pending readonly
!  * in the node, which must be handled by the callers.
!  * (See SMB_OFILE_IS_READONLY and SMB_PATHFILE_IS_READONLY)
   */
  boolean_t
  smb_node_file_is_readonly(smb_node_t *node)
  {
          smb_attr_t attr;
  
          if (node == NULL)
                  return (B_FALSE);       /* pipes */
  
-         if (node->n_pending_dosattr & FILE_ATTRIBUTE_READONLY)
-                 return (B_TRUE);
- 
          bzero(&attr, sizeof (smb_attr_t));
          attr.sa_mask = SMB_AT_DOSATTR;
          (void) smb_fsop_getattr(NULL, zone_kcred(), node, &attr);
          return ((attr.sa_dosattr & FILE_ATTRIBUTE_READONLY) != 0);
  }
--- 1530,1551 ----
  
  /*
   * smb_node_file_is_readonly
   *
   * Checks if the file (which node represents) is marked readonly
!  * in the filesystem.  Note that there may be handles open with
!  * modify rights, and those continue to allow access even after
!  * the DOS read-only flag has been set in the file system.
   */
  boolean_t
  smb_node_file_is_readonly(smb_node_t *node)
  {
          smb_attr_t attr;
  
          if (node == NULL)
                  return (B_FALSE);       /* pipes */
  
          bzero(&attr, sizeof (smb_attr_t));
          attr.sa_mask = SMB_AT_DOSATTR;
          (void) smb_fsop_getattr(NULL, zone_kcred(), node, &attr);
          return ((attr.sa_dosattr & FILE_ATTRIBUTE_READONLY) != 0);
  }
*** 1457,1466 ****
--- 1607,1617 ----
      cred_t *cr, smb_ofile_t *of, smb_attr_t *attr)
  {
          int rc;
          uint_t times_mask;
          smb_attr_t tmp_attr;
+         smb_node_t *unnamed_node;
  
          SMB_NODE_VALID(node);
  
          /* set attributes specified in attr */
          if (attr->sa_mask == 0)
*** 1557,1614 ****
                  if (times_mask & SMB_AT_CRTIME)
                          pa->sa_crtime =
                              attr->sa_crtime;
  
                  mutex_exit(&of->f_mutex);
                  /*
                   * The f_pending_attr times are reapplied in
                   * smb_ofile_close().
                   */
-         }
  
          /*
!          * After this point, tmp_attr is what we will actually
!          * store in the file system _now_, which may differ
!          * from the callers attr and f_pending_attr w.r.t.
!          * the DOS readonly flag etc.
           */
!         bcopy(attr, &tmp_attr, sizeof (tmp_attr));
!         if (attr->sa_mask & (SMB_AT_DOSATTR | SMB_AT_ALLOCSZ)) {
!                 mutex_enter(&node->n_mutex);
!                 if ((attr->sa_mask & SMB_AT_DOSATTR) != 0) {
!                         tmp_attr.sa_dosattr &= smb_vop_dosattr_settable;
!                         if (((tmp_attr.sa_dosattr &
!                             FILE_ATTRIBUTE_READONLY) != 0) &&
!                             (node->n_open_count != 0)) {
!                                 /* Delay setting readonly */
!                                 node->n_pending_dosattr =
!                                     tmp_attr.sa_dosattr;
!                                 tmp_attr.sa_dosattr &=
!                                     ~FILE_ATTRIBUTE_READONLY;
!                         } else {
!                                 node->n_pending_dosattr = 0;
                          }
                  }
                  /*
                   * Simulate n_allocsz persistence only while
                   * there are opens.  See smb_node_getattr
                   */
!                 if ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0 &&
!                     node->n_open_count != 0)
                          node->n_allocsz = attr->sa_allocsz;
                  mutex_exit(&node->n_mutex);
          }
  
!         rc = smb_fsop_setattr(sr, cr, node, &tmp_attr);
          if (rc != 0)
                  return (rc);
  
          if (node->n_dnode != NULL) {
                  smb_node_notify_change(node->n_dnode,
                      FILE_ACTION_MODIFIED, node->od_name);
          }
  
          return (0);
  }
  
  /*
   * smb_node_getattr
--- 1708,1760 ----
                  if (times_mask & SMB_AT_CRTIME)
                          pa->sa_crtime =
                              attr->sa_crtime;
  
                  mutex_exit(&of->f_mutex);
+ 
                  /*
                   * The f_pending_attr times are reapplied in
                   * smb_ofile_close().
                   */
  
                  /*
!                  * If this change is comming directly from a client
!                  * (sr != NULL) and it's a persistent handle, save
!                  * the "sticky times" in the handle.
                   */
!                 if (sr != NULL && of->dh_persist) {
!                         smb2_dh_update_times(sr, of, attr);
                  }
          }
+ 
+         if ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0) {
+                 mutex_enter(&node->n_mutex);
                  /*
                   * Simulate n_allocsz persistence only while
                   * there are opens.  See smb_node_getattr
                   */
!                 if (node->n_open_count != 0)
                          node->n_allocsz = attr->sa_allocsz;
                  mutex_exit(&node->n_mutex);
          }
  
!         rc = smb_fsop_setattr(sr, cr, node, attr);
          if (rc != 0)
                  return (rc);
  
          if (node->n_dnode != NULL) {
                  smb_node_notify_change(node->n_dnode,
                      FILE_ACTION_MODIFIED, node->od_name);
          }
  
+         if ((unnamed_node = SMB_IS_STREAM(node)) != NULL) {
+                 ASSERT(unnamed_node->n_magic == SMB_NODE_MAGIC);
+                 ASSERT(unnamed_node->n_state != SMB_NODE_STATE_DESTROYING);
+                 smb_node_notify_change(node->n_dnode,
+                     FILE_ACTION_MODIFIED_STREAM, node->od_name);
+         }
+ 
          return (0);
  }
  
  /*
   * smb_node_getattr
*** 1645,1669 ****
  
          isdir = smb_node_is_dir(node);
  
          mutex_enter(&node->n_mutex);
  
-         /*
-          * When there are open handles, and one of them has
-          * set the DOS readonly flag (in n_pending_dosattr),
-          * it will not have been stored in the file system.
-          * In this case use n_pending_dosattr. Note that
-          * n_pending_dosattr has only the settable bits,
-          * (setattr masks it with smb_vop_dosattr_settable)
-          * so we need to keep any non-settable bits we got
-          * from the file-system above.
-          */
          if (attr->sa_mask & SMB_AT_DOSATTR) {
-                 if (node->n_pending_dosattr) {
-                         attr->sa_dosattr &= ~smb_vop_dosattr_settable;
-                         attr->sa_dosattr |= node->n_pending_dosattr;
-                 }
                  if (attr->sa_dosattr == 0) {
                          attr->sa_dosattr = (isdir) ?
                              FILE_ATTRIBUTE_DIRECTORY:
                              FILE_ATTRIBUTE_NORMAL;
                  }
--- 1791,1801 ----