[Midnightbsd-cvs] src [10020] trunk/sys/fs/tmpfs: sync tmpfs with freebsd 10 stable

laffer1 at midnightbsd.org laffer1 at midnightbsd.org
Sun May 27 18:08:26 EDT 2018


Revision: 10020
          http://svnweb.midnightbsd.org/src/?rev=10020
Author:   laffer1
Date:     2018-05-27 18:08:25 -0400 (Sun, 27 May 2018)
Log Message:
-----------
sync tmpfs with freebsd 10 stable

Modified Paths:
--------------
    trunk/sys/fs/tmpfs/tmpfs.h
    trunk/sys/fs/tmpfs/tmpfs_fifoops.c
    trunk/sys/fs/tmpfs/tmpfs_fifoops.h
    trunk/sys/fs/tmpfs/tmpfs_subr.c
    trunk/sys/fs/tmpfs/tmpfs_vfsops.c
    trunk/sys/fs/tmpfs/tmpfs_vnops.c
    trunk/sys/fs/tmpfs/tmpfs_vnops.h

Modified: trunk/sys/fs/tmpfs/tmpfs.h
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs.h	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs.h	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs.h,v 1.26 2007/02/22 06:37:00 thorpej Exp $	*/
 
 /*-
@@ -29,15 +30,12 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  *
- * $MidnightBSD$
+ * $FreeBSD: stable/10/sys/fs/tmpfs/tmpfs.h 313095 2017-02-02 13:39:11Z kib $
  */
 
 #ifndef _FS_TMPFS_TMPFS_H_
 #define _FS_TMPFS_TMPFS_H_
 
-/* ---------------------------------------------------------------------
- * KERNEL-SPECIFIC DEFINITIONS
- * --------------------------------------------------------------------- */
 #include <sys/dirent.h>
 #include <sys/mount.h>
 #include <sys/queue.h>
@@ -46,9 +44,9 @@
 #include <sys/lock.h>
 #include <sys/mutex.h>
 
-/* --------------------------------------------------------------------- */
 #include <sys/malloc.h>
 #include <sys/systm.h>
+#include <sys/tree.h>
 #include <sys/vmmeter.h>
 #include <vm/swap_pager.h>
 
@@ -55,112 +53,90 @@
 MALLOC_DECLARE(M_TMPFSMNT);
 MALLOC_DECLARE(M_TMPFSNAME);
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Internal representation of a tmpfs directory entry.
  */
+
+LIST_HEAD(tmpfs_dir_duphead, tmpfs_dirent);
+
 struct tmpfs_dirent {
-	TAILQ_ENTRY(tmpfs_dirent)	td_entries;
+	/*
+	 * Depending on td_cookie flag entry can be of 3 types:
+	 * - regular -- no hash collisions, stored in RB-Tree
+	 * - duphead -- synthetic linked list head for dup entries
+	 * - dup -- stored in linked list instead of RB-Tree
+	 */
+	union {
+		/* regular and duphead entry types */
+		RB_ENTRY(tmpfs_dirent)		td_entries;
 
-	/* Length of the name stored in this directory entry.  This avoids
-	 * the need to recalculate it every time the name is used. */
-	uint16_t			td_namelen;
+		/* dup entry type */
+		struct {
+			LIST_ENTRY(tmpfs_dirent) entries;
+			LIST_ENTRY(tmpfs_dirent) index_entries;
+		} td_dup;
+	} uh;
 
-	/* The name of the entry, allocated from a string pool.  This
-	* string is not required to be zero-terminated; therefore, the
-	* td_namelen field must always be used when accessing its value. */
-	char *				td_name;
+	uint32_t			td_cookie;
+	uint32_t			td_hash;
+	u_int				td_namelen;
 
-	/* Pointer to the node this entry refers to.  In case this field
-	 * is NULL, the node is a whiteout. */
+	/*
+	 * Pointer to the node this entry refers to.  In case this field
+	 * is NULL, the node is a whiteout.
+	 */
 	struct tmpfs_node *		td_node;
+
+	union {
+		/*
+		 * The name of the entry, allocated from a string pool.  This
+		 * string is not required to be zero-terminated.
+		 */
+		char *			td_name;	/* regular, dup */
+		struct tmpfs_dir_duphead td_duphead;	/* duphead */
+	} ud;
 };
 
-/* A directory in tmpfs holds a sorted list of directory entries, which in
- * turn point to other files (which can be directories themselves).
+/*
+ * A directory in tmpfs holds a collection of directory entries, which
+ * in turn point to other files (which can be directories themselves).
  *
- * In tmpfs, this list is managed by a tail queue, whose head is defined by
- * the struct tmpfs_dir type.
+ * In tmpfs, this collection is managed by a RB-Tree, whose head is
+ * defined by the struct tmpfs_dir type.
  *
- * It is imporant to notice that directories do not have entries for . and
+ * It is important to notice that directories do not have entries for . and
  * .. as other file systems do.  These can be generated when requested
  * based on information available by other means, such as the pointer to
  * the node itself in the former case or the pointer to the parent directory
  * in the latter case.  This is done to simplify tmpfs's code and, more
- * importantly, to remove redundancy. */
-TAILQ_HEAD(tmpfs_dir, tmpfs_dirent);
+ * importantly, to remove redundancy.
+ */
+RB_HEAD(tmpfs_dir, tmpfs_dirent);
 
-/* Each entry in a directory has a cookie that identifies it.  Cookies
+/*
+ * Each entry in a directory has a cookie that identifies it.  Cookies
  * supersede offsets within directories because, given how tmpfs stores
- * directories in memory, there is no such thing as an offset.  (Emulating
- * a real offset could be very difficult.)
- * 
+ * directories in memory, there is no such thing as an offset.
+ *
  * The '.', '..' and the end of directory markers have fixed cookies which
  * cannot collide with the cookies generated by other entries.  The cookies
- * fot the other entries are generated based on the memory address on which
- * stores their information is stored.
+ * for the other entries are generated based on the file name hash value or
+ * unique number in case of name hash collision.
  *
- * Ideally, using the entry's memory pointer as the cookie would be enough
- * to represent it and it wouldn't cause collisions in any system.
- * Unfortunately, this results in "offsets" with very large values which
- * later raise problems in the Linux compatibility layer (and maybe in other
- * places) as described in PR kern/32034.  Hence we need to workaround this
- * with a rather ugly hack.
- *
- * Linux 32-bit binaries, unless built with _FILE_OFFSET_BITS=64, have off_t
- * set to 'long', which is a 32-bit *signed* long integer.  Regardless of
- * the macro value, GLIBC (2.3 at least) always uses the getdents64
- * system call (when calling readdir) which internally returns off64_t
- * offsets.  In order to make 32-bit binaries work, *GLIBC* converts the
- * 64-bit values returned by the kernel to 32-bit ones and aborts with
- * EOVERFLOW if the conversion results in values that won't fit in 32-bit
- * integers (which it assumes is because the directory is extremely large).
- * This wouldn't cause problems if we were dealing with unsigned integers,
- * but as we have signed integers, this check fails due to sign expansion.
- *
- * For example, consider that the kernel returns the 0xc1234567 cookie to
- * userspace in a off64_t integer.  Later on, GLIBC casts this value to
- * off_t (remember, signed) with code similar to:
- *     system call returns the offset in kernel_value;
- *     off_t casted_value = kernel_value;
- *     if (sizeof(off_t) != sizeof(off64_t) &&
- *         kernel_value != casted_value)
- *             error!
- * In this case, casted_value still has 0xc1234567, but when it is compared
- * for equality against kernel_value, it is promoted to a 64-bit integer and
- * becomes 0xffffffffc1234567, which is different than 0x00000000c1234567.
- * Then, GLIBC assumes this is because the directory is very large.
- *
- * Given that all the above happens in user-space, we have no control over
- * it; therefore we must workaround the issue here.  We do this by
- * truncating the pointer value to a 32-bit integer and hope that there
- * won't be collisions.  In fact, this will not cause any problems in
- * 32-bit platforms but some might arise in 64-bit machines (I'm not sure
- * if they can happen at all in practice).
- *
- * XXX A nicer solution shall be attempted. */
-#ifdef _KERNEL
-#define	TMPFS_DIRCOOKIE_DOT	0
-#define	TMPFS_DIRCOOKIE_DOTDOT	1
-#define	TMPFS_DIRCOOKIE_EOF	2
-static __inline
-off_t
-tmpfs_dircookie(struct tmpfs_dirent *de)
-{
-	off_t cookie;
+ * To preserve compatibility cookies are limited to 31 bits.
+ */
 
-	cookie = ((off_t)(uintptr_t)de >> 1) & 0x7FFFFFFF;
-	MPASS(cookie != TMPFS_DIRCOOKIE_DOT);
-	MPASS(cookie != TMPFS_DIRCOOKIE_DOTDOT);
-	MPASS(cookie != TMPFS_DIRCOOKIE_EOF);
+#define	TMPFS_DIRCOOKIE_DOT		0
+#define	TMPFS_DIRCOOKIE_DOTDOT		1
+#define	TMPFS_DIRCOOKIE_EOF		2
+#define	TMPFS_DIRCOOKIE_MASK		((off_t)0x3fffffffU)
+#define	TMPFS_DIRCOOKIE_MIN		((off_t)0x00000004U)
+#define	TMPFS_DIRCOOKIE_DUP		((off_t)0x40000000U)
+#define	TMPFS_DIRCOOKIE_DUPHEAD		((off_t)0x80000000U)
+#define	TMPFS_DIRCOOKIE_DUP_MIN		TMPFS_DIRCOOKIE_DUP
+#define	TMPFS_DIRCOOKIE_DUP_MAX		\
+	(TMPFS_DIRCOOKIE_DUP | TMPFS_DIRCOOKIE_MASK)
 
-	return cookie;
-}
-#endif
-
-/* --------------------------------------------------------------------- */
-
 /*
  * Internal representation of a tmpfs file system node.
  *
@@ -169,51 +145,67 @@
  * a particular type.  The code must be careful to only access those
  * attributes that are actually allowed by the node's type.
  *
- *
  * Below is the key of locks used to protected the fields in the following
  * structures.
- *
+ * (v)  vnode lock in exclusive mode
+ * (vi) vnode lock in exclusive mode, or vnode lock in shared vnode and
+ *	tn_interlock
+ * (i)  tn_interlock
+ * (m)  tmpfs_mount tm_allnode_lock
+ * (c)  stable after creation
  */
 struct tmpfs_node {
-	/* Doubly-linked list entry which links all existing nodes for a
-	 * single file system.  This is provided to ease the removal of
-	 * all nodes during the unmount operation. */
-	LIST_ENTRY(tmpfs_node)	tn_entries;
+	/*
+	 * Doubly-linked list entry which links all existing nodes for
+	 * a single file system.  This is provided to ease the removal
+	 * of all nodes during the unmount operation, and to support
+	 * the implementation of VOP_VNTOCNP().  tn_attached is false
+	 * when the node is removed from list and unlocked.
+	 */
+	LIST_ENTRY(tmpfs_node)	tn_entries;	/* (m) */
+	bool			tn_attached;	/* (m) */
 
-	/* The node's type.  Any of 'VBLK', 'VCHR', 'VDIR', 'VFIFO',
+	/*
+	 * The node's type.  Any of 'VBLK', 'VCHR', 'VDIR', 'VFIFO',
 	 * 'VLNK', 'VREG' and 'VSOCK' is allowed.  The usage of vnode
 	 * types instead of a custom enumeration is to make things simpler
-	 * and faster, as we do not need to convert between two types. */
-	enum vtype		tn_type;
+	 * and faster, as we do not need to convert between two types.
+	 */
+	enum vtype		tn_type;	/* (c) */
 
 	/* Node identifier. */
-	ino_t			tn_id;
+	ino_t			tn_id;		/* (c) */
 
-	/* Node's internal status.  This is used by several file system
+	/*
+	 * Node's internal status.  This is used by several file system
 	 * operations to do modifications to the node in a delayed
-	 * fashion. */
-	int			tn_status;
+	 * fashion.
+	 */
+	int			tn_status;	/* (vi) */
 #define	TMPFS_NODE_ACCESSED	(1 << 1)
 #define	TMPFS_NODE_MODIFIED	(1 << 2)
 #define	TMPFS_NODE_CHANGED	(1 << 3)
 
-	/* The node size.  It does not necessarily match the real amount
-	 * of memory consumed by it. */
-	off_t			tn_size;
+	/*
+	 * The node size.  It does not necessarily match the real amount
+	 * of memory consumed by it.
+	 */
+	off_t			tn_size;	/* (v) */
 
 	/* Generic node attributes. */
-	uid_t			tn_uid;
-	gid_t			tn_gid;
-	mode_t			tn_mode;
-	int			tn_flags;
-	nlink_t			tn_links;
-	struct timespec		tn_atime;
-	struct timespec		tn_mtime;
-	struct timespec		tn_ctime;
-	struct timespec		tn_birthtime;
-	unsigned long		tn_gen;
+	uid_t			tn_uid;		/* (v) */
+	gid_t			tn_gid;		/* (v) */
+	mode_t			tn_mode;	/* (v) */
+	u_long			tn_flags;	/* (v) */
+	nlink_t			tn_links;	/* (v) */
+	struct timespec		tn_atime;	/* (vi) */
+	struct timespec		tn_mtime;	/* (vi) */
+	struct timespec		tn_ctime;	/* (vi) */
+	struct timespec		tn_birthtime;	/* (v) */
+	unsigned long		tn_gen;		/* (c) */
 
-	/* As there is a single vnode for each active file within the
+	/*
+	 * As there is a single vnode for each active file within the
 	 * system, care has to be taken to avoid allocating more than one
 	 * vnode per file.  In order to do this, a bidirectional association
 	 * is kept between vnodes and nodes.
@@ -226,72 +218,84 @@
 	 * tn_vnode.
 	 *
 	 * May be NULL when the node is unused (that is, no vnode has been
-	 * allocated for it or it has been reclaimed). */
-	struct vnode *		tn_vnode;
+	 * allocated for it or it has been reclaimed).
+	 */
+	struct vnode *		tn_vnode;	/* (i) */
 
-	/* interlock to protect tn_vpstate */
+	/*
+	 * Interlock to protect tn_vpstate, and tn_status under shared
+	 * vnode lock.
+	 */
 	struct mtx	tn_interlock;
 
-	/* Identify if current node has vnode assiocate with
+	/*
+	 * Identify if current node has vnode assiocate with
 	 * or allocating vnode.
 	 */
-	int		tn_vpstate;
+	int		tn_vpstate;		/* (i) */
 
+	/* Transient refcounter on this node. */
+	u_int		tn_refcount;		/* (m) + (i) */
+
 	/* misc data field for different tn_type node */
 	union {
 		/* Valid when tn_type == VBLK || tn_type == VCHR. */
-		dev_t			tn_rdev;
+		dev_t			tn_rdev;	/* (c) */
 
 		/* Valid when tn_type == VDIR. */
-		struct tn_dir{
-			/* Pointer to the parent directory.  The root
+		struct tn_dir {
+			/*
+			 * Pointer to the parent directory.  The root
 			 * directory has a pointer to itself in this field;
-			 * this property identifies the root node. */
+			 * this property identifies the root node.
+			 */
 			struct tmpfs_node *	tn_parent;
 
-			/* Head of a tail-queue that links the contents of
-			 * the directory together.  See above for a
-			 * description of its contents. */
+			/*
+			 * Head of a tree that links the contents of
+			 * the directory together.
+			 */
 			struct tmpfs_dir	tn_dirhead;
 
-			/* Number and pointer of the first directory entry
+			/*
+			 * Head of a list the contains fake directory entries
+			 * heads, i.e. entries with TMPFS_DIRCOOKIE_DUPHEAD
+			 * flag.
+			 */
+			struct tmpfs_dir_duphead tn_dupindex;
+
+			/*
+			 * Number and pointer of the first directory entry
 			 * returned by the readdir operation if it were
 			 * called again to continue reading data from the
 			 * same directory as before.  This is used to speed
 			 * up reads of long directories, assuming that no
 			 * more than one read is in progress at a given time.
-			 * Otherwise, these values are discarded and a linear
-			 * scan is performed from the beginning up to the
-			 * point where readdir starts returning values. */
+			 * Otherwise, these values are discarded.
+			 */
 			off_t			tn_readdir_lastn;
 			struct tmpfs_dirent *	tn_readdir_lastp;
-		}tn_dir;
+		} tn_dir;
 
 		/* Valid when tn_type == VLNK. */
 		/* The link's target, allocated from a string pool. */
-		char *			tn_link;
+		char *			tn_link;	/* (c) */
 
 		/* Valid when tn_type == VREG. */
 		struct tn_reg {
-			/* The contents of regular files stored in a tmpfs
-			 * file system are represented by a single anonymous
-			 * memory object (aobj, for short).  The aobj provides
-			 * direct access to any position within the file,
-			 * because its contents are always mapped in a
-			 * contiguous region of virtual memory.  It is a task
-			 * of the memory management subsystem (see uvm(9)) to
-			 * issue the required page ins or page outs whenever
-			 * a position within the file is accessed. */
-			vm_object_t		tn_aobj;
-
-		}tn_reg;
-
-		/* Valid when tn_type = VFIFO */
-		struct tn_fifo {
-			fo_rdwr_t		*tn_fo_read;
-			fo_rdwr_t		*tn_fo_write;
-		}tn_fifo;
-	}tn_spec;
+			/*
+			 * The contents of regular files stored in a
+			 * tmpfs file system are represented by a
+			 * single anonymous memory object (aobj, for
+			 * short).  The aobj provides direct access to
+			 * any position within the file.  It is a task
+			 * of the memory management subsystem to issue
+			 * the required page ins or page outs whenever
+			 * a position within the file is accessed.
+			 */
+			vm_object_t		tn_aobj;	/* (c) */
+		} tn_reg;
+	} tn_spec;	/* (v) */
 };
 LIST_HEAD(tmpfs_node_list, tmpfs_node);
 
@@ -304,55 +308,54 @@
 #define TMPFS_NODE_LOCK(node) mtx_lock(&(node)->tn_interlock)
 #define TMPFS_NODE_UNLOCK(node) mtx_unlock(&(node)->tn_interlock)
 #define TMPFS_NODE_MTX(node) (&(node)->tn_interlock)
+#define	TMPFS_NODE_ASSERT_LOCKED(node) mtx_assert(TMPFS_NODE_MTX(node), \
+    MA_OWNED)
 
 #ifdef INVARIANTS
 #define TMPFS_ASSERT_LOCKED(node) do {					\
-		MPASS(node != NULL);					\
-		MPASS(node->tn_vnode != NULL);				\
-		if (!VOP_ISLOCKED(node->tn_vnode) &&			\
-		    !mtx_owned(TMPFS_NODE_MTX(node)))			\
-			panic("tmpfs: node is not locked: %p", node);	\
-	} while (0)
-#define TMPFS_ASSERT_ELOCKED(node) do {					\
 		MPASS((node) != NULL);					\
 		MPASS((node)->tn_vnode != NULL);			\
-		mtx_assert(TMPFS_NODE_MTX(node), MA_OWNED);		\
-		ASSERT_VOP_LOCKED((node)->tn_vnode, "tmpfs");		\
+		ASSERT_VOP_LOCKED((node)->tn_vnode, "tmpfs assert");	\
 	} while (0)
 #else
 #define TMPFS_ASSERT_LOCKED(node) (void)0
-#define TMPFS_ASSERT_ELOCKED(node) (void)0
 #endif
 
 #define TMPFS_VNODE_ALLOCATING	1
 #define TMPFS_VNODE_WANT	2
 #define TMPFS_VNODE_DOOMED	4
-/* --------------------------------------------------------------------- */
+#define	TMPFS_VNODE_WRECLAIM	8
 
 /*
  * Internal representation of a tmpfs mount point.
  */
 struct tmpfs_mount {
-	/* Maximum number of memory pages available for use by the file
+	/*
+	 * Maximum number of memory pages available for use by the file
 	 * system, set during mount time.  This variable must never be
 	 * used directly as it may be bigger than the current amount of
-	 * free memory; in the extreme case, it will hold the SIZE_MAX
-	 * value. */
-	size_t			tm_pages_max;
+	 * free memory; in the extreme case, it will hold the ULONG_MAX
+	 * value.
+	 */
+	u_long			tm_pages_max;
 
 	/* Number of pages in use by the file system. */
-	size_t			tm_pages_used;
+	u_long			tm_pages_used;
 
-	/* Pointer to the node representing the root directory of this
-	 * file system. */
+	/*
+	 * Pointer to the node representing the root directory of this
+	 * file system.
+	 */
 	struct tmpfs_node *	tm_root;
 
-	/* Maximum number of possible nodes for this file system; set
+	/*
+	 * Maximum number of possible nodes for this file system; set
 	 * during mount time.  We need a hard limit on the maximum number
 	 * of nodes to avoid allocating too much of them; their objects
 	 * cannot be released until the file system is unmounted.
 	 * Otherwise, we could easily run out of memory by creating lots
-	 * of empty files and then simply removing them. */
+	 * of empty files and then simply removing them.
+	 */
 	ino_t			tm_nodes_max;
 
 	/* unrhdr used to allocate inode numbers */
@@ -361,41 +364,34 @@
 	/* Number of nodes currently that are in use. */
 	ino_t			tm_nodes_inuse;
 
+	/* Refcounter on this struct tmpfs_mount. */
+	uint64_t		tm_refcount;
+
 	/* maximum representable file size */
 	u_int64_t		tm_maxfilesize;
 
-	/* Nodes are organized in two different lists.  The used list
-	 * contains all nodes that are currently used by the file system;
-	 * i.e., they refer to existing files.  The available list contains
-	 * all nodes that are currently available for use by new files.
-	 * Nodes must be kept in this list (instead of deleting them)
-	 * because we need to keep track of their generation number (tn_gen
-	 * field).
-	 *
-	 * Note that nodes are lazily allocated: if the available list is
-	 * empty and we have enough space to create more nodes, they will be
-	 * created and inserted in the used list.  Once these are released,
-	 * they will go into the available list, remaining alive until the
-	 * file system is unmounted. */
+	/*
+	 * The used list contains all nodes that are currently used by
+	 * the file system; i.e., they refer to existing files.
+	 */
 	struct tmpfs_node_list	tm_nodes_used;
 
-	/* All node lock to protect the node list and tmp_pages_used */
-	struct mtx allnode_lock;
+	/* All node lock to protect the node list and tmp_pages_used. */
+	struct mtx		tm_allnode_lock;
 
-	/* Pools used to store file system meta data.  These are not shared
-	 * across several instances of tmpfs for the reasons described in
-	 * tmpfs_pool.c. */
+	/* Zones used to store file system meta data, per tmpfs mount. */
 	uma_zone_t		tm_dirent_pool;
 	uma_zone_t		tm_node_pool;
 
 	/* Read-only status. */
-	int			tm_ronly;
+	bool			tm_ronly;
+	/* Do not use namecache. */
+	bool			tm_nonc;
 };
-#define TMPFS_LOCK(tm) mtx_lock(&(tm)->allnode_lock)
-#define TMPFS_UNLOCK(tm) mtx_unlock(&(tm)->allnode_lock)
+#define	TMPFS_LOCK(tm) mtx_lock(&(tm)->tm_allnode_lock)
+#define	TMPFS_UNLOCK(tm) mtx_unlock(&(tm)->tm_allnode_lock)
+#define	TMPFS_MP_ASSERT_LOCKED(tm) mtx_assert(&(tm)->tm_allnode_lock, MA_OWNED)
 
-/* --------------------------------------------------------------------- */
-
 /*
  * This structure maps a file identifier to a tmpfs node.  Used by the
  * NFS code.
@@ -407,7 +403,10 @@
 	unsigned long		tf_gen;
 };
 
-/* --------------------------------------------------------------------- */
+struct tmpfs_dir_cursor {
+	struct tmpfs_dirent	*tdc_current;
+	struct tmpfs_dirent	*tdc_tree;
+};
 
 #ifdef _KERNEL
 /*
@@ -414,46 +413,54 @@
  * Prototypes for tmpfs_subr.c.
  */
 
-int	tmpfs_alloc_node(struct tmpfs_mount *, enum vtype,
+void	tmpfs_ref_node(struct tmpfs_node *node);
+void	tmpfs_ref_node_locked(struct tmpfs_node *node);
+int	tmpfs_alloc_node(struct mount *mp, struct tmpfs_mount *, enum vtype,
 	    uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *,
 	    char *, dev_t, struct tmpfs_node **);
 void	tmpfs_free_node(struct tmpfs_mount *, struct tmpfs_node *);
+bool	tmpfs_free_node_locked(struct tmpfs_mount *, struct tmpfs_node *, bool);
+void	tmpfs_free_tmp(struct tmpfs_mount *);
 int	tmpfs_alloc_dirent(struct tmpfs_mount *, struct tmpfs_node *,
-	    const char *, uint16_t, struct tmpfs_dirent **);
-void	tmpfs_free_dirent(struct tmpfs_mount *, struct tmpfs_dirent *,
-	    boolean_t);
+	    const char *, u_int, struct tmpfs_dirent **);
+void	tmpfs_free_dirent(struct tmpfs_mount *, struct tmpfs_dirent *);
+void	tmpfs_dirent_init(struct tmpfs_dirent *, const char *, u_int);
+void	tmpfs_destroy_vobject(struct vnode *vp, vm_object_t obj);
 int	tmpfs_alloc_vp(struct mount *, struct tmpfs_node *, int,
 	    struct vnode **);
 void	tmpfs_free_vp(struct vnode *);
 int	tmpfs_alloc_file(struct vnode *, struct vnode **, struct vattr *,
 	    struct componentname *, char *);
+void	tmpfs_check_mtime(struct vnode *);
 void	tmpfs_dir_attach(struct vnode *, struct tmpfs_dirent *);
 void	tmpfs_dir_detach(struct vnode *, struct tmpfs_dirent *);
+void	tmpfs_dir_destroy(struct tmpfs_mount *, struct tmpfs_node *);
 struct tmpfs_dirent *	tmpfs_dir_lookup(struct tmpfs_node *node,
 			    struct tmpfs_node *f,
 			    struct componentname *cnp);
-int	tmpfs_dir_getdotdent(struct tmpfs_node *, struct uio *);
-int	tmpfs_dir_getdotdotdent(struct tmpfs_node *, struct uio *);
-struct tmpfs_dirent *	tmpfs_dir_lookupbycookie(struct tmpfs_node *, off_t);
-int	tmpfs_dir_getdents(struct tmpfs_node *, struct uio *, off_t *);
+int	tmpfs_dir_getdents(struct tmpfs_node *, struct uio *, int,
+	    u_long *, int *);
 int	tmpfs_dir_whiteout_add(struct vnode *, struct componentname *);
 void	tmpfs_dir_whiteout_remove(struct vnode *, struct componentname *);
 int	tmpfs_reg_resize(struct vnode *, off_t, boolean_t);
-int	tmpfs_chflags(struct vnode *, int, struct ucred *, struct thread *);
+int	tmpfs_chflags(struct vnode *, u_long, struct ucred *, struct thread *);
 int	tmpfs_chmod(struct vnode *, mode_t, struct ucred *, struct thread *);
 int	tmpfs_chown(struct vnode *, uid_t, gid_t, struct ucred *,
 	    struct thread *);
 int	tmpfs_chsize(struct vnode *, u_quad_t, struct ucred *, struct thread *);
-int	tmpfs_chtimes(struct vnode *, struct timespec *, struct timespec *,
-	    struct timespec *, int, struct ucred *, struct thread *);
+int	tmpfs_chtimes(struct vnode *, struct vattr *, struct ucred *cred,
+	    struct thread *);
 void	tmpfs_itimes(struct vnode *, const struct timespec *,
 	    const struct timespec *);
 
+void	tmpfs_set_status(struct tmpfs_node *node, int status);
 void	tmpfs_update(struct vnode *);
 int	tmpfs_truncate(struct vnode *, off_t);
+struct tmpfs_dirent *tmpfs_dir_first(struct tmpfs_node *dnode,
+	    struct tmpfs_dir_cursor *dc);
+struct tmpfs_dirent *tmpfs_dir_next(struct tmpfs_node *dnode,
+	    struct tmpfs_dir_cursor *dc);
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Convenience macros to simplify some logical expressions.
  */
@@ -460,35 +467,24 @@
 #define IMPLIES(a, b) (!(a) || (b))
 #define IFF(a, b) (IMPLIES(a, b) && IMPLIES(b, a))
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Checks that the directory entry pointed by 'de' matches the name 'name'
  * with a length of 'len'.
  */
 #define TMPFS_DIRENT_MATCHES(de, name, len) \
-    (de->td_namelen == (uint16_t)len && \
-    bcmp((de)->td_name, (name), (de)->td_namelen) == 0)
+    (de->td_namelen == len && \
+    bcmp((de)->ud.td_name, (name), (de)->td_namelen) == 0)
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Ensures that the node pointed by 'node' is a directory and that its
  * contents are consistent with respect to directories.
  */
-#define TMPFS_VALIDATE_DIR(node) \
-    MPASS((node)->tn_type == VDIR); \
-    MPASS((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \
-    MPASS((node)->tn_dir.tn_readdir_lastp == NULL || \
-	tmpfs_dircookie((node)->tn_dir.tn_readdir_lastp) == (node)->tn_dir.tn_readdir_lastn);
+#define TMPFS_VALIDATE_DIR(node) do { \
+	MPASS((node)->tn_type == VDIR); \
+	MPASS((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \
+} while (0)
 
-/* --------------------------------------------------------------------- */
-
 /*
- * Memory management stuff.
- */
-
-/*
  * Amount of memory pages to reserve for the system (e.g., to not use by
  * tmpfs).
  */
@@ -500,37 +496,32 @@
 
 #endif
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Macros/functions to convert from generic data structures to tmpfs
  * specific ones.
  */
 
-static inline
-struct tmpfs_mount *
+static inline struct tmpfs_mount *
 VFS_TO_TMPFS(struct mount *mp)
 {
 	struct tmpfs_mount *tmp;
 
-	MPASS((mp) != NULL && (mp)->mnt_data != NULL);
-	tmp = (struct tmpfs_mount *)(mp)->mnt_data;
-	return tmp;
+	MPASS(mp != NULL && mp->mnt_data != NULL);
+	tmp = (struct tmpfs_mount *)mp->mnt_data;
+	return (tmp);
 }
 
-static inline
-struct tmpfs_node *
+static inline struct tmpfs_node *
 VP_TO_TMPFS_NODE(struct vnode *vp)
 {
 	struct tmpfs_node *node;
 
-	MPASS((vp) != NULL && (vp)->v_data != NULL);
+	MPASS(vp != NULL && vp->v_data != NULL);
 	node = (struct tmpfs_node *)vp->v_data;
-	return node;
+	return (node);
 }
 
-static inline
-struct tmpfs_node *
+static inline struct tmpfs_node *
 VP_TO_TMPFS_DIR(struct vnode *vp)
 {
 	struct tmpfs_node *node;
@@ -537,7 +528,14 @@
 
 	node = VP_TO_TMPFS_NODE(vp);
 	TMPFS_VALIDATE_DIR(node);
-	return node;
+	return (node);
 }
 
+static inline bool
+tmpfs_use_nc(struct vnode *vp)
+{
+
+	return (!(VFS_TO_TMPFS(vp->v_mount)->tm_nonc));
+}
+
 #endif /* _FS_TMPFS_TMPFS_H_ */

Modified: trunk/sys/fs/tmpfs/tmpfs_fifoops.c
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs_fifoops.c	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs_fifoops.c	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs_fifoops.c,v 1.5 2005/12/11 12:24:29 christos Exp $	*/
 
 /*-
@@ -34,7 +35,7 @@
  * tmpfs vnode interface for named pipes.
  */
 #include <sys/cdefs.h>
- __MBSDID("$MidnightBSD$");
+ __FBSDID("$FreeBSD: stable/10/sys/fs/tmpfs/tmpfs_fifoops.c 312069 2017-01-13 12:47:44Z kib $");
 
 #include <sys/param.h>
 #include <sys/filedesc.h>
@@ -48,40 +49,15 @@
 #include <fs/tmpfs/tmpfs_fifoops.h>
 #include <fs/tmpfs/tmpfs_vnops.h>
 
-/* --------------------------------------------------------------------- */
-
 static int
-tmpfs_fifo_kqfilter(struct vop_kqfilter_args *ap)
+tmpfs_fifo_close(struct vop_close_args *v)
 {
-	struct vnode *vp;
 	struct tmpfs_node *node;
 
-	vp = ap->a_vp;
-	node = VP_TO_TMPFS_NODE(vp);
-
-	switch (ap->a_kn->kn_filter){
-	case EVFILT_READ:
-		node->tn_status |= TMPFS_NODE_ACCESSED;
-		break;
-	case EVFILT_WRITE:
-		node->tn_status |= TMPFS_NODE_MODIFIED;
-		break;
-	}
-
-	return fifo_specops.vop_kqfilter(ap);
-}
-
-/* --------------------------------------------------------------------- */
-
-static int
-tmpfs_fifo_close(struct vop_close_args *v)
-{
-	struct tmpfs_node *node;
 	node = VP_TO_TMPFS_NODE(v->a_vp);
-	node->tn_status |= TMPFS_NODE_ACCESSED;
-
+	tmpfs_set_status(node, TMPFS_NODE_ACCESSED);
 	tmpfs_update(v->a_vp);
-	return fifo_specops.vop_close(v);
+	return (fifo_specops.vop_close(v));
 }
 
 /*
@@ -94,6 +70,5 @@
 	.vop_access =			tmpfs_access,
 	.vop_getattr =			tmpfs_getattr,
 	.vop_setattr =			tmpfs_setattr,
-	.vop_kqfilter =			tmpfs_fifo_kqfilter,
 };
 

Modified: trunk/sys/fs/tmpfs/tmpfs_fifoops.h
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs_fifoops.h	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs_fifoops.h	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs_fifoops.h,v 1.4 2005/12/03 17:34:44 christos Exp $	*/
 
 /*-
@@ -29,7 +30,7 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  *
- * $MidnightBSD$
+ * $FreeBSD: stable/10/sys/fs/tmpfs/tmpfs_fifoops.h 269164 2014-07-28 00:43:42Z kib $
  */
 
 #ifndef _FS_TMPFS_TMPFS_FIFOOPS_H_
@@ -41,8 +42,6 @@
 
 #include <fs/tmpfs/tmpfs_vnops.h>
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Declarations for tmpfs_fifoops.c.
  */
@@ -49,5 +48,4 @@
 
 extern struct vop_vector tmpfs_fifoop_entries;
 
-/* --------------------------------------------------------------------- */
 #endif /* _FS_TMPFS_TMPFS_FIFOOPS_H_ */

Modified: trunk/sys/fs/tmpfs/tmpfs_subr.c
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs_subr.c	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs_subr.c	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs_subr.c,v 1.35 2007/07/09 21:10:50 ad Exp $	*/
 
 /*-
@@ -34,12 +35,15 @@
  * Efficient memory file system supporting functions.
  */
 #include <sys/cdefs.h>
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: stable/10/sys/fs/tmpfs/tmpfs_subr.c 330700 2018-03-09 17:59:22Z emaste $");
 
 #include <sys/param.h>
+#include <sys/fnv_hash.h>
+#include <sys/lock.h>
 #include <sys/namei.h>
 #include <sys/priv.h>
 #include <sys/proc.h>
+#include <sys/rwlock.h>
 #include <sys/stat.h>
 #include <sys/systm.h>
 #include <sys/sysctl.h>
@@ -87,6 +91,10 @@
     &tmpfs_pages_reserved, 0, sysctl_mem_reserved, "L",
     "Amount of available memory and swap below which tmpfs growth stops");
 
+static __inline int tmpfs_dirtree_cmp(struct tmpfs_dirent *a,
+    struct tmpfs_dirent *b);
+RB_PROTOTYPE_STATIC(tmpfs_dir, tmpfs_dirent, uh.td_entries, tmpfs_dirtree_cmp);
+
 size_t
 tmpfs_mem_avail(void)
 {
@@ -117,7 +125,7 @@
 	if (tmpfs_mem_avail() < req_pages)
 		return (0);
 
-	if (tmp->tm_pages_max != SIZE_MAX &&
+	if (tmp->tm_pages_max != ULONG_MAX &&
 	    tmp->tm_pages_max < req_pages + tmpfs_pages_used(tmp))
 			return (0);
 
@@ -124,8 +132,26 @@
 	return (1);
 }
 
-/* --------------------------------------------------------------------- */
+void
+tmpfs_ref_node(struct tmpfs_node *node)
+{
 
+	TMPFS_NODE_LOCK(node);
+	tmpfs_ref_node_locked(node);
+	TMPFS_NODE_UNLOCK(node);
+}
+
+void
+tmpfs_ref_node_locked(struct tmpfs_node *node)
+{
+
+	TMPFS_NODE_ASSERT_LOCKED(node);
+	KASSERT(node->tn_refcount > 0, ("node %p zero refcount", node));
+	KASSERT(node->tn_refcount < UINT_MAX, ("node %p refcount %u", node,
+	    node->tn_refcount));
+	node->tn_refcount++;
+}
+
 /*
  * Allocates a new node of type 'type' inside the 'tmp' mount point, with
  * its owner set to 'uid', its group to 'gid' and its mode set to 'mode',
@@ -149,15 +175,18 @@
  * Returns zero on success or an appropriate error code on failure.
  */
 int
-tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type,
+tmpfs_alloc_node(struct mount *mp, struct tmpfs_mount *tmp, enum vtype type,
     uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *parent,
     char *target, dev_t rdev, struct tmpfs_node **node)
 {
 	struct tmpfs_node *nnode;
+	vm_object_t obj;
 
 	/* If the root directory of the 'tmp' file system is not yet
 	 * allocated, this must be the request to do it. */
 	MPASS(IMPLIES(tmp->tm_root == NULL, parent == NULL && type == VDIR));
+	KASSERT(tmp->tm_root == NULL || mp->mnt_writeopcount > 0,
+	    ("creating node not under vn_start_write"));
 
 	MPASS(IFF(type == VLNK, target != NULL));
 	MPASS(IFF(type == VBLK || type == VCHR, rdev != VNOVAL));
@@ -167,9 +196,27 @@
 	if (tmpfs_pages_check_avail(tmp, 1) == 0)
 		return (ENOSPC);
 
-	nnode = (struct tmpfs_node *)uma_zalloc_arg(
-				tmp->tm_node_pool, tmp, M_WAITOK);
+	if ((mp->mnt_kern_flag & MNTK_UNMOUNT) != 0) {
+		/*
+		 * When a new tmpfs node is created for fully
+		 * constructed mount point, there must be a parent
+		 * node, which vnode is locked exclusively.  As
+		 * consequence, if the unmount is executing in
+		 * parallel, vflush() cannot reclaim the parent vnode.
+		 * Due to this, the check for MNTK_UNMOUNT flag is not
+		 * racy: if we did not see MNTK_UNMOUNT flag, then tmp
+		 * cannot be destroyed until node construction is
+		 * finished and the parent vnode unlocked.
+		 *
+		 * Tmpfs does not need to instantiate new nodes during
+		 * unmount.
+		 */
+		return (EBUSY);
+	}
 
+	nnode = (struct tmpfs_node *)uma_zalloc_arg(tmp->tm_node_pool, tmp,
+	    M_WAITOK);
+
 	/* Generic initialization. */
 	nnode->tn_type = type;
 	vfs_timestamp(&nnode->tn_atime);
@@ -179,6 +226,7 @@
 	nnode->tn_gid = gid;
 	nnode->tn_mode = mode;
 	nnode->tn_id = alloc_unr(tmp->tm_ino_unr);
+	nnode->tn_refcount = 1;
 
 	/* Type-specific initialization. */
 	switch (nnode->tn_type) {
@@ -188,7 +236,8 @@
 		break;
 
 	case VDIR:
-		TAILQ_INIT(&nnode->tn_dir.tn_dirhead);
+		RB_INIT(&nnode->tn_dir.tn_dirhead);
+		LIST_INIT(&nnode->tn_dir.tn_dupindex);
 		MPASS(parent != nnode);
 		MPASS(IMPLIES(parent == NULL, tmp->tm_root == NULL));
 		nnode->tn_dir.tn_parent = (parent == NULL) ? nnode : parent;
@@ -214,66 +263,76 @@
 		break;
 
 	case VREG:
-		nnode->tn_reg.tn_aobj =
+		obj = nnode->tn_reg.tn_aobj =
 		    vm_pager_allocate(OBJT_SWAP, NULL, 0, VM_PROT_DEFAULT, 0,
 			NULL /* XXXKIB - tmpfs needs swap reservation */);
+		VM_OBJECT_WLOCK(obj);
+		/* OBJ_TMPFS is set together with the setting of vp->v_object */
+		vm_object_set_flag(obj, OBJ_NOSPLIT | OBJ_TMPFS_NODE);
+		vm_object_clear_flag(obj, OBJ_ONEMAPPING);
+		VM_OBJECT_WUNLOCK(obj);
 		break;
 
 	default:
-		panic("tmpfs_alloc_node: type %p %d", nnode, (int)nnode->tn_type);
+		panic("tmpfs_alloc_node: type %p %d", nnode,
+		    (int)nnode->tn_type);
 	}
 
 	TMPFS_LOCK(tmp);
 	LIST_INSERT_HEAD(&tmp->tm_nodes_used, nnode, tn_entries);
+	nnode->tn_attached = true;
 	tmp->tm_nodes_inuse++;
+	tmp->tm_refcount++;
 	TMPFS_UNLOCK(tmp);
 
 	*node = nnode;
-	return 0;
+	return (0);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Destroys the node pointed to by node from the file system 'tmp'.
- * If the node does not belong to the given mount point, the results are
- * unpredicted.
- *
- * If the node references a directory; no entries are allowed because
- * their removal could need a recursive algorithm, something forbidden in
- * kernel space.  Furthermore, there is not need to provide such
- * functionality (recursive removal) because the only primitives offered
- * to the user are the removal of empty directories and the deletion of
- * individual files.
- *
- * Note that nodes are not really deleted; in fact, when a node has been
- * allocated, it cannot be deleted during the whole life of the file
- * system.  Instead, they are moved to the available list and remain there
- * until reused.
+ * If the node references a directory, no entries are allowed.
  */
 void
 tmpfs_free_node(struct tmpfs_mount *tmp, struct tmpfs_node *node)
 {
+
+	TMPFS_LOCK(tmp);
+	TMPFS_NODE_LOCK(node);
+	if (!tmpfs_free_node_locked(tmp, node, false)) {
+		TMPFS_NODE_UNLOCK(node);
+		TMPFS_UNLOCK(tmp);
+	}
+}
+
+bool
+tmpfs_free_node_locked(struct tmpfs_mount *tmp, struct tmpfs_node *node,
+    bool detach)
+{
 	vm_object_t uobj;
 
+	TMPFS_MP_ASSERT_LOCKED(tmp);
+	TMPFS_NODE_ASSERT_LOCKED(node);
+	KASSERT(node->tn_refcount > 0, ("node %p refcount zero", node));
+
+	node->tn_refcount--;
+	if (node->tn_attached && (detach || node->tn_refcount == 0)) {
+		MPASS(tmp->tm_nodes_inuse > 0);
+		tmp->tm_nodes_inuse--;
+		LIST_REMOVE(node, tn_entries);
+		node->tn_attached = false;
+	}
+	if (node->tn_refcount > 0)
+		return (false);
+
 #ifdef INVARIANTS
-	TMPFS_NODE_LOCK(node);
 	MPASS(node->tn_vnode == NULL);
 	MPASS((node->tn_vpstate & TMPFS_VNODE_ALLOCATING) == 0);
+#endif
 	TMPFS_NODE_UNLOCK(node);
-#endif
-
-	TMPFS_LOCK(tmp);
-	LIST_REMOVE(node, tn_entries);
-	tmp->tm_nodes_inuse--;
 	TMPFS_UNLOCK(tmp);
 
 	switch (node->tn_type) {
-	case VNON:
-		/* Do not do anything.  VNON is provided to let the
-		 * allocation routine clean itself easily by avoiding
-		 * duplicating code in it. */
-		/* FALLTHROUGH */
 	case VBLK:
 		/* FALLTHROUGH */
 	case VCHR:
@@ -292,9 +351,9 @@
 	case VREG:
 		uobj = node->tn_reg.tn_aobj;
 		if (uobj != NULL) {
-			TMPFS_LOCK(tmp);
-			tmp->tm_pages_used -= uobj->size;
-			TMPFS_UNLOCK(tmp);
+			atomic_subtract_long(&tmp->tm_pages_used, uobj->size);
+			KASSERT((uobj->flags & OBJ_TMPFS) == 0,
+			    ("leaked OBJ_TMPFS node %p vm_obj %p", node, uobj));
 			vm_object_deallocate(uobj);
 		}
 		break;
@@ -305,10 +364,57 @@
 
 	free_unr(tmp->tm_ino_unr, node->tn_id);
 	uma_zfree(tmp->tm_node_pool, node);
+	TMPFS_LOCK(tmp);
+	tmpfs_free_tmp(tmp);
+	return (true);
 }
 
-/* --------------------------------------------------------------------- */
+static __inline uint32_t
+tmpfs_dirent_hash(const char *name, u_int len)
+{
+	uint32_t hash;
 
+	hash = fnv_32_buf(name, len, FNV1_32_INIT + len) & TMPFS_DIRCOOKIE_MASK;
+#ifdef TMPFS_DEBUG_DIRCOOKIE_DUP
+	hash &= 0xf;
+#endif
+	if (hash < TMPFS_DIRCOOKIE_MIN)
+		hash += TMPFS_DIRCOOKIE_MIN;
+
+	return (hash);
+}
+
+static __inline off_t
+tmpfs_dirent_cookie(struct tmpfs_dirent *de)
+{
+	if (de == NULL)
+		return (TMPFS_DIRCOOKIE_EOF);
+
+	MPASS(de->td_cookie >= TMPFS_DIRCOOKIE_MIN);
+
+	return (de->td_cookie);
+}
+
+static __inline boolean_t
+tmpfs_dirent_dup(struct tmpfs_dirent *de)
+{
+	return ((de->td_cookie & TMPFS_DIRCOOKIE_DUP) != 0);
+}
+
+static __inline boolean_t
+tmpfs_dirent_duphead(struct tmpfs_dirent *de)
+{
+	return ((de->td_cookie & TMPFS_DIRCOOKIE_DUPHEAD) != 0);
+}
+
+void
+tmpfs_dirent_init(struct tmpfs_dirent *de, const char *name, u_int namelen)
+{
+	de->td_hash = de->td_cookie = tmpfs_dirent_hash(name, namelen);
+	memcpy(de->ud.td_name, name, namelen);
+	de->td_namelen = namelen;
+}
+
 /*
  * Allocates a new directory entry for the node node with a name of name.
  * The new directory entry is returned in *de.
@@ -320,17 +426,17 @@
  */
 int
 tmpfs_alloc_dirent(struct tmpfs_mount *tmp, struct tmpfs_node *node,
-    const char *name, uint16_t len, struct tmpfs_dirent **de)
+    const char *name, u_int len, struct tmpfs_dirent **de)
 {
 	struct tmpfs_dirent *nde;
 
-	nde = (struct tmpfs_dirent *)uma_zalloc(
-					tmp->tm_dirent_pool, M_WAITOK);
-	nde->td_name = malloc(len, M_TMPFSNAME, M_WAITOK);
-	nde->td_namelen = len;
-	memcpy(nde->td_name, name, len);
-
+	nde = uma_zalloc(tmp->tm_dirent_pool, M_WAITOK);
 	nde->td_node = node;
+	if (name != NULL) {
+		nde->ud.td_name = malloc(len, M_TMPFSNAME, M_WAITOK);
+		tmpfs_dirent_init(nde, name, len);
+	} else
+		nde->td_namelen = 0;
 	if (node != NULL)
 		node->tn_links++;
 
@@ -339,8 +445,6 @@
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Frees a directory entry.  It is the caller's responsibility to destroy
  * the node referenced by it if needed.
@@ -351,26 +455,52 @@
  * directory entry, as it may already have been released from the outside.
  */
 void
-tmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de,
-    boolean_t node_exists)
+tmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de)
 {
-	if (node_exists) {
-		struct tmpfs_node *node;
+	struct tmpfs_node *node;
 
-		node = de->td_node;
-		if (node != NULL) {
-			MPASS(node->tn_links > 0);
-			node->tn_links--;
-		}
+	node = de->td_node;
+	if (node != NULL) {
+		MPASS(node->tn_links > 0);
+		node->tn_links--;
 	}
-
-	free(de->td_name, M_TMPFSNAME);
+	if (!tmpfs_dirent_duphead(de) && de->ud.td_name != NULL)
+		free(de->ud.td_name, M_TMPFSNAME);
 	uma_zfree(tmp->tm_dirent_pool, de);
 }
 
-/* --------------------------------------------------------------------- */
+void
+tmpfs_destroy_vobject(struct vnode *vp, vm_object_t obj)
+{
 
+	ASSERT_VOP_ELOCKED(vp, "tmpfs_destroy_vobject");
+	if (vp->v_type != VREG || obj == NULL)
+		return;
+
+	VM_OBJECT_WLOCK(obj);
+	VI_LOCK(vp);
+	vm_object_clear_flag(obj, OBJ_TMPFS);
+	obj->un_pager.swp.swp_tmpfs = NULL;
+	VI_UNLOCK(vp);
+	VM_OBJECT_WUNLOCK(obj);
+}
+
 /*
+ * Need to clear v_object for insmntque failure.
+ */
+static void
+tmpfs_insmntque_dtr(struct vnode *vp, void *dtr_arg)
+{
+
+	tmpfs_destroy_vobject(vp, vp->v_object);
+	vp->v_object = NULL;
+	vp->v_data = NULL;
+	vp->v_op = &dead_vnodeops;
+	vgone(vp);
+	vput(vp);
+}
+
+/*
  * Allocates a new vnode for the node node or returns a new reference to
  * an existing one if the node had already a vnode referencing it.  The
  * resulting locked vnode is returned in *vpp.
@@ -381,16 +511,44 @@
 tmpfs_alloc_vp(struct mount *mp, struct tmpfs_node *node, int lkflag,
     struct vnode **vpp)
 {
-	int error = 0;
 	struct vnode *vp;
+	struct tmpfs_mount *tm;
+	vm_object_t object;
+	int error;
 
+	error = 0;
+	tm = VFS_TO_TMPFS(mp);
+	TMPFS_NODE_LOCK(node);
+	tmpfs_ref_node_locked(node);
 loop:
-	TMPFS_NODE_LOCK(node);
+	TMPFS_NODE_ASSERT_LOCKED(node);
 	if ((vp = node->tn_vnode) != NULL) {
 		MPASS((node->tn_vpstate & TMPFS_VNODE_DOOMED) == 0);
 		VI_LOCK(vp);
+		if ((node->tn_type == VDIR && node->tn_dir.tn_parent == NULL) ||
+		    ((vp->v_iflag & VI_DOOMED) != 0 &&
+		    (lkflag & LK_NOWAIT) != 0)) {
+			VI_UNLOCK(vp);
+			TMPFS_NODE_UNLOCK(node);
+			error = ENOENT;
+			vp = NULL;
+			goto out;
+		}
+		if ((vp->v_iflag & VI_DOOMED) != 0) {
+			VI_UNLOCK(vp);
+			node->tn_vpstate |= TMPFS_VNODE_WRECLAIM;
+			while ((node->tn_vpstate & TMPFS_VNODE_WRECLAIM) != 0) {
+				msleep(&node->tn_vnode, TMPFS_NODE_MTX(node),
+				    0, "tmpfsE", 0);
+			}
+			goto loop;
+		}
 		TMPFS_NODE_UNLOCK(node);
 		error = vget(vp, lkflag | LK_INTERLOCK, curthread);
+		if (error == ENOENT) {
+			TMPFS_NODE_LOCK(node);
+			goto loop;
+		}
 		if (error != 0) {
 			vp = NULL;
 			goto out;
@@ -402,6 +560,7 @@
 		 */
 		if (node->tn_vnode == NULL || node->tn_vnode != vp) {
 			vput(vp);
+			TMPFS_NODE_LOCK(node);
 			goto loop;
 		}
 
@@ -423,11 +582,9 @@
 	if (node->tn_vpstate & TMPFS_VNODE_ALLOCATING) {
 		node->tn_vpstate |= TMPFS_VNODE_WANT;
 		error = msleep((caddr_t) &node->tn_vpstate,
-		    TMPFS_NODE_MTX(node), PDROP | PCATCH,
-		    "tmpfs_alloc_vp", 0);
-		if (error)
-			return error;
-
+		    TMPFS_NODE_MTX(node), 0, "tmpfs_alloc_vp", 0);
+		if (error != 0)
+			goto out;
 		goto loop;
 	} else
 		node->tn_vpstate |= TMPFS_VNODE_ALLOCATING;
@@ -435,11 +592,13 @@
 	TMPFS_NODE_UNLOCK(node);
 
 	/* Get a new vnode and associate it with our node. */
-	error = getnewvnode("tmpfs", mp, &tmpfs_vnodeop_entries, &vp);
+	error = getnewvnode("tmpfs", mp, VFS_TO_TMPFS(mp)->tm_nonc ?
+	    &tmpfs_vnodeop_nonc_entries : &tmpfs_vnodeop_entries, &vp);
 	if (error != 0)
 		goto unlock;
 	MPASS(vp != NULL);
 
+	/* lkflag is ignored, the lock is exclusive */
 	(void) vn_lock(vp, lkflag | LK_RETRY);
 
 	vp->v_data = node;
@@ -453,13 +612,22 @@
 		/* FALLTHROUGH */
 	case VLNK:
 		/* FALLTHROUGH */
-	case VREG:
-		/* FALLTHROUGH */
 	case VSOCK:
 		break;
 	case VFIFO:
 		vp->v_op = &tmpfs_fifoop_entries;
 		break;
+	case VREG:
+		object = node->tn_reg.tn_aobj;
+		VM_OBJECT_WLOCK(object);
+		VI_LOCK(vp);
+		KASSERT(vp->v_object == NULL, ("Not NULL v_object in tmpfs"));
+		vp->v_object = object;
+		object->un_pager.swp.swp_tmpfs = vp;
+		vm_object_set_flag(object, OBJ_TMPFS);
+		VI_UNLOCK(vp);
+		VM_OBJECT_WUNLOCK(object);
+		break;
 	case VDIR:
 		MPASS(node->tn_dir.tn_parent != NULL);
 		if (node->tn_dir.tn_parent == node)
@@ -469,10 +637,11 @@
 	default:
 		panic("tmpfs_alloc_vp: type %p %d", node, (int)node->tn_type);
 	}
+	if (vp->v_type != VFIFO)
+		VN_LOCK_ASHARE(vp);
 
-	vnode_pager_setsize(vp, node->tn_size);
-	error = insmntque(vp, mp);
-	if (error)
+	error = insmntque1(vp, mp, tmpfs_insmntque_dtr, NULL);
+	if (error != 0)
 		vp = NULL;
 
 unlock:
@@ -490,22 +659,21 @@
 		TMPFS_NODE_UNLOCK(node);
 
 out:
-	*vpp = vp;
+	if (error == 0) {
+		*vpp = vp;
 
 #ifdef INVARIANTS
-	if (error == 0) {
 		MPASS(*vpp != NULL && VOP_ISLOCKED(*vpp));
 		TMPFS_NODE_LOCK(node);
 		MPASS(*vpp == node->tn_vnode);
 		TMPFS_NODE_UNLOCK(node);
+#endif
 	}
-#endif
+	tmpfs_free_node(tm, node);
 
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Destroys the association between the vnode vp and the node it
  * references.
@@ -517,13 +685,14 @@
 
 	node = VP_TO_TMPFS_NODE(vp);
 
-	mtx_assert(TMPFS_NODE_MTX(node), MA_OWNED);
+	TMPFS_NODE_ASSERT_LOCKED(node);
 	node->tn_vnode = NULL;
+	if ((node->tn_vpstate & TMPFS_VNODE_WRECLAIM) != 0)
+		wakeup(&node->tn_vnode);
+	node->tn_vpstate &= ~TMPFS_VNODE_WRECLAIM;
 	vp->v_data = NULL;
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Allocates a new file of type 'type' and adds it to the parent directory
  * 'dvp'; this addition is done using the component name given in 'cnp'.
@@ -545,7 +714,7 @@
 	struct tmpfs_node *node;
 	struct tmpfs_node *parent;
 
-	MPASS(VOP_ISLOCKED(dvp));
+	ASSERT_VOP_ELOCKED(dvp, "tmpfs_alloc_file");
 	MPASS(cnp->cn_flags & HASBUF);
 
 	tmp = VFS_TO_TMPFS(dvp->v_mount);
@@ -560,8 +729,7 @@
 		 * imposed by the system. */
 		MPASS(dnode->tn_links <= LINK_MAX);
 		if (dnode->tn_links == LINK_MAX) {
-			error = EMLINK;
-			goto out;
+			return (EMLINK);
 		}
 
 		parent = dnode;
@@ -570,10 +738,11 @@
 		parent = NULL;
 
 	/* Allocate a node that represents the new file. */
-	error = tmpfs_alloc_node(tmp, vap->va_type, cnp->cn_cred->cr_uid,
-	    dnode->tn_gid, vap->va_mode, parent, target, vap->va_rdev, &node);
+	error = tmpfs_alloc_node(dvp->v_mount, tmp, vap->va_type,
+	    cnp->cn_cred->cr_uid, dnode->tn_gid, vap->va_mode, parent,
+	    target, vap->va_rdev, &node);
 	if (error != 0)
-		goto out;
+		return (error);
 
 	/* Allocate a directory entry that points to the new file. */
 	error = tmpfs_alloc_dirent(tmp, node, cnp->cn_nameptr, cnp->cn_namelen,
@@ -580,15 +749,15 @@
 	    &de);
 	if (error != 0) {
 		tmpfs_free_node(tmp, node);
-		goto out;
+		return (error);
 	}
 
 	/* Allocate a vnode for the new file. */
 	error = tmpfs_alloc_vp(dvp->v_mount, node, LK_EXCLUSIVE, vpp);
 	if (error != 0) {
-		tmpfs_free_dirent(tmp, de, TRUE);
+		tmpfs_free_dirent(tmp, de);
 		tmpfs_free_node(tmp, node);
-		goto out;
+		return (error);
 	}
 
 	/* Now that all required items are allocated, we can proceed to
@@ -597,14 +766,221 @@
 	if (cnp->cn_flags & ISWHITEOUT)
 		tmpfs_dir_whiteout_remove(dvp, cnp);
 	tmpfs_dir_attach(dvp, de);
+	return (0);
+}
 
+struct tmpfs_dirent *
+tmpfs_dir_first(struct tmpfs_node *dnode, struct tmpfs_dir_cursor *dc)
+{
+	struct tmpfs_dirent *de;
+
+	de = RB_MIN(tmpfs_dir, &dnode->tn_dir.tn_dirhead);
+	dc->tdc_tree = de;
+	if (de != NULL && tmpfs_dirent_duphead(de))
+		de = LIST_FIRST(&de->ud.td_duphead);
+	dc->tdc_current = de;
+
+	return (dc->tdc_current);
+}
+
+struct tmpfs_dirent *
+tmpfs_dir_next(struct tmpfs_node *dnode, struct tmpfs_dir_cursor *dc)
+{
+	struct tmpfs_dirent *de;
+
+	MPASS(dc->tdc_tree != NULL);
+	if (tmpfs_dirent_dup(dc->tdc_current)) {
+		dc->tdc_current = LIST_NEXT(dc->tdc_current, uh.td_dup.entries);
+		if (dc->tdc_current != NULL)
+			return (dc->tdc_current);
+	}
+	dc->tdc_tree = dc->tdc_current = RB_NEXT(tmpfs_dir,
+	    &dnode->tn_dir.tn_dirhead, dc->tdc_tree);
+	if ((de = dc->tdc_current) != NULL && tmpfs_dirent_duphead(de)) {
+		dc->tdc_current = LIST_FIRST(&de->ud.td_duphead);
+		MPASS(dc->tdc_current != NULL);
+	}
+
+	return (dc->tdc_current);
+}
+
+/* Lookup directory entry in RB-Tree. Function may return duphead entry. */
+static struct tmpfs_dirent *
+tmpfs_dir_xlookup_hash(struct tmpfs_node *dnode, uint32_t hash)
+{
+	struct tmpfs_dirent *de, dekey;
+
+	dekey.td_hash = hash;
+	de = RB_FIND(tmpfs_dir, &dnode->tn_dir.tn_dirhead, &dekey);
+	return (de);
+}
+
+/* Lookup directory entry by cookie, initialize directory cursor accordingly. */
+static struct tmpfs_dirent *
+tmpfs_dir_lookup_cookie(struct tmpfs_node *node, off_t cookie,
+    struct tmpfs_dir_cursor *dc)
+{
+	struct tmpfs_dir *dirhead = &node->tn_dir.tn_dirhead;
+	struct tmpfs_dirent *de, dekey;
+
+	MPASS(cookie >= TMPFS_DIRCOOKIE_MIN);
+
+	if (cookie == node->tn_dir.tn_readdir_lastn &&
+	    (de = node->tn_dir.tn_readdir_lastp) != NULL) {
+		/* Protect against possible race, tn_readdir_last[pn]
+		 * may be updated with only shared vnode lock held. */
+		if (cookie == tmpfs_dirent_cookie(de))
+			goto out;
+	}
+
+	if ((cookie & TMPFS_DIRCOOKIE_DUP) != 0) {
+		LIST_FOREACH(de, &node->tn_dir.tn_dupindex,
+		    uh.td_dup.index_entries) {
+			MPASS(tmpfs_dirent_dup(de));
+			if (de->td_cookie == cookie)
+				goto out;
+			/* dupindex list is sorted. */
+			if (de->td_cookie < cookie) {
+				de = NULL;
+				goto out;
+			}
+		}
+		MPASS(de == NULL);
+		goto out;
+	}
+
+	if ((cookie & TMPFS_DIRCOOKIE_MASK) != cookie) {
+		de = NULL;
+	} else {
+		dekey.td_hash = cookie;
+		/* Recover if direntry for cookie was removed */
+		de = RB_NFIND(tmpfs_dir, dirhead, &dekey);
+	}
+	dc->tdc_tree = de;
+	dc->tdc_current = de;
+	if (de != NULL && tmpfs_dirent_duphead(de)) {
+		dc->tdc_current = LIST_FIRST(&de->ud.td_duphead);
+		MPASS(dc->tdc_current != NULL);
+	}
+	return (dc->tdc_current);
+
 out:
+	dc->tdc_tree = de;
+	dc->tdc_current = de;
+	if (de != NULL && tmpfs_dirent_dup(de))
+		dc->tdc_tree = tmpfs_dir_xlookup_hash(node,
+		    de->td_hash);
+	return (dc->tdc_current);
+}
 
-	return error;
+/*
+ * Looks for a directory entry in the directory represented by node.
+ * 'cnp' describes the name of the entry to look for.  Note that the .
+ * and .. components are not allowed as they do not physically exist
+ * within directories.
+ *
+ * Returns a pointer to the entry when found, otherwise NULL.
+ */
+struct tmpfs_dirent *
+tmpfs_dir_lookup(struct tmpfs_node *node, struct tmpfs_node *f,
+    struct componentname *cnp)
+{
+	struct tmpfs_dir_duphead *duphead;
+	struct tmpfs_dirent *de;
+	uint32_t hash;
+
+	MPASS(IMPLIES(cnp->cn_namelen == 1, cnp->cn_nameptr[0] != '.'));
+	MPASS(IMPLIES(cnp->cn_namelen == 2, !(cnp->cn_nameptr[0] == '.' &&
+	    cnp->cn_nameptr[1] == '.')));
+	TMPFS_VALIDATE_DIR(node);
+
+	hash = tmpfs_dirent_hash(cnp->cn_nameptr, cnp->cn_namelen);
+	de = tmpfs_dir_xlookup_hash(node, hash);
+	if (de != NULL && tmpfs_dirent_duphead(de)) {
+		duphead = &de->ud.td_duphead;
+		LIST_FOREACH(de, duphead, uh.td_dup.entries) {
+			if (TMPFS_DIRENT_MATCHES(de, cnp->cn_nameptr,
+			    cnp->cn_namelen))
+				break;
+		}
+	} else if (de != NULL) {
+		if (!TMPFS_DIRENT_MATCHES(de, cnp->cn_nameptr,
+		    cnp->cn_namelen))
+			de = NULL;
+	}
+	if (de != NULL && f != NULL && de->td_node != f)
+		de = NULL;
+
+	return (de);
 }
 
-/* --------------------------------------------------------------------- */
+/*
+ * Attach duplicate-cookie directory entry nde to dnode and insert to dupindex
+ * list, allocate new cookie value.
+ */
+static void
+tmpfs_dir_attach_dup(struct tmpfs_node *dnode,
+    struct tmpfs_dir_duphead *duphead, struct tmpfs_dirent *nde)
+{
+	struct tmpfs_dir_duphead *dupindex;
+	struct tmpfs_dirent *de, *pde;
 
+	dupindex = &dnode->tn_dir.tn_dupindex;
+	de = LIST_FIRST(dupindex);
+	if (de == NULL || de->td_cookie < TMPFS_DIRCOOKIE_DUP_MAX) {
+		if (de == NULL)
+			nde->td_cookie = TMPFS_DIRCOOKIE_DUP_MIN;
+		else
+			nde->td_cookie = de->td_cookie + 1;
+		MPASS(tmpfs_dirent_dup(nde));
+		LIST_INSERT_HEAD(dupindex, nde, uh.td_dup.index_entries);
+		LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries);
+		return;
+	}
+
+	/*
+	 * Cookie numbers are near exhaustion. Scan dupindex list for unused
+	 * numbers. dupindex list is sorted in descending order. Keep it so
+	 * after inserting nde.
+	 */
+	while (1) {
+		pde = de;
+		de = LIST_NEXT(de, uh.td_dup.index_entries);
+		if (de == NULL && pde->td_cookie != TMPFS_DIRCOOKIE_DUP_MIN) {
+			/*
+			 * Last element of the index doesn't have minimal cookie
+			 * value, use it.
+			 */
+			nde->td_cookie = TMPFS_DIRCOOKIE_DUP_MIN;
+			LIST_INSERT_AFTER(pde, nde, uh.td_dup.index_entries);
+			LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries);
+			return;
+		} else if (de == NULL) {
+			/*
+			 * We are so lucky have 2^30 hash duplicates in single
+			 * directory :) Return largest possible cookie value.
+			 * It should be fine except possible issues with
+			 * VOP_READDIR restart.
+			 */
+			nde->td_cookie = TMPFS_DIRCOOKIE_DUP_MAX;
+			LIST_INSERT_HEAD(dupindex, nde,
+			    uh.td_dup.index_entries);
+			LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries);
+			return;
+		}
+		if (de->td_cookie + 1 == pde->td_cookie ||
+		    de->td_cookie >= TMPFS_DIRCOOKIE_DUP_MAX)
+			continue;	/* No hole or invalid cookie. */
+		nde->td_cookie = de->td_cookie + 1;
+		MPASS(tmpfs_dirent_dup(nde));
+		MPASS(pde->td_cookie > nde->td_cookie);
+		MPASS(nde->td_cookie > de->td_cookie);
+		LIST_INSERT_BEFORE(de, nde, uh.td_dup.index_entries);
+		LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries);
+		return;
+	};
+}
+
 /*
  * Attaches the directory entry de to the directory represented by vp.
  * Note that this does not change the link count of the node pointed by
@@ -614,17 +990,44 @@
 tmpfs_dir_attach(struct vnode *vp, struct tmpfs_dirent *de)
 {
 	struct tmpfs_node *dnode;
+	struct tmpfs_dirent *xde, *nde;
 
 	ASSERT_VOP_ELOCKED(vp, __func__);
+	MPASS(de->td_namelen > 0);
+	MPASS(de->td_hash >= TMPFS_DIRCOOKIE_MIN);
+	MPASS(de->td_cookie == de->td_hash);
+
 	dnode = VP_TO_TMPFS_DIR(vp);
-	TAILQ_INSERT_TAIL(&dnode->tn_dir.tn_dirhead, de, td_entries);
+	dnode->tn_dir.tn_readdir_lastn = 0;
+	dnode->tn_dir.tn_readdir_lastp = NULL;
+
+	MPASS(!tmpfs_dirent_dup(de));
+	xde = RB_INSERT(tmpfs_dir, &dnode->tn_dir.tn_dirhead, de);
+	if (xde != NULL && tmpfs_dirent_duphead(xde))
+		tmpfs_dir_attach_dup(dnode, &xde->ud.td_duphead, de);
+	else if (xde != NULL) {
+		/*
+		 * Allocate new duphead. Swap xde with duphead to avoid
+		 * adding/removing elements with the same hash.
+		 */
+		MPASS(!tmpfs_dirent_dup(xde));
+		tmpfs_alloc_dirent(VFS_TO_TMPFS(vp->v_mount), NULL, NULL, 0,
+		    &nde);
+		/* *nde = *xde; XXX gcc 4.2.1 may generate invalid code. */
+		memcpy(nde, xde, sizeof(*xde));
+		xde->td_cookie |= TMPFS_DIRCOOKIE_DUPHEAD;
+		LIST_INIT(&xde->ud.td_duphead);
+		xde->td_namelen = 0;
+		xde->td_node = NULL;
+		tmpfs_dir_attach_dup(dnode, &xde->ud.td_duphead, nde);
+		tmpfs_dir_attach_dup(dnode, &xde->ud.td_duphead, de);
+	}
 	dnode->tn_size += sizeof(struct tmpfs_dirent);
 	dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \
 	    TMPFS_NODE_MODIFIED;
+	tmpfs_update(vp);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Detaches the directory entry de from the directory represented by vp.
  * Note that this does not change the link count of the node pointed by
@@ -633,62 +1036,65 @@
 void
 tmpfs_dir_detach(struct vnode *vp, struct tmpfs_dirent *de)
 {
+	struct tmpfs_mount *tmp;
+	struct tmpfs_dir *head;
 	struct tmpfs_node *dnode;
+	struct tmpfs_dirent *xde;
 
 	ASSERT_VOP_ELOCKED(vp, __func__);
+
 	dnode = VP_TO_TMPFS_DIR(vp);
+	head = &dnode->tn_dir.tn_dirhead;
+	dnode->tn_dir.tn_readdir_lastn = 0;
+	dnode->tn_dir.tn_readdir_lastp = NULL;
 
-	if (dnode->tn_dir.tn_readdir_lastp == de) {
-		dnode->tn_dir.tn_readdir_lastn = 0;
-		dnode->tn_dir.tn_readdir_lastp = NULL;
-	}
+	if (tmpfs_dirent_dup(de)) {
+		/* Remove duphead if de was last entry. */
+		if (LIST_NEXT(de, uh.td_dup.entries) == NULL) {
+			xde = tmpfs_dir_xlookup_hash(dnode, de->td_hash);
+			MPASS(tmpfs_dirent_duphead(xde));
+		} else
+			xde = NULL;
+		LIST_REMOVE(de, uh.td_dup.entries);
+		LIST_REMOVE(de, uh.td_dup.index_entries);
+		if (xde != NULL) {
+			if (LIST_EMPTY(&xde->ud.td_duphead)) {
+				RB_REMOVE(tmpfs_dir, head, xde);
+				tmp = VFS_TO_TMPFS(vp->v_mount);
+				MPASS(xde->td_node == NULL);
+				tmpfs_free_dirent(tmp, xde);
+			}
+		}
+		de->td_cookie = de->td_hash;
+	} else
+		RB_REMOVE(tmpfs_dir, head, de);
 
-	TAILQ_REMOVE(&dnode->tn_dir.tn_dirhead, de, td_entries);
 	dnode->tn_size -= sizeof(struct tmpfs_dirent);
 	dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \
 	    TMPFS_NODE_MODIFIED;
+	tmpfs_update(vp);
 }
 
-/* --------------------------------------------------------------------- */
-
-/*
- * Looks for a directory entry in the directory represented by node.
- * 'cnp' describes the name of the entry to look for.  Note that the .
- * and .. components are not allowed as they do not physically exist
- * within directories.
- *
- * Returns a pointer to the entry when found, otherwise NULL.
- */
-struct tmpfs_dirent *
-tmpfs_dir_lookup(struct tmpfs_node *node, struct tmpfs_node *f,
-    struct componentname *cnp)
+void
+tmpfs_dir_destroy(struct tmpfs_mount *tmp, struct tmpfs_node *dnode)
 {
-	boolean_t found;
-	struct tmpfs_dirent *de;
+	struct tmpfs_dirent *de, *dde, *nde;
 
-	MPASS(IMPLIES(cnp->cn_namelen == 1, cnp->cn_nameptr[0] != '.'));
-	MPASS(IMPLIES(cnp->cn_namelen == 2, !(cnp->cn_nameptr[0] == '.' &&
-	    cnp->cn_nameptr[1] == '.')));
-	TMPFS_VALIDATE_DIR(node);
-
-	found = 0;
-	TAILQ_FOREACH(de, &node->tn_dir.tn_dirhead, td_entries) {
-		if (f != NULL && de->td_node != f)
-		    continue;
-		MPASS(cnp->cn_namelen < 0xffff);
-		if (de->td_namelen == (uint16_t)cnp->cn_namelen &&
-		    bcmp(de->td_name, cnp->cn_nameptr, de->td_namelen) == 0) {
-			found = 1;
-			break;
+	RB_FOREACH_SAFE(de, tmpfs_dir, &dnode->tn_dir.tn_dirhead, nde) {
+		RB_REMOVE(tmpfs_dir, &dnode->tn_dir.tn_dirhead, de);
+		/* Node may already be destroyed. */
+		de->td_node = NULL;
+		if (tmpfs_dirent_duphead(de)) {
+			while ((dde = LIST_FIRST(&de->ud.td_duphead)) != NULL) {
+				LIST_REMOVE(dde, uh.td_dup.entries);
+				dde->td_node = NULL;
+				tmpfs_free_dirent(tmp, dde);
+			}
 		}
+		tmpfs_free_dirent(tmp, de);
 	}
-	node->tn_status |= TMPFS_NODE_ACCESSED;
-
-	return found ? de : NULL;
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Helper function for tmpfs_readdir.  Creates a '.' entry for the given
  * directory and returns it in the uio space.  The function returns 0
@@ -696,7 +1102,7 @@
  * hold the directory entry or an appropriate error code if another
  * error happens.
  */
-int
+static int
 tmpfs_dir_getdotdent(struct tmpfs_node *node, struct uio *uio)
 {
 	int error;
@@ -713,20 +1119,15 @@
 	dent.d_reclen = GENERIC_DIRSIZ(&dent);
 
 	if (dent.d_reclen > uio->uio_resid)
-		error = -1;
-	else {
+		error = EJUSTRETURN;
+	else
 		error = uiomove(&dent, dent.d_reclen, uio);
-		if (error == 0)
-			uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT;
-	}
 
-	node->tn_status |= TMPFS_NODE_ACCESSED;
+	tmpfs_set_status(node, TMPFS_NODE_ACCESSED);
 
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Helper function for tmpfs_readdir.  Creates a '..' entry for the given
  * directory and returns it in the uio space.  The function returns 0
@@ -734,7 +1135,7 @@
  * hold the directory entry or an appropriate error code if another
  * error happens.
  */
-int
+static int
 tmpfs_dir_getdotdotdent(struct tmpfs_node *node, struct uio *uio)
 {
 	int error;
@@ -747,9 +1148,8 @@
 	 * Return ENOENT if the current node is already removed.
 	 */
 	TMPFS_ASSERT_LOCKED(node);
-	if (node->tn_dir.tn_parent == NULL) {
+	if (node->tn_dir.tn_parent == NULL)
 		return (ENOENT);
-	}
 
 	TMPFS_NODE_LOCK(node->tn_dir.tn_parent);
 	dent.d_fileno = node->tn_dir.tn_parent->tn_id;
@@ -763,52 +1163,16 @@
 	dent.d_reclen = GENERIC_DIRSIZ(&dent);
 
 	if (dent.d_reclen > uio->uio_resid)
-		error = -1;
-	else {
+		error = EJUSTRETURN;
+	else
 		error = uiomove(&dent, dent.d_reclen, uio);
-		if (error == 0) {
-			struct tmpfs_dirent *de;
 
-			de = TAILQ_FIRST(&node->tn_dir.tn_dirhead);
-			if (de == NULL)
-				uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
-			else
-				uio->uio_offset = tmpfs_dircookie(de);
-		}
-	}
+	tmpfs_set_status(node, TMPFS_NODE_ACCESSED);
 
-	node->tn_status |= TMPFS_NODE_ACCESSED;
-
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
- * Lookup a directory entry by its associated cookie.
- */
-struct tmpfs_dirent *
-tmpfs_dir_lookupbycookie(struct tmpfs_node *node, off_t cookie)
-{
-	struct tmpfs_dirent *de;
-
-	if (cookie == node->tn_dir.tn_readdir_lastn &&
-	    node->tn_dir.tn_readdir_lastp != NULL) {
-		return node->tn_dir.tn_readdir_lastp;
-	}
-
-	TAILQ_FOREACH(de, &node->tn_dir.tn_dirhead, td_entries) {
-		if (tmpfs_dircookie(de) == cookie) {
-			break;
-		}
-	}
-
-	return de;
-}
-
-/* --------------------------------------------------------------------- */
-
-/*
  * Helper function for tmpfs_readdir.  Returns as much directory entries
  * as can fit in the uio space.  The read starts at uio->uio_offset.
  * The function returns 0 on success, -1 if there was not enough space
@@ -816,28 +1180,56 @@
  * error code if another error happens.
  */
 int
-tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, off_t *cntp)
+tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, int maxcookies,
+    u_long *cookies, int *ncookies)
 {
+	struct tmpfs_dir_cursor dc;
+	struct tmpfs_dirent *de;
+	off_t off;
 	int error;
-	off_t startcookie;
-	struct tmpfs_dirent *de;
 
 	TMPFS_VALIDATE_DIR(node);
 
-	/* Locate the first directory entry we have to return.  We have cached
-	 * the last readdir in the node, so use those values if appropriate.
-	 * Otherwise do a linear scan to find the requested entry. */
-	startcookie = uio->uio_offset;
-	MPASS(startcookie != TMPFS_DIRCOOKIE_DOT);
-	MPASS(startcookie != TMPFS_DIRCOOKIE_DOTDOT);
-	if (startcookie == TMPFS_DIRCOOKIE_EOF) {
-		return 0;
-	} else {
-		de = tmpfs_dir_lookupbycookie(node, startcookie);
+	off = 0;
+
+	/*
+	 * Lookup the node from the current offset.  The starting offset of
+	 * 0 will lookup both '.' and '..', and then the first real entry,
+	 * or EOF if there are none.  Then find all entries for the dir that
+	 * fit into the buffer.  Once no more entries are found (de == NULL),
+	 * the offset is set to TMPFS_DIRCOOKIE_EOF, which will cause the next
+	 * call to return 0.
+	 */
+	switch (uio->uio_offset) {
+	case TMPFS_DIRCOOKIE_DOT:
+		error = tmpfs_dir_getdotdent(node, uio);
+		if (error != 0)
+			return (error);
+		uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT;
+		if (cookies != NULL)
+			cookies[(*ncookies)++] = off = uio->uio_offset;
+		/* FALLTHROUGH */
+	case TMPFS_DIRCOOKIE_DOTDOT:
+		error = tmpfs_dir_getdotdotdent(node, uio);
+		if (error != 0)
+			return (error);
+		de = tmpfs_dir_first(node, &dc);
+		uio->uio_offset = tmpfs_dirent_cookie(de);
+		if (cookies != NULL)
+			cookies[(*ncookies)++] = off = uio->uio_offset;
+		/* EOF. */
+		if (de == NULL)
+			return (0);
+		break;
+	case TMPFS_DIRCOOKIE_EOF:
+		return (0);
+	default:
+		de = tmpfs_dir_lookup_cookie(node, uio->uio_offset, &dc);
+		if (de == NULL)
+			return (EINVAL);
+		if (cookies != NULL)
+			off = tmpfs_dirent_cookie(de);
 	}
-	if (de == NULL) {
-		return EINVAL;
-	}
 
 	/* Read as much entries as possible; i.e., until we reach the end of
 	 * the directory or we exhaust uio space. */
@@ -887,7 +1279,7 @@
 		}
 		d.d_namlen = de->td_namelen;
 		MPASS(de->td_namelen < sizeof(d.d_name));
-		(void)memcpy(d.d_name, de->td_name, de->td_namelen);
+		(void)memcpy(d.d_name, de->ud.td_name, de->td_namelen);
 		d.d_name[de->td_namelen] = '\0';
 		d.d_reclen = GENERIC_DIRSIZ(&d);
 
@@ -894,7 +1286,7 @@
 		/* Stop reading if the directory entry we are treating is
 		 * bigger than the amount of data that can be returned. */
 		if (d.d_reclen > uio->uio_resid) {
-			error = -1;
+			error = EJUSTRETURN;
 			break;
 		}
 
@@ -902,22 +1294,25 @@
 		 * advance pointers. */
 		error = uiomove(&d, d.d_reclen, uio);
 		if (error == 0) {
-			(*cntp)++;
-			de = TAILQ_NEXT(de, td_entries);
+			de = tmpfs_dir_next(node, &dc);
+			if (cookies != NULL) {
+				off = tmpfs_dirent_cookie(de);
+				MPASS(*ncookies < maxcookies);
+				cookies[(*ncookies)++] = off;
+			}
 		}
 	} while (error == 0 && uio->uio_resid > 0 && de != NULL);
 
+	/* Skip setting off when using cookies as it is already done above. */
+	if (cookies == NULL)
+		off = tmpfs_dirent_cookie(de);
+
 	/* Update the offset and cache. */
-	if (de == NULL) {
-		uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
-		node->tn_dir.tn_readdir_lastn = 0;
-		node->tn_dir.tn_readdir_lastp = NULL;
-	} else {
-		node->tn_dir.tn_readdir_lastn = uio->uio_offset = tmpfs_dircookie(de);
-		node->tn_dir.tn_readdir_lastp = de;
-	}
+	uio->uio_offset = off;
+	node->tn_dir.tn_readdir_lastn = off;
+	node->tn_dir.tn_readdir_lastp = de;
 
-	node->tn_status |= TMPFS_NODE_ACCESSED;
+	tmpfs_set_status(node, TMPFS_NODE_ACCESSED);
 	return error;
 }
 
@@ -943,11 +1338,9 @@
 	de = tmpfs_dir_lookup(VP_TO_TMPFS_DIR(dvp), NULL, cnp);
 	MPASS(de != NULL && de->td_node == NULL);
 	tmpfs_dir_detach(dvp, de);
-	tmpfs_free_dirent(VFS_TO_TMPFS(dvp->v_mount), de, TRUE);
+	tmpfs_free_dirent(VFS_TO_TMPFS(dvp->v_mount), de);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Resizes the aobj associated with the regular file pointed to by 'vp' to the
  * size 'newsize'.  'vp' must point to a vnode that represents a regular file.
@@ -987,7 +1380,7 @@
 	    tmpfs_pages_check_avail(tmp, newpages - oldpages) == 0)
 		return (ENOSPC);
 
-	VM_OBJECT_LOCK(uobj);
+	VM_OBJECT_WLOCK(uobj);
 	if (newsize < oldsize) {
 		/*
 		 * Zero the truncated part of the last page.
@@ -998,18 +1391,15 @@
 retry:
 			m = vm_page_lookup(uobj, idx);
 			if (m != NULL) {
-				if ((m->oflags & VPO_BUSY) != 0 ||
-				    m->busy != 0) {
-					vm_page_sleep(m, "tmfssz");
+				if (vm_page_sleep_if_busy(m, "tmfssz"))
 					goto retry;
-				}
 				MPASS(m->valid == VM_PAGE_BITS_ALL);
 			} else if (vm_pager_has_page(uobj, idx, NULL, NULL)) {
 				m = vm_page_alloc(uobj, idx, VM_ALLOC_NORMAL);
 				if (m == NULL) {
-					VM_OBJECT_UNLOCK(uobj);
+					VM_OBJECT_WUNLOCK(uobj);
 					VM_WAIT;
-					VM_OBJECT_LOCK(uobj);
+					VM_OBJECT_WLOCK(uobj);
 					goto retry;
 				} else if (m->valid != VM_PAGE_BITS_ALL) {
 					ma[0] = m;
@@ -1022,7 +1412,7 @@
 				if (rv == VM_PAGER_OK) {
 					vm_page_deactivate(m);
 					vm_page_unlock(m);
-					vm_page_wakeup(m);
+					vm_page_xunbusy(m);
 				} else {
 					vm_page_free(m);
 					vm_page_unlock(m);
@@ -1029,7 +1419,7 @@
 					if (ignerr)
 						m = NULL;
 					else {
-						VM_OBJECT_UNLOCK(uobj);
+						VM_OBJECT_WUNLOCK(uobj);
 						return (EIO);
 					}
 				}
@@ -1051,19 +1441,39 @@
 		}
 	}
 	uobj->size = newpages;
-	VM_OBJECT_UNLOCK(uobj);
+	VM_OBJECT_WUNLOCK(uobj);
 
-	TMPFS_LOCK(tmp);
-	tmp->tm_pages_used += (newpages - oldpages);
-	TMPFS_UNLOCK(tmp);
+	atomic_add_long(&tmp->tm_pages_used, newpages - oldpages);
 
 	node->tn_size = newsize;
-	vnode_pager_setsize(vp, newsize);
 	return (0);
 }
 
-/* --------------------------------------------------------------------- */
+void
+tmpfs_check_mtime(struct vnode *vp)
+{
+	struct tmpfs_node *node;
+	struct vm_object *obj;
 
+	ASSERT_VOP_ELOCKED(vp, "check_mtime");
+	if (vp->v_type != VREG)
+		return;
+	obj = vp->v_object;
+	KASSERT((obj->flags & (OBJ_TMPFS_NODE | OBJ_TMPFS)) ==
+	    (OBJ_TMPFS_NODE | OBJ_TMPFS), ("non-tmpfs obj"));
+	/* unlocked read */
+	if ((obj->flags & OBJ_TMPFS_DIRTY) != 0) {
+		VM_OBJECT_WLOCK(obj);
+		if ((obj->flags & OBJ_TMPFS_DIRTY) != 0) {
+			obj->flags &= ~OBJ_TMPFS_DIRTY;
+			node = VP_TO_TMPFS_NODE(vp);
+			node->tn_status |= TMPFS_NODE_MODIFIED |
+			    TMPFS_NODE_CHANGED;
+		}
+		VM_OBJECT_WUNLOCK(obj);
+	}
+}
+
 /*
  * Change flags of the given vnode.
  * Caller should execute tmpfs_update on vp after a successful execution.
@@ -1070,15 +1480,22 @@
  * The vnode must be locked on entry and remain locked on exit.
  */
 int
-tmpfs_chflags(struct vnode *vp, int flags, struct ucred *cred, struct thread *p)
+tmpfs_chflags(struct vnode *vp, u_long flags, struct ucred *cred,
+    struct thread *p)
 {
 	int error;
 	struct tmpfs_node *node;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chflags");
 
 	node = VP_TO_TMPFS_NODE(vp);
 
+	if ((flags & ~(SF_APPEND | SF_ARCHIVED | SF_IMMUTABLE | SF_NOUNLINK |
+	    UF_APPEND | UF_ARCHIVE | UF_HIDDEN | UF_IMMUTABLE | UF_NODUMP |
+	    UF_NOUNLINK | UF_OFFLINE | UF_OPAQUE | UF_READONLY | UF_REPARSE |
+	    UF_SPARSE | UF_SYSTEM)) != 0)
+		return (EOPNOTSUPP);
+
 	/* Disallow this operation if the file system is mounted read-only. */
 	if (vp->v_mount->mnt_flag & MNT_RDONLY)
 		return EROFS;
@@ -1094,36 +1511,26 @@
 	 * flags, or modify flags if any system flags are set.
 	 */
 	if (!priv_check_cred(cred, PRIV_VFS_SYSFLAGS, 0)) {
-		if (node->tn_flags
-		  & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) {
+		if (node->tn_flags &
+		    (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) {
 			error = securelevel_gt(cred, 0);
 			if (error)
 				return (error);
 		}
-		/* Snapshot flag cannot be set or cleared */
-		if (((flags & SF_SNAPSHOT) != 0 &&
-		  (node->tn_flags & SF_SNAPSHOT) == 0) ||
-		  ((flags & SF_SNAPSHOT) == 0 &&
-		  (node->tn_flags & SF_SNAPSHOT) != 0))
-			return (EPERM);
-		node->tn_flags = flags;
 	} else {
-		if (node->tn_flags
-		  & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) ||
-		  (flags & UF_SETTABLE) != flags)
+		if (node->tn_flags &
+		    (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) ||
+		    ((flags ^ node->tn_flags) & SF_SETTABLE))
 			return (EPERM);
-		node->tn_flags &= SF_SETTABLE;
-		node->tn_flags |= (flags & UF_SETTABLE);
 	}
+	node->tn_flags = flags;
 	node->tn_status |= TMPFS_NODE_CHANGED;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chflags2");
 
-	return 0;
+	return (0);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Change access mode on the given vnode.
  * Caller should execute tmpfs_update on vp after a successful execution.
@@ -1135,7 +1542,7 @@
 	int error;
 	struct tmpfs_node *node;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chmod");
 
 	node = VP_TO_TMPFS_NODE(vp);
 
@@ -1175,13 +1582,11 @@
 
 	node->tn_status |= TMPFS_NODE_CHANGED;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chmod2");
 
-	return 0;
+	return (0);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Change ownership of the given vnode.  At least one of uid or gid must
  * be different than VNOVAL.  If one is set to that value, the attribute
@@ -1198,7 +1603,7 @@
 	uid_t ouid;
 	gid_t ogid;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chown");
 
 	node = VP_TO_TMPFS_NODE(vp);
 
@@ -1248,13 +1653,11 @@
 			node->tn_mode &= ~(S_ISUID | S_ISGID);
 	}
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chown2");
 
-	return 0;
+	return (0);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Change size of the given vnode.
  * Caller should execute tmpfs_update on vp after a successful execution.
@@ -1267,7 +1670,7 @@
 	int error;
 	struct tmpfs_node *node;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chsize");
 
 	node = VP_TO_TMPFS_NODE(vp);
 
@@ -1305,13 +1708,11 @@
 	/* tmpfs_truncate will raise the NOTE_EXTEND and NOTE_ATTRIB kevents
 	 * for us, as will update tn_status; no need to do that here. */
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chsize2");
 
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Change access and modification times of the given vnode.
  * Caller should execute tmpfs_update on vp after a successful execution.
@@ -1318,13 +1719,13 @@
  * The vnode must be locked on entry and remain locked on exit.
  */
 int
-tmpfs_chtimes(struct vnode *vp, struct timespec *atime, struct timespec *mtime,
-	struct timespec *birthtime, int vaflags, struct ucred *cred, struct thread *l)
+tmpfs_chtimes(struct vnode *vp, struct vattr *vap,
+    struct ucred *cred, struct thread *l)
 {
 	int error;
 	struct tmpfs_node *node;
 
-	MPASS(VOP_ISLOCKED(vp));
+	ASSERT_VOP_ELOCKED(vp, "chtimes");
 
 	node = VP_TO_TMPFS_NODE(vp);
 
@@ -1336,35 +1737,39 @@
 	if (node->tn_flags & (IMMUTABLE | APPEND))
 		return EPERM;
 
-	/* Determine if the user have proper privilege to update time. */
-	if (vaflags & VA_UTIMES_NULL) {
-		error = VOP_ACCESS(vp, VADMIN, cred, l);
-		if (error)
-			error = VOP_ACCESS(vp, VWRITE, cred, l);
-	} else
-		error = VOP_ACCESS(vp, VADMIN, cred, l);
-	if (error)
+	error = vn_utimes_perm(vp, vap, cred, l);
+	if (error != 0)
 		return (error);
 
-	if (atime->tv_sec != VNOVAL && atime->tv_nsec != VNOVAL)
+	if (vap->va_atime.tv_sec != VNOVAL)
 		node->tn_status |= TMPFS_NODE_ACCESSED;
 
-	if (mtime->tv_sec != VNOVAL && mtime->tv_nsec != VNOVAL)
+	if (vap->va_mtime.tv_sec != VNOVAL)
 		node->tn_status |= TMPFS_NODE_MODIFIED;
 
-	if (birthtime->tv_nsec != VNOVAL && birthtime->tv_nsec != VNOVAL)
+	if (vap->va_birthtime.tv_sec != VNOVAL)
 		node->tn_status |= TMPFS_NODE_MODIFIED;
 
-	tmpfs_itimes(vp, atime, mtime);
+	tmpfs_itimes(vp, &vap->va_atime, &vap->va_mtime);
 
-	if (birthtime->tv_nsec != VNOVAL && birthtime->tv_nsec != VNOVAL)
-		node->tn_birthtime = *birthtime;
-	MPASS(VOP_ISLOCKED(vp));
+	if (vap->va_birthtime.tv_sec != VNOVAL)
+		node->tn_birthtime = vap->va_birthtime;
+	ASSERT_VOP_ELOCKED(vp, "chtimes2");
 
-	return 0;
+	return (0);
 }
 
-/* --------------------------------------------------------------------- */
+void
+tmpfs_set_status(struct tmpfs_node *node, int status)
+{
+
+	if ((node->tn_status & status) == status)
+		return;
+	TMPFS_NODE_LOCK(node);
+	node->tn_status |= status;
+	TMPFS_NODE_UNLOCK(node);
+}
+
 /* Sync timestamps */
 void
 tmpfs_itimes(struct vnode *vp, const struct timespec *acc,
@@ -1373,6 +1778,7 @@
 	struct tmpfs_node *node;
 	struct timespec now;
 
+	ASSERT_VOP_LOCKED(vp, "tmpfs_itimes");
 	node = VP_TO_TMPFS_NODE(vp);
 
 	if ((node->tn_status & (TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED |
@@ -1380,6 +1786,7 @@
 		return;
 
 	vfs_timestamp(&now);
+	TMPFS_NODE_LOCK(node);
 	if (node->tn_status & TMPFS_NODE_ACCESSED) {
 		if (acc == NULL)
 			 acc = &now;
@@ -1390,15 +1797,14 @@
 			mod = &now;
 		node->tn_mtime = *mod;
 	}
-	if (node->tn_status & TMPFS_NODE_CHANGED) {
+	if (node->tn_status & TMPFS_NODE_CHANGED)
 		node->tn_ctime = now;
-	}
-	node->tn_status &=
-	    ~(TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | TMPFS_NODE_CHANGED);
+	node->tn_status &= ~(TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED |
+	    TMPFS_NODE_CHANGED);
+	TMPFS_NODE_UNLOCK(node);
+
 }
 
-/* --------------------------------------------------------------------- */
-
 void
 tmpfs_update(struct vnode *vp)
 {
@@ -1406,8 +1812,6 @@
 	tmpfs_itimes(vp, NULL, NULL);
 }
 
-/* --------------------------------------------------------------------- */
-
 int
 tmpfs_truncate(struct vnode *vp, off_t length)
 {
@@ -1430,12 +1834,23 @@
 		return (EFBIG);
 
 	error = tmpfs_reg_resize(vp, length, FALSE);
-	if (error == 0) {
+	if (error == 0)
 		node->tn_status |= TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED;
-	}
 
 out:
 	tmpfs_update(vp);
 
-	return error;
+	return (error);
 }
+
+static __inline int
+tmpfs_dirtree_cmp(struct tmpfs_dirent *a, struct tmpfs_dirent *b)
+{
+	if (a->td_hash > b->td_hash)
+		return (1);
+	else if (a->td_hash < b->td_hash)
+		return (-1);
+	return (0);
+}
+
+RB_GENERATE_STATIC(tmpfs_dir, tmpfs_dirent, uh.td_entries, tmpfs_dirtree_cmp);

Modified: trunk/sys/fs/tmpfs/tmpfs_vfsops.c
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs_vfsops.c	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs_vfsops.c	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs_vfsops.c,v 1.10 2005/12/11 12:24:29 christos Exp $	*/
 
 /*-
@@ -33,21 +34,24 @@
 /*
  * Efficient memory file system.
  *
- * tmpfs is a file system that uses NetBSD's virtual memory sub-system
- * (the well-known UVM) to store file data and metadata in an efficient
- * way.  This means that it does not follow the structure of an on-disk
- * file system because it simply does not need to.  Instead, it uses
+ * tmpfs is a file system that uses FreeBSD's virtual memory
+ * sub-system to store file data and metadata in an efficient way.
+ * This means that it does not follow the structure of an on-disk file
+ * system because it simply does not need to.  Instead, it uses
  * memory-specific data structures and algorithms to automatically
  * allocate and release resources.
  */
 #include <sys/cdefs.h>
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: stable/10/sys/fs/tmpfs/tmpfs_vfsops.c 313095 2017-02-02 13:39:11Z kib $");
 
 #include <sys/param.h>
 #include <sys/limits.h>
 #include <sys/lock.h>
 #include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/jail.h>
 #include <sys/kernel.h>
+#include <sys/rwlock.h>
 #include <sys/stat.h>
 #include <sys/systm.h>
 #include <sys/sysctl.h>
@@ -66,8 +70,6 @@
 MALLOC_DEFINE(M_TMPFSMNT, "tmpfs mount", "tmpfs mount structures");
 MALLOC_DEFINE(M_TMPFSNAME, "tmpfs name", "tmpfs file names");
 
-/* --------------------------------------------------------------------- */
-
 static int	tmpfs_mount(struct mount *);
 static int	tmpfs_unmount(struct mount *, int);
 static int	tmpfs_root(struct mount *, int flags, struct vnode **);
@@ -75,11 +77,9 @@
 		    struct vnode **);
 static int	tmpfs_statfs(struct mount *, struct statfs *);
 
-/* --------------------------------------------------------------------- */
-
 static const char *tmpfs_opts[] = {
 	"from", "size", "maxfilesize", "inodes", "uid", "gid", "mode", "export",
-	NULL
+	"union", "nonc", NULL
 };
 
 static const char *tmpfs_updateopts[] = {
@@ -86,52 +86,7 @@
 	"from", "export", NULL
 };
 
-/* --------------------------------------------------------------------- */
-
 static int
-tmpfs_getopt_size(struct vfsoptlist *opts, const char *name, off_t *value)
-{
-	char *opt_value, *vtp;
-	quad_t iv;
-	int error, opt_len;
-
-	error = vfs_getopt(opts, name, (void **)&opt_value, &opt_len);
-	if (error != 0)
-		return (error);
-	if (opt_len == 0 || opt_value == NULL)
-		return (EINVAL);
-	if (opt_value[0] == '\0' || opt_value[opt_len - 1] != '\0')
-		return (EINVAL);
-	iv = strtoq(opt_value, &vtp, 0);
-	if (vtp == opt_value || (vtp[0] != '\0' && vtp[1] != '\0'))
-		return (EINVAL);
-	if (iv < 0)
-		return (EINVAL);
-	switch (vtp[0]) {
-	case 't':
-	case 'T':
-		iv *= 1024;
-	case 'g':
-	case 'G':
-		iv *= 1024;
-	case 'm':
-	case 'M':
-		iv *= 1024;
-	case 'k':
-	case 'K':
-		iv *= 1024;
-	case '\0':
-		break;
-	default:
-		return (EINVAL);
-	}
-	*value = iv;
-
-	return (0);
-}
-
-
-static int
 tmpfs_node_ctor(void *mem, int size, void *arg, int flags)
 {
 	struct tmpfs_node *node = (struct tmpfs_node *)mem;
@@ -181,7 +136,9 @@
 	    sizeof(struct tmpfs_dirent) + sizeof(struct tmpfs_node));
 	struct tmpfs_mount *tmp;
 	struct tmpfs_node *root;
+	struct thread *td = curthread;
 	int error;
+	bool nonc;
 	/* Size counters. */
 	u_quad_t pages;
 	off_t nodes_max, size_max, maxfilesize;
@@ -193,6 +150,9 @@
 
 	struct vattr va;
 
+	if (!prison_allow(td->td_ucred, PR_ALLOW_MOUNT_TMPFS))
+		return (EPERM);
+
 	if (vfs_filteropt(mp->mnt_optnew, tmpfs_opts))
 		return (EINVAL);
 
@@ -221,27 +181,30 @@
 	if (mp->mnt_cred->cr_ruid != 0 ||
 	    vfs_scanopt(mp->mnt_optnew, "mode", "%ho", &root_mode) != 1)
 		root_mode = va.va_mode;
-	if (tmpfs_getopt_size(mp->mnt_optnew, "inodes", &nodes_max) != 0)
+	if (vfs_getopt_size(mp->mnt_optnew, "inodes", &nodes_max) != 0)
 		nodes_max = 0;
-	if (tmpfs_getopt_size(mp->mnt_optnew, "size", &size_max) != 0)
+	if (vfs_getopt_size(mp->mnt_optnew, "size", &size_max) != 0)
 		size_max = 0;
-	if (tmpfs_getopt_size(mp->mnt_optnew, "maxfilesize", &maxfilesize) != 0)
+	if (vfs_getopt_size(mp->mnt_optnew, "maxfilesize", &maxfilesize) != 0)
 		maxfilesize = 0;
+	nonc = vfs_getopt(mp->mnt_optnew, "nonc", NULL, NULL) == 0;
 
 	/* Do not allow mounts if we do not have enough memory to preserve
 	 * the minimum reserved pages. */
 	if (tmpfs_mem_avail() < TMPFS_PAGES_MINRESERVED)
-		return ENOSPC;
+		return (ENOSPC);
 
 	/* Get the maximum number of memory pages this file system is
 	 * allowed to use, based on the maximum size the user passed in
 	 * the mount structure.  A value of zero is treated as if the
 	 * maximum available space was requested. */
-	if (size_max < PAGE_SIZE || size_max > OFF_MAX - PAGE_SIZE ||
+	if (size_max == 0 || size_max > OFF_MAX - PAGE_SIZE ||
 	    (SIZE_MAX < OFF_MAX && size_max / PAGE_SIZE >= SIZE_MAX))
 		pages = SIZE_MAX;
-	else
+	else {
+		size_max = roundup(size_max, PAGE_SIZE);
 		pages = howmany(size_max, PAGE_SIZE);
+	}
 	MPASS(pages > 0);
 
 	if (nodes_max <= 3) {
@@ -258,44 +221,42 @@
 	tmp = (struct tmpfs_mount *)malloc(sizeof(struct tmpfs_mount),
 	    M_TMPFSMNT, M_WAITOK | M_ZERO);
 
-	mtx_init(&tmp->allnode_lock, "tmpfs allnode lock", NULL, MTX_DEF);
+	mtx_init(&tmp->tm_allnode_lock, "tmpfs allnode lock", NULL, MTX_DEF);
 	tmp->tm_nodes_max = nodes_max;
 	tmp->tm_nodes_inuse = 0;
+	tmp->tm_refcount = 1;
 	tmp->tm_maxfilesize = maxfilesize > 0 ? maxfilesize : OFF_MAX;
 	LIST_INIT(&tmp->tm_nodes_used);
 
 	tmp->tm_pages_max = pages;
 	tmp->tm_pages_used = 0;
-	tmp->tm_ino_unr = new_unrhdr(2, INT_MAX, &tmp->allnode_lock);
+	tmp->tm_ino_unr = new_unrhdr(2, INT_MAX, &tmp->tm_allnode_lock);
 	tmp->tm_dirent_pool = uma_zcreate("TMPFS dirent",
-	    sizeof(struct tmpfs_dirent),
-	    NULL, NULL, NULL, NULL,
+	    sizeof(struct tmpfs_dirent), NULL, NULL, NULL, NULL,
 	    UMA_ALIGN_PTR, 0);
 	tmp->tm_node_pool = uma_zcreate("TMPFS node",
-	    sizeof(struct tmpfs_node),
-	    tmpfs_node_ctor, tmpfs_node_dtor,
-	    tmpfs_node_init, tmpfs_node_fini,
-	    UMA_ALIGN_PTR, 0);
+	    sizeof(struct tmpfs_node), tmpfs_node_ctor, tmpfs_node_dtor,
+	    tmpfs_node_init, tmpfs_node_fini, UMA_ALIGN_PTR, 0);
 	tmp->tm_ronly = (mp->mnt_flag & MNT_RDONLY) != 0;
+	tmp->tm_nonc = nonc;
 
 	/* Allocate the root node. */
-	error = tmpfs_alloc_node(tmp, VDIR, root_uid,
-	    root_gid, root_mode & ALLPERMS, NULL, NULL,
-	    VNOVAL, &root);
+	error = tmpfs_alloc_node(mp, tmp, VDIR, root_uid, root_gid,
+	    root_mode & ALLPERMS, NULL, NULL, VNOVAL, &root);
 
 	if (error != 0 || root == NULL) {
-	    uma_zdestroy(tmp->tm_node_pool);
-	    uma_zdestroy(tmp->tm_dirent_pool);
-	    delete_unrhdr(tmp->tm_ino_unr);
-	    free(tmp, M_TMPFSMNT);
-	    return error;
+		uma_zdestroy(tmp->tm_node_pool);
+		uma_zdestroy(tmp->tm_dirent_pool);
+		delete_unrhdr(tmp->tm_ino_unr);
+		free(tmp, M_TMPFSMNT);
+		return (error);
 	}
-	KASSERT(root->tn_id == 2, ("tmpfs root with invalid ino: %d", root->tn_id));
+	KASSERT(root->tn_id == 2,
+	    ("tmpfs root with invalid ino: %ju", (uintmax_t)root->tn_id));
 	tmp->tm_root = root;
 
 	MNT_ILOCK(mp);
 	mp->mnt_flag |= MNT_LOCAL;
-	mp->mnt_kern_flag |= MNTK_MPSAFE;
 	MNT_IUNLOCK(mp);
 
 	mp->mnt_data = tmp;
@@ -306,128 +267,136 @@
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
-
 /* ARGSUSED2 */
 static int
 tmpfs_unmount(struct mount *mp, int mntflags)
 {
-	int error;
-	int flags = 0;
 	struct tmpfs_mount *tmp;
 	struct tmpfs_node *node;
+	int error, flags;
 
-	/* Handle forced unmounts. */
-	if (mntflags & MNT_FORCE)
-		flags |= FORCECLOSE;
+	flags = (mntflags & MNT_FORCE) != 0 ? FORCECLOSE : 0;
+	tmp = VFS_TO_TMPFS(mp);
 
-	/* Finalize all pending I/O. */
-	error = vflush(mp, 0, flags, curthread);
+	/* Stop writers */
+	error = vfs_write_suspend_umnt(mp);
 	if (error != 0)
-		return error;
+		return (error);
+	/*
+	 * At this point, nodes cannot be destroyed by any other
+	 * thread because write suspension is started.
+	 */
 
-	tmp = VFS_TO_TMPFS(mp);
+	for (;;) {
+		error = vflush(mp, 0, flags, curthread);
+		if (error != 0) {
+			vfs_write_resume(mp, VR_START_WRITE);
+			return (error);
+		}
+		MNT_ILOCK(mp);
+		if (mp->mnt_nvnodelistsize == 0) {
+			MNT_IUNLOCK(mp);
+			break;
+		}
+		MNT_IUNLOCK(mp);
+		if ((mntflags & MNT_FORCE) == 0) {
+			vfs_write_resume(mp, VR_START_WRITE);
+			return (EBUSY);
+		}
+	}
 
-	/* Free all associated data.  The loop iterates over the linked list
-	 * we have containing all used nodes.  For each of them that is
-	 * a directory, we free all its directory entries.  Note that after
-	 * freeing a node, it will automatically go to the available list,
-	 * so we will later have to iterate over it to release its items. */
-	node = LIST_FIRST(&tmp->tm_nodes_used);
-	while (node != NULL) {
-		struct tmpfs_node *next;
+	TMPFS_LOCK(tmp);
+	while ((node = LIST_FIRST(&tmp->tm_nodes_used)) != NULL) {
+		TMPFS_NODE_LOCK(node);
+		if (node->tn_type == VDIR)
+			tmpfs_dir_destroy(tmp, node);
+		if (tmpfs_free_node_locked(tmp, node, true))
+			TMPFS_LOCK(tmp);
+		else
+			TMPFS_NODE_UNLOCK(node);
+	}
 
-		if (node->tn_type == VDIR) {
-			struct tmpfs_dirent *de;
+	mp->mnt_data = NULL;
+	tmpfs_free_tmp(tmp);
+	vfs_write_resume(mp, VR_START_WRITE);
 
-			de = TAILQ_FIRST(&node->tn_dir.tn_dirhead);
-			while (de != NULL) {
-				struct tmpfs_dirent *nde;
+	MNT_ILOCK(mp);
+	mp->mnt_flag &= ~MNT_LOCAL;
+	MNT_IUNLOCK(mp);
 
-				nde = TAILQ_NEXT(de, td_entries);
-				tmpfs_free_dirent(tmp, de, FALSE);
-				de = nde;
-				node->tn_size -= sizeof(struct tmpfs_dirent);
-			}
-		}
+	return (0);
+}
 
-		next = LIST_NEXT(node, tn_entries);
-		tmpfs_free_node(tmp, node);
-		node = next;
+void
+tmpfs_free_tmp(struct tmpfs_mount *tmp)
+{
+
+	MPASS(tmp->tm_refcount > 0);
+	tmp->tm_refcount--;
+	if (tmp->tm_refcount > 0) {
+		TMPFS_UNLOCK(tmp);
+		return;
 	}
+	TMPFS_UNLOCK(tmp);
 
 	uma_zdestroy(tmp->tm_dirent_pool);
 	uma_zdestroy(tmp->tm_node_pool);
 	delete_unrhdr(tmp->tm_ino_unr);
 
-	mtx_destroy(&tmp->allnode_lock);
+	mtx_destroy(&tmp->tm_allnode_lock);
 	MPASS(tmp->tm_pages_used == 0);
 	MPASS(tmp->tm_nodes_inuse == 0);
 
-	/* Throw away the tmpfs_mount structure. */
-	free(mp->mnt_data, M_TMPFSMNT);
-	mp->mnt_data = NULL;
-
-	MNT_ILOCK(mp);
-	mp->mnt_flag &= ~MNT_LOCAL;
-	MNT_IUNLOCK(mp);
-	return 0;
+	free(tmp, M_TMPFSMNT);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_root(struct mount *mp, int flags, struct vnode **vpp)
 {
 	int error;
+
 	error = tmpfs_alloc_vp(mp, VFS_TO_TMPFS(mp)->tm_root, flags, vpp);
-
-	if (!error)
+	if (error == 0)
 		(*vpp)->v_vflag |= VV_ROOT;
-
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_fhtovp(struct mount *mp, struct fid *fhp, int flags,
     struct vnode **vpp)
 {
-	boolean_t found;
 	struct tmpfs_fid *tfhp;
 	struct tmpfs_mount *tmp;
 	struct tmpfs_node *node;
+	int error;
 
 	tmp = VFS_TO_TMPFS(mp);
 
 	tfhp = (struct tmpfs_fid *)fhp;
 	if (tfhp->tf_len != sizeof(struct tmpfs_fid))
-		return EINVAL;
+		return (EINVAL);
 
 	if (tfhp->tf_id >= tmp->tm_nodes_max)
-		return EINVAL;
+		return (EINVAL);
 
-	found = FALSE;
-
 	TMPFS_LOCK(tmp);
 	LIST_FOREACH(node, &tmp->tm_nodes_used, tn_entries) {
 		if (node->tn_id == tfhp->tf_id &&
 		    node->tn_gen == tfhp->tf_gen) {
-			found = TRUE;
+			tmpfs_ref_node(node);
 			break;
 		}
 	}
 	TMPFS_UNLOCK(tmp);
 
-	if (found)
-		return (tmpfs_alloc_vp(mp, node, LK_EXCLUSIVE, vpp));
-
-	return (EINVAL);
+	if (node != NULL) {
+		error = tmpfs_alloc_vp(mp, node, LK_EXCLUSIVE, vpp);
+		tmpfs_free_node(tmp, node);
+	} else
+		error = EINVAL;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 /* ARGSUSED2 */
 static int
 tmpfs_statfs(struct mount *mp, struct statfs *sbp)
@@ -441,7 +410,7 @@
 	sbp->f_bsize = PAGE_SIZE;
 
 	used = tmpfs_pages_used(tmp);
-	if (tmp->tm_pages_max != SIZE_MAX)
+	if (tmp->tm_pages_max != ULONG_MAX)
 		 sbp->f_blocks = tmp->tm_pages_max;
 	else
 		 sbp->f_blocks = used + tmpfs_mem_avail();
@@ -461,8 +430,52 @@
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
+static int
+tmpfs_sync(struct mount *mp, int waitfor)
+{
+	struct vnode *vp, *mvp;
+	struct vm_object *obj;
 
+	if (waitfor == MNT_SUSPEND) {
+		MNT_ILOCK(mp);
+		mp->mnt_kern_flag |= MNTK_SUSPEND2 | MNTK_SUSPENDED;
+		MNT_IUNLOCK(mp);
+	} else if (waitfor == MNT_LAZY) {
+		/*
+		 * Handle lazy updates of mtime from writes to mmaped
+		 * regions.  Use MNT_VNODE_FOREACH_ALL instead of
+		 * MNT_VNODE_FOREACH_ACTIVE, since unmap of the
+		 * tmpfs-backed vnode does not call vinactive(), due
+		 * to vm object type is OBJT_SWAP.
+		 */
+		MNT_VNODE_FOREACH_ALL(vp, mp, mvp) {
+			if (vp->v_type != VREG) {
+				VI_UNLOCK(vp);
+				continue;
+			}
+			obj = vp->v_object;
+			KASSERT((obj->flags & (OBJ_TMPFS_NODE | OBJ_TMPFS)) ==
+			    (OBJ_TMPFS_NODE | OBJ_TMPFS), ("non-tmpfs obj"));
+
+			/*
+			 * Unlocked read, avoid taking vnode lock if
+			 * not needed.  Lost update will be handled on
+			 * the next call.
+			 */
+			if ((obj->flags & OBJ_TMPFS_DIRTY) == 0) {
+				VI_UNLOCK(vp);
+				continue;
+			}
+			if (vget(vp, LK_EXCLUSIVE | LK_RETRY | LK_INTERLOCK,
+			    curthread) != 0)
+				continue;
+			tmpfs_check_mtime(vp);
+			vput(vp);
+		}
+	}
+	return (0);
+}
+
 /*
  * tmpfs vfs operations.
  */
@@ -473,5 +486,6 @@
 	.vfs_root =			tmpfs_root,
 	.vfs_statfs =			tmpfs_statfs,
 	.vfs_fhtovp =			tmpfs_fhtovp,
+	.vfs_sync =			tmpfs_sync,
 };
-VFS_SET(tmpfs_vfsops, tmpfs, 0);
+VFS_SET(tmpfs_vfsops, tmpfs, VFCF_JAIL);

Modified: trunk/sys/fs/tmpfs/tmpfs_vnops.c
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs_vnops.c	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs_vnops.c	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs_vnops.c,v 1.39 2007/07/23 15:41:01 jmmv Exp $	*/
 
 /*-
@@ -34,16 +35,17 @@
  * tmpfs vnode interface.
  */
 #include <sys/cdefs.h>
-__MBSDID("$MidnightBSD$");
+__FBSDID("$FreeBSD: stable/10/sys/fs/tmpfs/tmpfs_vnops.c 313095 2017-02-02 13:39:11Z kib $");
 
 #include <sys/param.h>
 #include <sys/fcntl.h>
 #include <sys/lockf.h>
+#include <sys/lock.h>
 #include <sys/namei.h>
 #include <sys/priv.h>
 #include <sys/proc.h>
+#include <sys/rwlock.h>
 #include <sys/sched.h>
-#include <sys/sf_buf.h>
 #include <sys/stat.h>
 #include <sys/systm.h>
 #include <sys/sysctl.h>
@@ -56,9 +58,6 @@
 #include <vm/vm_page.h>
 #include <vm/vm_pager.h>
 
-#include <machine/_inttypes.h>
-
-#include <fs/fifofs/fifo.h>
 #include <fs/tmpfs/tmpfs_vnops.h>
 #include <fs/tmpfs/tmpfs.h>
 
@@ -69,18 +68,21 @@
     __DEVOLATILE(int *, &tmpfs_rename_restarts), 0,
     "Times rename had to restart due to lock contention");
 
-/* --------------------------------------------------------------------- */
+static int
+tmpfs_vn_get_ino_alloc(struct mount *mp, void *arg, int lkflags,
+    struct vnode **rvp)
+{
 
+	return (tmpfs_alloc_vp(mp, arg, lkflags, rvp));
+}
+
 static int
-tmpfs_lookup(struct vop_cachedlookup_args *v)
+tmpfs_lookup1(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp)
 {
-	struct vnode *dvp = v->a_dvp;
-	struct vnode **vpp = v->a_vpp;
-	struct componentname *cnp = v->a_cnp;
-
+	struct tmpfs_dirent *de;
+	struct tmpfs_node *dnode, *pnode;
+	struct tmpfs_mount *tm;
 	int error;
-	struct tmpfs_dirent *de;
-	struct tmpfs_node *dnode;
 
 	dnode = VP_TO_TMPFS_DIR(dvp);
 	*vpp = NULLVP;
@@ -101,17 +103,14 @@
 		goto out;
 	}
 	if (cnp->cn_flags & ISDOTDOT) {
-		int ltype = 0;
-
-		ltype = VOP_ISLOCKED(dvp);
-		vhold(dvp);
-		VOP_UNLOCK(dvp, 0);
-		/* Allocate a new vnode on the matching entry. */
-		error = tmpfs_alloc_vp(dvp->v_mount, dnode->tn_dir.tn_parent,
-		    cnp->cn_lkflags, vpp);
-
-		vn_lock(dvp, ltype | LK_RETRY);
-		vdrop(dvp);
+		tm = VFS_TO_TMPFS(dvp->v_mount);
+		pnode = dnode->tn_dir.tn_parent;
+		tmpfs_ref_node(pnode);
+		error = vn_vget_ino_gen(dvp, tmpfs_vn_get_ino_alloc,
+		    pnode, cnp->cn_lkflags, vpp);
+		tmpfs_free_node(tm, pnode);
+		if (error != 0)
+			goto out;
 	} else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
 		VREF(dvp);
 		*vpp = dvp;
@@ -121,10 +120,12 @@
 		if (de != NULL && de->td_node == NULL)
 			cnp->cn_flags |= ISWHITEOUT;
 		if (de == NULL || de->td_node == NULL) {
-			/* The entry was not found in the directory.
+			/*
+			 * The entry was not found in the directory.
 			 * This is OK if we are creating or renaming an
 			 * entry and are working on the last component of
-			 * the path name. */
+			 * the path name.
+			 */
 			if ((cnp->cn_flags & ISLASTCN) &&
 			    (cnp->cn_nameiop == CREATE || \
 			    cnp->cn_nameiop == RENAME ||
@@ -136,8 +137,10 @@
 				if (error != 0)
 					goto out;
 
-				/* Keep the component name in the buffer for
-				 * future uses. */
+				/*
+				 * Keep the component name in the buffer for
+				 * future uses.
+				 */
 				cnp->cn_flags |= SAVENAME;
 
 				error = EJUSTRETURN;
@@ -146,14 +149,18 @@
 		} else {
 			struct tmpfs_node *tnode;
 
-			/* The entry was found, so get its associated
-			 * tmpfs_node. */
+			/*
+			 * The entry was found, so get its associated
+			 * tmpfs_node.
+			 */
 			tnode = de->td_node;
 
-			/* If we are not at the last path component and
+			/*
+			 * If we are not at the last path component and
 			 * found a non-directory or non-link entry (which
 			 * may itself be pointing to a directory), raise
-			 * an error. */
+			 * an error.
+			 */
 			if ((tnode->tn_type != VDIR &&
 			    tnode->tn_type != VLNK) &&
 			    !(cnp->cn_flags & ISLASTCN)) {
@@ -161,9 +168,11 @@
 				goto out;
 			}
 
-			/* If we are deleting or renaming the entry, keep
+			/*
+			 * If we are deleting or renaming the entry, keep
 			 * track of its tmpfs_dirent so that it can be
-			 * easily deleted later. */
+			 * easily deleted later.
+			 */
 			if ((cnp->cn_flags & ISLASTCN) &&
 			    (cnp->cn_nameiop == DELETE ||
 			    cnp->cn_nameiop == RENAME)) {
@@ -174,13 +183,14 @@
 
 				/* Allocate a new vnode on the matching entry. */
 				error = tmpfs_alloc_vp(dvp->v_mount, tnode,
-						cnp->cn_lkflags, vpp);
+				    cnp->cn_lkflags, vpp);
 				if (error != 0)
 					goto out;
 
 				if ((dnode->tn_mode & S_ISTXT) &&
-				  VOP_ACCESS(dvp, VADMIN, cnp->cn_cred, cnp->cn_thread) &&
-				  VOP_ACCESS(*vpp, VADMIN, cnp->cn_cred, cnp->cn_thread)) {
+				  VOP_ACCESS(dvp, VADMIN, cnp->cn_cred,
+				  cnp->cn_thread) && VOP_ACCESS(*vpp, VADMIN,
+				  cnp->cn_cred, cnp->cn_thread)) {
 					error = EPERM;
 					vput(*vpp);
 					*vpp = NULL;
@@ -189,28 +199,46 @@
 				cnp->cn_flags |= SAVENAME;
 			} else {
 				error = tmpfs_alloc_vp(dvp->v_mount, tnode,
-						cnp->cn_lkflags, vpp);
+				    cnp->cn_lkflags, vpp);
+				if (error != 0)
+					goto out;
 			}
 		}
 	}
 
-	/* Store the result of this lookup in the cache.  Avoid this if the
+	/*
+	 * Store the result of this lookup in the cache.  Avoid this if the
 	 * request was for creation, as it does not improve timings on
-	 * emprical tests. */
-	if ((cnp->cn_flags & MAKEENTRY) && cnp->cn_nameiop != CREATE)
+	 * emprical tests.
+	 */
+	if ((cnp->cn_flags & MAKEENTRY) != 0 && tmpfs_use_nc(dvp))
 		cache_enter(dvp, *vpp, cnp);
 
 out:
-	/* If there were no errors, *vpp cannot be null and it must be
-	 * locked. */
+	/*
+	 * If there were no errors, *vpp cannot be null and it must be
+	 * locked.
+	 */
 	MPASS(IFF(error == 0, *vpp != NULLVP && VOP_ISLOCKED(*vpp)));
 
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
+static int
+tmpfs_cached_lookup(struct vop_cachedlookup_args *v)
+{
 
+	return (tmpfs_lookup1(v->a_dvp, v->a_vpp, v->a_cnp));
+}
+
 static int
+tmpfs_lookup(struct vop_lookup_args *v)
+{
+
+	return (tmpfs_lookup1(v->a_dvp, v->a_vpp, v->a_cnp));
+}
+
+static int
 tmpfs_create(struct vop_create_args *v)
 {
 	struct vnode *dvp = v->a_dvp;
@@ -217,12 +245,15 @@
 	struct vnode **vpp = v->a_vpp;
 	struct componentname *cnp = v->a_cnp;
 	struct vattr *vap = v->a_vap;
+	int error;
 
 	MPASS(vap->va_type == VREG || vap->va_type == VSOCK);
 
-	return tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL);
+	error = tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL);
+	if (error == 0 && (cnp->cn_flags & MAKEENTRY) != 0 && tmpfs_use_nc(dvp))
+		cache_enter(dvp, *vpp, cnp);
+	return (error);
 }
-/* --------------------------------------------------------------------- */
 
 static int
 tmpfs_mknod(struct vop_mknod_args *v)
@@ -239,8 +270,6 @@
 	return tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_open(struct vop_open_args *v)
 {
@@ -265,6 +294,9 @@
 		error = EPERM;
 	else {
 		error = 0;
+		/* For regular files, the call below is nop. */
+		KASSERT(vp->v_type != VREG || (node->tn_reg.tn_aobj->flags &
+		    OBJ_DEAD) == 0, ("dead object"));
 		vnode_create_vobject(vp, node->tn_size, v->a_td);
 	}
 
@@ -272,15 +304,11 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_close(struct vop_close_args *v)
 {
 	struct vnode *vp = v->a_vp;
 
-	MPASS(VOP_ISLOCKED(vp));
-
 	/* Update node times. */
 	tmpfs_update(vp);
 
@@ -287,8 +315,6 @@
 	return (0);
 }
 
-/* --------------------------------------------------------------------- */
-
 int
 tmpfs_access(struct vop_access_args *v)
 {
@@ -343,8 +369,6 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 int
 tmpfs_getattr(struct vop_getattr_args *v)
 {
@@ -380,10 +404,6 @@
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
-
-/* XXX Should this operation be atomic?  I think it should, but code in
- * XXX other places (e.g., ufs) doesn't seem to be... */
 int
 tmpfs_setattr(struct vop_setattr_args *v)
 {
@@ -427,8 +447,7 @@
 	    vap->va_mtime.tv_nsec != VNOVAL) ||
 	    (vap->va_birthtime.tv_sec != VNOVAL &&
 	    vap->va_birthtime.tv_nsec != VNOVAL)))
-		error = tmpfs_chtimes(vp, &vap->va_atime, &vap->va_mtime,
-			&vap->va_birthtime, vap->va_vaflags, cred, td);
+		error = tmpfs_chtimes(vp, vap, cred, td);
 
 	/* Update the node times.  We give preference to the error codes
 	 * generated by this function rather than the ones that may arise
@@ -440,322 +459,52 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
 static int
-tmpfs_nocacheread(vm_object_t tobj, vm_pindex_t idx,
-    vm_offset_t offset, size_t tlen, struct uio *uio)
-{
-	vm_page_t	m;
-	int		error, rv;
-
-	VM_OBJECT_LOCK(tobj);
-	m = vm_page_grab(tobj, idx, VM_ALLOC_WIRED |
-	    VM_ALLOC_NORMAL | VM_ALLOC_RETRY);
-	if (m->valid != VM_PAGE_BITS_ALL) {
-		if (vm_pager_has_page(tobj, idx, NULL, NULL)) {
-			rv = vm_pager_get_pages(tobj, &m, 1, 0);
-			if (rv != VM_PAGER_OK) {
-				vm_page_lock(m);
-				vm_page_free(m);
-				vm_page_unlock(m);
-				VM_OBJECT_UNLOCK(tobj);
-				return (EIO);
-			}
-		} else
-			vm_page_zero_invalid(m, TRUE);
-	}
-	VM_OBJECT_UNLOCK(tobj);
-	error = uiomove_fromphys(&m, offset, tlen, uio);
-	VM_OBJECT_LOCK(tobj);
-	vm_page_lock(m);
-	vm_page_unwire(m, TRUE);
-	vm_page_unlock(m);
-	vm_page_wakeup(m);
-	VM_OBJECT_UNLOCK(tobj);
-
-	return (error);
-}
-
-static __inline int
-tmpfs_nocacheread_buf(vm_object_t tobj, vm_pindex_t idx,
-    vm_offset_t offset, size_t tlen, void *buf)
-{
-	struct uio uio;
-	struct iovec iov;
-
-	uio.uio_iovcnt = 1;
-	uio.uio_iov = &iov;
-	iov.iov_base = buf;
-	iov.iov_len = tlen;
-
-	uio.uio_offset = 0;
-	uio.uio_resid = tlen;
-	uio.uio_rw = UIO_READ;
-	uio.uio_segflg = UIO_SYSSPACE;
-	uio.uio_td = curthread;
-
-	return (tmpfs_nocacheread(tobj, idx, offset, tlen, &uio));
-}
-
-static int
-tmpfs_mappedread(vm_object_t vobj, vm_object_t tobj, size_t len, struct uio *uio)
-{
-	struct sf_buf	*sf;
-	vm_pindex_t	idx;
-	vm_page_t	m;
-	vm_offset_t	offset;
-	off_t		addr;
-	size_t		tlen;
-	char		*ma;
-	int		error;
-
-	addr = uio->uio_offset;
-	idx = OFF_TO_IDX(addr);
-	offset = addr & PAGE_MASK;
-	tlen = MIN(PAGE_SIZE - offset, len);
-
-	if ((vobj == NULL) ||
-	    (vobj->resident_page_count == 0 && vobj->cache == NULL))
-		goto nocache;
-
-	VM_OBJECT_LOCK(vobj);
-lookupvpg:
-	if (((m = vm_page_lookup(vobj, idx)) != NULL) &&
-	    vm_page_is_valid(m, offset, tlen)) {
-		if ((m->oflags & VPO_BUSY) != 0) {
-			/*
-			 * Reference the page before unlocking and sleeping so
-			 * that the page daemon is less likely to reclaim it.  
-			 */
-			vm_page_reference(m);
-			vm_page_sleep(m, "tmfsmr");
-			goto lookupvpg;
-		}
-		vm_page_busy(m);
-		VM_OBJECT_UNLOCK(vobj);
-		error = uiomove_fromphys(&m, offset, tlen, uio);
-		VM_OBJECT_LOCK(vobj);
-		vm_page_wakeup(m);
-		VM_OBJECT_UNLOCK(vobj);
-		return	(error);
-	} else if (m != NULL && uio->uio_segflg == UIO_NOCOPY) {
-		KASSERT(offset == 0,
-		    ("unexpected offset in tmpfs_mappedread for sendfile"));
-		if ((m->oflags & VPO_BUSY) != 0) {
-			/*
-			 * Reference the page before unlocking and sleeping so
-			 * that the page daemon is less likely to reclaim it.  
-			 */
-			vm_page_reference(m);
-			vm_page_sleep(m, "tmfsmr");
-			goto lookupvpg;
-		}
-		vm_page_busy(m);
-		VM_OBJECT_UNLOCK(vobj);
-		sched_pin();
-		sf = sf_buf_alloc(m, SFB_CPUPRIVATE);
-		ma = (char *)sf_buf_kva(sf);
-		error = tmpfs_nocacheread_buf(tobj, idx, 0, tlen, ma);
-		if (error == 0) {
-			if (tlen != PAGE_SIZE)
-				bzero(ma + tlen, PAGE_SIZE - tlen);
-			uio->uio_offset += tlen;
-			uio->uio_resid -= tlen;
-		}
-		sf_buf_free(sf);
-		sched_unpin();
-		VM_OBJECT_LOCK(vobj);
-		if (error == 0)
-			m->valid = VM_PAGE_BITS_ALL;
-		vm_page_wakeup(m);
-		VM_OBJECT_UNLOCK(vobj);
-		return	(error);
-	}
-	VM_OBJECT_UNLOCK(vobj);
-nocache:
-	error = tmpfs_nocacheread(tobj, idx, offset, tlen, uio);
-
-	return	(error);
-}
-
-static int
 tmpfs_read(struct vop_read_args *v)
 {
-	struct vnode *vp = v->a_vp;
-	struct uio *uio = v->a_uio;
-
+	struct vnode *vp;
+	struct uio *uio;
 	struct tmpfs_node *node;
-	vm_object_t uobj;
-	size_t len;
-	int resid;
 
-	int error = 0;
-
+	vp = v->a_vp;
+	if (vp->v_type != VREG)
+		return (EISDIR);
+	uio = v->a_uio;
+	if (uio->uio_offset < 0)
+		return (EINVAL);
 	node = VP_TO_TMPFS_NODE(vp);
-
-	if (vp->v_type != VREG) {
-		error = EISDIR;
-		goto out;
-	}
-
-	if (uio->uio_offset < 0) {
-		error = EINVAL;
-		goto out;
-	}
-
-	node->tn_status |= TMPFS_NODE_ACCESSED;
-
-	uobj = node->tn_reg.tn_aobj;
-	while ((resid = uio->uio_resid) > 0) {
-		error = 0;
-		if (node->tn_size <= uio->uio_offset)
-			break;
-		len = MIN(node->tn_size - uio->uio_offset, resid);
-		if (len == 0)
-			break;
-		error = tmpfs_mappedread(vp->v_object, uobj, len, uio);
-		if ((error != 0) || (resid == uio->uio_resid))
-			break;
-	}
-
-out:
-
-	return error;
+	tmpfs_set_status(node, TMPFS_NODE_ACCESSED);
+	return (uiomove_object(node->tn_reg.tn_aobj, node->tn_size, uio));
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
-tmpfs_mappedwrite(vm_object_t vobj, vm_object_t tobj, size_t len, struct uio *uio)
-{
-	vm_pindex_t	idx;
-	vm_page_t	vpg, tpg;
-	vm_offset_t	offset;
-	off_t		addr;
-	size_t		tlen;
-	int		error, rv;
-
-	error = 0;
-	
-	addr = uio->uio_offset;
-	idx = OFF_TO_IDX(addr);
-	offset = addr & PAGE_MASK;
-	tlen = MIN(PAGE_SIZE - offset, len);
-
-	if ((vobj == NULL) ||
-	    (vobj->resident_page_count == 0 && vobj->cache == NULL)) {
-		vpg = NULL;
-		goto nocache;
-	}
-
-	VM_OBJECT_LOCK(vobj);
-lookupvpg:
-	if (((vpg = vm_page_lookup(vobj, idx)) != NULL) &&
-	    vm_page_is_valid(vpg, offset, tlen)) {
-		if ((vpg->oflags & VPO_BUSY) != 0) {
-			/*
-			 * Reference the page before unlocking and sleeping so
-			 * that the page daemon is less likely to reclaim it.  
-			 */
-			vm_page_reference(vpg);
-			vm_page_sleep(vpg, "tmfsmw");
-			goto lookupvpg;
-		}
-		vm_page_busy(vpg);
-		vm_page_undirty(vpg);
-		VM_OBJECT_UNLOCK(vobj);
-		error = uiomove_fromphys(&vpg, offset, tlen, uio);
-	} else {
-		if (__predict_false(vobj->cache != NULL))
-			vm_page_cache_free(vobj, idx, idx + 1);
-		VM_OBJECT_UNLOCK(vobj);
-		vpg = NULL;
-	}
-nocache:
-	VM_OBJECT_LOCK(tobj);
-	tpg = vm_page_grab(tobj, idx, VM_ALLOC_WIRED |
-	    VM_ALLOC_NORMAL | VM_ALLOC_RETRY);
-	if (tpg->valid != VM_PAGE_BITS_ALL) {
-		if (vm_pager_has_page(tobj, idx, NULL, NULL)) {
-			rv = vm_pager_get_pages(tobj, &tpg, 1, 0);
-			if (rv != VM_PAGER_OK) {
-				vm_page_lock(tpg);
-				vm_page_free(tpg);
-				vm_page_unlock(tpg);
-				error = EIO;
-				goto out;
-			}
-		} else
-			vm_page_zero_invalid(tpg, TRUE);
-	}
-	VM_OBJECT_UNLOCK(tobj);
-	if (vpg == NULL)
-		error = uiomove_fromphys(&tpg, offset, tlen, uio);
-	else {
-		KASSERT(vpg->valid == VM_PAGE_BITS_ALL, ("parts of vpg invalid"));
-		pmap_copy_page(vpg, tpg);
-	}
-	VM_OBJECT_LOCK(tobj);
-	if (error == 0) {
-		KASSERT(tpg->valid == VM_PAGE_BITS_ALL,
-		    ("parts of tpg invalid"));
-		vm_page_dirty(tpg);
-	}
-	vm_page_lock(tpg);
-	vm_page_unwire(tpg, TRUE);
-	vm_page_unlock(tpg);
-	vm_page_wakeup(tpg);
-out:
-	VM_OBJECT_UNLOCK(tobj);
-	if (vpg != NULL) {
-		VM_OBJECT_LOCK(vobj);
-		vm_page_wakeup(vpg);
-		VM_OBJECT_UNLOCK(vobj);
-	}
-
-	return	(error);
-}
-
-static int
 tmpfs_write(struct vop_write_args *v)
 {
-	struct vnode *vp = v->a_vp;
-	struct uio *uio = v->a_uio;
-	int ioflag = v->a_ioflag;
-
-	boolean_t extended;
-	int error = 0;
+	struct vnode *vp;
+	struct uio *uio;
+	struct tmpfs_node *node;
 	off_t oldsize;
-	struct tmpfs_node *node;
-	vm_object_t uobj;
-	size_t len;
-	int resid;
+	int error, ioflag;
 
+	vp = v->a_vp;
+	uio = v->a_uio;
+	ioflag = v->a_ioflag;
+	error = 0;
 	node = VP_TO_TMPFS_NODE(vp);
 	oldsize = node->tn_size;
 
-	if (uio->uio_offset < 0 || vp->v_type != VREG) {
-		error = EINVAL;
-		goto out;
-	}
-
-	if (uio->uio_resid == 0) {
-		error = 0;
-		goto out;
-	}
-
+	if (uio->uio_offset < 0 || vp->v_type != VREG)
+		return (EINVAL);
+	if (uio->uio_resid == 0)
+		return (0);
 	if (ioflag & IO_APPEND)
 		uio->uio_offset = node->tn_size;
-
 	if (uio->uio_offset + uio->uio_resid >
 	  VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize)
 		return (EFBIG);
-
 	if (vn_rlimit_fsize(vp, uio, uio->uio_td))
 		return (EFBIG);
-
-	extended = uio->uio_offset + uio->uio_resid > node->tn_size;
-	if (extended) {
+	if (uio->uio_offset + uio->uio_resid > node->tn_size) {
 		error = tmpfs_reg_resize(vp, uio->uio_offset + uio->uio_resid,
 		    FALSE);
 		if (error != 0)
@@ -762,26 +511,13 @@
 			goto out;
 	}
 
-	uobj = node->tn_reg.tn_aobj;
-	while ((resid = uio->uio_resid) > 0) {
-		if (node->tn_size <= uio->uio_offset)
-			break;
-		len = MIN(node->tn_size - uio->uio_offset, resid);
-		if (len == 0)
-			break;
-		error = tmpfs_mappedwrite(vp->v_object, uobj, len, uio);
-		if ((error != 0) || (resid == uio->uio_resid))
-			break;
-	}
-
+	error = uiomove_object(node->tn_reg.tn_aobj, node->tn_size, uio);
 	node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED |
-	    (extended ? TMPFS_NODE_CHANGED : 0);
-
+	    TMPFS_NODE_CHANGED;
 	if (node->tn_mode & (S_ISUID | S_ISGID)) {
 		if (priv_check_cred(v->a_cred, PRIV_VFS_RETAINSUGID, 0))
 			node->tn_mode &= ~(S_ISUID | S_ISGID);
 	}
-
 	if (error != 0)
 		(void)tmpfs_reg_resize(vp, oldsize, TRUE);
 
@@ -789,11 +525,9 @@
 	MPASS(IMPLIES(error == 0, uio->uio_resid == 0));
 	MPASS(IMPLIES(error != 0, oldsize == node->tn_size));
 
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_fsync(struct vop_fsync_args *v)
 {
@@ -801,13 +535,12 @@
 
 	MPASS(VOP_ISLOCKED(vp));
 
+	tmpfs_check_mtime(vp);
 	tmpfs_update(vp);
 
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_remove(struct vop_remove_args *v)
 {
@@ -850,7 +583,7 @@
 	/* Free the directory entry we just deleted.  Note that the node
 	 * referred by it will not be removed until the vnode is really
 	 * reclaimed. */
-	tmpfs_free_dirent(tmp, de, TRUE);
+	tmpfs_free_dirent(tmp, de);
 
 	node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED;
 	error = 0;
@@ -860,8 +593,6 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_link(struct vop_link_args *v)
 {
@@ -876,23 +607,8 @@
 	MPASS(VOP_ISLOCKED(dvp));
 	MPASS(cnp->cn_flags & HASBUF);
 	MPASS(dvp != vp); /* XXX When can this be false? */
-
 	node = VP_TO_TMPFS_NODE(vp);
 
-	/* XXX: Why aren't the following two tests done by the caller? */
-
-	/* Hard links of directories are forbidden. */
-	if (vp->v_type == VDIR) {
-		error = EPERM;
-		goto out;
-	}
-
-	/* Cannot create cross-device links. */
-	if (dvp->v_mount != vp->v_mount) {
-		error = EXDEV;
-		goto out;
-	}
-
 	/* Ensure that we do not overflow the maximum number of links imposed
 	 * by the system. */
 	MPASS(node->tn_links <= LINK_MAX);
@@ -928,8 +644,6 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 /*
  * We acquire all but fdvp locks using non-blocking acquisitions.  If we
  * fail to acquire any lock in the path we will drop all held locks,
@@ -1266,26 +980,25 @@
 			fdnode->tn_links--;
 			TMPFS_NODE_UNLOCK(fdnode);
 		}
-
-		/* Do the move: just remove the entry from the source directory
-		 * and insert it into the target one. */
-		tmpfs_dir_detach(fdvp, de);
-		if (fcnp->cn_flags & DOWHITEOUT)
-			tmpfs_dir_whiteout_add(fdvp, fcnp);
-		if (tcnp->cn_flags & ISWHITEOUT)
-			tmpfs_dir_whiteout_remove(tdvp, tcnp);
-		tmpfs_dir_attach(tdvp, de);
 	}
 
+	/* Do the move: just remove the entry from the source directory
+	 * and insert it into the target one. */
+	tmpfs_dir_detach(fdvp, de);
+
+	if (fcnp->cn_flags & DOWHITEOUT)
+		tmpfs_dir_whiteout_add(fdvp, fcnp);
+	if (tcnp->cn_flags & ISWHITEOUT)
+		tmpfs_dir_whiteout_remove(tdvp, tcnp);
+
 	/* If the name has changed, we need to make it effective by changing
 	 * it in the directory entry. */
 	if (newname != NULL) {
 		MPASS(tcnp->cn_namelen <= MAXNAMLEN);
 
-		free(de->td_name, M_TMPFSNAME);
-		de->td_namelen = (uint16_t)tcnp->cn_namelen;
-		memcpy(newname, tcnp->cn_nameptr, tcnp->cn_namelen);
-		de->td_name = newname;
+		free(de->ud.td_name, M_TMPFSNAME);
+		de->ud.td_name = newname;
+		tmpfs_dirent_init(de, tcnp->cn_nameptr, tcnp->cn_namelen);
 
 		fnode->tn_status |= TMPFS_NODE_CHANGED;
 		tdnode->tn_status |= TMPFS_NODE_MODIFIED;
@@ -1294,20 +1007,27 @@
 	/* If we are overwriting an entry, we have to remove the old one
 	 * from the target directory. */
 	if (tvp != NULL) {
+		struct tmpfs_dirent *tde;
+
 		/* Remove the old entry from the target directory. */
-		de = tmpfs_dir_lookup(tdnode, tnode, tcnp);
-		tmpfs_dir_detach(tdvp, de);
+		tde = tmpfs_dir_lookup(tdnode, tnode, tcnp);
+		tmpfs_dir_detach(tdvp, tde);
 
 		/* Free the directory entry we just deleted.  Note that the
 		 * node referred by it will not be removed until the vnode is
 		 * really reclaimed. */
-		tmpfs_free_dirent(VFS_TO_TMPFS(tvp->v_mount), de, TRUE);
+		tmpfs_free_dirent(VFS_TO_TMPFS(tvp->v_mount), tde);
 	}
-	cache_purge(fvp);
-	if (tvp != NULL)
-		cache_purge(tvp);
-	cache_purge_negative(tdvp);
 
+	tmpfs_dir_attach(tdvp, de);
+
+	if (tmpfs_use_nc(fvp)) {
+		cache_purge(fvp);
+		if (tvp != NULL)
+			cache_purge(tvp);
+		cache_purge_negative(tdvp);
+	}
+
 	error = 0;
 
 out_locked:
@@ -1335,8 +1055,6 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_mkdir(struct vop_mkdir_args *v)
 {
@@ -1350,8 +1068,6 @@
 	return tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_rmdir(struct vop_rmdir_args *v)
 {
@@ -1396,8 +1112,8 @@
 	    v->a_cnp->cn_namelen));
 
 	/* Check flags to see if we are allowed to remove the directory. */
-	if (dnode->tn_flags & APPEND
-		|| node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) {
+	if ((dnode->tn_flags & APPEND) != 0 ||
+	    (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) != 0) {
 		error = EPERM;
 		goto out;
 	}
@@ -1410,28 +1126,28 @@
 
 	/* No vnode should be allocated for this entry from this point */
 	TMPFS_NODE_LOCK(node);
-	TMPFS_ASSERT_ELOCKED(node);
 	node->tn_links--;
 	node->tn_dir.tn_parent = NULL;
-	node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \
+	node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED |
 	    TMPFS_NODE_MODIFIED;
 
 	TMPFS_NODE_UNLOCK(node);
 
 	TMPFS_NODE_LOCK(dnode);
-	TMPFS_ASSERT_ELOCKED(dnode);
 	dnode->tn_links--;
-	dnode->tn_status |= TMPFS_NODE_ACCESSED | \
-	    TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED;
+	dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED |
+	    TMPFS_NODE_MODIFIED;
 	TMPFS_NODE_UNLOCK(dnode);
 
-	cache_purge(dvp);
-	cache_purge(vp);
+	if (tmpfs_use_nc(dvp)) {
+		cache_purge(dvp);
+		cache_purge(vp);
+	}
 
 	/* Free the directory entry we just deleted.  Note that the node
 	 * referred by it will not be removed until the vnode is really
 	 * reclaimed. */
-	tmpfs_free_dirent(tmp, de, TRUE);
+	tmpfs_free_dirent(tmp, de);
 
 	/* Release the deleted vnode (will destroy the node, notify
 	 * interested parties and clean it from the cache). */
@@ -1445,8 +1161,6 @@
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_symlink(struct vop_symlink_args *v)
 {
@@ -1465,8 +1179,6 @@
 	return tmpfs_alloc_file(dvp, vpp, vap, cnp, target);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_readdir(struct vop_readdir_args *v)
 {
@@ -1477,8 +1189,8 @@
 	int *ncookies = v->a_ncookies;
 
 	int error;
-	off_t startoff;
-	off_t cnt = 0;
+	ssize_t startresid;
+	int maxcookies;
 	struct tmpfs_node *node;
 
 	/* This operation only makes sense on directory nodes. */
@@ -1485,76 +1197,43 @@
 	if (vp->v_type != VDIR)
 		return ENOTDIR;
 
+	maxcookies = 0;
 	node = VP_TO_TMPFS_DIR(vp);
 
-	startoff = uio->uio_offset;
+	startresid = uio->uio_resid;
 
-	if (uio->uio_offset == TMPFS_DIRCOOKIE_DOT) {
-		error = tmpfs_dir_getdotdent(node, uio);
-		if (error != 0)
-			goto outok;
-		cnt++;
+	/* Allocate cookies for NFS and compat modules. */
+	if (cookies != NULL && ncookies != NULL) {
+		maxcookies = howmany(node->tn_size,
+		    sizeof(struct tmpfs_dirent)) + 2;
+		*cookies = malloc(maxcookies * sizeof(**cookies), M_TEMP,
+		    M_WAITOK);
+		*ncookies = 0;
 	}
 
-	if (uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT) {
-		error = tmpfs_dir_getdotdotdent(node, uio);
-		if (error != 0)
-			goto outok;
-		cnt++;
-	}
+	if (cookies == NULL)
+		error = tmpfs_dir_getdents(node, uio, 0, NULL, NULL);
+	else
+		error = tmpfs_dir_getdents(node, uio, maxcookies, *cookies,
+		    ncookies);
 
-	error = tmpfs_dir_getdents(node, uio, &cnt);
+	/* Buffer was filled without hitting EOF. */
+	if (error == EJUSTRETURN)
+		error = (uio->uio_resid != startresid) ? 0 : EINVAL;
 
-outok:
-	MPASS(error >= -1);
+	if (error != 0 && cookies != NULL && ncookies != NULL) {
+		free(*cookies, M_TEMP);
+		*cookies = NULL;
+		*ncookies = 0;
+	}
 
-	if (error == -1)
-		error = (cnt != 0) ? 0 : EINVAL;
-
 	if (eofflag != NULL)
 		*eofflag =
 		    (error == 0 && uio->uio_offset == TMPFS_DIRCOOKIE_EOF);
 
-	/* Update NFS-related variables. */
-	if (error == 0 && cookies != NULL && ncookies != NULL) {
-		off_t i;
-		off_t off = startoff;
-		struct tmpfs_dirent *de = NULL;
-
-		*ncookies = cnt;
-		*cookies = malloc(cnt * sizeof(off_t), M_TEMP, M_WAITOK);
-
-		for (i = 0; i < cnt; i++) {
-			MPASS(off != TMPFS_DIRCOOKIE_EOF);
-			if (off == TMPFS_DIRCOOKIE_DOT) {
-				off = TMPFS_DIRCOOKIE_DOTDOT;
-			} else {
-				if (off == TMPFS_DIRCOOKIE_DOTDOT) {
-					de = TAILQ_FIRST(&node->tn_dir.tn_dirhead);
-				} else if (de != NULL) {
-					de = TAILQ_NEXT(de, td_entries);
-				} else {
-					de = tmpfs_dir_lookupbycookie(node,
-					    off);
-					MPASS(de != NULL);
-					de = TAILQ_NEXT(de, td_entries);
-				}
-				if (de == NULL)
-					off = TMPFS_DIRCOOKIE_EOF;
-				else
-					off = tmpfs_dircookie(de);
-			}
-
-			(*cookies)[i] = off;
-		}
-		MPASS(uio->uio_offset == off);
-	}
-
 	return error;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_readlink(struct vop_readlink_args *v)
 {
@@ -1571,33 +1250,26 @@
 
 	error = uiomove(node->tn_link, MIN(node->tn_size, uio->uio_resid),
 	    uio);
-	node->tn_status |= TMPFS_NODE_ACCESSED;
+	tmpfs_set_status(node, TMPFS_NODE_ACCESSED);
 
-	return error;
+	return (error);
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_inactive(struct vop_inactive_args *v)
 {
-	struct vnode *vp = v->a_vp;
-	struct thread *l = v->a_td;
-
+	struct vnode *vp;
 	struct tmpfs_node *node;
 
-	MPASS(VOP_ISLOCKED(vp));
-
+	vp = v->a_vp;
 	node = VP_TO_TMPFS_NODE(vp);
-
 	if (node->tn_links == 0)
-		vrecycle(vp, l);
-
-	return 0;
+		vrecycle(vp);
+	else
+		tmpfs_check_mtime(vp);
+	return (0);
 }
 
-/* --------------------------------------------------------------------- */
-
 int
 tmpfs_reclaim(struct vop_reclaim_args *v)
 {
@@ -1609,11 +1281,15 @@
 	node = VP_TO_TMPFS_NODE(vp);
 	tmp = VFS_TO_TMPFS(vp->v_mount);
 
-	vnode_destroy_vobject(vp);
-	cache_purge(vp);
+	if (vp->v_type == VREG)
+		tmpfs_destroy_vobject(vp, node->tn_reg.tn_aobj);
+	else
+		vnode_destroy_vobject(vp);
+	vp->v_object = NULL;
+	if (tmpfs_use_nc(vp))
+		cache_purge(vp);
 
 	TMPFS_NODE_LOCK(node);
-	TMPFS_ASSERT_ELOCKED(node);
 	tmpfs_free_vp(vp);
 
 	/* If the node referenced by this vnode was deleted by the user,
@@ -1631,8 +1307,6 @@
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_print(struct vop_print_args *v)
 {
@@ -1642,12 +1316,11 @@
 
 	node = VP_TO_TMPFS_NODE(vp);
 
-	printf("tag VT_TMPFS, tmpfs_node %p, flags 0x%x, links %d\n",
+	printf("tag VT_TMPFS, tmpfs_node %p, flags 0x%lx, links %d\n",
 	    node, node->tn_flags, node->tn_links);
-	printf("\tmode 0%o, owner %d, group %d, size %" PRIdMAX
-	    ", status 0x%x\n",
+	printf("\tmode 0%o, owner %d, group %d, size %jd, status 0x%x\n",
 	    node->tn_mode, node->tn_uid, node->tn_gid,
-	    (uintmax_t)node->tn_size, node->tn_status);
+	    (intmax_t)node->tn_size, node->tn_status);
 
 	if (vp->v_type == VFIFO)
 		fifo_printinfo(vp);
@@ -1657,8 +1330,6 @@
 	return 0;
 }
 
-/* --------------------------------------------------------------------- */
-
 static int
 tmpfs_pathconf(struct vop_pathconf_args *v)
 {
@@ -1748,15 +1419,139 @@
 	}
 }
 
-/* --------------------------------------------------------------------- */
+static int
+tmpfs_vptocnp_dir(struct tmpfs_node *tn, struct tmpfs_node *tnp,
+    struct tmpfs_dirent **pde)
+{
+	struct tmpfs_dir_cursor dc;
+	struct tmpfs_dirent *de;
 
+	for (de = tmpfs_dir_first(tnp, &dc); de != NULL;
+	     de = tmpfs_dir_next(tnp, &dc)) {
+		if (de->td_node == tn) {
+			*pde = de;
+			return (0);
+		}
+	}
+	return (ENOENT);
+}
+
+static int
+tmpfs_vptocnp_fill(struct vnode *vp, struct tmpfs_node *tn,
+    struct tmpfs_node *tnp, char *buf, int *buflen, struct vnode **dvp)
+{
+	struct tmpfs_dirent *de;
+	int error, i;
+
+	error = vn_vget_ino_gen(vp, tmpfs_vn_get_ino_alloc, tnp, LK_SHARED,
+	    dvp);
+	if (error != 0)
+		return (error);
+	error = tmpfs_vptocnp_dir(tn, tnp, &de);
+	if (error == 0) {
+		i = *buflen;
+		i -= de->td_namelen;
+		if (i < 0) {
+			error = ENOMEM;
+		} else {
+			bcopy(de->ud.td_name, buf + i, de->td_namelen);
+			*buflen = i;
+		}
+	}
+	if (error == 0) {
+		if (vp != *dvp)
+			VOP_UNLOCK(*dvp, 0);
+	} else {
+		if (vp != *dvp)
+			vput(*dvp);
+		else
+			vrele(vp);
+	}
+	return (error);
+}
+
+static int
+tmpfs_vptocnp(struct vop_vptocnp_args *ap)
+{
+	struct vnode *vp, **dvp;
+	struct tmpfs_node *tn, *tnp, *tnp1;
+	struct tmpfs_dirent *de;
+	struct tmpfs_mount *tm;
+	char *buf;
+	int *buflen;
+	int error;
+
+	vp = ap->a_vp;
+	dvp = ap->a_vpp;
+	buf = ap->a_buf;
+	buflen = ap->a_buflen;
+
+	tm = VFS_TO_TMPFS(vp->v_mount);
+	tn = VP_TO_TMPFS_NODE(vp);
+	if (tn->tn_type == VDIR) {
+		tnp = tn->tn_dir.tn_parent;
+		if (tnp == NULL)
+			return (ENOENT);
+		tmpfs_ref_node(tnp);
+		error = tmpfs_vptocnp_fill(vp, tn, tn->tn_dir.tn_parent, buf,
+		    buflen, dvp);
+		tmpfs_free_node(tm, tnp);
+		return (error);
+	}
+restart:
+	TMPFS_LOCK(tm);
+	LIST_FOREACH_SAFE(tnp, &tm->tm_nodes_used, tn_entries, tnp1) {
+		if (tnp->tn_type != VDIR)
+			continue;
+		TMPFS_NODE_LOCK(tnp);
+		tmpfs_ref_node_locked(tnp);
+
+		/*
+		 * tn_vnode cannot be instantiated while we hold the
+		 * node lock, so the directory cannot be changed while
+		 * we iterate over it.  Do this to avoid instantiating
+		 * vnode for directories which cannot point to our
+		 * node.
+		 */
+		error = tnp->tn_vnode == NULL ? tmpfs_vptocnp_dir(tn, tnp,
+		    &de) : 0;
+
+		if (error == 0) {
+			TMPFS_NODE_UNLOCK(tnp);
+			TMPFS_UNLOCK(tm);
+			error = tmpfs_vptocnp_fill(vp, tn, tnp, buf, buflen,
+			    dvp);
+			if (error == 0) {
+				tmpfs_free_node(tm, tnp);
+				return (0);
+			}
+			if ((vp->v_iflag & VI_DOOMED) != 0) {
+				tmpfs_free_node(tm, tnp);
+				return (ENOENT);
+			}
+			TMPFS_LOCK(tm);
+			TMPFS_NODE_LOCK(tnp);
+		}
+		if (tmpfs_free_node_locked(tm, tnp, false)) {
+			goto restart;
+		} else {
+			KASSERT(tnp->tn_refcount > 0,
+			    ("node %p refcount zero", tnp));
+			tnp1 = LIST_NEXT(tnp, tn_entries);
+			TMPFS_NODE_UNLOCK(tnp);
+		}
+	}
+	TMPFS_UNLOCK(tm);
+	return (ENOENT);
+}
+
 /*
- * vnode operations vector used for files stored in a tmpfs file system.
+ * Vnode operations vector used for files stored in a tmpfs file system.
  */
 struct vop_vector tmpfs_vnodeop_entries = {
 	.vop_default =			&default_vnodeops,
 	.vop_lookup =			vfs_cache_lookup,
-	.vop_cachedlookup =		tmpfs_lookup,
+	.vop_cachedlookup =		tmpfs_cached_lookup,
 	.vop_create =			tmpfs_create,
 	.vop_mknod =			tmpfs_mknod,
 	.vop_open =			tmpfs_open,
@@ -1782,5 +1577,13 @@
 	.vop_vptofh =			tmpfs_vptofh,
 	.vop_whiteout =			tmpfs_whiteout,
 	.vop_bmap =			VOP_EOPNOTSUPP,
+	.vop_vptocnp =			tmpfs_vptocnp,
 };
 
+/*
+ * Same vector for mounts which do not use namecache.
+ */
+struct vop_vector tmpfs_vnodeop_nonc_entries = {
+	.vop_default =			&tmpfs_vnodeop_entries,
+	.vop_lookup =			tmpfs_lookup,
+};

Modified: trunk/sys/fs/tmpfs/tmpfs_vnops.h
===================================================================
--- trunk/sys/fs/tmpfs/tmpfs_vnops.h	2018-05-27 22:07:49 UTC (rev 10019)
+++ trunk/sys/fs/tmpfs/tmpfs_vnops.h	2018-05-27 22:08:25 UTC (rev 10020)
@@ -1,3 +1,4 @@
+/* $MidnightBSD$ */
 /*	$NetBSD: tmpfs_vnops.h,v 1.7 2005/12/03 17:34:44 christos Exp $	*/
 
 /*-
@@ -29,7 +30,7 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  *
- * $MidnightBSD$
+ * $FreeBSD: stable/10/sys/fs/tmpfs/tmpfs_vnops.h 313095 2017-02-02 13:39:11Z kib $
  */
 
 #ifndef _FS_TMPFS_TMPFS_VNOPS_H_
@@ -39,13 +40,12 @@
 #error not supposed to be exposed to userland.
 #endif
 
-/* --------------------------------------------------------------------- */
-
 /*
  * Declarations for tmpfs_vnops.c.
  */
 
 extern struct vop_vector tmpfs_vnodeop_entries;
+extern struct vop_vector tmpfs_vnodeop_nonc_entries;
 
 vop_access_t	tmpfs_access;
 vop_getattr_t	tmpfs_getattr;
@@ -52,6 +52,4 @@
 vop_setattr_t	tmpfs_setattr;
 vop_reclaim_t	tmpfs_reclaim;
 
-/* --------------------------------------------------------------------- */
-
 #endif /* _FS_TMPFS_TMPFS_VNOPS_H_ */



More information about the Midnightbsd-cvs mailing list