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,11 +18,11 @@
  *
  * CDDL HEADER END
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 /*
  * SMB Node State Machine
  * ----------------------
  *

@@ -86,11 +86,11 @@
  *    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/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,11 +97,10 @@
 #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 *);

@@ -188,10 +187,11 @@
         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,13 +203,18 @@
                  * 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);
+                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,11 +485,13 @@
                         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,20 +512,23 @@
         }
         smb_node_audit(node);
         mutex_exit(&node->n_mutex);
 }
 
-static void
+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) {
+
+        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,11 +535,12 @@
                             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,56 +597,63 @@
         /*
          * 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);
+            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.
- * We distinguish between readdir failure and non-empty dir by returning
- * different values.
+ * 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, uint32_t flags)
+smb_rmdir_possible(smb_node_t *n)
 {
         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);
+        char *buf;
+        char *bufptr;
+        struct dirent64 *dp;
+        uint32_t status = NT_STATUS_SUCCESS;
+        int bsize = SMB_ODIR_BUFSIZE;
+        int eof = 0;
 
-        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
+        buf = kmem_alloc(SMB_ODIR_BUFSIZE, KM_SLEEP);
 
-        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);
+        /* 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 ((bufptr += reclen) < buf + bsize) {
-                if (edp) {
-                        reclen = extdp->ed_reclen;
-                        name = extdp->ed_name;
-                } else {
-                        reclen = dp->d_reclen;
-                        name = dp->d_name;
+        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(name, ".") != 0 && strcmp(name, "..") != 0)
-                        return (NT_STATUS_DIRECTORY_NOT_EMPTY);
+
+                if (strcmp(dp->d_name, ".") != 0 &&
+                    strcmp(dp->d_name, "..") != 0) {
+                        status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+                        break;
         }
-        return (0);
+        }
+
+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,44 +663,35 @@
  *
  * 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)
 {
-        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);
+                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_CANNOT_DELETE);
+                return (NT_STATUS_SUCCESS);
         }
 
         crhold(cr);
         node->delete_on_close_cred = cr;
         node->n_delete_on_close_flags = flags;

@@ -692,11 +701,11 @@
         /*
          * 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);
+        smb_node_notify_change(node, FILE_ACTION_DELETE_PENDING, NULL);
 
         return (NT_STATUS_SUCCESS);
 }
 
 void

@@ -740,10 +749,14 @@
                 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,10 +785,11 @@
                 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,10 +823,11 @@
                 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,77 +860,90 @@
 /*
  * SMB Change Notification
  */
 
 void
-smb_node_fcn_subscribe(smb_node_t *node, smb_request_t *sr)
+smb_node_fcn_subscribe(smb_node_t *node)
 {
-        smb_node_fcn_t          *fcn = &node->n_fcn;
 
-        mutex_enter(&fcn->fcn_mutex);
-        if (fcn->fcn_count == 0)
+        mutex_enter(&node->n_mutex);
+        if (node->n_fcn_count == 0)
                 (void) smb_fem_fcn_install(node);
-        fcn->fcn_count++;
-        list_insert_tail(&fcn->fcn_watchers, sr);
-        mutex_exit(&fcn->fcn_mutex);
+        node->n_fcn_count++;
+        mutex_exit(&node->n_mutex);
 }
 
 void
-smb_node_fcn_unsubscribe(smb_node_t *node, smb_request_t *sr)
+smb_node_fcn_unsubscribe(smb_node_t *node)
 {
-        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)
+        mutex_enter(&node->n_mutex);
+        node->n_fcn_count--;
+        if (node->n_fcn_count == 0)
                 smb_fem_fcn_uninstall(node);
-        mutex_exit(&fcn->fcn_mutex);
+        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_notify_event(node, action, name);
-
+        smb_llist_enter(&node->n_ofile_list, RW_READER);
+        of = smb_llist_head(&node->n_ofile_list);
+        while (of) {
         /*
-         * These two events come as a pair:
-         *   FILE_ACTION_RENAMED_OLD_NAME
-         *   FILE_ACTION_RENAMED_NEW_NAME
-         * Only do the parent notify for "new".
+                 * 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 (action == FILE_ACTION_RENAMED_OLD_NAME)
-                return;
+                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);
 
-        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.
+        /*
+         * After changes that add or remove a name,
+         * we know the directory attributes changed,
+         * and we can tell the immediate parent.
  */
-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;
+        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,10 +1159,145 @@
 
         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,21 +1317,17 @@
         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;
 

@@ -1193,13 +1352,12 @@
         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_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);

@@ -1216,20 +1374,17 @@
         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));
+            offsetof(smb_ofile_t, f_node_lnd));
         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);
+        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);
-        list_create(&node->n_oplock.ol_grants, sizeof (smb_oplock_grant_t),
-            offsetof(smb_oplock_grant_t, og_lnd));
+        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,17 +1400,15 @@
         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);
+        cv_destroy(&node->n_oplock.WaitingOpenCV);
         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_wlock_list);
         smb_llist_destructor(&node->n_ofile_list);
-        list_destroy(&node->n_oplock.ol_grants);
 }
 
 /*
  * smb_node_create_audit_buf
  */

@@ -1377,25 +1530,22 @@
 
 /*
  * 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)
+ * 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 */
 
-        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);
 }

@@ -1457,10 +1607,11 @@
     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,58 +1708,53 @@
                 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.
+                 * If this change is comming directly from a client
+                 * (sr != NULL) and it's a persistent handle, save
+                 * the "sticky times" in the handle.
          */
-        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;
+                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 ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0 &&
-                    node->n_open_count != 0)
+                if (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);
+        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,25 +1791,11 @@
 
         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;
                 }