Print this page
NEX-19996 exi_id_get_next() calls should be WRITER locked
NEX-20014 NFS v4 state lock mutex exited before entered (on error path)
Reviewed by: Sanjay Nadkarni <sanjay.nadkarni@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
NEX-15279 support NFS server in zone
NEX-15520 online NFS shares cause zoneadm halt to hang in nfs_export_zone_fini
Portions contributed by: Dan Kruchinin dan.kruchinin@nexenta.com
Portions contributed by: Stepan Zastupov stepan.zastupov@gmail.com
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
Reviewed by: Rob Gittins <rob.gittins@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
NEX-9275 Got "bad mutex" panic when run IO to nfs share from clients
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Sanjay Nadkarni <sanjay.nadkarni@nexenta.com>
Reviewed by: Rob Gittins <rob.gittins@nexenta.com>
NEX-6778 NFS kstats leak and cause system to hang
Revert "NEX-4261 Per-client NFS server IOPS, bandwidth, and latency kstats"
This reverts commit 586c3ab1927647487f01c337ddc011c642575a52.
Revert "NEX-5354 Aggregated IOPS, bandwidth, and latency kstats for NFS server"
This reverts commit c91d7614da8618ef48018102b077f60ecbbac8c2.
Revert "NEX-5667 nfssrv_stats_flags does not work for aggregated kstats"
This reverts commit 3dcf42618be7dd5f408c327f429c81e07ca08e74.
Revert "NEX-5750 Time values for aggregated NFS server kstats should be normalized"
This reverts commit 1f4d4f901153b0191027969fa4a8064f9d3b9ee1.
Revert "NEX-5942 Panic in rfs4_minorvers_mismatch() with NFSv4.1 client"
This reverts commit 40766417094a162f5e4cc8786c0fa0a7e5871cd9.
Revert "NEX-5752 NFS server: namespace collision in kstats"
This reverts commit ae81e668db86050da8e483264acb0cce0444a132.
Reviewed by: Rob Gittins <rob.gittins@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-4261 Per-client NFS server IOPS, bandwidth, and latency kstats
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-3097 IOPS, bandwidth, and latency kstats for NFS server
Reviewed by: Josef 'Jeff' Sipek <josef.sipek@nexenta.com>
NEX-2345 nfsauth_cache_get() could spend a lot of time walking exi_cache
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
        
@@ -18,19 +18,21 @@
  *
  * CDDL HEADER END
  */
 
 /*
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 /*
  *      Copyright 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.
  *              All rights reserved.
  */
 
+/*
+ * Copyright 2018 Nexenta Systems, Inc.
+ */
 
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/time.h>
 #include <sys/vfs.h>
@@ -63,18 +65,31 @@
 #include <nfs/nfs_clnt.h>
 #include <nfs/nfs_acl.h>
 #include <nfs/nfs_log.h>
 #include <nfs/lm.h>
 #include <sys/sunddi.h>
-#include <sys/pkp_hash.h>
 
-treenode_t *ns_root;
+static zone_key_t nfs_export_key;
 
-struct exportinfo *exptable_path_hash[PKP_HASH_SIZE];
-struct exportinfo *exptable[EXPTABLESIZE];
+/*
+ * exi_id support
+ *
+ * exi_id_next          The next exi_id available.
+ * exi_id_overflow      The exi_id_next already overflowed, so we should
+ *                      thoroughly check for duplicates.
+ * exi_id_tree          AVL tree indexed by exi_id.
+ * nfs_exi_id_lock      Lock to protect the export ID list
+ *
+ * All exi_id_next, exi_id_overflow, and exi_id_tree are protected by
+ * nfs_exi_id_lock.
+ */
+static int exi_id_next;
+static bool_t exi_id_overflow;
+avl_tree_t exi_id_tree;
+kmutex_t nfs_exi_id_lock;
 
-static int      unexport(exportinfo_t *);
+static int      unexport(nfs_export_t *, exportinfo_t *);
 static void     exportfree(exportinfo_t *);
 static int      loadindex(exportdata_t *);
 
 extern void     nfsauth_cache_free(exportinfo_t *);
 extern int      sec_svc_loadrootnames(int, int, caddr_t **, model_t);
@@ -81,32 +96,19 @@
 extern void     sec_svc_freerootnames(int, int, caddr_t *);
 
 static int build_seclist_nodups(exportdata_t *, secinfo_t *, int);
 static void srv_secinfo_add(secinfo_t **, int *, secinfo_t *, int, int);
 static void srv_secinfo_remove(secinfo_t **, int *, secinfo_t *, int);
-static void srv_secinfo_treeclimb(exportinfo_t *, secinfo_t *, int, bool_t);
+static void     srv_secinfo_treeclimb(nfs_export_t *, exportinfo_t *,
+                    secinfo_t *, int, bool_t);
 
 #ifdef VOLATILE_FH_TEST
 static struct ex_vol_rename *find_volrnm_fh(exportinfo_t *, nfs_fh4 *);
 static uint32_t find_volrnm_fh_id(exportinfo_t *, nfs_fh4 *);
 static void free_volrnm_list(exportinfo_t *);
 #endif /* VOLATILE_FH_TEST */
 
-/*
- * exported_lock        Read/Write lock that protects the exportinfo list.
- *                      This lock must be held when searching or modifiying
- *                      the exportinfo list.
- */
-krwlock_t exported_lock;
-
-/*
- * "public" and default (root) location for public filehandle
- */
-struct exportinfo *exi_public, *exi_root;
-
-fid_t exi_rootfid;      /* for checking the default public file handle */
-
 fhandle_t nullfh2;      /* for comparing V2 filehandles */
 
 /*
  * macro for static dtrace probes to trace server namespace ref count mods.
  */
@@ -115,10 +117,16 @@
                 char *, (tag), int, (int)(flav), int, (int)(aftcnt))
 
 
 #define exptablehash(fsid, fid) (nfs_fhhash((fsid), (fid)) & (EXPTABLESIZE - 1))
 
+extern nfs_export_t *
+nfs_get_export(void)
+{
+        return (zone_getspecific(nfs_export_key, curzone));
+}
+
 static uint8_t
 xor_hash(uint8_t *data, int len)
 {
         uint8_t h = 0;
 
@@ -701,16 +709,16 @@
  * For NFS V4.
  * Add or remove the newly exported or unexported security flavors of the
  * given exportinfo from its ancestors upto the system root.
  */
 void
-srv_secinfo_treeclimb(exportinfo_t *exip, secinfo_t *sec, int seccnt,
-    bool_t isadd)
+srv_secinfo_treeclimb(nfs_export_t *ne, exportinfo_t *exip, secinfo_t *sec,
+    int seccnt, bool_t isadd)
 {
         treenode_t *tnode = exip->exi_tree;
 
-        ASSERT(RW_WRITE_HELD(&exported_lock));
+        ASSERT(RW_WRITE_HELD(&ne->exported_lock));
         ASSERT(tnode != NULL);
 
         if (seccnt == 0)
                 return;
 
@@ -780,115 +788,220 @@
         if ((exi)->hash_name.next) \
                 (exi)->hash_name.next->hash_name.prev = (exi); \
         *(bucket) = (exi);
 
 void
-export_link(exportinfo_t *exi)
+export_link(nfs_export_t *ne, exportinfo_t *exi)
 {
         exportinfo_t **bckt;
 
-        bckt = &exptable[exptablehash(&exi->exi_fsid, &exi->exi_fid)];
+        ASSERT(RW_WRITE_HELD(&ne->exported_lock));
+
+        bckt = &ne->exptable[exptablehash(&exi->exi_fsid, &exi->exi_fid)];
         exp_hash_link(exi, fid_hash, bckt);
 
-        bckt = &exptable_path_hash[pkp_tab_hash(exi->exi_export.ex_path,
+        bckt = &ne->exptable_path_hash[pkp_tab_hash(exi->exi_export.ex_path,
             strlen(exi->exi_export.ex_path))];
         exp_hash_link(exi, path_hash, bckt);
 }
 
 /*
- * Initialization routine for export routines. Should only be called once.
+ * Helper functions for exi_id handling
  */
+static int
+exi_id_compar(const void *v1, const void *v2)
+{
+        const struct exportinfo *e1 = v1;
+        const struct exportinfo *e2 = v2;
+
+        if (e1->exi_id < e2->exi_id)
+                return (-1);
+        if (e1->exi_id > e2->exi_id)
+                return (1);
+
+        return (0);
+}
+
 int
-nfs_exportinit(void)
+exi_id_get_next()
 {
-        int error;
+        struct exportinfo e;
+        int ret = exi_id_next;
+
+        ASSERT(MUTEX_HELD(&nfs_exi_id_lock));
+
+        do {
+                exi_id_next++;
+                if (exi_id_next == 0)
+                        exi_id_overflow = TRUE;
+
+                if (!exi_id_overflow)
+                        break;
+
+                if (exi_id_next == ret)
+                        cmn_err(CE_PANIC, "exi_id exhausted");
+
+                e.exi_id = exi_id_next;
+        } while (avl_find(&exi_id_tree, &e, NULL) != NULL);
+
+        return (ret);
+}
+
+/*ARGSUSED*/
+static void *
+nfs_export_zone_init(zoneid_t zoneid)
+{
         int i;
+        nfs_export_t *ne;
 
-        rw_init(&exported_lock, NULL, RW_DEFAULT, NULL);
+        ne = kmem_zalloc(sizeof (*ne), KM_SLEEP);
 
+        rw_init(&ne->exported_lock, NULL, RW_DEFAULT, NULL);
+
         /*
          * Allocate the place holder for the public file handle, which
          * is all zeroes. It is initially set to the root filesystem.
          */
-        exi_root = kmem_zalloc(sizeof (*exi_root), KM_SLEEP);
-        exi_public = exi_root;
+        ne->exi_root = kmem_zalloc(sizeof (*ne->exi_root), KM_SLEEP);
+        ne->exi_public = ne->exi_root;
 
-        exi_root->exi_export.ex_flags = EX_PUBLIC;
-        exi_root->exi_export.ex_pathlen = 1;    /* length of "/" */
-        exi_root->exi_export.ex_path =
-            kmem_alloc(exi_root->exi_export.ex_pathlen + 1, KM_SLEEP);
-        exi_root->exi_export.ex_path[0] = '/';
-        exi_root->exi_export.ex_path[1] = '\0';
+        ne->exi_root->exi_export.ex_flags = EX_PUBLIC;
+        ne->exi_root->exi_export.ex_pathlen = 1;        /* length of "/" */
+        ne->exi_root->exi_export.ex_path =
+            kmem_alloc(ne->exi_root->exi_export.ex_pathlen + 1, KM_SLEEP);
+        ne->exi_root->exi_export.ex_path[0] = '/';
+        ne->exi_root->exi_export.ex_path[1] = '\0';
 
-        exi_root->exi_count = 1;
-        mutex_init(&exi_root->exi_lock, NULL, MUTEX_DEFAULT, NULL);
+        ne->exi_root->exi_count = 1;
+        mutex_init(&ne->exi_root->exi_lock, NULL, MUTEX_DEFAULT, NULL);
 
-        exi_root->exi_vp = rootdir;
-        exi_rootfid.fid_len = MAXFIDSZ;
-        error = vop_fid_pseudo(exi_root->exi_vp, &exi_rootfid);
-        if (error) {
-                mutex_destroy(&exi_root->exi_lock);
-                kmem_free(exi_root, sizeof (*exi_root));
-                return (error);
+        ne->exi_root->exi_vp = ZONE_ROOTVP();
+        ne->exi_rootfid.fid_len = MAXFIDSZ;
+        if (vop_fid_pseudo(ne->exi_root->exi_vp, &ne->exi_rootfid) != 0) {
+                mutex_destroy(&ne->exi_root->exi_lock);
+                kmem_free(ne->exi_root->exi_export.ex_path,
+                    ne->exi_root->exi_export.ex_pathlen + 1);
+                kmem_free(ne->exi_root, sizeof (*ne->exi_root));
+                return (NULL);
         }
 
-        /*
-         * Initialize auth cache and auth cache lock
-         */
+        /* Initialize auth cache and auth cache lock */
         for (i = 0; i < AUTH_TABLESIZE; i++) {
-                exi_root->exi_cache[i] = kmem_alloc(sizeof (avl_tree_t),
+                ne->exi_root->exi_cache[i] = kmem_alloc(sizeof (avl_tree_t),
                     KM_SLEEP);
-                avl_create(exi_root->exi_cache[i], nfsauth_cache_clnt_compar,
-                    sizeof (struct auth_cache_clnt),
+                avl_create(ne->exi_root->exi_cache[i],
+                    nfsauth_cache_clnt_compar, sizeof (struct auth_cache_clnt),
                     offsetof(struct auth_cache_clnt, authc_link));
         }
-        rw_init(&exi_root->exi_cache_lock, NULL, RW_DEFAULT, NULL);
+        rw_init(&ne->exi_root->exi_cache_lock, NULL, RW_DEFAULT, NULL);
 
-        /* setup the fhandle template */
-        exi_root->exi_fh.fh_fsid = rootdir->v_vfsp->vfs_fsid;
-        exi_root->exi_fh.fh_xlen = exi_rootfid.fid_len;
-        bcopy(exi_rootfid.fid_data, exi_root->exi_fh.fh_xdata,
-            exi_rootfid.fid_len);
-        exi_root->exi_fh.fh_len = sizeof (exi_root->exi_fh.fh_data);
+        /* Setup the fhandle template */
+        ne->exi_root->exi_fh.fh_fsid = rootdir->v_vfsp->vfs_fsid;
+        ne->exi_root->exi_fh.fh_xlen = ne->exi_rootfid.fid_len;
+        bcopy(ne->exi_rootfid.fid_data, ne->exi_root->exi_fh.fh_xdata,
+            ne->exi_rootfid.fid_len);
+        ne->exi_root->exi_fh.fh_len = sizeof (ne->exi_root->exi_fh.fh_data);
 
-        /*
-         * Publish the exportinfo in the hash table
-         */
-        export_link(exi_root);
+        rw_enter(&ne->exported_lock, RW_WRITER);
 
-        nfslog_init();
-        ns_root = NULL;
+        /* Publish the exportinfo in the hash table */
+        export_link(ne, ne->exi_root);
 
-        return (0);
+        /* Initialize exi_id and exi_kstats */
+        mutex_enter(&nfs_exi_id_lock);
+        ne->exi_root->exi_id = exi_id_get_next();
+        avl_add(&exi_id_tree, ne->exi_root);
+        mutex_exit(&nfs_exi_id_lock);
+        ne->exi_root->exi_kstats = exp_kstats_init(zoneid,
+            ne->exi_root->exi_id, ne->exi_root->exi_export.ex_path,
+            ne->exi_root->exi_export.ex_pathlen, FALSE);
+
+        rw_exit(&ne->exported_lock);
+        ne->ns_root = NULL;
+
+        return (ne);
 }
 
-/*
- * Finalization routine for export routines. Called to cleanup previously
- * initialization work when the NFS server module could not be loaded correctly.
- */
-void
-nfs_exportfini(void)
+/*ARGSUSED*/
+static void
+nfs_export_zone_fini(zoneid_t zoneid, void *data)
 {
         int i;
+        nfs_export_t *ne = data;
+        struct exportinfo *exi;
 
-        /*
-         * Deallocate the place holder for the public file handle.
-         */
-        srv_secinfo_list_free(exi_root->exi_export.ex_secinfo,
-            exi_root->exi_export.ex_seccnt);
-        mutex_destroy(&exi_root->exi_lock);
-        rw_destroy(&exi_root->exi_cache_lock);
+        rw_enter(&ne->exported_lock, RW_WRITER);
+        mutex_enter(&nfs_exi_id_lock);
+
+        exp_kstats_delete(ne->exi_root->exi_kstats);
+        avl_remove(&exi_id_tree, ne->exi_root);
+        export_unlink(ne, ne->exi_root);
+
+        mutex_exit(&nfs_exi_id_lock);
+        rw_exit(&ne->exported_lock);
+
+        /* Deallocate the place holder for the public file handle */
+        srv_secinfo_list_free(ne->exi_root->exi_export.ex_secinfo,
+            ne->exi_root->exi_export.ex_seccnt);
+        mutex_destroy(&ne->exi_root->exi_lock);
+
+        rw_destroy(&ne->exi_root->exi_cache_lock);
         for (i = 0; i < AUTH_TABLESIZE; i++) {
-                avl_destroy(exi_root->exi_cache[i]);
-                kmem_free(exi_root->exi_cache[i], sizeof (avl_tree_t));
+                avl_destroy(ne->exi_root->exi_cache[i]);
+                kmem_free(ne->exi_root->exi_cache[i], sizeof (avl_tree_t));
         }
-        kmem_free(exi_root, sizeof (*exi_root));
 
-        rw_destroy(&exported_lock);
+        exp_kstats_fini(ne->exi_root->exi_kstats);
+        kmem_free(ne->exi_root->exi_export.ex_path,
+            ne->exi_root->exi_export.ex_pathlen + 1);
+        kmem_free(ne->exi_root, sizeof (*ne->exi_root));
+
+        exi = avl_first(&exi_id_tree);
+        while (exi != NULL) {
+                struct exportinfo *nexi = AVL_NEXT(&exi_id_tree, exi);
+                if (zoneid == exi->exi_zoneid)
+                        (void) unexport(ne, exi);
+                exi = nexi;
+        }
+
+        rw_destroy(&ne->exported_lock);
+        kmem_free(ne, sizeof (*ne));
 }
 
 /*
+ * Initialization routine for export routines.
+ * Should only be called once.
+ */
+void
+nfs_exportinit(void)
+{
+        mutex_init(&nfs_exi_id_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        /* exi_id handling initialization */
+        exi_id_next = 0;
+        exi_id_overflow = FALSE;
+        avl_create(&exi_id_tree, exi_id_compar, sizeof (struct exportinfo),
+            offsetof(struct exportinfo, exi_id_link));
+
+        zone_key_create(&nfs_export_key, nfs_export_zone_init,
+            NULL, nfs_export_zone_fini);
+
+        nfslog_init();
+}
+
+/*
+ * Finalization routine for export routines.
+ */
+void
+nfs_exportfini(void)
+{
+        (void) zone_key_delete(nfs_export_key);
+        avl_destroy(&exi_id_tree);
+        mutex_destroy(&nfs_exi_id_lock);
+}
+
+/*
  *  Check if 2 gss mechanism identifiers are the same.
  *
  *  return FALSE if not the same.
  *  return TRUE if the same.
  */
@@ -920,10 +1033,11 @@
     rpc_gss_lock_t *lock, void **cookie)
 {
         int i, j;
         rpc_gss_rawcred_t *raw_cred;
         struct exportinfo *exi;
+        nfs_export_t *ne = nfs_get_export();
 
         /*
          * We don't deal with delegated credentials.
          */
         if (deleg != GSS_C_NO_CREDENTIAL)
@@ -930,13 +1044,14 @@
                 return (FALSE);
 
         raw_cred = lock->raw_cred;
         *cookie = NULL;
 
-        rw_enter(&exported_lock, RW_READER);
+        rw_enter(&ne->exported_lock, RW_READER);
+
         for (i = 0; i < EXPTABLESIZE; i++) {
-                exi = exptable[i];
+                exi = ne->exptable[i];
                 while (exi) {
                         if (exi->exi_export.ex_seccnt > 0) {
                                 struct secinfo *secp;
                                 seconfig_t *se;
                                 int seccnt;
@@ -972,11 +1087,11 @@
                         }
                         exi = exi->fid_hash.next;
                 }
         }
 done:
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
 
         /*
          * If no nfs pseudo number mapping can be found in the export
          * table, assign the nfsflavor to NFS_FLAVOR_NOMAP. In V4, we may
          * recover the flavor mismatch from NFS layer (NFS4ERR_WRONGSEC).
@@ -1039,38 +1154,39 @@
         int newcnt;
         struct secinfo oldsec[MAX_FLAVORS];
         int oldcnt;
         int i;
         struct pathname lookpn;
+        nfs_export_t *ne = nfs_get_export();
 
         STRUCT_SET_HANDLE(uap, model, args);
 
         /* Read in pathname from userspace */
         if (error = pn_get(STRUCT_FGETP(uap, dname), UIO_USERSPACE, &lookpn))
                 return (error);
 
         /* Walk the export list looking for that pathname */
-        rw_enter(&exported_lock, RW_READER);
+        rw_enter(&ne->exported_lock, RW_READER);
         DTRACE_PROBE(nfss__i__exported_lock1_start);
-        for (ex1 = exptable_path_hash[pkp_tab_hash(lookpn.pn_path,
+        for (ex1 = ne->exptable_path_hash[pkp_tab_hash(lookpn.pn_path,
             strlen(lookpn.pn_path))]; ex1; ex1 = ex1->path_hash.next) {
-                if (ex1 != exi_root && 0 ==
+                if (ex1 != ne->exi_root && 0 ==
                     strcmp(ex1->exi_export.ex_path, lookpn.pn_path)) {
                         exi_hold(ex1);
                         break;
                 }
         }
         DTRACE_PROBE(nfss__i__exported_lock1_stop);
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
 
         /* Is this an unshare? */
         if (STRUCT_FGETP(uap, uex) == NULL) {
                 pn_free(&lookpn);
                 if (ex1 == NULL)
                         return (EINVAL);
-                error = unexport(ex1);
-                exi_rele(ex1);
+                error = unexport(ne, ex1);
+                exi_rele(&ex1);
                 return (error);
         }
 
         /* It is a share or a re-share */
         error = lookupname(STRUCT_FGETP(uap, dname), UIO_USERSPACE,
@@ -1092,11 +1208,11 @@
                 error = ENOENT;
         }
         if (error) {
                 pn_free(&lookpn);
                 if (ex1)
-                        exi_rele(ex1);
+                        exi_rele(&ex1);
                 return (error);
         }
 
         /*
          * 'vp' may be an AUTOFS node, so we perform a
@@ -1118,11 +1234,11 @@
                         VN_RELE(vp);
                         if (dvp != NULL)
                                 VN_RELE(dvp);
                         pn_free(&lookpn);
                         if (ex1)
-                                exi_rele(ex1);
+                                exi_rele(&ex1);
                         return (error);
                 }
         }
 
         /* Do not allow sharing another vnode for already shared path */
@@ -1129,15 +1245,15 @@
         if (ex1 && !PSEUDO(ex1) && !VN_CMP(ex1->exi_vp, vp)) {
                 VN_RELE(vp);
                 if (dvp != NULL)
                         VN_RELE(dvp);
                 pn_free(&lookpn);
-                exi_rele(ex1);
+                exi_rele(&ex1);
                 return (EEXIST);
         }
         if (ex1)
-                exi_rele(ex1);
+                exi_rele(&ex1);
 
         /*
          * Get the vfs id
          */
         bzero(&fid, sizeof (fid));
@@ -1161,35 +1277,36 @@
 
         /*
          * Do not allow re-sharing a shared vnode under a different path
          * PSEUDO export has ex_path fabricated, e.g. "/tmp (pseudo)", skip it.
          */
-        rw_enter(&exported_lock, RW_READER);
+        rw_enter(&ne->exported_lock, RW_READER);
         DTRACE_PROBE(nfss__i__exported_lock2_start);
-        for (ex2 = exptable[exptablehash(&fsid, &fid)]; ex2;
+        for (ex2 = ne->exptable[exptablehash(&fsid, &fid)]; ex2;
             ex2 = ex2->fid_hash.next) {
-                if (ex2 != exi_root && !PSEUDO(ex2) &&
+                if (ex2 != ne->exi_root && !PSEUDO(ex2) &&
                     VN_CMP(ex2->exi_vp, vp) &&
                     strcmp(ex2->exi_export.ex_path, lookpn.pn_path) != 0) {
                         DTRACE_PROBE(nfss__i__exported_lock2_stop);
-                        rw_exit(&exported_lock);
+                        rw_exit(&ne->exported_lock);
                         VN_RELE(vp);
                         if (dvp != NULL)
                                 VN_RELE(dvp);
                         pn_free(&lookpn);
                         return (EEXIST);
                 }
         }
         DTRACE_PROBE(nfss__i__exported_lock2_stop);
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
         pn_free(&lookpn);
 
         exi = kmem_zalloc(sizeof (*exi), KM_SLEEP);
         exi->exi_fsid = fsid;
         exi->exi_fid = fid;
         exi->exi_vp = vp;
         exi->exi_count = 1;
+        exi->exi_zoneid = crgetzoneid(cr);
         exi->exi_volatile_dev = (vfssw[vp->v_vfsp->vfs_fstype].vsw_flag &
             VSW_VOLATILEDEV) ? 1 : 0;
         mutex_init(&exi->exi_lock, NULL, MUTEX_DEFAULT, NULL);
         exi->exi_dvp = dvp;
 
@@ -1459,41 +1576,44 @@
         }
 
         /*
          * Insert the new entry at the front of the export list
          */
-        rw_enter(&exported_lock, RW_WRITER);
+        rw_enter(&ne->exported_lock, RW_WRITER);
         DTRACE_PROBE(nfss__i__exported_lock3_start);
 
-        export_link(exi);
+        export_link(ne, exi);
 
         /*
          * Check the rest of the list for an old entry for the fs.
          * If one is found then unlink it, wait until this is the
          * only reference and then free it.
          */
         for (ex = exi->fid_hash.next; ex != NULL; ex = ex->fid_hash.next) {
-                if (ex != exi_root && VN_CMP(ex->exi_vp, vp)) {
-                        export_unlink(ex);
+                if (ex != ne->exi_root && VN_CMP(ex->exi_vp, vp)) {
+                        mutex_enter(&nfs_exi_id_lock);
+                        avl_remove(&exi_id_tree, ex);
+                        mutex_exit(&nfs_exi_id_lock);
+                        export_unlink(ne, ex);
                         break;
                 }
         }
 
         /*
          * If the public filehandle is pointing at the
          * old entry, then point it back at the root.
          */
-        if (ex != NULL && ex == exi_public)
-                exi_public = exi_root;
+        if (ex != NULL && ex == ne->exi_public)
+                ne->exi_public = ne->exi_root;
 
         /*
          * If the public flag is on, make the global exi_public
          * point to this entry and turn off the public bit so that
          * we can distinguish it from the place holder export.
          */
         if (kex->ex_flags & EX_PUBLIC) {
-                exi_public = exi;
+                ne->exi_public = exi;
                 kex->ex_flags &= ~EX_PUBLIC;
         }
 
 #ifdef VOLATILE_FH_TEST
         /*
@@ -1521,21 +1641,21 @@
                 /* If it's a re-export update namespace tree */
                 exi->exi_tree = ex->exi_tree;
                 exi->exi_tree->tree_exi = exi;
 
                 /* Update the change timestamp */
-                tree_update_change(exi->exi_tree, NULL);
+                tree_update_change(ne, exi->exi_tree, NULL);
         }
 
         /*
          * build a unique flavor list from the flavors specified
          * in the share cmd.  unique means that each flavor only
          * appears once in the secinfo list -- no duplicates allowed.
          */
         newcnt = build_seclist_nodups(&exi->exi_export, newsec, FALSE);
 
-        srv_secinfo_treeclimb(exi, newsec, newcnt, TRUE);
+        srv_secinfo_treeclimb(ne, exi, newsec, newcnt, TRUE);
 
         /*
          * If re-sharing an old export entry, update the secinfo data
          * depending on if the old entry is a pseudo node or not.
          */
@@ -1556,11 +1676,11 @@
                         /*
                          * First transfer implicit flavor refs to new export.
                          * Remove old flavor refs last.
                          */
                         srv_secinfo_exp2exp(&exi->exi_export, oldsec, oldcnt);
-                        srv_secinfo_treeclimb(ex, oldsec, oldcnt, FALSE);
+                        srv_secinfo_treeclimb(ne, ex, oldsec, oldcnt, FALSE);
                 }
         }
 
         /*
          * If it's a re-export and the old entry has a pseudonode list,
@@ -1569,30 +1689,50 @@
         if (ex != NULL && (ex->exi_visible != NULL)) {
                 exi->exi_visible = ex->exi_visible;
                 ex->exi_visible = NULL;
         }
 
+        /*
+         * Initialize exi_id and exi_kstats
+         */
+        if (ex != NULL) {
+                exi->exi_id = ex->exi_id;
+                exi->exi_kstats = ex->exi_kstats;
+                ex->exi_kstats = NULL;
+                exp_kstats_reset(exi->exi_kstats, kex->ex_path,
+                    kex->ex_pathlen, FALSE);
+        } else {
+                mutex_enter(&nfs_exi_id_lock);
+                exi->exi_id = exi_id_get_next();
+                mutex_exit(&nfs_exi_id_lock);
+                exi->exi_kstats = exp_kstats_init(crgetzoneid(cr), exi->exi_id,
+                    kex->ex_path, kex->ex_pathlen, FALSE);
+        }
+        mutex_enter(&nfs_exi_id_lock);
+        avl_add(&exi_id_tree, exi);
+        mutex_exit(&nfs_exi_id_lock);
+
         DTRACE_PROBE(nfss__i__exported_lock3_stop);
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
 
-        if (exi_public == exi || kex->ex_flags & EX_LOG) {
+        if (ne->exi_public == exi || kex->ex_flags & EX_LOG) {
                 /*
                  * Log share operation to this buffer only.
                  */
                 nfslog_share_record(exi, cr);
         }
 
         if (ex != NULL)
-                exi_rele(ex);
+                exi_rele(&ex);
 
         return (0);
 
 out7:
         /* Unlink the new export in exptable. */
-        export_unlink(exi);
+        export_unlink(ne, exi);
         DTRACE_PROBE(nfss__i__exported_lock3_stop);
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
 out6:
         if (kex->ex_flags & EX_INDEX)
                 kmem_free(kex->ex_index, strlen(kex->ex_index) + 1);
 out5:
         /* free partially completed allocation */
@@ -1632,68 +1772,71 @@
 
 /*
  * Remove the exportinfo from the export list
  */
 void
-export_unlink(struct exportinfo *exi)
+export_unlink(nfs_export_t *ne, struct exportinfo *exi)
 {
-        ASSERT(RW_WRITE_HELD(&exported_lock));
+        ASSERT(RW_WRITE_HELD(&ne->exported_lock));
 
         exp_hash_unlink(exi, fid_hash);
         exp_hash_unlink(exi, path_hash);
 }
 
 /*
  * Unexport an exported filesystem
  */
 static int
-unexport(struct exportinfo *exi)
+unexport(nfs_export_t *ne, struct exportinfo *exi)
 {
         struct secinfo cursec[MAX_FLAVORS];
         int curcnt;
 
-        rw_enter(&exported_lock, RW_WRITER);
+        rw_enter(&ne->exported_lock, RW_WRITER);
 
         /* Check if exi is still linked in the export table */
         if (!EXP_LINKED(exi) || PSEUDO(exi)) {
-                rw_exit(&exported_lock);
+                rw_exit(&ne->exported_lock);
                 return (EINVAL);
         }
 
-        export_unlink(exi);
+        exp_kstats_delete(exi->exi_kstats);
+        mutex_enter(&nfs_exi_id_lock);
+        avl_remove(&exi_id_tree, exi);
+        mutex_exit(&nfs_exi_id_lock);
+        export_unlink(ne, exi);
 
         /*
          * Remove security flavors before treeclimb_unexport() is called
          * because srv_secinfo_treeclimb needs the namespace tree
          */
         curcnt = build_seclist_nodups(&exi->exi_export, cursec, TRUE);
+        srv_secinfo_treeclimb(ne, exi, cursec, curcnt, FALSE);
 
-        srv_secinfo_treeclimb(exi, cursec, curcnt, FALSE);
-
         /*
          * If there's a visible list, then need to leave
          * a pseudo export here to retain the visible list
          * for paths to exports below.
          */
         if (exi->exi_visible != NULL) {
                 struct exportinfo *newexi;
 
-                newexi = pseudo_exportfs(exi->exi_vp, &exi->exi_fid,
+                newexi = pseudo_exportfs(ne, exi->exi_vp, &exi->exi_fid,
                     exi->exi_visible, &exi->exi_export);
                 exi->exi_visible = NULL;
 
                 /* interconnect the existing treenode with the new exportinfo */
                 newexi->exi_tree = exi->exi_tree;
                 newexi->exi_tree->tree_exi = newexi;
 
                 /* Update the change timestamp */
-                tree_update_change(exi->exi_tree, NULL);
+                tree_update_change(ne, exi->exi_tree, NULL);
         } else {
-                treeclimb_unexport(exi);
+                treeclimb_unexport(ne, exi);
         }
 
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
 
         /*
          * Need to call into the NFSv4 server and release all data
          * held on this particular export.  This is important since
          * the v4 server may be holding file locks or vnodes under
@@ -1709,21 +1852,20 @@
 
         /*
          * If this was a public export, restore
          * the public filehandle to the root.
          */
-        if (exi == exi_public) {
-                exi_public = exi_root;
+        if (exi == ne->exi_public) {
+                ne->exi_public = ne->exi_root;
 
-                nfslog_share_record(exi_public, CRED());
+                nfslog_share_record(ne->exi_public, CRED());
         }
 
-        if (exi->exi_export.ex_flags & EX_LOG) {
+        if (exi->exi_export.ex_flags & EX_LOG)
                 nfslog_unshare_record(exi, CRED());
-        }
 
-        exi_rele(exi);
+        exi_rele(&exi);
         return (0);
 }
 
 /*
  * Get file handle system call.
@@ -1875,11 +2017,11 @@
                 }
                 if (!error && exi->exi_export.ex_flags & EX_LOG) {
                         nfslog_getfh(exi, (fhandle_t *)logptr,
                             STRUCT_FGETP(uap, fname), UIO_USERSPACE, cr);
                 }
-                exi_rele(exi);
+                exi_rele(&exi);
                 if (!error) {
                         if (copyout(&l, STRUCT_FGETP(uap, lenp), sizeof (int)))
                                 error = EFAULT;
                         if (copyout(buf, STRUCT_FGETP(uap, fhp), l))
                                 error = EFAULT;
@@ -2444,13 +2586,14 @@
  */
 struct exportinfo *
 checkexport(fsid_t *fsid, fid_t *fid)
 {
         struct exportinfo *exi;
+        nfs_export_t *ne = nfs_get_export();
 
-        rw_enter(&exported_lock, RW_READER);
-        for (exi = exptable[exptablehash(fsid, fid)];
+        rw_enter(&ne->exported_lock, RW_READER);
+        for (exi = ne->exptable[exptablehash(fsid, fid)];
             exi != NULL;
             exi = exi->fid_hash.next) {
                 if (exportmatch(exi, fsid, fid)) {
                         /*
                          * If this is the place holder for the
@@ -2457,19 +2600,19 @@
                          * public file handle, then return the
                          * real export entry for the public file
                          * handle.
                          */
                         if (exi->exi_export.ex_flags & EX_PUBLIC) {
-                                exi = exi_public;
+                                exi = ne->exi_public;
                         }
 
                         exi_hold(exi);
-                        rw_exit(&exported_lock);
+                        rw_exit(&ne->exported_lock);
                         return (exi);
                 }
         }
-        rw_exit(&exported_lock);
+        rw_exit(&ne->exported_lock);
         return (NULL);
 }
 
 
 /*
@@ -2481,14 +2624,15 @@
  */
 struct exportinfo *
 checkexport4(fsid_t *fsid, fid_t *fid, vnode_t *vp)
 {
         struct exportinfo *exi;
+        nfs_export_t *ne = nfs_get_export();
 
-        ASSERT(RW_LOCK_HELD(&exported_lock));
+        ASSERT(RW_LOCK_HELD(&ne->exported_lock));
 
-        for (exi = exptable[exptablehash(fsid, fid)];
+        for (exi = ne->exptable[exptablehash(fsid, fid)];
             exi != NULL;
             exi = exi->fid_hash.next) {
                 if (exportmatch(exi, fsid, fid)) {
                         /*
                          * If this is the place holder for the
@@ -2495,11 +2639,11 @@
                          * public file handle, then return the
                          * real export entry for the public file
                          * handle.
                          */
                         if (exi->exi_export.ex_flags & EX_PUBLIC) {
-                                exi = exi_public;
+                                exi = ne->exi_public;
                         }
 
                         /*
                          * If vp is given, check if vp is the
                          * same vnode as the exported node.
@@ -2518,11 +2662,11 @@
 }
 
 /*
  * Free an entire export list node
  */
-void
+static void
 exportfree(struct exportinfo *exi)
 {
         struct exportdata *ex;
         struct charset_cache *cache;
         int i;
@@ -2581,10 +2725,12 @@
         for (i = 0; i < AUTH_TABLESIZE; i++) {
                 avl_destroy(exi->exi_cache[i]);
                 kmem_free(exi->exi_cache[i], sizeof (avl_tree_t));
         }
 
+        exp_kstats_fini(exi->exi_kstats);
+
         kmem_free(exi, sizeof (*exi));
 }
 
 /*
  * load the index file from user space into kernel space.
@@ -2622,19 +2768,28 @@
  * When a thread completes using exi, it should call exi_rele().
  * exi_rele() decrements exi_count. It releases exi if exi_count == 0, i.e.
  * if this is the last user of exi and exi is not on exportinfo list anymore
  */
 void
-exi_rele(struct exportinfo *exi)
+exi_rele(struct exportinfo **exi)
 {
-        mutex_enter(&exi->exi_lock);
-        exi->exi_count--;
-        if (exi->exi_count == 0) {
-                mutex_exit(&exi->exi_lock);
-                exportfree(exi);
+        struct exportinfo *exip = *exi;
+        mutex_enter(&exip->exi_lock);
+        exip->exi_count--;
+        if (exip->exi_count == 0) {
+                mutex_exit(&exip->exi_lock);
+                /*
+                 * The exportinfo structure needs to be cleared here
+                 * since the control point, for when we free the structure,
+                 * is in this function and is triggered by the reference
+                 * count. The caller does not necessarily know when that
+                 * will be the case.
+                 */
+                *exi = NULL;
+                exportfree(exip);
         } else
-                mutex_exit(&exi->exi_lock);
+                mutex_exit(&exip->exi_lock);
 }
 
 #ifdef VOLATILE_FH_TEST
 /*
  * Test for volatile fh's - add file handle to list and set its volatile id