xref: /illumos-gate/usr/src/uts/common/fs/tmpfs/tmp_dir.c (revision 2d6eb4a5)
17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5df2381bfSpraks  * Common Development and Distribution License (the "License").
6df2381bfSpraks  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
217c478bd9Sstevel@tonic-gate /*
22df2381bfSpraks  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
237c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
247c478bd9Sstevel@tonic-gate  */
257c478bd9Sstevel@tonic-gate 
267c478bd9Sstevel@tonic-gate #include <sys/types.h>
277c478bd9Sstevel@tonic-gate #include <sys/param.h>
287c478bd9Sstevel@tonic-gate #include <sys/sysmacros.h>
297c478bd9Sstevel@tonic-gate #include <sys/systm.h>
307c478bd9Sstevel@tonic-gate #include <sys/time.h>
317c478bd9Sstevel@tonic-gate #include <sys/vfs.h>
327c478bd9Sstevel@tonic-gate #include <sys/vnode.h>
337c478bd9Sstevel@tonic-gate #include <sys/errno.h>
347c478bd9Sstevel@tonic-gate #include <sys/cmn_err.h>
357c478bd9Sstevel@tonic-gate #include <sys/cred.h>
367c478bd9Sstevel@tonic-gate #include <sys/stat.h>
377c478bd9Sstevel@tonic-gate #include <sys/debug.h>
387c478bd9Sstevel@tonic-gate #include <sys/policy.h>
397c478bd9Sstevel@tonic-gate #include <sys/fs/tmpnode.h>
407c478bd9Sstevel@tonic-gate #include <sys/fs/tmp.h>
417c478bd9Sstevel@tonic-gate #include <sys/vtrace.h>
427c478bd9Sstevel@tonic-gate 
437c478bd9Sstevel@tonic-gate static int tdircheckpath(struct tmpnode *, struct tmpnode *, struct cred *);
447c478bd9Sstevel@tonic-gate static int tdirrename(struct tmpnode *, struct tmpnode *, struct tmpnode *,
457c478bd9Sstevel@tonic-gate 	char *, struct tmpnode *, struct tdirent *, struct cred *);
467c478bd9Sstevel@tonic-gate static void tdirfixdotdot(struct tmpnode *, struct tmpnode *, struct tmpnode *);
477c478bd9Sstevel@tonic-gate static int tdirmaketnode(struct tmpnode *, struct tmount *, struct vattr *,
487c478bd9Sstevel@tonic-gate 	enum de_op, struct tmpnode **, struct cred *);
497c478bd9Sstevel@tonic-gate static int tdiraddentry(struct tmpnode *, struct tmpnode *, char *,
507c478bd9Sstevel@tonic-gate 	enum de_op, struct tmpnode *);
517c478bd9Sstevel@tonic-gate 
527c478bd9Sstevel@tonic-gate 
537c478bd9Sstevel@tonic-gate #define	T_HASH_SIZE	8192		/* must be power of 2 */
547c478bd9Sstevel@tonic-gate #define	T_MUTEX_SIZE	64
557c478bd9Sstevel@tonic-gate 
56*f859e717SDan McDonald /* Non-static so compilers won't constant-fold these away. */
57*f859e717SDan McDonald clock_t tmpfs_rename_backoff_delay = 1;
58*f859e717SDan McDonald unsigned int tmpfs_rename_backoff_tries = 0;
59*f859e717SDan McDonald unsigned long tmpfs_rename_loops = 0;
60*f859e717SDan McDonald 
617c478bd9Sstevel@tonic-gate static struct tdirent	*t_hashtable[T_HASH_SIZE];
627c478bd9Sstevel@tonic-gate static kmutex_t		 t_hashmutex[T_MUTEX_SIZE];
637c478bd9Sstevel@tonic-gate 
647c478bd9Sstevel@tonic-gate #define	T_HASH_INDEX(a)		((a) & (T_HASH_SIZE-1))
657c478bd9Sstevel@tonic-gate #define	T_MUTEX_INDEX(a)	((a) & (T_MUTEX_SIZE-1))
667c478bd9Sstevel@tonic-gate 
677c478bd9Sstevel@tonic-gate #define	TMPFS_HASH(tp, name, hash)				\
687c478bd9Sstevel@tonic-gate 	{							\
697c478bd9Sstevel@tonic-gate 		char Xc, *Xcp;					\
707c478bd9Sstevel@tonic-gate 		hash = (uint_t)(uintptr_t)(tp) >> 8;		\
717c478bd9Sstevel@tonic-gate 		for (Xcp = (name); (Xc = *Xcp) != 0; Xcp++)	\
727c478bd9Sstevel@tonic-gate 			hash = (hash << 4) + hash + (uint_t)Xc;	\
737c478bd9Sstevel@tonic-gate 	}
747c478bd9Sstevel@tonic-gate 
757c478bd9Sstevel@tonic-gate void
tmpfs_hash_init(void)767c478bd9Sstevel@tonic-gate tmpfs_hash_init(void)
777c478bd9Sstevel@tonic-gate {
787c478bd9Sstevel@tonic-gate 	int	ix;
797c478bd9Sstevel@tonic-gate 
807c478bd9Sstevel@tonic-gate 	for (ix = 0; ix < T_MUTEX_SIZE; ix++)
817c478bd9Sstevel@tonic-gate 		mutex_init(&t_hashmutex[ix], NULL, MUTEX_DEFAULT, NULL);
827c478bd9Sstevel@tonic-gate }
837c478bd9Sstevel@tonic-gate 
847c478bd9Sstevel@tonic-gate /*
857c478bd9Sstevel@tonic-gate  * This routine is where the rubber meets the road for identities.
867c478bd9Sstevel@tonic-gate  */
877c478bd9Sstevel@tonic-gate static void
tmpfs_hash_in(struct tdirent * t)887c478bd9Sstevel@tonic-gate tmpfs_hash_in(struct tdirent *t)
897c478bd9Sstevel@tonic-gate {
907c478bd9Sstevel@tonic-gate 	uint_t		hash;
917c478bd9Sstevel@tonic-gate 	struct tdirent	**prevpp;
927c478bd9Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
937c478bd9Sstevel@tonic-gate 
947c478bd9Sstevel@tonic-gate 	TMPFS_HASH(t->td_parent, t->td_name, hash);
957c478bd9Sstevel@tonic-gate 	t->td_hash = hash;
967c478bd9Sstevel@tonic-gate 	prevpp = &t_hashtable[T_HASH_INDEX(hash)];
977c478bd9Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
987c478bd9Sstevel@tonic-gate 	mutex_enter(t_hmtx);
997c478bd9Sstevel@tonic-gate 	t->td_link = *prevpp;
1007c478bd9Sstevel@tonic-gate 	*prevpp = t;
1017c478bd9Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1027c478bd9Sstevel@tonic-gate }
1037c478bd9Sstevel@tonic-gate 
1047c478bd9Sstevel@tonic-gate /*
1057c478bd9Sstevel@tonic-gate  * Remove tdirent *t from the hash list.
1067c478bd9Sstevel@tonic-gate  */
1077c478bd9Sstevel@tonic-gate static void
tmpfs_hash_out(struct tdirent * t)1087c478bd9Sstevel@tonic-gate tmpfs_hash_out(struct tdirent *t)
1097c478bd9Sstevel@tonic-gate {
1107c478bd9Sstevel@tonic-gate 	uint_t		hash;
1117c478bd9Sstevel@tonic-gate 	struct tdirent	**prevpp;
1127c478bd9Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
1137c478bd9Sstevel@tonic-gate 
1147c478bd9Sstevel@tonic-gate 	hash = t->td_hash;
1157c478bd9Sstevel@tonic-gate 	prevpp = &t_hashtable[T_HASH_INDEX(hash)];
1167c478bd9Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
1177c478bd9Sstevel@tonic-gate 	mutex_enter(t_hmtx);
1187c478bd9Sstevel@tonic-gate 	while (*prevpp != t)
1197c478bd9Sstevel@tonic-gate 		prevpp = &(*prevpp)->td_link;
1207c478bd9Sstevel@tonic-gate 	*prevpp = t->td_link;
1217c478bd9Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1227c478bd9Sstevel@tonic-gate }
1237c478bd9Sstevel@tonic-gate 
1247c478bd9Sstevel@tonic-gate /*
1257c478bd9Sstevel@tonic-gate  * Currently called by tdirrename() only.
1267c478bd9Sstevel@tonic-gate  * rename operation needs to be done with lock held, to ensure that
1277c478bd9Sstevel@tonic-gate  * no other operations can access the tmpnode at the same instance.
1287c478bd9Sstevel@tonic-gate  */
1297c478bd9Sstevel@tonic-gate static void
tmpfs_hash_change(struct tdirent * tdp,struct tmpnode * fromtp)1307c478bd9Sstevel@tonic-gate tmpfs_hash_change(struct tdirent *tdp, struct tmpnode *fromtp)
1317c478bd9Sstevel@tonic-gate {
1327c478bd9Sstevel@tonic-gate 	uint_t		hash;
1337c478bd9Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
1347c478bd9Sstevel@tonic-gate 
1357c478bd9Sstevel@tonic-gate 	hash = tdp->td_hash;
1367c478bd9Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
1377c478bd9Sstevel@tonic-gate 	mutex_enter(t_hmtx);
1387c478bd9Sstevel@tonic-gate 	tdp->td_tmpnode = fromtp;
1397c478bd9Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1407c478bd9Sstevel@tonic-gate }
1417c478bd9Sstevel@tonic-gate 
1427c478bd9Sstevel@tonic-gate static struct tdirent *
tmpfs_hash_lookup(char * name,struct tmpnode * parent,uint_t hold,struct tmpnode ** found)1437c478bd9Sstevel@tonic-gate tmpfs_hash_lookup(char *name, struct tmpnode *parent, uint_t hold,
1447c478bd9Sstevel@tonic-gate 	struct tmpnode **found)
1457c478bd9Sstevel@tonic-gate {
1467c478bd9Sstevel@tonic-gate 	struct tdirent	*l;
1477c478bd9Sstevel@tonic-gate 	uint_t		hash;
1487c478bd9Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
1497c478bd9Sstevel@tonic-gate 	struct tmpnode	*tnp;
1507c478bd9Sstevel@tonic-gate 
1517c478bd9Sstevel@tonic-gate 	TMPFS_HASH(parent, name, hash);
1527c478bd9Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
1537c478bd9Sstevel@tonic-gate 	mutex_enter(t_hmtx);
1547c478bd9Sstevel@tonic-gate 	l = t_hashtable[T_HASH_INDEX(hash)];
1557c478bd9Sstevel@tonic-gate 	while (l) {
1567c478bd9Sstevel@tonic-gate 		if ((l->td_hash == hash) &&
1577c478bd9Sstevel@tonic-gate 		    (l->td_parent == parent) &&
1587c478bd9Sstevel@tonic-gate 		    (strcmp(l->td_name, name) == 0)) {
1597c478bd9Sstevel@tonic-gate 			/*
1607c478bd9Sstevel@tonic-gate 			 * We need to make sure that the tmpnode that
1617c478bd9Sstevel@tonic-gate 			 * we put a hold on is the same one that we pass back.
1627c478bd9Sstevel@tonic-gate 			 * Hence, temporary variable tnp is necessary.
1637c478bd9Sstevel@tonic-gate 			 */
1647c478bd9Sstevel@tonic-gate 			tnp = l->td_tmpnode;
1657c478bd9Sstevel@tonic-gate 			if (hold) {
1667c478bd9Sstevel@tonic-gate 				ASSERT(tnp);
1677c478bd9Sstevel@tonic-gate 				tmpnode_hold(tnp);
1687c478bd9Sstevel@tonic-gate 			}
1697c478bd9Sstevel@tonic-gate 			if (found)
1707c478bd9Sstevel@tonic-gate 				*found = tnp;
1717c478bd9Sstevel@tonic-gate 			mutex_exit(t_hmtx);
1727c478bd9Sstevel@tonic-gate 			return (l);
1737c478bd9Sstevel@tonic-gate 		} else {
1747c478bd9Sstevel@tonic-gate 			l = l->td_link;
1757c478bd9Sstevel@tonic-gate 		}
1767c478bd9Sstevel@tonic-gate 	}
1777c478bd9Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1787c478bd9Sstevel@tonic-gate 	return (NULL);
1797c478bd9Sstevel@tonic-gate }
1807c478bd9Sstevel@tonic-gate 
1817c478bd9Sstevel@tonic-gate /*
1827c478bd9Sstevel@tonic-gate  * Search directory 'parent' for entry 'name'.
1837c478bd9Sstevel@tonic-gate  *
1847c478bd9Sstevel@tonic-gate  * The calling thread can't hold the write version
1857c478bd9Sstevel@tonic-gate  * of the rwlock for the directory being searched
1867c478bd9Sstevel@tonic-gate  *
1877c478bd9Sstevel@tonic-gate  * 0 is returned on success and *foundtp points
1887c478bd9Sstevel@tonic-gate  * to the found tmpnode with its vnode held.
1897c478bd9Sstevel@tonic-gate  */
1907c478bd9Sstevel@tonic-gate int
tdirlookup(struct tmpnode * parent,char * name,struct tmpnode ** foundtp,struct cred * cred)1917c478bd9Sstevel@tonic-gate tdirlookup(
1927c478bd9Sstevel@tonic-gate 	struct tmpnode *parent,
1937c478bd9Sstevel@tonic-gate 	char *name,
1947c478bd9Sstevel@tonic-gate 	struct tmpnode **foundtp,
1957c478bd9Sstevel@tonic-gate 	struct cred *cred)
1967c478bd9Sstevel@tonic-gate {
1977c478bd9Sstevel@tonic-gate 	int error;
1987c478bd9Sstevel@tonic-gate 
1997c478bd9Sstevel@tonic-gate 	*foundtp = NULL;
2007c478bd9Sstevel@tonic-gate 	if (parent->tn_type != VDIR)
2017c478bd9Sstevel@tonic-gate 		return (ENOTDIR);
2027c478bd9Sstevel@tonic-gate 
2037c478bd9Sstevel@tonic-gate 	if ((error = tmp_taccess(parent, VEXEC, cred)))
2047c478bd9Sstevel@tonic-gate 		return (error);
2057c478bd9Sstevel@tonic-gate 
2067c478bd9Sstevel@tonic-gate 	if (*name == '\0') {
2077c478bd9Sstevel@tonic-gate 		tmpnode_hold(parent);
2087c478bd9Sstevel@tonic-gate 		*foundtp = parent;
2097c478bd9Sstevel@tonic-gate 		return (0);
2107c478bd9Sstevel@tonic-gate 	}
2117c478bd9Sstevel@tonic-gate 
2127c478bd9Sstevel@tonic-gate 	/*
2137c478bd9Sstevel@tonic-gate 	 * Search the directory for the matching name
2147c478bd9Sstevel@tonic-gate 	 * We need the lock protecting the tn_dir list
2157c478bd9Sstevel@tonic-gate 	 * so that it doesn't change out from underneath us.
2167c478bd9Sstevel@tonic-gate 	 * tmpfs_hash_lookup() will pass back the tmpnode
2177c478bd9Sstevel@tonic-gate 	 * with a hold on it.
2187c478bd9Sstevel@tonic-gate 	 */
2197c478bd9Sstevel@tonic-gate 
2207c478bd9Sstevel@tonic-gate 	if (tmpfs_hash_lookup(name, parent, 1, foundtp) != NULL) {
2217c478bd9Sstevel@tonic-gate 		ASSERT(*foundtp);
2227c478bd9Sstevel@tonic-gate 		return (0);
2237c478bd9Sstevel@tonic-gate 	}
2247c478bd9Sstevel@tonic-gate 
2257c478bd9Sstevel@tonic-gate 	return (ENOENT);
2267c478bd9Sstevel@tonic-gate }
2277c478bd9Sstevel@tonic-gate 
2287c478bd9Sstevel@tonic-gate /*
2297c478bd9Sstevel@tonic-gate  * Enter a directory entry for 'name' and 'tp' into directory 'dir'
2307c478bd9Sstevel@tonic-gate  *
2317c478bd9Sstevel@tonic-gate  * Returns 0 on success.
2327c478bd9Sstevel@tonic-gate  */
2337c478bd9Sstevel@tonic-gate int
tdirenter(struct tmount * tm,struct tmpnode * dir,char * name,enum de_op op,struct tmpnode * fromparent,struct tmpnode * tp,struct vattr * va,struct tmpnode ** tpp,struct cred * cred,caller_context_t * ctp)2347c478bd9Sstevel@tonic-gate tdirenter(
2357c478bd9Sstevel@tonic-gate 	struct tmount	*tm,
2367c478bd9Sstevel@tonic-gate 	struct tmpnode	*dir,		/* target directory to make entry in */
2377c478bd9Sstevel@tonic-gate 	char		*name,		/* name of entry */
2387c478bd9Sstevel@tonic-gate 	enum de_op	op,		/* entry operation */
2397c478bd9Sstevel@tonic-gate 	struct tmpnode	*fromparent,	/* source directory if rename */
2407c478bd9Sstevel@tonic-gate 	struct tmpnode	*tp,		/* source tmpnode, if link/rename */
2417c478bd9Sstevel@tonic-gate 	struct vattr	*va,
2427c478bd9Sstevel@tonic-gate 	struct tmpnode	**tpp,		/* return tmpnode, if create/mkdir */
243da6c28aaSamw 	struct cred	*cred,
244da6c28aaSamw 	caller_context_t *ctp)
2457c478bd9Sstevel@tonic-gate {
2467c478bd9Sstevel@tonic-gate 	struct tdirent *tdp;
2477c478bd9Sstevel@tonic-gate 	struct tmpnode *found = NULL;
2487c478bd9Sstevel@tonic-gate 	int error = 0;
2497c478bd9Sstevel@tonic-gate 	char *s;
2507c478bd9Sstevel@tonic-gate 
2517c478bd9Sstevel@tonic-gate 	/*
2527c478bd9Sstevel@tonic-gate 	 * tn_rwlock is held to serialize direnter and dirdeletes
2537c478bd9Sstevel@tonic-gate 	 */
2547c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
2557c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
2567c478bd9Sstevel@tonic-gate 
2577c478bd9Sstevel@tonic-gate 	/*
2587c478bd9Sstevel@tonic-gate 	 * Don't allow '/' characters in pathname component
2597c478bd9Sstevel@tonic-gate 	 * (thus in ufs_direnter()).
2607c478bd9Sstevel@tonic-gate 	 */
2617c478bd9Sstevel@tonic-gate 	for (s = name; *s; s++)
2627c478bd9Sstevel@tonic-gate 		if (*s == '/')
2637c478bd9Sstevel@tonic-gate 			return (EACCES);
2647c478bd9Sstevel@tonic-gate 
2657c478bd9Sstevel@tonic-gate 	if (name[0] == '\0')
2667c478bd9Sstevel@tonic-gate 		panic("tdirenter: NULL name");
2677c478bd9Sstevel@tonic-gate 
2687c478bd9Sstevel@tonic-gate 	/*
2697c478bd9Sstevel@tonic-gate 	 * For link and rename lock the source entry and check the link count
2707c478bd9Sstevel@tonic-gate 	 * to see if it has been removed while it was unlocked.
2717c478bd9Sstevel@tonic-gate 	 */
2727c478bd9Sstevel@tonic-gate 	if (op == DE_LINK || op == DE_RENAME) {
273*f859e717SDan McDonald 		if (tp != dir) {
274*f859e717SDan McDonald 			unsigned int tries = 0;
275*f859e717SDan McDonald 
276*f859e717SDan McDonald 			/*
277*f859e717SDan McDonald 			 * If we are acquiring tp->tn_rwlock (for SOURCE)
278*f859e717SDan McDonald 			 * inside here, we must consider the following:
279*f859e717SDan McDonald 			 *
280*f859e717SDan McDonald 			 * - dir->tn_rwlock (TARGET) is already HELD (see
281*f859e717SDan McDonald 			 * above ASSERT()).
282*f859e717SDan McDonald 			 *
283*f859e717SDan McDonald 			 * - It is possible our SOURCE is a parent of our
284*f859e717SDan McDonald 			 * TARGET. Yes it's unusual, but it will return an
285*f859e717SDan McDonald 			 * error below via tdircheckpath().
286*f859e717SDan McDonald 			 *
287*f859e717SDan McDonald 			 * - It is also possible that another thread,
288*f859e717SDan McDonald 			 * concurrent to this one, is performing
289*f859e717SDan McDonald 			 * rmdir(TARGET), which means it will first acquire
290*f859e717SDan McDonald 			 * SOURCE's lock, THEN acquire TARGET's lock, which
291*f859e717SDan McDonald 			 * could result in this thread holding TARGET and
292*f859e717SDan McDonald 			 * trying for SOURCE, but the other thread holding
293*f859e717SDan McDonald 			 * SOURCE and trying for TARGET.  This is deadlock,
294*f859e717SDan McDonald 			 * and it's inducible.
295*f859e717SDan McDonald 			 *
296*f859e717SDan McDonald 			 * To prevent this, we borrow some techniques from UFS
297*f859e717SDan McDonald 			 * and rw_tryenter(), delaying if we fail, and
298*f859e717SDan McDonald 			 * if someone tweaks the number of backoff tries to be
299*f859e717SDan McDonald 			 * nonzero, return EBUSY after that number of tries.
300*f859e717SDan McDonald 			 */
301*f859e717SDan McDonald 			while (!rw_tryenter(&tp->tn_rwlock, RW_WRITER)) {
302*f859e717SDan McDonald 				/*
303*f859e717SDan McDonald 				 * Sloppy, but this is a diagnostic so atomic
304*f859e717SDan McDonald 				 * increment would be overkill.
305*f859e717SDan McDonald 				 */
306*f859e717SDan McDonald 				tmpfs_rename_loops++;
307*f859e717SDan McDonald 
308*f859e717SDan McDonald 				if (tmpfs_rename_backoff_tries != 0) {
309*f859e717SDan McDonald 					if (tries > tmpfs_rename_backoff_tries)
310*f859e717SDan McDonald 						return (EBUSY);
311*f859e717SDan McDonald 					tries++;
312*f859e717SDan McDonald 				}
313*f859e717SDan McDonald 				/*
314*f859e717SDan McDonald 				 * NOTE: We're still holding dir->tn_rwlock,
315*f859e717SDan McDonald 				 * so drop it over the delay, so any other
316*f859e717SDan McDonald 				 * thread can get its business done.
317*f859e717SDan McDonald 				 *
318*f859e717SDan McDonald 				 * No state change or state inspection happens
319*f859e717SDan McDonald 				 * prior to here, so it is not wholly dangerous
320*f859e717SDan McDonald 				 * to release-and-reacquire dir->tn_rwlock.
321*f859e717SDan McDonald 				 *
322*f859e717SDan McDonald 				 * Hold the vnode of dir in case it gets
323*f859e717SDan McDonald 				 * released by another thread, though.
324*f859e717SDan McDonald 				 */
325*f859e717SDan McDonald 				VN_HOLD(TNTOV(dir));
326*f859e717SDan McDonald 				rw_exit(&dir->tn_rwlock);
327*f859e717SDan McDonald 				delay(tmpfs_rename_backoff_delay);
328*f859e717SDan McDonald 				rw_enter(&dir->tn_rwlock, RW_WRITER);
329*f859e717SDan McDonald 				VN_RELE(TNTOV(dir));
330*f859e717SDan McDonald 			}
331*f859e717SDan McDonald 		}
3327c478bd9Sstevel@tonic-gate 		mutex_enter(&tp->tn_tlock);
3337c478bd9Sstevel@tonic-gate 		if (tp->tn_nlink == 0) {
3347c478bd9Sstevel@tonic-gate 			mutex_exit(&tp->tn_tlock);
3357c478bd9Sstevel@tonic-gate 			if (tp != dir)
3367c478bd9Sstevel@tonic-gate 				rw_exit(&tp->tn_rwlock);
3377c478bd9Sstevel@tonic-gate 			return (ENOENT);
3387c478bd9Sstevel@tonic-gate 		}
3397c478bd9Sstevel@tonic-gate 
3407c478bd9Sstevel@tonic-gate 		if (tp->tn_nlink == MAXLINK) {
3417c478bd9Sstevel@tonic-gate 			mutex_exit(&tp->tn_tlock);
3427c478bd9Sstevel@tonic-gate 			if (tp != dir)
3437c478bd9Sstevel@tonic-gate 				rw_exit(&tp->tn_rwlock);
3447c478bd9Sstevel@tonic-gate 			return (EMLINK);
3457c478bd9Sstevel@tonic-gate 		}
3467c478bd9Sstevel@tonic-gate 		tp->tn_nlink++;
3477c478bd9Sstevel@tonic-gate 		gethrestime(&tp->tn_ctime);
3487c478bd9Sstevel@tonic-gate 		mutex_exit(&tp->tn_tlock);
3497c478bd9Sstevel@tonic-gate 		if (tp != dir)
3507c478bd9Sstevel@tonic-gate 			rw_exit(&tp->tn_rwlock);
3517c478bd9Sstevel@tonic-gate 	}
3527c478bd9Sstevel@tonic-gate 
3537c478bd9Sstevel@tonic-gate 	/*
3547c478bd9Sstevel@tonic-gate 	 * This might be a "dangling detached directory".
3557c478bd9Sstevel@tonic-gate 	 * it could have been removed, but a reference
3567c478bd9Sstevel@tonic-gate 	 * to it kept in u_cwd.  don't bother searching
3577c478bd9Sstevel@tonic-gate 	 * it, and with any luck the user will get tired
3587c478bd9Sstevel@tonic-gate 	 * of dealing with us and cd to some absolute
3597c478bd9Sstevel@tonic-gate 	 * pathway.  *sigh*, thus in ufs, too.
3607c478bd9Sstevel@tonic-gate 	 */
3617c478bd9Sstevel@tonic-gate 	if (dir->tn_nlink == 0) {
3627c478bd9Sstevel@tonic-gate 		error = ENOENT;
3637c478bd9Sstevel@tonic-gate 		goto out;
3647c478bd9Sstevel@tonic-gate 	}
3657c478bd9Sstevel@tonic-gate 
3667c478bd9Sstevel@tonic-gate 	/*
3677c478bd9Sstevel@tonic-gate 	 * If this is a rename of a directory and the parent is
3687c478bd9Sstevel@tonic-gate 	 * different (".." must be changed), then the source
3697c478bd9Sstevel@tonic-gate 	 * directory must not be in the directory hierarchy
3707c478bd9Sstevel@tonic-gate 	 * above the target, as this would orphan everything
3717c478bd9Sstevel@tonic-gate 	 * below the source directory.
3727c478bd9Sstevel@tonic-gate 	 */
3737c478bd9Sstevel@tonic-gate 	if (op == DE_RENAME) {
3747c478bd9Sstevel@tonic-gate 		if (tp == dir) {
3757c478bd9Sstevel@tonic-gate 			error = EINVAL;
3767c478bd9Sstevel@tonic-gate 			goto out;
3777c478bd9Sstevel@tonic-gate 		}
3787c478bd9Sstevel@tonic-gate 		if (tp->tn_type == VDIR) {
3797c478bd9Sstevel@tonic-gate 			if ((fromparent != dir) &&
3807c478bd9Sstevel@tonic-gate 			    (error = tdircheckpath(tp, dir, cred))) {
3817c478bd9Sstevel@tonic-gate 				goto out;
3827c478bd9Sstevel@tonic-gate 			}
3837c478bd9Sstevel@tonic-gate 		}
3847c478bd9Sstevel@tonic-gate 	}
3857c478bd9Sstevel@tonic-gate 
3867c478bd9Sstevel@tonic-gate 	/*
3877c478bd9Sstevel@tonic-gate 	 * Search for the entry.  Return "found" if it exists.
3887c478bd9Sstevel@tonic-gate 	 */
3897c478bd9Sstevel@tonic-gate 	tdp = tmpfs_hash_lookup(name, dir, 1, &found);
3907c478bd9Sstevel@tonic-gate 
3917c478bd9Sstevel@tonic-gate 	if (tdp) {
3927c478bd9Sstevel@tonic-gate 		ASSERT(found);
3937c478bd9Sstevel@tonic-gate 		switch (op) {
3947c478bd9Sstevel@tonic-gate 		case DE_CREATE:
3957c478bd9Sstevel@tonic-gate 		case DE_MKDIR:
3967c478bd9Sstevel@tonic-gate 			if (tpp) {
3977c478bd9Sstevel@tonic-gate 				*tpp = found;
3987c478bd9Sstevel@tonic-gate 				error = EEXIST;
3997c478bd9Sstevel@tonic-gate 			} else {
4007c478bd9Sstevel@tonic-gate 				tmpnode_rele(found);
4017c478bd9Sstevel@tonic-gate 			}
4027c478bd9Sstevel@tonic-gate 			break;
4037c478bd9Sstevel@tonic-gate 
4047c478bd9Sstevel@tonic-gate 		case DE_RENAME:
4057c478bd9Sstevel@tonic-gate 			error = tdirrename(fromparent, tp,
4067c478bd9Sstevel@tonic-gate 			    dir, name, found, tdp, cred);
4077c478bd9Sstevel@tonic-gate 			if (error == 0) {
408df2381bfSpraks 				if (found != NULL) {
409df2381bfSpraks 					vnevent_rename_dest(TNTOV(found),
410da6c28aaSamw 					    TNTOV(dir), name, ctp);
411df2381bfSpraks 				}
4127c478bd9Sstevel@tonic-gate 			}
413df2381bfSpraks 
4147c478bd9Sstevel@tonic-gate 			tmpnode_rele(found);
4157c478bd9Sstevel@tonic-gate 			break;
4167c478bd9Sstevel@tonic-gate 
4177c478bd9Sstevel@tonic-gate 		case DE_LINK:
4187c478bd9Sstevel@tonic-gate 			/*
4197c478bd9Sstevel@tonic-gate 			 * Can't link to an existing file.
4207c478bd9Sstevel@tonic-gate 			 */
4217c478bd9Sstevel@tonic-gate 			error = EEXIST;
4227c478bd9Sstevel@tonic-gate 			tmpnode_rele(found);
4237c478bd9Sstevel@tonic-gate 			break;
4247c478bd9Sstevel@tonic-gate 		}
4257c478bd9Sstevel@tonic-gate 	} else {
4267c478bd9Sstevel@tonic-gate 
4277c478bd9Sstevel@tonic-gate 		/*
4287c478bd9Sstevel@tonic-gate 		 * The entry does not exist. Check write permission in
4297c478bd9Sstevel@tonic-gate 		 * directory to see if entry can be created.
4307c478bd9Sstevel@tonic-gate 		 */
4317c478bd9Sstevel@tonic-gate 		if (error = tmp_taccess(dir, VWRITE, cred))
4327c478bd9Sstevel@tonic-gate 			goto out;
4337c478bd9Sstevel@tonic-gate 		if (op == DE_CREATE || op == DE_MKDIR) {
4347c478bd9Sstevel@tonic-gate 			/*
4357c478bd9Sstevel@tonic-gate 			 * Make new tmpnode and directory entry as required.
4367c478bd9Sstevel@tonic-gate 			 */
4377c478bd9Sstevel@tonic-gate 			error = tdirmaketnode(dir, tm, va, op, &tp, cred);
4387c478bd9Sstevel@tonic-gate 			if (error)
4397c478bd9Sstevel@tonic-gate 				goto out;
4407c478bd9Sstevel@tonic-gate 		}
4417c478bd9Sstevel@tonic-gate 		if (error = tdiraddentry(dir, tp, name, op, fromparent)) {
4427c478bd9Sstevel@tonic-gate 			if (op == DE_CREATE || op == DE_MKDIR) {
4437c478bd9Sstevel@tonic-gate 				/*
4447c478bd9Sstevel@tonic-gate 				 * Unmake the inode we just made.
4457c478bd9Sstevel@tonic-gate 				 */
4467c478bd9Sstevel@tonic-gate 				rw_enter(&tp->tn_rwlock, RW_WRITER);
4477c478bd9Sstevel@tonic-gate 				if ((tp->tn_type) == VDIR) {
4487c478bd9Sstevel@tonic-gate 					ASSERT(tdp == NULL);
4497c478bd9Sstevel@tonic-gate 					/*
4507c478bd9Sstevel@tonic-gate 					 * cleanup allocs made by tdirinit()
4517c478bd9Sstevel@tonic-gate 					 */
4527c478bd9Sstevel@tonic-gate 					tdirtrunc(tp);
4537c478bd9Sstevel@tonic-gate 				}
4547c478bd9Sstevel@tonic-gate 				mutex_enter(&tp->tn_tlock);
4557c478bd9Sstevel@tonic-gate 				tp->tn_nlink = 0;
4567c478bd9Sstevel@tonic-gate 				mutex_exit(&tp->tn_tlock);
4577c478bd9Sstevel@tonic-gate 				gethrestime(&tp->tn_ctime);
4587c478bd9Sstevel@tonic-gate 				rw_exit(&tp->tn_rwlock);
4597c478bd9Sstevel@tonic-gate 				tmpnode_rele(tp);
4607c478bd9Sstevel@tonic-gate 				tp = NULL;
4617c478bd9Sstevel@tonic-gate 			}
4627c478bd9Sstevel@tonic-gate 		} else if (tpp) {
4637c478bd9Sstevel@tonic-gate 			*tpp = tp;
4647c478bd9Sstevel@tonic-gate 		} else if (op == DE_CREATE || op == DE_MKDIR) {
4657c478bd9Sstevel@tonic-gate 			tmpnode_rele(tp);
4667c478bd9Sstevel@tonic-gate 		}
4677c478bd9Sstevel@tonic-gate 	}
4687c478bd9Sstevel@tonic-gate 
4697c478bd9Sstevel@tonic-gate out:
4707c478bd9Sstevel@tonic-gate 	if (error && (op == DE_LINK || op == DE_RENAME)) {
4717c478bd9Sstevel@tonic-gate 		/*
4727c478bd9Sstevel@tonic-gate 		 * Undo bumped link count.
4737c478bd9Sstevel@tonic-gate 		 */
4747c478bd9Sstevel@tonic-gate 		DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
4757c478bd9Sstevel@tonic-gate 		gethrestime(&tp->tn_ctime);
4767c478bd9Sstevel@tonic-gate 	}
4777c478bd9Sstevel@tonic-gate 	return (error);
4787c478bd9Sstevel@tonic-gate }
4797c478bd9Sstevel@tonic-gate 
4807c478bd9Sstevel@tonic-gate /*
4817c478bd9Sstevel@tonic-gate  * Delete entry tp of name "nm" from dir.
4827c478bd9Sstevel@tonic-gate  * Free dir entry space and decrement link count on tmpnode(s).
4837c478bd9Sstevel@tonic-gate  *
4847c478bd9Sstevel@tonic-gate  * Return 0 on success.
4857c478bd9Sstevel@tonic-gate  */
4867c478bd9Sstevel@tonic-gate int
tdirdelete(struct tmpnode * dir,struct tmpnode * tp,char * nm,enum dr_op op,struct cred * cred)4877c478bd9Sstevel@tonic-gate tdirdelete(
4887c478bd9Sstevel@tonic-gate 	struct tmpnode *dir,
4897c478bd9Sstevel@tonic-gate 	struct tmpnode *tp,
4907c478bd9Sstevel@tonic-gate 	char *nm,
4917c478bd9Sstevel@tonic-gate 	enum dr_op op,
4927c478bd9Sstevel@tonic-gate 	struct cred *cred)
4937c478bd9Sstevel@tonic-gate {
4947c478bd9Sstevel@tonic-gate 	struct tdirent *tpdp;
4957c478bd9Sstevel@tonic-gate 	int error;
4967c478bd9Sstevel@tonic-gate 	size_t namelen;
4977c478bd9Sstevel@tonic-gate 	struct tmpnode *tnp;
4987c478bd9Sstevel@tonic-gate 	timestruc_t now;
4997c478bd9Sstevel@tonic-gate 
5007c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
5017c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&tp->tn_rwlock));
5027c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
5037c478bd9Sstevel@tonic-gate 
5047c478bd9Sstevel@tonic-gate 	if (nm[0] == '\0')
5057c478bd9Sstevel@tonic-gate 		panic("tdirdelete: NULL name for %p", (void *)tp);
5067c478bd9Sstevel@tonic-gate 
5077c478bd9Sstevel@tonic-gate 	/*
5087c478bd9Sstevel@tonic-gate 	 * return error when removing . and ..
5097c478bd9Sstevel@tonic-gate 	 */
5107c478bd9Sstevel@tonic-gate 	if (nm[0] == '.') {
5117c478bd9Sstevel@tonic-gate 		if (nm[1] == '\0')
5127c478bd9Sstevel@tonic-gate 			return (EINVAL);
5137c478bd9Sstevel@tonic-gate 		if (nm[1] == '.' && nm[2] == '\0')
5147c478bd9Sstevel@tonic-gate 			return (EEXIST); /* thus in ufs */
5157c478bd9Sstevel@tonic-gate 	}
5167c478bd9Sstevel@tonic-gate 
5177c478bd9Sstevel@tonic-gate 	if (error = tmp_taccess(dir, VEXEC|VWRITE, cred))
5187c478bd9Sstevel@tonic-gate 		return (error);
5197c478bd9Sstevel@tonic-gate 
5207c478bd9Sstevel@tonic-gate 	/*
5217c478bd9Sstevel@tonic-gate 	 * If the parent directory is "sticky", then the user must
5227c478bd9Sstevel@tonic-gate 	 * own the parent directory or the file in it, or else must
5237c478bd9Sstevel@tonic-gate 	 * have permission to write the file.  Otherwise it may not
5247c478bd9Sstevel@tonic-gate 	 * be deleted (except by privileged users).
5257c478bd9Sstevel@tonic-gate 	 * Same as ufs_dirremove.
5267c478bd9Sstevel@tonic-gate 	 */
5277c478bd9Sstevel@tonic-gate 	if ((error = tmp_sticky_remove_access(dir, tp, cred)) != 0)
5287c478bd9Sstevel@tonic-gate 		return (error);
5297c478bd9Sstevel@tonic-gate 
5307c478bd9Sstevel@tonic-gate 	if (dir->tn_dir == NULL)
5317c478bd9Sstevel@tonic-gate 		return (ENOENT);
5327c478bd9Sstevel@tonic-gate 
5337c478bd9Sstevel@tonic-gate 	tpdp = tmpfs_hash_lookup(nm, dir, 0, &tnp);
5347c478bd9Sstevel@tonic-gate 	if (tpdp == NULL) {
5357c478bd9Sstevel@tonic-gate 		/*
5367c478bd9Sstevel@tonic-gate 		 * If it is gone, some other thread got here first!
5377c478bd9Sstevel@tonic-gate 		 * Return error ENOENT.
5387c478bd9Sstevel@tonic-gate 		 */
5397c478bd9Sstevel@tonic-gate 		return (ENOENT);
5407c478bd9Sstevel@tonic-gate 	}
5417c478bd9Sstevel@tonic-gate 
5427c478bd9Sstevel@tonic-gate 	/*
5437c478bd9Sstevel@tonic-gate 	 * If the tmpnode in the tdirent changed, we were probably
5447c478bd9Sstevel@tonic-gate 	 * the victim of a concurrent rename operation.  The original
5457c478bd9Sstevel@tonic-gate 	 * is gone, so return that status (same as UFS).
5467c478bd9Sstevel@tonic-gate 	 */
5477c478bd9Sstevel@tonic-gate 	if (tp != tnp)
5487c478bd9Sstevel@tonic-gate 		return (ENOENT);
5497c478bd9Sstevel@tonic-gate 
5507c478bd9Sstevel@tonic-gate 	tmpfs_hash_out(tpdp);
5517c478bd9Sstevel@tonic-gate 
5527c478bd9Sstevel@tonic-gate 	/*
5537c478bd9Sstevel@tonic-gate 	 * Take tpdp out of the directory list.
5547c478bd9Sstevel@tonic-gate 	 */
5557c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
5567c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
5577c478bd9Sstevel@tonic-gate 	if (tpdp->td_prev) {
5587c478bd9Sstevel@tonic-gate 		tpdp->td_prev->td_next = tpdp->td_next;
5597c478bd9Sstevel@tonic-gate 	}
5607c478bd9Sstevel@tonic-gate 	if (tpdp->td_next) {
5617c478bd9Sstevel@tonic-gate 		tpdp->td_next->td_prev = tpdp->td_prev;
5627c478bd9Sstevel@tonic-gate 	}
5637c478bd9Sstevel@tonic-gate 
5647c478bd9Sstevel@tonic-gate 	/*
5657c478bd9Sstevel@tonic-gate 	 * If the roving slot pointer happens to match tpdp,
5667c478bd9Sstevel@tonic-gate 	 * point it at the previous dirent.
5677c478bd9Sstevel@tonic-gate 	 */
5687c478bd9Sstevel@tonic-gate 	if (dir->tn_dir->td_prev == tpdp) {
5697c478bd9Sstevel@tonic-gate 		dir->tn_dir->td_prev = tpdp->td_prev;
5707c478bd9Sstevel@tonic-gate 	}
5717c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
5727c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
5737c478bd9Sstevel@tonic-gate 
5747c478bd9Sstevel@tonic-gate 	/*
5757c478bd9Sstevel@tonic-gate 	 * tpdp points to the correct directory entry
5767c478bd9Sstevel@tonic-gate 	 */
5777c478bd9Sstevel@tonic-gate 	namelen = strlen(tpdp->td_name) + 1;
5787c478bd9Sstevel@tonic-gate 
5797c478bd9Sstevel@tonic-gate 	tmp_memfree(tpdp, sizeof (struct tdirent) + namelen);
5807c478bd9Sstevel@tonic-gate 	dir->tn_size -= (sizeof (struct tdirent) + namelen);
5817c478bd9Sstevel@tonic-gate 	dir->tn_dirents--;
5827c478bd9Sstevel@tonic-gate 
5837c478bd9Sstevel@tonic-gate 	gethrestime(&now);
5847c478bd9Sstevel@tonic-gate 	dir->tn_mtime = now;
5857c478bd9Sstevel@tonic-gate 	dir->tn_ctime = now;
5867c478bd9Sstevel@tonic-gate 	tp->tn_ctime = now;
5877c478bd9Sstevel@tonic-gate 
5887c478bd9Sstevel@tonic-gate 	ASSERT(tp->tn_nlink > 0);
5897c478bd9Sstevel@tonic-gate 	DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
5907c478bd9Sstevel@tonic-gate 	if (op == DR_RMDIR && tp->tn_type == VDIR) {
5917c478bd9Sstevel@tonic-gate 		tdirtrunc(tp);
5927c478bd9Sstevel@tonic-gate 		ASSERT(tp->tn_nlink == 0);
5937c478bd9Sstevel@tonic-gate 	}
5947c478bd9Sstevel@tonic-gate 	return (0);
5957c478bd9Sstevel@tonic-gate }
5967c478bd9Sstevel@tonic-gate 
5977c478bd9Sstevel@tonic-gate /*
5987c478bd9Sstevel@tonic-gate  * tdirinit is used internally to initialize a directory (dir)
5997c478bd9Sstevel@tonic-gate  * with '.' and '..' entries without checking permissions and locking
6007c478bd9Sstevel@tonic-gate  */
6017c478bd9Sstevel@tonic-gate void
tdirinit(struct tmpnode * parent,struct tmpnode * dir)6027c478bd9Sstevel@tonic-gate tdirinit(
6037c478bd9Sstevel@tonic-gate 	struct tmpnode *parent,		/* parent of directory to initialize */
6047c478bd9Sstevel@tonic-gate 	struct tmpnode *dir)		/* the new directory */
6057c478bd9Sstevel@tonic-gate {
6067c478bd9Sstevel@tonic-gate 	struct tdirent *dot, *dotdot;
6077c478bd9Sstevel@tonic-gate 	timestruc_t now;
6087c478bd9Sstevel@tonic-gate 
6097c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&parent->tn_rwlock));
6107c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
6117c478bd9Sstevel@tonic-gate 
6127c478bd9Sstevel@tonic-gate 	dot = tmp_memalloc(sizeof (struct tdirent) + 2, TMP_MUSTHAVE);
6137c478bd9Sstevel@tonic-gate 	dotdot = tmp_memalloc(sizeof (struct tdirent) + 3, TMP_MUSTHAVE);
6147c478bd9Sstevel@tonic-gate 
6157c478bd9Sstevel@tonic-gate 	/*
6167c478bd9Sstevel@tonic-gate 	 * Initialize the entries
6177c478bd9Sstevel@tonic-gate 	 */
6187c478bd9Sstevel@tonic-gate 	dot->td_tmpnode = dir;
6197c478bd9Sstevel@tonic-gate 	dot->td_offset = 0;
6207c478bd9Sstevel@tonic-gate 	dot->td_name = (char *)dot + sizeof (struct tdirent);
6217c478bd9Sstevel@tonic-gate 	dot->td_name[0] = '.';
6227c478bd9Sstevel@tonic-gate 	dot->td_parent = dir;
6237c478bd9Sstevel@tonic-gate 	tmpfs_hash_in(dot);
6247c478bd9Sstevel@tonic-gate 
6257c478bd9Sstevel@tonic-gate 	dotdot->td_tmpnode = parent;
6267c478bd9Sstevel@tonic-gate 	dotdot->td_offset = 1;
6277c478bd9Sstevel@tonic-gate 	dotdot->td_name = (char *)dotdot + sizeof (struct tdirent);
6287c478bd9Sstevel@tonic-gate 	dotdot->td_name[0] = '.';
6297c478bd9Sstevel@tonic-gate 	dotdot->td_name[1] = '.';
6307c478bd9Sstevel@tonic-gate 	dotdot->td_parent = dir;
6317c478bd9Sstevel@tonic-gate 	tmpfs_hash_in(dotdot);
6327c478bd9Sstevel@tonic-gate 
6337c478bd9Sstevel@tonic-gate 	/*
6347c478bd9Sstevel@tonic-gate 	 * Initialize directory entry list.
6357c478bd9Sstevel@tonic-gate 	 */
6367c478bd9Sstevel@tonic-gate 	dot->td_next = dotdot;
6377c478bd9Sstevel@tonic-gate 	dot->td_prev = dotdot;	/* dot's td_prev holds roving slot pointer */
6387c478bd9Sstevel@tonic-gate 	dotdot->td_next = NULL;
6397c478bd9Sstevel@tonic-gate 	dotdot->td_prev = dot;
6407c478bd9Sstevel@tonic-gate 
6417c478bd9Sstevel@tonic-gate 	gethrestime(&now);
6427c478bd9Sstevel@tonic-gate 	dir->tn_mtime = now;
6437c478bd9Sstevel@tonic-gate 	dir->tn_ctime = now;
6447c478bd9Sstevel@tonic-gate 
6457c478bd9Sstevel@tonic-gate 	/*
6467c478bd9Sstevel@tonic-gate 	 * Link counts are special for the hidden attribute directory.
6477c478bd9Sstevel@tonic-gate 	 * The only explicit reference in the name space is "." and
6487c478bd9Sstevel@tonic-gate 	 * the reference through ".." is not counted on the parent
6497c478bd9Sstevel@tonic-gate 	 * file. The attrdir is created as a side effect to lookup,
6507c478bd9Sstevel@tonic-gate 	 * so don't change the ctime of the parent.
6517c478bd9Sstevel@tonic-gate 	 * Since tdirinit is called with both dir and parent being the
6527c478bd9Sstevel@tonic-gate 	 * same for the root vnode, we need to increment this before we set
6537c478bd9Sstevel@tonic-gate 	 * tn_nlink = 2 below.
6547c478bd9Sstevel@tonic-gate 	 */
6557c478bd9Sstevel@tonic-gate 	if (!(dir->tn_vnode->v_flag & V_XATTRDIR)) {
6567c478bd9Sstevel@tonic-gate 		INCR_COUNT(&parent->tn_nlink, &parent->tn_tlock);
6577c478bd9Sstevel@tonic-gate 		parent->tn_ctime = now;
6587c478bd9Sstevel@tonic-gate 	}
6597c478bd9Sstevel@tonic-gate 
6607c478bd9Sstevel@tonic-gate 	dir->tn_dir = dot;
6617c478bd9Sstevel@tonic-gate 	dir->tn_size = 2 * sizeof (struct tdirent) + 5;	/* dot and dotdot */
6627c478bd9Sstevel@tonic-gate 	dir->tn_dirents = 2;
6637c478bd9Sstevel@tonic-gate 	dir->tn_nlink = 2;
6647c478bd9Sstevel@tonic-gate }
6657c478bd9Sstevel@tonic-gate 
6667c478bd9Sstevel@tonic-gate 
6677c478bd9Sstevel@tonic-gate /*
6687c478bd9Sstevel@tonic-gate  * tdirtrunc is called to remove all directory entries under this directory.
6697c478bd9Sstevel@tonic-gate  */
6707c478bd9Sstevel@tonic-gate void
tdirtrunc(struct tmpnode * dir)6717c478bd9Sstevel@tonic-gate tdirtrunc(struct tmpnode *dir)
6727c478bd9Sstevel@tonic-gate {
6737c478bd9Sstevel@tonic-gate 	struct tdirent *tdp;
6747c478bd9Sstevel@tonic-gate 	struct tmpnode *tp;
6757c478bd9Sstevel@tonic-gate 	size_t namelen;
6767c478bd9Sstevel@tonic-gate 	timestruc_t now;
6777c478bd9Sstevel@tonic-gate 	int isvattrdir, isdotdot, skip_decr;
6787c478bd9Sstevel@tonic-gate 
6797c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
6807c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
6817c478bd9Sstevel@tonic-gate 
6827c478bd9Sstevel@tonic-gate 	isvattrdir = (dir->tn_vnode->v_flag & V_XATTRDIR) ? 1 : 0;
6837c478bd9Sstevel@tonic-gate 	for (tdp = dir->tn_dir; tdp; tdp = dir->tn_dir) {
6847c478bd9Sstevel@tonic-gate 		ASSERT(tdp->td_next != tdp);
6857c478bd9Sstevel@tonic-gate 		ASSERT(tdp->td_prev != tdp);
6867c478bd9Sstevel@tonic-gate 		ASSERT(tdp->td_tmpnode);
6877c478bd9Sstevel@tonic-gate 
6887c478bd9Sstevel@tonic-gate 		dir->tn_dir = tdp->td_next;
6897c478bd9Sstevel@tonic-gate 		namelen = strlen(tdp->td_name) + 1;
6907c478bd9Sstevel@tonic-gate 
6917c478bd9Sstevel@tonic-gate 		/*
6927c478bd9Sstevel@tonic-gate 		 * Adjust the link counts to account for this directory
6937c478bd9Sstevel@tonic-gate 		 * entry removal. Hidden attribute directories may
6947c478bd9Sstevel@tonic-gate 		 * not be empty as they may be truncated as a side-
6957c478bd9Sstevel@tonic-gate 		 * effect of removing the parent. We do hold/rele
6967c478bd9Sstevel@tonic-gate 		 * operations to free up these tmpnodes.
6977c478bd9Sstevel@tonic-gate 		 *
6987c478bd9Sstevel@tonic-gate 		 * Skip the link count adjustment for parents of
6997c478bd9Sstevel@tonic-gate 		 * attribute directories as those link counts
7007c478bd9Sstevel@tonic-gate 		 * do not include the ".." reference in the hidden
7017c478bd9Sstevel@tonic-gate 		 * directories.
7027c478bd9Sstevel@tonic-gate 		 */
7037c478bd9Sstevel@tonic-gate 		tp = tdp->td_tmpnode;
7047c478bd9Sstevel@tonic-gate 		isdotdot = (strcmp("..", tdp->td_name) == 0);
7057c478bd9Sstevel@tonic-gate 		skip_decr = (isvattrdir && isdotdot);
7067c478bd9Sstevel@tonic-gate 		if (!skip_decr) {
7077c478bd9Sstevel@tonic-gate 			ASSERT(tp->tn_nlink > 0);
7087c478bd9Sstevel@tonic-gate 			DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
7097c478bd9Sstevel@tonic-gate 		}
7107c478bd9Sstevel@tonic-gate 
7117c478bd9Sstevel@tonic-gate 		tmpfs_hash_out(tdp);
7127c478bd9Sstevel@tonic-gate 
7137c478bd9Sstevel@tonic-gate 		tmp_memfree(tdp, sizeof (struct tdirent) + namelen);
7147c478bd9Sstevel@tonic-gate 		dir->tn_size -= (sizeof (struct tdirent) + namelen);
7157c478bd9Sstevel@tonic-gate 		dir->tn_dirents--;
7167c478bd9Sstevel@tonic-gate 	}
7177c478bd9Sstevel@tonic-gate 
7187c478bd9Sstevel@tonic-gate 	gethrestime(&now);
7197c478bd9Sstevel@tonic-gate 	dir->tn_mtime = now;
7207c478bd9Sstevel@tonic-gate 	dir->tn_ctime = now;
7217c478bd9Sstevel@tonic-gate 
7227c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_dir == NULL);
7237c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_size == 0);
7247c478bd9Sstevel@tonic-gate 	ASSERT(dir->tn_dirents == 0);
7257c478bd9Sstevel@tonic-gate }
7267c478bd9Sstevel@tonic-gate 
7277c478bd9Sstevel@tonic-gate /*
7287c478bd9Sstevel@tonic-gate  * Check if the source directory is in the path of the target directory.
7297c478bd9Sstevel@tonic-gate  * The target directory is locked by the caller.
7307c478bd9Sstevel@tonic-gate  *
7317c478bd9Sstevel@tonic-gate  * XXX - The source and target's should be different upon entry.
7327c478bd9Sstevel@tonic-gate  */
7337c478bd9Sstevel@tonic-gate static int
tdircheckpath(struct tmpnode * fromtp,struct tmpnode * toparent,struct cred * cred)7347c478bd9Sstevel@tonic-gate tdircheckpath(
7357c478bd9Sstevel@tonic-gate 	struct tmpnode *fromtp,
7367c478bd9Sstevel@tonic-gate 	struct tmpnode	*toparent,
7377c478bd9Sstevel@tonic-gate 	struct cred	*cred)
7387c478bd9Sstevel@tonic-gate {
7397c478bd9Sstevel@tonic-gate 	int	error = 0;
7407c478bd9Sstevel@tonic-gate 	struct tmpnode *dir, *dotdot;
7417c478bd9Sstevel@tonic-gate 	struct tdirent *tdp;
7427c478bd9Sstevel@tonic-gate 
7437c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&toparent->tn_rwlock));
7447c478bd9Sstevel@tonic-gate 
7457c478bd9Sstevel@tonic-gate 	tdp = tmpfs_hash_lookup("..", toparent, 1, &dotdot);
7467c478bd9Sstevel@tonic-gate 	if (tdp == NULL)
7477c478bd9Sstevel@tonic-gate 		return (ENOENT);
7487c478bd9Sstevel@tonic-gate 
7497c478bd9Sstevel@tonic-gate 	ASSERT(dotdot);
7507c478bd9Sstevel@tonic-gate 
7517c478bd9Sstevel@tonic-gate 	if (dotdot == toparent) {
7527c478bd9Sstevel@tonic-gate 		/* root of fs.  search trivially satisfied. */
7537c478bd9Sstevel@tonic-gate 		tmpnode_rele(dotdot);
7547c478bd9Sstevel@tonic-gate 		return (0);
7557c478bd9Sstevel@tonic-gate 	}
7567c478bd9Sstevel@tonic-gate 	for (;;) {
7577c478bd9Sstevel@tonic-gate 		/*
7587c478bd9Sstevel@tonic-gate 		 * Return error for cases like "mv c c/d",
7597c478bd9Sstevel@tonic-gate 		 * "mv c c/d/e" and so on.
7607c478bd9Sstevel@tonic-gate 		 */
7617c478bd9Sstevel@tonic-gate 		if (dotdot == fromtp) {
7627c478bd9Sstevel@tonic-gate 			tmpnode_rele(dotdot);
7637c478bd9Sstevel@tonic-gate 			error = EINVAL;
7647c478bd9Sstevel@tonic-gate 			break;
7657c478bd9Sstevel@tonic-gate 		}
7667c478bd9Sstevel@tonic-gate 		dir = dotdot;
7677c478bd9Sstevel@tonic-gate 		error = tdirlookup(dir, "..", &dotdot, cred);
7687c478bd9Sstevel@tonic-gate 		if (error) {
7697c478bd9Sstevel@tonic-gate 			tmpnode_rele(dir);
7707c478bd9Sstevel@tonic-gate 			break;
7717c478bd9Sstevel@tonic-gate 		}
7727c478bd9Sstevel@tonic-gate 		/*
7737c478bd9Sstevel@tonic-gate 		 * We're okay if we traverse the directory tree up to
7747c478bd9Sstevel@tonic-gate 		 * the root directory and don't run into the
7757c478bd9Sstevel@tonic-gate 		 * parent directory.
7767c478bd9Sstevel@tonic-gate 		 */
7777c478bd9Sstevel@tonic-gate 		if (dir == dotdot) {
7787c478bd9Sstevel@tonic-gate 			tmpnode_rele(dir);
7797c478bd9Sstevel@tonic-gate 			tmpnode_rele(dotdot);
7807c478bd9Sstevel@tonic-gate 			break;
7817c478bd9Sstevel@tonic-gate 		}
7827c478bd9Sstevel@tonic-gate 		tmpnode_rele(dir);
7837c478bd9Sstevel@tonic-gate 	}
7847c478bd9Sstevel@tonic-gate 	return (error);
7857c478bd9Sstevel@tonic-gate }
7867c478bd9Sstevel@tonic-gate 
7877c478bd9Sstevel@tonic-gate static int
tdirrename(struct tmpnode * fromparent,struct tmpnode * fromtp,struct tmpnode * toparent,char * nm,struct tmpnode * to,struct tdirent * where,struct cred * cred)7887c478bd9Sstevel@tonic-gate tdirrename(
7897c478bd9Sstevel@tonic-gate 	struct tmpnode *fromparent,	/* parent directory of source */
7907c478bd9Sstevel@tonic-gate 	struct tmpnode *fromtp,		/* source tmpnode */
7917c478bd9Sstevel@tonic-gate 	struct tmpnode *toparent,	/* parent directory of target */
7927c478bd9Sstevel@tonic-gate 	char *nm,			/* entry we are trying to change */
7937c478bd9Sstevel@tonic-gate 	struct tmpnode *to,		/* target tmpnode */
7947c478bd9Sstevel@tonic-gate 	struct tdirent *where,		/* target tmpnode directory entry */
7957c478bd9Sstevel@tonic-gate 	struct cred *cred)		/* credentials */
7967c478bd9Sstevel@tonic-gate {
7977c478bd9Sstevel@tonic-gate 	int error = 0;
7987c478bd9Sstevel@tonic-gate 	int doingdirectory;
7997c478bd9Sstevel@tonic-gate 	timestruc_t now;
8007c478bd9Sstevel@tonic-gate 
8017c478bd9Sstevel@tonic-gate #if defined(lint)
8027c478bd9Sstevel@tonic-gate 	nm = nm;
8037c478bd9Sstevel@tonic-gate #endif
8047c478bd9Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&toparent->tn_rwlock));
8057c478bd9Sstevel@tonic-gate 
8067c478bd9Sstevel@tonic-gate 	/*
8077c478bd9Sstevel@tonic-gate 	 * Short circuit rename of something to itself.
8087c478bd9Sstevel@tonic-gate 	 */
8097c478bd9Sstevel@tonic-gate 	if (fromtp == to)
8107c478bd9Sstevel@tonic-gate 		return (ESAME);		/* special KLUDGE error code */
8117c478bd9Sstevel@tonic-gate 
8127c478bd9Sstevel@tonic-gate 	rw_enter(&fromtp->tn_rwlock, RW_READER);
8137c478bd9Sstevel@tonic-gate 	rw_enter(&to->tn_rwlock, RW_READER);
8147c478bd9Sstevel@tonic-gate 
8157c478bd9Sstevel@tonic-gate 	/*
8167c478bd9Sstevel@tonic-gate 	 * Check that everything is on the same filesystem.
8177c478bd9Sstevel@tonic-gate 	 */
8187c478bd9Sstevel@tonic-gate 	if (to->tn_vnode->v_vfsp != toparent->tn_vnode->v_vfsp ||
8197c478bd9Sstevel@tonic-gate 	    to->tn_vnode->v_vfsp != fromtp->tn_vnode->v_vfsp) {
8207c478bd9Sstevel@tonic-gate 		error = EXDEV;
8217c478bd9Sstevel@tonic-gate 		goto out;
8227c478bd9Sstevel@tonic-gate 	}
8237c478bd9Sstevel@tonic-gate 
8247c478bd9Sstevel@tonic-gate 	/*
8257c478bd9Sstevel@tonic-gate 	 * Must have write permission to rewrite target entry.
8267c478bd9Sstevel@tonic-gate 	 * Check for stickyness.
8277c478bd9Sstevel@tonic-gate 	 */
8287c478bd9Sstevel@tonic-gate 	if ((error = tmp_taccess(toparent, VWRITE, cred)) != 0 ||
8297c478bd9Sstevel@tonic-gate 	    (error = tmp_sticky_remove_access(toparent, to, cred)) != 0)
8307c478bd9Sstevel@tonic-gate 		goto out;
8317c478bd9Sstevel@tonic-gate 
8327c478bd9Sstevel@tonic-gate 	/*
8337c478bd9Sstevel@tonic-gate 	 * Ensure source and target are compatible (both directories
8347c478bd9Sstevel@tonic-gate 	 * or both not directories).  If target is a directory it must
8357c478bd9Sstevel@tonic-gate 	 * be empty and have no links to it; in addition it must not
8367c478bd9Sstevel@tonic-gate 	 * be a mount point, and both the source and target must be
8377c478bd9Sstevel@tonic-gate 	 * writable.
8387c478bd9Sstevel@tonic-gate 	 */
8397c478bd9Sstevel@tonic-gate 	doingdirectory = (fromtp->tn_type == VDIR);
8407c478bd9Sstevel@tonic-gate 	if (to->tn_type == VDIR) {
8417c478bd9Sstevel@tonic-gate 		if (!doingdirectory) {
8427c478bd9Sstevel@tonic-gate 			error = EISDIR;
8437c478bd9Sstevel@tonic-gate 			goto out;
8447c478bd9Sstevel@tonic-gate 		}
8457c478bd9Sstevel@tonic-gate 		/*
846d5dbd18dSbatschul 		 * vn_vfswlock will prevent mounts from using the directory
8477c478bd9Sstevel@tonic-gate 		 * until we are done.
8487c478bd9Sstevel@tonic-gate 		 */
849d5dbd18dSbatschul 		if (vn_vfswlock(TNTOV(to))) {
8507c478bd9Sstevel@tonic-gate 			error = EBUSY;
8517c478bd9Sstevel@tonic-gate 			goto out;
8527c478bd9Sstevel@tonic-gate 		}
8537c478bd9Sstevel@tonic-gate 		if (vn_mountedvfs(TNTOV(to)) != NULL) {
8547c478bd9Sstevel@tonic-gate 			vn_vfsunlock(TNTOV(to));
8557c478bd9Sstevel@tonic-gate 			error = EBUSY;
8567c478bd9Sstevel@tonic-gate 			goto out;
8577c478bd9Sstevel@tonic-gate 		}
8587c478bd9Sstevel@tonic-gate 
8597c478bd9Sstevel@tonic-gate 		mutex_enter(&to->tn_tlock);
8607c478bd9Sstevel@tonic-gate 		if (to->tn_dirents > 2 || to->tn_nlink > 2) {
8617c478bd9Sstevel@tonic-gate 			mutex_exit(&to->tn_tlock);
8627c478bd9Sstevel@tonic-gate 			vn_vfsunlock(TNTOV(to));
8637c478bd9Sstevel@tonic-gate 			error = EEXIST; /* SIGH should be ENOTEMPTY */
8647c478bd9Sstevel@tonic-gate 			/*
8657c478bd9Sstevel@tonic-gate 			 * Update atime because checking tn_dirents is
8667c478bd9Sstevel@tonic-gate 			 * logically equivalent to reading the directory
8677c478bd9Sstevel@tonic-gate 			 */
8687c478bd9Sstevel@tonic-gate 			gethrestime(&to->tn_atime);
8697c478bd9Sstevel@tonic-gate 			goto out;
8707c478bd9Sstevel@tonic-gate 		}
8717c478bd9Sstevel@tonic-gate 		mutex_exit(&to->tn_tlock);
8727c478bd9Sstevel@tonic-gate 	} else if (doingdirectory) {
8737c478bd9Sstevel@tonic-gate 		error = ENOTDIR;
8747c478bd9Sstevel@tonic-gate 		goto out;
8757c478bd9Sstevel@tonic-gate 	}
8767c478bd9Sstevel@tonic-gate 
8777c478bd9Sstevel@tonic-gate 	tmpfs_hash_change(where, fromtp);
8787c478bd9Sstevel@tonic-gate 	gethrestime(&now);
8797c478bd9Sstevel@tonic-gate 	toparent->tn_mtime = now;
8807c478bd9Sstevel@tonic-gate 	toparent->tn_ctime = now;
8817c478bd9Sstevel@tonic-gate 
8827c478bd9Sstevel@tonic-gate 	/*
8837c478bd9Sstevel@tonic-gate 	 * Upgrade to write lock on "to" (i.e., the target tmpnode).
8847c478bd9Sstevel@tonic-gate 	 */
8857c478bd9Sstevel@tonic-gate 	rw_exit(&to->tn_rwlock);
8867c478bd9Sstevel@tonic-gate 	rw_enter(&to->tn_rwlock, RW_WRITER);
8877c478bd9Sstevel@tonic-gate 
8887c478bd9Sstevel@tonic-gate 	/*
8897c478bd9Sstevel@tonic-gate 	 * Decrement the link count of the target tmpnode.
8907c478bd9Sstevel@tonic-gate 	 */
8917c478bd9Sstevel@tonic-gate 	DECR_COUNT(&to->tn_nlink, &to->tn_tlock);
8927c478bd9Sstevel@tonic-gate 	to->tn_ctime = now;
8937c478bd9Sstevel@tonic-gate 
8947c478bd9Sstevel@tonic-gate 	if (doingdirectory) {
8957c478bd9Sstevel@tonic-gate 		/*
8967c478bd9Sstevel@tonic-gate 		 * The entry for "to" no longer exists so release the vfslock.
8977c478bd9Sstevel@tonic-gate 		 */
8987c478bd9Sstevel@tonic-gate 		vn_vfsunlock(TNTOV(to));
8997c478bd9Sstevel@tonic-gate 
9007c478bd9Sstevel@tonic-gate 		/*
9017c478bd9Sstevel@tonic-gate 		 * Decrement the target link count and delete all entires.
9027c478bd9Sstevel@tonic-gate 		 */
9037c478bd9Sstevel@tonic-gate 		tdirtrunc(to);
9047c478bd9Sstevel@tonic-gate 		ASSERT(to->tn_nlink == 0);
9057c478bd9Sstevel@tonic-gate 
9067c478bd9Sstevel@tonic-gate 		/*
9077c478bd9Sstevel@tonic-gate 		 * Renaming a directory with the parent different
9087c478bd9Sstevel@tonic-gate 		 * requires that ".." be rewritten.  The window is
9097c478bd9Sstevel@tonic-gate 		 * still there for ".." to be inconsistent, but this
9107c478bd9Sstevel@tonic-gate 		 * is unavoidable, and a lot shorter than when it was
9117c478bd9Sstevel@tonic-gate 		 * done in a user process.
9127c478bd9Sstevel@tonic-gate 		 */
9137c478bd9Sstevel@tonic-gate 		if (fromparent != toparent)
9147c478bd9Sstevel@tonic-gate 			tdirfixdotdot(fromtp, fromparent, toparent);
9157c478bd9Sstevel@tonic-gate 	}
9167c478bd9Sstevel@tonic-gate out:
9177c478bd9Sstevel@tonic-gate 	rw_exit(&to->tn_rwlock);
9187c478bd9Sstevel@tonic-gate 	rw_exit(&fromtp->tn_rwlock);
9197c478bd9Sstevel@tonic-gate 	return (error);
9207c478bd9Sstevel@tonic-gate }
9217c478bd9Sstevel@tonic-gate 
9227c478bd9Sstevel@tonic-gate static void
tdirfixdotdot(struct tmpnode * fromtp,struct tmpnode * fromparent,struct tmpnode * toparent)9237c478bd9Sstevel@tonic-gate tdirfixdotdot(
9247c478bd9Sstevel@tonic-gate 	struct tmpnode	*fromtp,	/* child directory */
9257c478bd9Sstevel@tonic-gate 	struct tmpnode	*fromparent,	/* old parent directory */
9267c478bd9Sstevel@tonic-gate 	struct tmpnode	*toparent)	/* new parent directory */
9277c478bd9Sstevel@tonic-gate {
9287c478bd9Sstevel@tonic-gate 	struct tdirent	*dotdot;
9297c478bd9Sstevel@tonic-gate 
9307c478bd9Sstevel@tonic-gate 	ASSERT(RW_LOCK_HELD(&toparent->tn_rwlock));
9317c478bd9Sstevel@tonic-gate 
9327c478bd9Sstevel@tonic-gate 	/*
9337c478bd9Sstevel@tonic-gate 	 * Increment the link count in the new parent tmpnode
9347c478bd9Sstevel@tonic-gate 	 */
9357c478bd9Sstevel@tonic-gate 	INCR_COUNT(&toparent->tn_nlink, &toparent->tn_tlock);
9367c478bd9Sstevel@tonic-gate 	gethrestime(&toparent->tn_ctime);
9377c478bd9Sstevel@tonic-gate 
9387c478bd9Sstevel@tonic-gate 	dotdot = tmpfs_hash_lookup("..", fromtp, 0, NULL);
9397c478bd9Sstevel@tonic-gate 
9407c478bd9Sstevel@tonic-gate 	ASSERT(dotdot->td_tmpnode == fromparent);
9417c478bd9Sstevel@tonic-gate 	dotdot->td_tmpnode = toparent;
9427c478bd9Sstevel@tonic-gate 
9437c478bd9Sstevel@tonic-gate 	/*
9447c478bd9Sstevel@tonic-gate 	 * Decrement the link count of the old parent tmpnode.
9457c478bd9Sstevel@tonic-gate 	 * If fromparent is NULL, then this is a new directory link;
9467c478bd9Sstevel@tonic-gate 	 * it has no parent, so we need not do anything.
9477c478bd9Sstevel@tonic-gate 	 */
9487c478bd9Sstevel@tonic-gate 	if (fromparent != NULL) {
9497c478bd9Sstevel@tonic-gate 		mutex_enter(&fromparent->tn_tlock);
9507c478bd9Sstevel@tonic-gate 		if (fromparent->tn_nlink != 0) {
9517c478bd9Sstevel@tonic-gate 			fromparent->tn_nlink--;
9527c478bd9Sstevel@tonic-gate 			gethrestime(&fromparent->tn_ctime);
9537c478bd9Sstevel@tonic-gate 		}
9547c478bd9Sstevel@tonic-gate 		mutex_exit(&fromparent->tn_tlock);
9557c478bd9Sstevel@tonic-gate 	}
9567c478bd9Sstevel@tonic-gate }
9577c478bd9Sstevel@tonic-gate 
9587c478bd9Sstevel@tonic-gate static int
tdiraddentry(struct tmpnode * dir,struct tmpnode * tp,char * name,enum de_op op,struct tmpnode * fromtp)9597c478bd9Sstevel@tonic-gate tdiraddentry(
9607c478bd9Sstevel@tonic-gate 	struct tmpnode	*dir,	/* target directory to make entry in */
9617c478bd9Sstevel@tonic-gate 	struct tmpnode	*tp,	/* new tmpnode */
9627c478bd9Sstevel@tonic-gate 	char		*name,
9637c478bd9Sstevel@tonic-gate 	enum de_op	op,
9647c478bd9Sstevel@tonic-gate 	struct tmpnode	*fromtp)
9657c478bd9Sstevel@tonic-gate {
9667c478bd9Sstevel@tonic-gate 	struct tdirent *tdp, *tpdp;
9677c478bd9Sstevel@tonic-gate 	size_t		namelen, alloc_size;
9687c478bd9Sstevel@tonic-gate 	timestruc_t	now;
9697c478bd9Sstevel@tonic-gate 
9707c478bd9Sstevel@tonic-gate 	/*
9717c478bd9Sstevel@tonic-gate 	 * Make sure the parent directory wasn't removed from
9727c478bd9Sstevel@tonic-gate 	 * underneath the caller.
9737c478bd9Sstevel@tonic-gate 	 */
9747c478bd9Sstevel@tonic-gate 	if (dir->tn_dir == NULL)
9757c478bd9Sstevel@tonic-gate 		return (ENOENT);
9767c478bd9Sstevel@tonic-gate 
9777c478bd9Sstevel@tonic-gate 	/*
9787c478bd9Sstevel@tonic-gate 	 * Check that everything is on the same filesystem.
9797c478bd9Sstevel@tonic-gate 	 */
9807c478bd9Sstevel@tonic-gate 	if (tp->tn_vnode->v_vfsp != dir->tn_vnode->v_vfsp)
9817c478bd9Sstevel@tonic-gate 		return (EXDEV);
9827c478bd9Sstevel@tonic-gate 
9837c478bd9Sstevel@tonic-gate 	/*
9847c478bd9Sstevel@tonic-gate 	 * Allocate and initialize directory entry
9857c478bd9Sstevel@tonic-gate 	 */
9867c478bd9Sstevel@tonic-gate 	namelen = strlen(name) + 1;
9877c478bd9Sstevel@tonic-gate 	alloc_size = namelen + sizeof (struct tdirent);
9887c478bd9Sstevel@tonic-gate 	tdp = tmp_memalloc(alloc_size, 0);
9897c478bd9Sstevel@tonic-gate 	if (tdp == NULL)
9907c478bd9Sstevel@tonic-gate 		return (ENOSPC);
9917c478bd9Sstevel@tonic-gate 
9927c478bd9Sstevel@tonic-gate 	if ((op == DE_RENAME) && (tp->tn_type == VDIR))
9937c478bd9Sstevel@tonic-gate 		tdirfixdotdot(tp, fromtp, dir);
9947c478bd9Sstevel@tonic-gate 
9957c478bd9Sstevel@tonic-gate 	dir->tn_size += alloc_size;
9967c478bd9Sstevel@tonic-gate 	dir->tn_dirents++;
9977c478bd9Sstevel@tonic-gate 	tdp->td_tmpnode = tp;
9987c478bd9Sstevel@tonic-gate 	tdp->td_parent = dir;
9997c478bd9Sstevel@tonic-gate 
10007c478bd9Sstevel@tonic-gate 	/*
10017c478bd9Sstevel@tonic-gate 	 * The directory entry and its name were allocated sequentially.
10027c478bd9Sstevel@tonic-gate 	 */
10037c478bd9Sstevel@tonic-gate 	tdp->td_name = (char *)tdp + sizeof (struct tdirent);
10047c478bd9Sstevel@tonic-gate 	(void) strcpy(tdp->td_name, name);
10057c478bd9Sstevel@tonic-gate 
10067c478bd9Sstevel@tonic-gate 	tmpfs_hash_in(tdp);
10077c478bd9Sstevel@tonic-gate 
10087c478bd9Sstevel@tonic-gate 	/*
10097c478bd9Sstevel@tonic-gate 	 * Some utilities expect the size of a directory to remain
10107c478bd9Sstevel@tonic-gate 	 * somewhat static.  For example, a routine which unlinks
10117c478bd9Sstevel@tonic-gate 	 * files between calls to readdir(); the size of the
10127c478bd9Sstevel@tonic-gate 	 * directory changes from underneath it and so the real
10137c478bd9Sstevel@tonic-gate 	 * directory offset in bytes is invalid.  To circumvent
10147c478bd9Sstevel@tonic-gate 	 * this problem, we initialize a directory entry with an
10157c478bd9Sstevel@tonic-gate 	 * phony offset, and use this offset to determine end of
10167c478bd9Sstevel@tonic-gate 	 * file in tmp_readdir.
10177c478bd9Sstevel@tonic-gate 	 */
10187c478bd9Sstevel@tonic-gate 	tpdp = dir->tn_dir->td_prev;
10197c478bd9Sstevel@tonic-gate 	/*
10207c478bd9Sstevel@tonic-gate 	 * Install at first empty "slot" in directory list.
10217c478bd9Sstevel@tonic-gate 	 */
10227c478bd9Sstevel@tonic-gate 	while (tpdp->td_next != NULL && (tpdp->td_next->td_offset -
10237c478bd9Sstevel@tonic-gate 	    tpdp->td_offset) <= 1) {
10247c478bd9Sstevel@tonic-gate 		ASSERT(tpdp->td_next != tpdp);
10257c478bd9Sstevel@tonic-gate 		ASSERT(tpdp->td_prev != tpdp);
10267c478bd9Sstevel@tonic-gate 		ASSERT(tpdp->td_next->td_offset > tpdp->td_offset);
10277c478bd9Sstevel@tonic-gate 		tpdp = tpdp->td_next;
10287c478bd9Sstevel@tonic-gate 	}
10297c478bd9Sstevel@tonic-gate 	tdp->td_offset = tpdp->td_offset + 1;
10307c478bd9Sstevel@tonic-gate 
10317c478bd9Sstevel@tonic-gate 	/*
10327c478bd9Sstevel@tonic-gate 	 * If we're at the end of the dirent list and the offset (which
10337c478bd9Sstevel@tonic-gate 	 * is necessarily the largest offset in this directory) is more
10347c478bd9Sstevel@tonic-gate 	 * than twice the number of dirents, that means the directory is
10357c478bd9Sstevel@tonic-gate 	 * 50% holes.  At this point we reset the slot pointer back to
10367c478bd9Sstevel@tonic-gate 	 * the beginning of the directory so we start using the holes.
10377c478bd9Sstevel@tonic-gate 	 * The idea is that if there are N dirents, there must also be
10387c478bd9Sstevel@tonic-gate 	 * N holes, so we can satisfy the next N creates by walking at
10397c478bd9Sstevel@tonic-gate 	 * most 2N entries; thus the average cost of a create is constant.
10407c478bd9Sstevel@tonic-gate 	 * Note that we use the first dirent's td_prev as the roving
10417c478bd9Sstevel@tonic-gate 	 * slot pointer; it's ugly, but it saves a word in every dirent.
10427c478bd9Sstevel@tonic-gate 	 */
10437c478bd9Sstevel@tonic-gate 	if (tpdp->td_next == NULL && tpdp->td_offset > 2 * dir->tn_dirents)
10447c478bd9Sstevel@tonic-gate 		dir->tn_dir->td_prev = dir->tn_dir->td_next;
10457c478bd9Sstevel@tonic-gate 	else
10467c478bd9Sstevel@tonic-gate 		dir->tn_dir->td_prev = tdp;
10477c478bd9Sstevel@tonic-gate 
10487c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
10497c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
10507c478bd9Sstevel@tonic-gate 
10517c478bd9Sstevel@tonic-gate 	tdp->td_next = tpdp->td_next;
10527c478bd9Sstevel@tonic-gate 	if (tdp->td_next) {
10537c478bd9Sstevel@tonic-gate 		tdp->td_next->td_prev = tdp;
10547c478bd9Sstevel@tonic-gate 	}
10557c478bd9Sstevel@tonic-gate 	tdp->td_prev = tpdp;
10567c478bd9Sstevel@tonic-gate 	tpdp->td_next = tdp;
10577c478bd9Sstevel@tonic-gate 
10587c478bd9Sstevel@tonic-gate 	ASSERT(tdp->td_next != tdp);
10597c478bd9Sstevel@tonic-gate 	ASSERT(tdp->td_prev != tdp);
10607c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
10617c478bd9Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
10627c478bd9Sstevel@tonic-gate 
10637c478bd9Sstevel@tonic-gate 	gethrestime(&now);
10647c478bd9Sstevel@tonic-gate 	dir->tn_mtime = now;
10657c478bd9Sstevel@tonic-gate 	dir->tn_ctime = now;
10667c478bd9Sstevel@tonic-gate 
10677c478bd9Sstevel@tonic-gate 	return (0);
10687c478bd9Sstevel@tonic-gate }
10697c478bd9Sstevel@tonic-gate 
10707c478bd9Sstevel@tonic-gate static int
tdirmaketnode(struct tmpnode * dir,struct tmount * tm,struct vattr * va,enum de_op op,struct tmpnode ** newnode,struct cred * cred)10717c478bd9Sstevel@tonic-gate tdirmaketnode(
10727c478bd9Sstevel@tonic-gate 	struct tmpnode *dir,
10737c478bd9Sstevel@tonic-gate 	struct tmount	*tm,
10747c478bd9Sstevel@tonic-gate 	struct vattr	*va,
10757c478bd9Sstevel@tonic-gate 	enum	de_op	op,
10767c478bd9Sstevel@tonic-gate 	struct tmpnode **newnode,
10777c478bd9Sstevel@tonic-gate 	struct cred	*cred)
10787c478bd9Sstevel@tonic-gate {
10797c478bd9Sstevel@tonic-gate 	struct tmpnode *tp;
10807c478bd9Sstevel@tonic-gate 	enum vtype	type;
10817c478bd9Sstevel@tonic-gate 
10827c478bd9Sstevel@tonic-gate 	ASSERT(va != NULL);
10837c478bd9Sstevel@tonic-gate 	ASSERT(op == DE_CREATE || op == DE_MKDIR);
10847c478bd9Sstevel@tonic-gate 	if (((va->va_mask & AT_ATIME) && TIMESPEC_OVERFLOW(&va->va_atime)) ||
10857c478bd9Sstevel@tonic-gate 	    ((va->va_mask & AT_MTIME) && TIMESPEC_OVERFLOW(&va->va_mtime)))
10867c478bd9Sstevel@tonic-gate 		return (EOVERFLOW);
10877c478bd9Sstevel@tonic-gate 	type = va->va_type;
10887c478bd9Sstevel@tonic-gate 	tp = tmp_memalloc(sizeof (struct tmpnode), TMP_MUSTHAVE);
10897c478bd9Sstevel@tonic-gate 	tmpnode_init(tm, tp, va, cred);
10907c478bd9Sstevel@tonic-gate 
10917c478bd9Sstevel@tonic-gate 	/* setup normal file/dir's extended attribute directory */
10927c478bd9Sstevel@tonic-gate 	if (dir->tn_flags & ISXATTR) {
10937c478bd9Sstevel@tonic-gate 		/* parent dir is , mark file as xattr */
10947c478bd9Sstevel@tonic-gate 		tp->tn_flags |= ISXATTR;
10957c478bd9Sstevel@tonic-gate 	}
10967c478bd9Sstevel@tonic-gate 
10977c478bd9Sstevel@tonic-gate 
10987c478bd9Sstevel@tonic-gate 	if (type == VBLK || type == VCHR) {
10997c478bd9Sstevel@tonic-gate 		tp->tn_vnode->v_rdev = tp->tn_rdev = va->va_rdev;
11007c478bd9Sstevel@tonic-gate 	} else {
11017c478bd9Sstevel@tonic-gate 		tp->tn_vnode->v_rdev = tp->tn_rdev = NODEV;
11027c478bd9Sstevel@tonic-gate 	}
11037c478bd9Sstevel@tonic-gate 	tp->tn_vnode->v_type = type;
11047c478bd9Sstevel@tonic-gate 	tp->tn_uid = crgetuid(cred);
11057c478bd9Sstevel@tonic-gate 
11067c478bd9Sstevel@tonic-gate 	/*
11077c478bd9Sstevel@tonic-gate 	 * To determine the group-id of the created file:
11087c478bd9Sstevel@tonic-gate 	 *   1) If the gid is set in the attribute list (non-Sun & pre-4.0
11097c478bd9Sstevel@tonic-gate 	 *	clients are not likely to set the gid), then use it if
11107c478bd9Sstevel@tonic-gate 	 *	the process is privileged, belongs to the target group,
11117c478bd9Sstevel@tonic-gate 	 *	or the group is the same as the parent directory.
11127c478bd9Sstevel@tonic-gate 	 *   2) If the filesystem was not mounted with the Old-BSD-compatible
11137c478bd9Sstevel@tonic-gate 	 *	GRPID option, and the directory's set-gid bit is clear,
11147c478bd9Sstevel@tonic-gate 	 *	then use the process's gid.
11157c478bd9Sstevel@tonic-gate 	 *   3) Otherwise, set the group-id to the gid of the parent directory.
11167c478bd9Sstevel@tonic-gate 	 */
11177c478bd9Sstevel@tonic-gate 	if ((va->va_mask & AT_GID) &&
11187c478bd9Sstevel@tonic-gate 	    ((va->va_gid == dir->tn_gid) || groupmember(va->va_gid, cred) ||
11197c478bd9Sstevel@tonic-gate 	    secpolicy_vnode_create_gid(cred) == 0)) {
11207c478bd9Sstevel@tonic-gate 		/*
11217c478bd9Sstevel@tonic-gate 		 * XXX - is this only the case when a 4.0 NFS client, or a
11227c478bd9Sstevel@tonic-gate 		 * client derived from that code, makes a call over the wire?
11237c478bd9Sstevel@tonic-gate 		 */
11247c478bd9Sstevel@tonic-gate 		tp->tn_gid = va->va_gid;
11257c478bd9Sstevel@tonic-gate 	} else {
11267c478bd9Sstevel@tonic-gate 		if (dir->tn_mode & VSGID)
11277c478bd9Sstevel@tonic-gate 			tp->tn_gid = dir->tn_gid;
11287c478bd9Sstevel@tonic-gate 		else
11297c478bd9Sstevel@tonic-gate 			tp->tn_gid = crgetgid(cred);
11307c478bd9Sstevel@tonic-gate 	}
11317c478bd9Sstevel@tonic-gate 	/*
11327c478bd9Sstevel@tonic-gate 	 * If we're creating a directory, and the parent directory has the
11337c478bd9Sstevel@tonic-gate 	 * set-GID bit set, set it on the new directory.
11347c478bd9Sstevel@tonic-gate 	 * Otherwise, if the user is neither privileged nor a member of the
11357c478bd9Sstevel@tonic-gate 	 * file's new group, clear the file's set-GID bit.
11367c478bd9Sstevel@tonic-gate 	 */
11377c478bd9Sstevel@tonic-gate 	if (dir->tn_mode & VSGID && type == VDIR)
11387c478bd9Sstevel@tonic-gate 		tp->tn_mode |= VSGID;
11397c478bd9Sstevel@tonic-gate 	else {
11407c478bd9Sstevel@tonic-gate 		if ((tp->tn_mode & VSGID) &&
11417c478bd9Sstevel@tonic-gate 		    secpolicy_vnode_setids_setgids(cred, tp->tn_gid) != 0)
11427c478bd9Sstevel@tonic-gate 			tp->tn_mode &= ~VSGID;
11437c478bd9Sstevel@tonic-gate 	}
11447c478bd9Sstevel@tonic-gate 
11457c478bd9Sstevel@tonic-gate 	if (va->va_mask & AT_ATIME)
11467c478bd9Sstevel@tonic-gate 		tp->tn_atime = va->va_atime;
11477c478bd9Sstevel@tonic-gate 	if (va->va_mask & AT_MTIME)
11487c478bd9Sstevel@tonic-gate 		tp->tn_mtime = va->va_mtime;
11497c478bd9Sstevel@tonic-gate 
11507c478bd9Sstevel@tonic-gate 	if (op == DE_MKDIR)
11517c478bd9Sstevel@tonic-gate 		tdirinit(dir, tp);
11527c478bd9Sstevel@tonic-gate 
11537c478bd9Sstevel@tonic-gate 	*newnode = tp;
11547c478bd9Sstevel@tonic-gate 	return (0);
11557c478bd9Sstevel@tonic-gate }
1156