--- old/usr/src/cmd/fs.d/nfs/lib/sharetab.h	Tue Nov 26 15:08:27 2019
+++ new/usr/src/cmd/fs.d/nfs/lib/sharetab.h	Tue Nov 26 15:08:27 2019
@@ -28,7 +28,7 @@
  */
 
 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
-/*	  All Rights Reserved  	*/
+/*	All Rights Reserved	*/
 
 /*
  * Portions of this source code were derived from Berkeley 4.3 BSD
@@ -65,6 +65,7 @@
 #define	SHOPT_NOACLFAB	"noaclfab"
 #define	SHOPT_UIDMAP	"uidmap"
 #define	SHOPT_GIDMAP	"gidmap"
+#define	SHOPT_NOHIDE	"nohide"
 
 /* XXX The following are added for testing volatile fh's purposes only */
 #ifdef VOLATILE_FH_TEST
--- old/usr/src/lib/libshare/nfs/libshare_nfs.c	Tue Nov 26 15:08:27 2019
+++ new/usr/src/lib/libshare/nfs/libshare_nfs.c	Tue Nov 26 15:08:27 2019
@@ -174,8 +174,10 @@
 	{SHOPT_UIDMAP, OPT_UIDMAP, OPT_TYPE_MAPPING},
 #define	OPT_GIDMAP	19
 	{SHOPT_GIDMAP, OPT_GIDMAP, OPT_TYPE_MAPPING},
+#define	OPT_NOHIDE	20
+	{SHOPT_NOHIDE, OPT_NOHIDE, OPT_TYPE_BOOLEAN},
 #ifdef VOLATILE_FH_TEST	/* XXX added for testing volatile fh's only */
-#define	OPT_VOLFH	20
+#define	OPT_VOLFH	21
 	{SHOPT_VOLFH, OPT_VOLFH},
 #endif /* VOLATILE_FH_TEST */
 	NULL
@@ -1027,6 +1029,14 @@
 			else
 				export->ex_flags &= ~EX_NOACLFAB;
 			break;
+		case OPT_NOHIDE:
+			if (value != NULL && (strcasecmp(value, "true") == 0 ||
+			    strcmp(value, "1") == 0))
+				export->ex_flags |= EX_NOHIDE;
+			else
+				export->ex_flags &= ~EX_NOHIDE;
+
+			break;
 		default:
 			/* have a syntactic error */
 			(void) printf(dgettext(TEXT_DOMAIN,
--- old/usr/src/lib/libshare/nfs/libshare_nfs.h	Tue Nov 26 15:08:28 2019
+++ new/usr/src/lib/libshare/nfs/libshare_nfs.h	Tue Nov 26 15:08:28 2019
@@ -56,6 +56,7 @@
 #define	SHOPT_NOACLFAB	"noaclfab"
 #define	SHOPT_UIDMAP	"uidmap"
 #define	SHOPT_GIDMAP	"gidmap"
+#define	SHOPT_NOHIDE	"nohide"
 
 /*
  * defined options types. These should be in a file rather than
--- old/usr/src/man/man1m/share_nfs.1m	Tue Nov 26 15:08:28 2019
+++ new/usr/src/man/man1m/share_nfs.1m	Tue Nov 26 15:08:28 2019
@@ -172,6 +172,25 @@
 are used.
 Support of NFS server logging is only available for NFS Version 2 and
 Version 3 requests.
+.It Sy nohide
+By default, if server exports two filesystems, one of which is mounted as a
+child of the other, NFS Version 2 and Version 3 clients must mount both
+filesystems explicitly in order to access them.
+If a client only mounts the parent, it will see an empty directory at the
+location where the other filesystem is mounted.
+.Pp
+Setting the
+.Sy nohide
+option on a filesystem causes it to no longer be hidden in this manner, and the
+client will be able to move from the parent filesystem to this one without
+noticing the change.
+However, some NFS clients or applications may not function correctly when
+this option is used.
+In particular, files on different underlying filesystems may appear to have
+the same inode numbers.
+The
+.Sy nohide
+option only applies to NFS Version 2 and Version 3 requests.
 .It Sy noaclfab
 By default, the NFS server will fabricate POSIX-draft style ACLs in response
 to ACL requests from NFS Version 2 or Version 3 clients accessing shared
@@ -786,3 +805,14 @@
 does not work as it does with NFS Version 2 and Version 3 and the client
 receives an error when attempting to mount
 .Pa /export/foo .
+.Pp
+The
+.Sy nohide
+option violates RFC 1094,
+.%T "Network File System Protocol Specification"
+and RFC 1813,
+.%T "NFS: Network File System Version 3 Protocol Specification"
+.Pp
+The
+.Sy nohide
+option is provided for compatibility with Linux NFS.
--- old/usr/src/uts/common/fs/nfs/nfs3_srv.c	Tue Nov 26 15:08:28 2019
+++ new/usr/src/uts/common/fs/nfs/nfs3_srv.c	Tue Nov 26 15:08:28 2019
@@ -382,6 +382,9 @@
 
 	dvap = NULL;
 
+	if (exi != NULL)
+		exi_hold(exi);
+
 	/*
 	 * Allow lookups from the root - the default
 	 * location of the public filehandle.
@@ -420,8 +423,19 @@
 	fhp = &args->what.dir;
 	if (strcmp(args->what.name, "..") == 0 &&
 	    EQFID(&exi->exi_fid, FH3TOFIDP(fhp))) {
-		resp->status = NFS3ERR_NOENT;
-		goto out1;
+		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) {
+				resp->status = NFS3ERR_ACCES;
+				goto out1;
+			}
+		} else {
+			resp->status = NFS3ERR_NOENT;
+			goto out1;
+		}
 	}
 
 	ca = (struct sockaddr *)svc_getrpccaller(req->rq_xprt)->buf;
@@ -439,10 +453,12 @@
 	 */
 	if (PUBLIC_FH3(&args->what.dir)) {
 		publicfh_flag = TRUE;
+
+		exi_rele(exi);
+
 		error = rfs_publicfh_mclookup(name, dvp, cr, &vp,
 		    &exi, &sec);
-		if (error && exi != NULL)
-			exi_rele(exi); /* See comment below Re: publicfh_flag */
+
 		/*
 		 * Since WebNFS may bypass MOUNT, we need to ensure this
 		 * request didn't come from an unlabeled admin_low client.
@@ -464,8 +480,6 @@
 			if (tp == NULL || tp->tpc_tp.tp_doi !=
 			    l_admin_low->tsl_doi || tp->tpc_tp.host_type !=
 			    SUN_CIPSO) {
-				if (exi != NULL)
-					exi_rele(exi);
 				VN_RELE(vp);
 				error = EACCES;
 			}
@@ -480,6 +494,12 @@
 	if (name != args->what.name)
 		kmem_free(name, MAXPATHLEN + 1);
 
+	if (error == 0 && vn_ismntpt(vp)) {
+		error = rfs_cross_mnt(&vp, &exi);
+		if (error)
+			VN_RELE(vp);
+	}
+
 	if (is_system_labeled() && error == 0) {
 		bslabel_t *clabel = req->rq_label;
 
@@ -490,8 +510,6 @@
 		if (!blequal(&l_admin_low->tsl_label, clabel)) {
 			if (!do_rfs_label_check(clabel, dvp,
 			    DOMINANCE_CHECK, exi)) {
-				if (publicfh_flag && exi != NULL)
-					exi_rele(exi);
 				VN_RELE(vp);
 				error = EACCES;
 			}
@@ -512,15 +530,6 @@
 			auth_weak = TRUE;
 	}
 
-	/*
-	 * 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 that the original exportinfo pointed to by exi
-	 * will be released by the caller, common_dispatch.
-	 */
-	if (publicfh_flag)
-		exi_rele(exi);
-
 	if (error) {
 		VN_RELE(vp);
 		goto out;
@@ -529,6 +538,7 @@
 	va.va_mask = AT_ALL;
 	vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va;
 
+	exi_rele(exi);
 	VN_RELE(vp);
 
 	resp->status = NFS3_OK;
@@ -556,6 +566,9 @@
 	} else
 		resp->status = puterrno3(error);
 out1:
+	if (exi != NULL)
+		exi_rele(exi);
+
 	DTRACE_NFSV3_4(op__lookup__done, struct svc_req *, req,
 	    cred_t *, cr, vnode_t *, dvp, LOOKUP3res *, resp);
 
@@ -3609,14 +3622,19 @@
 		if (vn_is_nfs_reparse(nvp, cr))
 			nvap->va_type = VLNK;
 
-		vattr_to_post_op_attr(nvap, &infop[i].attr);
-
-		error = makefh3(&infop[i].fh.handle, nvp, exi);
-		if (!error)
-			infop[i].fh.handle_follows = TRUE;
-		else
+		if (vn_ismntpt(nvp)) {
+			infop[i].attr.attributes = FALSE;
 			infop[i].fh.handle_follows = FALSE;
+		} else {
+			vattr_to_post_op_attr(nvap, &infop[i].attr);
 
+			error = makefh3(&infop[i].fh.handle, nvp, exi);
+			if (!error)
+				infop[i].fh.handle_follows = TRUE;
+			else
+				infop[i].fh.handle_follows = FALSE;
+		}
+
 		VN_RELE(nvp);
 		dp = nextdp(dp);
 	}
--- old/usr/src/uts/common/fs/nfs/nfs_srv.c	Tue Nov 26 15:08:28 2019
+++ new/usr/src/uts/common/fs/nfs/nfs_srv.c	Tue Nov 26 15:08:28 2019
@@ -18,9 +18,10 @@
  *
  * CDDL HEADER END
  */
+
 /*
  * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
@@ -329,7 +330,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.
  */
@@ -381,6 +456,8 @@
 		}
 	}
 
+	exi_hold(exi);
+
 	/*
 	 * Not allow lookup beyond root.
 	 * If the filehandle matches a filehandle of the exi,
@@ -388,9 +465,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 +485,8 @@
 	    MAXPATHLEN);
 
 	if (name == NULL) {
-		dr->dr_status = NFSERR_ACCES;
-		return;
+		error = NFSERR_ACCES;
+		goto out;
 	}
 
 	/*
@@ -413,6 +500,9 @@
 	 */
 	if (PUBLIC_FH2(fhp)) {
 		publicfh_flag = TRUE;
+
+		exi_rele(exi);
+
 		error = rfs_publicfh_mclookup(name, dvp, cr, &vp, &exi,
 		    &sec);
 	} else {
@@ -426,6 +516,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,15 +547,10 @@
 		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)
+	if (exi != NULL)
 		exi_rele(exi);
 
 	/*
--- old/usr/src/uts/common/nfs/export.h	Tue Nov 26 15:08:28 2019
+++ new/usr/src/uts/common/nfs/export.h	Tue Nov 26 15:08:28 2019
@@ -20,8 +20,8 @@
  */
 
 /*
- * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2016 Jason King.
  */
 
@@ -68,7 +68,7 @@
 	int32_t		s_refcnt;	/* reference count for tracking */
 					/* how many children (self included) */
 					/* use this flavor. */
-	int 		s_window;	/* window */
+	int		s_window;	/* window */
 	uint_t		s_rootid;	/* UID to use for authorized roots */
 	int		s_rootcnt;	/* count of root names */
 	caddr_t		*s_rootnames;	/* array of root names */
@@ -83,7 +83,7 @@
 	int32_t		s_refcnt;	/* reference count for tracking */
 					/* how many children (self included) */
 					/* use this flavor. */
-	int32_t 	s_window;	/* window */
+	int32_t		s_window;	/* window */
 	uint32_t	s_rootid;	/* UID to use for authorized roots */
 	int32_t		s_rootcnt;	/* count of root names */
 	caddr32_t	s_rootnames;	/* array of root names */
@@ -189,6 +189,7 @@
 #define	EX_NOACLFAB	0x2000	/* If set, NFSv2 and v3 servers won't */
 				/* fabricate an aclent_t ACL on file systems */
 				/* that don't support aclent_t ACLs */
+#define	EX_NOHIDE	0x4000	/* traversable from exported parent */
 
 #ifdef	_KERNEL
 
--- old/usr/src/uts/common/nfs/nfs.h	Tue Nov 26 15:08:29 2019
+++ new/usr/src/uts/common/nfs/nfs.h	Tue Nov 26 15:08:29 2019
@@ -31,7 +31,7 @@
 #ifndef	_NFS_NFS_H
 #define	_NFS_NFS_H
 
-/*	nfs.h 2.38 88/08/19 SMI 	*/
+/*	nfs.h 2.38 88/08/19 SMI	*/
 
 #include <sys/isa_defs.h>
 #include <sys/vfs.h>
@@ -2278,6 +2278,8 @@
     struct exportinfo **, struct sec_ol *);
 extern int	rfs_pathname(char *, vnode_t **, vnode_t **, vnode_t *,
     cred_t *, int);
+extern int	rfs_cross_mnt(vnode_t **, struct exportinfo **);
+extern int	rfs_climb_crossmnt(vnode_t **, struct exportinfo **, cred_t *);
 
 extern vtype_t		nf3_to_vt[];
 extern kstat_named_t	*rfsproccnt_v3_ptr;
