--- old/usr/src/uts/common/fs/nfs/nfs_srv.c	Mon Aug  5 17:26:06 2019
+++ new/usr/src/uts/common/fs/nfs/nfs_srv.c	Mon Aug  5 17:26:06 2019
@@ -18,10 +18,9 @@
  *
  * CDDL HEADER END
  */
+
 /*
  * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
- * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
 /*
@@ -29,6 +28,11 @@
  *	All rights reserved.
  */
 
+/*
+ * Copyright 2018 Nexenta Systems, Inc.
+ * Copyright (c) 2016 by Delphix. All rights reserved.
+ */
+
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/systm.h>
@@ -69,7 +73,22 @@
 
 #include <sys/strsubr.h>
 
+struct rfs_async_write_list;
+
 /*
+ * Zone globals of NFSv2 server
+ */
+typedef struct nfs_srv {
+	kmutex_t			async_write_lock;
+	struct rfs_async_write_list	*async_write_head;
+
+	/*
+	 * enables write clustering if == 1
+	 */
+	int		write_async;
+} nfs_srv_t;
+
+/*
  * These are the interface routines for the server side of the
  * Network File System.  See the NFS version 2 protocol specification
  * for a description of this interface.
@@ -78,7 +97,10 @@
 static int	sattr_to_vattr(struct nfssattr *, struct vattr *);
 static void	acl_perm(struct vnode *, struct exportinfo *, struct vattr *,
 			cred_t *);
+static void	*rfs_zone_init(zoneid_t zoneid);
+static void	rfs_zone_fini(zoneid_t zoneid, void *data);
 
+
 /*
  * Some "over the wire" UNIX file types.  These are encoded
  * into the mode.  This needs to be fixed in the next rev.
@@ -89,6 +111,7 @@
 #define	IFSOCK		0140000		/* socket */
 
 u_longlong_t nfs2_srv_caller_id;
+static zone_key_t rfs_zone_key;
 
 /*
  * Get file attributes.
@@ -329,7 +352,81 @@
 	return (&args->saa_fh);
 }
 
+/* Change and release @exip and @vpp only in success */
+int
+rfs_cross_mnt(vnode_t **vpp, struct exportinfo **exip)
+{
+	struct exportinfo *exi;
+	vnode_t *vp = *vpp;
+	fid_t fid;
+	int error;
+
+	VN_HOLD(vp);
+
+	if ((error = traverse(&vp)) != 0) {
+		VN_RELE(vp);
+		return (error);
+	}
+
+	bzero(&fid, sizeof (fid));
+	fid.fid_len = MAXFIDSZ;
+	error = VOP_FID(vp, &fid, NULL);
+	if (error) {
+		VN_RELE(vp);
+		return (error);
+	}
+
+	exi = checkexport(&vp->v_vfsp->vfs_fsid, &fid);
+	if (exi == NULL ||
+	    (exi->exi_export.ex_flags & EX_NOHIDE) == 0) {
+		/*
+		 * It is not error, just subdir is not exported
+		 * or "nohide" is not set
+		 */
+		if (exi != NULL)
+			exi_rele(&exi);
+		VN_RELE(vp);
+	} else {
+		/* go to submount */
+		exi_rele(exip);
+		*exip = exi;
+
+		VN_RELE(*vpp);
+		*vpp = vp;
+	}
+
+	return (0);
+}
+
 /*
+ * Given mounted "dvp" and "exi", go upper mountpoint
+ * with dvp/exi correction
+ * Return 0 in success
+ */
+int
+rfs_climb_crossmnt(vnode_t **dvpp, struct exportinfo **exip, cred_t *cr)
+{
+	struct exportinfo *exi;
+	vnode_t *dvp = *dvpp;
+
+	ASSERT(dvp->v_flag & VROOT);
+
+	VN_HOLD(dvp);
+	dvp = untraverse(dvp);
+	exi = nfs_vptoexi(NULL, dvp, cr, NULL, NULL, FALSE);
+	if (exi == NULL) {
+		VN_RELE(dvp);
+		return (-1);
+	}
+
+	exi_rele(exip);
+	*exip = exi;
+	VN_RELE(*dvpp);
+	*dvpp = dvp;
+
+	return (0);
+}
+/*
  * Directory lookup.
  * Returns an fhandle and file attributes for file name in a directory.
  */
@@ -371,7 +468,7 @@
 	 * location of the public filehandle.
 	 */
 	if (exi != NULL && (exi->exi_export.ex_flags & EX_PUBLIC)) {
-		dvp = rootdir;
+		dvp = ZONE_ROOTVP();
 		VN_HOLD(dvp);
 	} else {
 		dvp = nfs_fhtovp(fhp, exi);
@@ -381,6 +478,8 @@
 		}
 	}
 
+	exi_hold(exi);
+
 	/*
 	 * Not allow lookup beyond root.
 	 * If the filehandle matches a filehandle of the exi,
@@ -388,9 +487,19 @@
 	 */
 	if (strcmp(da->da_name, "..") == 0 &&
 	    EQFID(&exi->exi_fid, (fid_t *)&fhp->fh_len)) {
-		VN_RELE(dvp);
-		dr->dr_status = NFSERR_NOENT;
-		return;
+		if ((exi->exi_export.ex_flags & EX_NOHIDE) &&
+		    (dvp->v_flag & VROOT)) {
+			/*
+			 * special case for ".." and 'nohide'exported root
+			 */
+			if (rfs_climb_crossmnt(&dvp, &exi, cr) != 0) {
+				error = NFSERR_ACCES;
+				goto out;
+			}
+		} else  {
+			error = NFSERR_NOENT;
+			goto out;
+		}
 	}
 
 	ca = (struct sockaddr *)svc_getrpccaller(req->rq_xprt)->buf;
@@ -398,8 +507,8 @@
 	    MAXPATHLEN);
 
 	if (name == NULL) {
-		dr->dr_status = NFSERR_ACCES;
-		return;
+		error = NFSERR_ACCES;
+		goto out;
 	}
 
 	/*
@@ -413,6 +522,9 @@
 	 */
 	if (PUBLIC_FH2(fhp)) {
 		publicfh_flag = TRUE;
+
+		exi_rele(&exi);
+
 		error = rfs_publicfh_mclookup(name, dvp, cr, &vp, &exi,
 		    &sec);
 	} else {
@@ -426,6 +538,11 @@
 	if (name != da->da_name)
 		kmem_free(name, MAXPATHLEN);
 
+	if (error == 0 && vn_ismntpt(vp)) {
+		error = rfs_cross_mnt(&vp, &exi);
+		if (error)
+			VN_RELE(vp);
+	}
 
 	if (!error) {
 		va.va_mask = AT_ALL;	/* we want everything */
@@ -452,16 +569,11 @@
 		VN_RELE(vp);
 	}
 
+out:
 	VN_RELE(dvp);
 
-	/*
-	 * If publicfh_flag is true then we have called rfs_publicfh_mclookup
-	 * and have obtained a new exportinfo in exi which needs to be
-	 * released. Note the the original exportinfo pointed to by exi
-	 * will be released by the caller, comon_dispatch.
-	 */
-	if (publicfh_flag && exi != NULL)
-		exi_rele(exi);
+	if (exi != NULL)
+		exi_rele(&exi);
 
 	/*
 	 * If it's public fh, no 0x81, and client's flavor is
@@ -685,6 +797,8 @@
 
 	/* check if a monitor detected a delegation conflict */
 	if (error == EAGAIN && (ct.cc_flags & CC_WOULDBLOCK)) {
+		if (in_crit)
+			nbl_end_crit(vp);
 		VN_RELE(vp);
 		/* mark as wouldblock so response is dropped */
 		curthread->t_flag |= T_WOULDBLOCK;
@@ -1010,10 +1124,7 @@
 
 	/* check if a monitor detected a delegation conflict */
 	if (error == EAGAIN && (ct.cc_flags & CC_WOULDBLOCK)) {
-		VN_RELE(vp);
-		/* mark as wouldblock so response is dropped */
-		curthread->t_flag |= T_WOULDBLOCK;
-		return;
+		goto out;
 	}
 
 	if (wa->wa_data || wa->wa_rlist) {
@@ -1053,6 +1164,7 @@
 		error = VOP_WRITE(vp, &uio, FSYNC, cr, &ct);
 		curthread->t_cred = savecred;
 	} else {
+
 		iovcnt = 0;
 		for (m = wa->wa_mblk; m != NULL; m = m->b_cont)
 			iovcnt++;
@@ -1151,7 +1263,7 @@
 
 static struct rfs_async_write_list *rfs_async_write_head = NULL;
 static kmutex_t rfs_async_write_lock;
-static int rfs_write_async = 1;	/* enables write clustering if == 1 */
+volatile int rfs_write_async = 1;	/* enables write clustering if == 1 */
 
 #define	MAXCLIOVECS	42
 #define	RFSWRITE_INITVAL (enum nfsstat) -1
@@ -1196,8 +1308,10 @@
 	cred_t *savecred;
 	int in_crit = 0;
 	caller_context_t ct;
+	nfs_srv_t *nsrv;
 
-	if (!rfs_write_async) {
+	nsrv = zone_getspecific(rfs_zone_key, curzone);
+	if (!nsrv->write_async) {
 		rfs_write_sync(wa, ns, exi, req, cr, ro);
 		return;
 	}
@@ -1222,8 +1336,8 @@
 	 * Look to see if there is already a cluster started
 	 * for this file.
 	 */
-	mutex_enter(&rfs_async_write_lock);
-	for (lp = rfs_async_write_head; lp != NULL; lp = lp->next) {
+	mutex_enter(&nsrv->async_write_lock);
+	for (lp = nsrv->async_write_head; lp != NULL; lp = lp->next) {
 		if (bcmp(&wa->wa_fhandle, lp->fhp,
 		    sizeof (fhandle_t)) == 0)
 			break;
@@ -1249,8 +1363,8 @@
 		else
 			trp->list = nrp;
 		while (nrp->ns->ns_status == RFSWRITE_INITVAL)
-			cv_wait(&lp->cv, &rfs_async_write_lock);
-		mutex_exit(&rfs_async_write_lock);
+			cv_wait(&lp->cv, &nsrv->async_write_lock);
+		mutex_exit(&nsrv->async_write_lock);
 
 		return;
 	}
@@ -1267,15 +1381,15 @@
 	nlp->list = nrp;
 	nlp->next = NULL;
 
-	if (rfs_async_write_head == NULL) {
-		rfs_async_write_head = nlp;
+	if (nsrv->async_write_head == NULL) {
+		nsrv->async_write_head = nlp;
 	} else {
-		lp = rfs_async_write_head;
+		lp = nsrv->async_write_head;
 		while (lp->next != NULL)
 			lp = lp->next;
 		lp->next = nlp;
 	}
-	mutex_exit(&rfs_async_write_lock);
+	mutex_exit(&nsrv->async_write_lock);
 
 	/*
 	 * Convert the file handle common to all of the requests
@@ -1283,11 +1397,11 @@
 	 */
 	vp = nfs_fhtovp(&wa->wa_fhandle, exi);
 	if (vp == NULL) {
-		mutex_enter(&rfs_async_write_lock);
-		if (rfs_async_write_head == nlp)
-			rfs_async_write_head = nlp->next;
+		mutex_enter(&nsrv->async_write_lock);
+		if (nsrv->async_write_head == nlp)
+			nsrv->async_write_head = nlp->next;
 		else {
-			lp = rfs_async_write_head;
+			lp = nsrv->async_write_head;
 			while (lp->next != nlp)
 				lp = lp->next;
 			lp->next = nlp->next;
@@ -1298,7 +1412,7 @@
 			rp->thread->t_flag |= t_flag;
 		}
 		cv_broadcast(&nlp->cv);
-		mutex_exit(&rfs_async_write_lock);
+		mutex_exit(&nsrv->async_write_lock);
 
 		return;
 	}
@@ -1309,11 +1423,11 @@
 	 */
 	if (vp->v_type != VREG) {
 		VN_RELE(vp);
-		mutex_enter(&rfs_async_write_lock);
-		if (rfs_async_write_head == nlp)
-			rfs_async_write_head = nlp->next;
+		mutex_enter(&nsrv->async_write_lock);
+		if (nsrv->async_write_head == nlp)
+			nsrv->async_write_head = nlp->next;
 		else {
-			lp = rfs_async_write_head;
+			lp = nsrv->async_write_head;
 			while (lp->next != nlp)
 				lp = lp->next;
 			lp->next = nlp->next;
@@ -1324,7 +1438,7 @@
 			rp->thread->t_flag |= t_flag;
 		}
 		cv_broadcast(&nlp->cv);
-		mutex_exit(&rfs_async_write_lock);
+		mutex_exit(&nsrv->async_write_lock);
 
 		return;
 	}
@@ -1356,11 +1470,11 @@
 		VN_RELE(vp);
 		/* mark as wouldblock so response is dropped */
 		curthread->t_flag |= T_WOULDBLOCK;
-		mutex_enter(&rfs_async_write_lock);
-		if (rfs_async_write_head == nlp)
-			rfs_async_write_head = nlp->next;
+		mutex_enter(&nsrv->async_write_lock);
+		if (nsrv->async_write_head == nlp)
+			nsrv->async_write_head = nlp->next;
 		else {
-			lp = rfs_async_write_head;
+			lp = nsrv->async_write_head;
 			while (lp->next != nlp)
 				lp = lp->next;
 			lp->next = nlp->next;
@@ -1372,7 +1486,7 @@
 			}
 		}
 		cv_broadcast(&nlp->cv);
-		mutex_exit(&rfs_async_write_lock);
+		mutex_exit(&nsrv->async_write_lock);
 
 		return;
 	}
@@ -1394,16 +1508,16 @@
 	 * will allow more requests to be clustered in this
 	 * second cluster.
 	 */
-	mutex_enter(&rfs_async_write_lock);
-	if (rfs_async_write_head == nlp)
-		rfs_async_write_head = nlp->next;
+	mutex_enter(&nsrv->async_write_lock);
+	if (nsrv->async_write_head == nlp)
+		nsrv->async_write_head = nlp->next;
 	else {
-		lp = rfs_async_write_head;
+		lp = nsrv->async_write_head;
 		while (lp->next != nlp)
 			lp = lp->next;
 		lp->next = nlp->next;
 	}
-	mutex_exit(&rfs_async_write_lock);
+	mutex_exit(&nsrv->async_write_lock);
 
 	/*
 	 * Step through the list of requests in this cluster.
@@ -1648,7 +1762,7 @@
 	VN_RELE(vp);
 
 	t_flag = curthread->t_flag & T_WOULDBLOCK;
-	mutex_enter(&rfs_async_write_lock);
+	mutex_enter(&nsrv->async_write_lock);
 	for (rp = nlp->list; rp != NULL; rp = rp->list) {
 		if (rp->ns->ns_status == RFSWRITE_INITVAL) {
 			rp->ns->ns_status = puterrno(error);
@@ -1656,7 +1770,7 @@
 		}
 	}
 	cv_broadcast(&nlp->cv);
-	mutex_exit(&rfs_async_write_lock);
+	mutex_exit(&nsrv->async_write_lock);
 
 }
 
@@ -1718,6 +1832,12 @@
 		return;
 	}
 
+	if (protect_zfs_mntpt(dvp) != 0) {
+		VN_RELE(dvp);
+		dr->dr_status = NFSERR_ACCES;
+		return;
+	}
+
 	/*
 	 * This is a completely gross hack to make mknod
 	 * work over the wire until we can wack the protocol
@@ -2057,7 +2177,7 @@
 		*status = NFSERR_ACCES;
 		return;
 	}
-	exi_rele(to_exi);
+	exi_rele(&to_exi);
 
 	if (to_exi != exi) {
 		VN_RELE(fromvp);
@@ -2097,6 +2217,13 @@
 		return;
 	}
 
+	if (protect_zfs_mntpt(tovp) != 0) {
+		VN_RELE(tovp);
+		VN_RELE(fromvp);
+		*status = NFSERR_ACCES;
+		return;
+	}
+
 	/*
 	 * Check for a conflict with a non-blocking mandatory share reservation.
 	 */
@@ -2121,7 +2248,7 @@
 
 	/* Check for delegation on the file being renamed over, if it exists */
 
-	if (rfs4_deleg_policy != SRV_NEVER_DELEGATE &&
+	if (nfs4_get_deleg_policy() != SRV_NEVER_DELEGATE &&
 	    VOP_LOOKUP(tovp, args->rna_to.da_name, &targvp, NULL, 0, NULL, cr,
 	    NULL, NULL, NULL) == 0) {
 
@@ -2203,7 +2330,7 @@
 		*status = NFSERR_ACCES;
 		return;
 	}
-	exi_rele(to_exi);
+	exi_rele(&to_exi);
 
 	if (to_exi != exi) {
 		VN_RELE(fromvp);
@@ -2241,6 +2368,13 @@
 		return;
 	}
 
+	if (protect_zfs_mntpt(tovp) != 0) {
+		VN_RELE(tovp);
+		VN_RELE(fromvp);
+		*status = NFSERR_ACCES;
+		return;
+	}
+
 	error = VOP_LINK(tovp, fromvp, args->la_to.da_name, cr, NULL, 0);
 
 	/*
@@ -2263,7 +2397,7 @@
 
 /*
  * Symbolicly link to a file.
- * Create a file (to) with the given attributes which is a symbolic link
+ * Create a file (from) with the given attributes which is a symbolic link
  * to the given path name (to).
  */
 void
@@ -2311,6 +2445,12 @@
 		return;
 	}
 
+	if (protect_zfs_mntpt(vp) != 0) {
+		VN_RELE(vp);
+		*status = NFSERR_ACCES;
+		return;
+	}
+
 	ca = (struct sockaddr *)svc_getrpccaller(req->rq_xprt)->buf;
 	name = nfscmd_convname(ca, exi, args->sla_tnm,
 	    NFSCMD_CONV_INBOUND, MAXPATHLEN);
@@ -2403,6 +2543,12 @@
 		return;
 	}
 
+	if (protect_zfs_mntpt(vp) != 0) {
+		VN_RELE(vp);
+		dr->dr_status = NFSERR_ACCES;
+		return;
+	}
+
 	va.va_type = VDIR;
 	va.va_mask |= AT_TYPE;
 
@@ -2488,7 +2634,7 @@
 	 * supplying a vnode known to exist and illegal to
 	 * remove.
 	 */
-	error = VOP_RMDIR(vp, da->da_name, rootdir, cr, NULL, 0);
+	error = VOP_RMDIR(vp, da->da_name, ZONE_ROOTVP(), cr, NULL, 0);
 
 	/*
 	 * Force modified data and metadata out to stable storage.
@@ -2515,6 +2661,11 @@
 	return (da->da_fhandle);
 }
 
+#ifdef nextdp
+#undef nextdp
+#endif
+#define	nextdp(dp)	((struct dirent64 *)((char *)(dp) + (dp)->d_reclen))
+
 /* ARGSUSED */
 void
 rfs_readdir(struct nfsrddirargs *rda, struct nfsrddirres *rd,
@@ -2521,18 +2672,25 @@
     struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro)
 {
 	int error;
-	int iseof;
+	vnode_t *vp;
 	struct iovec iov;
 	struct uio uio;
-	vnode_t *vp;
-	char *ndata = NULL;
+	int iseof;
+
+	uint32_t count = rda->rda_count;
+	uint32_t size;		/* size of the readdirres structure */
+	int overflow = 0;
+
+	size_t datasz;
+	char *data = NULL;
+	dirent64_t *dp;
+
 	struct sockaddr *ca;
-	size_t nents;
-	int ret;
+	struct nfsentry **eptr;
+	struct nfsentry *entry;
 
 	vp = nfs_fhtovp(&rda->rda_fh, exi);
 	if (vp == NULL) {
-		rd->rd_entries = NULL;
 		rd->rd_status = NFSERR_STALE;
 		return;
 	}
@@ -2539,7 +2697,6 @@
 
 	if (vp->v_type != VDIR) {
 		VN_RELE(vp);
-		rd->rd_entries = NULL;
 		rd->rd_status = NFSERR_NOTDIR;
 		return;
 	}
@@ -2547,87 +2704,157 @@
 	(void) VOP_RWLOCK(vp, V_WRITELOCK_FALSE, NULL);
 
 	error = VOP_ACCESS(vp, VREAD, 0, cr, NULL);
-
-	if (error) {
-		rd->rd_entries = NULL;
+	if (error)
 		goto bad;
-	}
 
-	if (rda->rda_count == 0) {
-		rd->rd_entries = NULL;
-		rd->rd_size = 0;
-		rd->rd_eof = FALSE;
-		goto bad;
-	}
+	/*
+	 * Don't allow arbitrary counts for allocation
+	 */
+	if (count > NFS_MAXDATA)
+		count = NFS_MAXDATA;
 
-	rda->rda_count = MIN(rda->rda_count, NFS_MAXDATA);
+	/*
+	 * struct readdirres:
+	 *   status:		1
+	 *   entries (bool):	1
+	 *   eof:		1
+	 */
+	size = (1 + 1 + 1) * BYTES_PER_XDR_UNIT;
 
+	if (size > count) {
+		eptr = &rd->rd_entries;
+		iseof = 0;
+		size = 0;
+
+		goto done;
+	}
+
 	/*
-	 * Allocate data for entries.  This will be freed by rfs_rddirfree.
+	 * This is simplification.  The dirent64_t size is not the same as the
+	 * size of XDR representation of entry, but the sizes are similar so
+	 * we'll assume they are same.  This assumption should not cause any
+	 * harm.  In worst case we will need to issue VOP_READDIR() once more.
 	 */
-	rd->rd_bufsize = (uint_t)rda->rda_count;
-	rd->rd_entries = kmem_alloc(rd->rd_bufsize, KM_SLEEP);
+	datasz = count;
 
 	/*
-	 * Set up io vector to read directory data
+	 * Make sure that there is room to read at least one entry
+	 * if any are available.
 	 */
-	iov.iov_base = (caddr_t)rd->rd_entries;
-	iov.iov_len = rda->rda_count;
+	if (datasz < DIRENT64_RECLEN(MAXNAMELEN))
+		datasz = DIRENT64_RECLEN(MAXNAMELEN);
+
+	data = kmem_alloc(datasz, KM_NOSLEEP);
+	if (data == NULL) {
+		/* The allocation failed; downsize and wait for it this time */
+		if (datasz > MAXBSIZE)
+			datasz = MAXBSIZE;
+		data = kmem_alloc(datasz, KM_SLEEP);
+	}
+
 	uio.uio_iov = &iov;
 	uio.uio_iovcnt = 1;
 	uio.uio_segflg = UIO_SYSSPACE;
 	uio.uio_extflg = UIO_COPY_CACHED;
 	uio.uio_loffset = (offset_t)rda->rda_offset;
-	uio.uio_resid = rda->rda_count;
+	uio.uio_resid = datasz;
 
-	/*
-	 * read directory
-	 */
+	ca = (struct sockaddr *)svc_getrpccaller(req->rq_xprt)->buf;
+	eptr = &rd->rd_entries;
+	entry = NULL;
+
+getmoredents:
+	iov.iov_base = data;
+	iov.iov_len = datasz;
+
 	error = VOP_READDIR(vp, &uio, cr, &iseof, NULL, 0);
+	if (error) {
+		iseof = 0;
+		goto done;
+	}
 
-	/*
-	 * Clean up
-	 */
-	if (!error) {
-		/*
-		 * set size and eof
-		 */
-		if (uio.uio_resid == rda->rda_count) {
-			rd->rd_size = 0;
-			rd->rd_eof = TRUE;
-		} else {
-			rd->rd_size = (uint32_t)(rda->rda_count -
-			    uio.uio_resid);
-			rd->rd_eof = iseof ? TRUE : FALSE;
+	if (iov.iov_len == datasz)
+		goto done;
+
+	for (dp = (dirent64_t *)data;
+	    (char *)dp - data < datasz - iov.iov_len && !overflow;
+	    dp = nextdp(dp)) {
+		char *name;
+		uint32_t esize;
+		uint32_t cookie;
+
+		overflow = (uint64_t)dp->d_off > UINT32_MAX;
+		if (overflow) {
+			cookie = 0;
+			iseof = 1;
+		} else
+			cookie = (uint32_t)dp->d_off;
+
+		if (dp->d_ino == 0 || (uint64_t)dp->d_ino > UINT32_MAX) {
+			if (entry != NULL)
+				entry->cookie = cookie;
+			continue;
 		}
-	}
 
-	ca = (struct sockaddr *)svc_getrpccaller(req->rq_xprt)->buf;
-	nents = nfscmd_countents((char *)rd->rd_entries, rd->rd_size);
-	ret = nfscmd_convdirplus(ca, exi, (char *)rd->rd_entries, nents,
-	    rda->rda_count, &ndata);
+		name = nfscmd_convname(ca, exi, dp->d_name,
+		    NFSCMD_CONV_OUTBOUND, NFS_MAXPATHLEN + 1);
+		if (name == NULL) {
+			if (entry != NULL)
+				entry->cookie = cookie;
+			continue;
+		}
 
-	if (ret != 0) {
-		size_t dropbytes;
 		/*
-		 * We had to drop one or more entries in order to fit
-		 * during the character conversion.  We need to patch
-		 * up the size and eof info.
+		 * struct entry:
+		 *   fileid:		1
+		 *   name (length):	1
+		 *   name (data):	length (rounded up)
+		 *   cookie:		1
+		 *   nextentry (bool):	1
 		 */
-		if (rd->rd_eof)
-			rd->rd_eof = FALSE;
-		dropbytes = nfscmd_dropped_entrysize(
-		    (struct dirent64 *)rd->rd_entries, nents, ret);
-		rd->rd_size -= dropbytes;
+		esize = (1 + 1 + 1 + 1) * BYTES_PER_XDR_UNIT +
+		    RNDUP(strlen(name));
+
+		/* If the new entry does not fit, discard it */
+		if (esize > count - size) {
+			if (name != dp->d_name)
+				kmem_free(name, NFS_MAXPATHLEN + 1);
+			iseof = 0;
+			goto done;
+		}
+
+		entry = kmem_alloc(sizeof (struct nfsentry), KM_SLEEP);
+
+		entry->fileid = (uint32_t)dp->d_ino;
+		entry->name = strdup(name);
+		if (name != dp->d_name)
+			kmem_free(name, NFS_MAXPATHLEN + 1);
+		entry->cookie = cookie;
+
+		size += esize;
+
+		/* Add the entry to the linked list */
+		*eptr = entry;
+		eptr = &entry->nextentry;
 	}
-	if (ndata == NULL) {
-		ndata = (char *)rd->rd_entries;
-	} else if (ndata != (char *)rd->rd_entries) {
-		kmem_free(rd->rd_entries, rd->rd_bufsize);
-		rd->rd_entries = (void *)ndata;
-		rd->rd_bufsize = rda->rda_count;
+
+	if (!iseof && size < count) {
+		uio.uio_resid = MIN(datasz, MAXBSIZE);
+		goto getmoredents;
 	}
 
+done:
+	*eptr = NULL;
+
+	if (iseof || rd->rd_entries != NULL || !error) {
+		error = 0;
+		rd->rd_eof = iseof ? TRUE : FALSE;
+
+		/* This is for nfslog only */
+		rd->rd_offset = rda->rda_offset;
+		rd->rd_size = size;
+	}
+
 bad:
 	VOP_RWUNLOCK(vp, V_WRITELOCK_FALSE, NULL);
 
@@ -2647,6 +2874,8 @@
 
 	rd->rd_status = puterrno(error);
 
+	if (data != NULL)
+		kmem_free(data, datasz);
 }
 void *
 rfs_readdir_getfh(struct nfsrddirargs *rda)
@@ -2656,8 +2885,15 @@
 void
 rfs_rddirfree(struct nfsrddirres *rd)
 {
-	if (rd->rd_entries != NULL)
-		kmem_free(rd->rd_entries, rd->rd_bufsize);
+	if (rd->rd_status == NFS_OK) {
+		struct nfsentry *entry, *nentry;
+
+		for (entry = rd->rd_entries; entry != NULL; entry = nentry) {
+			nentry = entry->nextentry;
+			strfree(entry->name);
+			kmem_free(entry, sizeof (struct nfsentry));
+		}
+	}
 }
 
 /* ARGSUSED */
@@ -2763,7 +2999,7 @@
 	return (0);
 }
 
-static enum nfsftype vt_to_nf[] = {
+static const enum nfsftype vt_to_nf[] = {
 	0, NFREG, NFDIR, NFBLK, NFCHR, NFLNK, 0, 0, 0, NFSOC, 0
 };
 
@@ -2982,16 +3218,40 @@
 void
 rfs_srvrinit(void)
 {
-	mutex_init(&rfs_async_write_lock, NULL, MUTEX_DEFAULT, NULL);
 	nfs2_srv_caller_id = fs_new_caller_id();
+	zone_key_create(&rfs_zone_key, rfs_zone_init, NULL, rfs_zone_fini);
 }
 
 void
 rfs_srvrfini(void)
 {
-	mutex_destroy(&rfs_async_write_lock);
 }
 
+/* ARGSUSED */
+static void *
+rfs_zone_init(zoneid_t zoneid)
+{
+	nfs_srv_t *ns;
+
+	ns = kmem_zalloc(sizeof (*ns), KM_SLEEP);
+
+	mutex_init(&ns->async_write_lock, NULL, MUTEX_DEFAULT, NULL);
+	ns->write_async = 1;
+
+	return (ns);
+}
+
+/* ARGSUSED */
+static void
+rfs_zone_fini(zoneid_t zoneid, void *data)
+{
+	nfs_srv_t *ns;
+
+	ns = (nfs_srv_t *)data;
+	mutex_destroy(&ns->async_write_lock);
+	kmem_free(ns, sizeof (*ns));
+}
+
 static int
 rdma_setup_read_data2(struct nfsreadargs *ra, struct nfsrdresult *rr)
 {
