10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
50Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
60Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
70Sstevel@tonic-gate  * with the License.
80Sstevel@tonic-gate  *
90Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
100Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
110Sstevel@tonic-gate  * See the License for the specific language governing permissions
120Sstevel@tonic-gate  * and limitations under the License.
130Sstevel@tonic-gate  *
140Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
150Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
160Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
170Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
180Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
190Sstevel@tonic-gate  *
200Sstevel@tonic-gate  * CDDL HEADER END
210Sstevel@tonic-gate  */
220Sstevel@tonic-gate /*
23*569Sbatschul  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
240Sstevel@tonic-gate  * Use is subject to license terms.
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate 
270Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
280Sstevel@tonic-gate 
290Sstevel@tonic-gate #include <sys/types.h>
300Sstevel@tonic-gate #include <sys/param.h>
310Sstevel@tonic-gate #include <sys/sysmacros.h>
320Sstevel@tonic-gate #include <sys/systm.h>
330Sstevel@tonic-gate #include <sys/time.h>
340Sstevel@tonic-gate #include <sys/vfs.h>
350Sstevel@tonic-gate #include <sys/vnode.h>
360Sstevel@tonic-gate #include <sys/errno.h>
370Sstevel@tonic-gate #include <sys/cmn_err.h>
380Sstevel@tonic-gate #include <sys/cred.h>
390Sstevel@tonic-gate #include <sys/stat.h>
400Sstevel@tonic-gate #include <sys/debug.h>
410Sstevel@tonic-gate #include <sys/policy.h>
420Sstevel@tonic-gate #include <sys/fs/tmpnode.h>
430Sstevel@tonic-gate #include <sys/fs/tmp.h>
440Sstevel@tonic-gate #include <sys/vtrace.h>
450Sstevel@tonic-gate 
460Sstevel@tonic-gate static int tdircheckpath(struct tmpnode *, struct tmpnode *, struct cred *);
470Sstevel@tonic-gate static int tdirrename(struct tmpnode *, struct tmpnode *, struct tmpnode *,
480Sstevel@tonic-gate 	char *, struct tmpnode *, struct tdirent *, struct cred *);
490Sstevel@tonic-gate static void tdirfixdotdot(struct tmpnode *, struct tmpnode *, struct tmpnode *);
500Sstevel@tonic-gate static int tdirmaketnode(struct tmpnode *, struct tmount *, struct vattr *,
510Sstevel@tonic-gate 	enum de_op, struct tmpnode **, struct cred *);
520Sstevel@tonic-gate static int tdiraddentry(struct tmpnode *, struct tmpnode *, char *,
530Sstevel@tonic-gate 	enum de_op, struct tmpnode *);
540Sstevel@tonic-gate 
550Sstevel@tonic-gate 
560Sstevel@tonic-gate #define	T_HASH_SIZE	8192		/* must be power of 2 */
570Sstevel@tonic-gate #define	T_MUTEX_SIZE	64
580Sstevel@tonic-gate 
590Sstevel@tonic-gate static struct tdirent	*t_hashtable[T_HASH_SIZE];
600Sstevel@tonic-gate static kmutex_t		 t_hashmutex[T_MUTEX_SIZE];
610Sstevel@tonic-gate 
620Sstevel@tonic-gate #define	T_HASH_INDEX(a)		((a) & (T_HASH_SIZE-1))
630Sstevel@tonic-gate #define	T_MUTEX_INDEX(a)	((a) & (T_MUTEX_SIZE-1))
640Sstevel@tonic-gate 
650Sstevel@tonic-gate #define	TMPFS_HASH(tp, name, hash)				\
660Sstevel@tonic-gate 	{							\
670Sstevel@tonic-gate 		char Xc, *Xcp;					\
680Sstevel@tonic-gate 		hash = (uint_t)(uintptr_t)(tp) >> 8;		\
690Sstevel@tonic-gate 		for (Xcp = (name); (Xc = *Xcp) != 0; Xcp++)	\
700Sstevel@tonic-gate 			hash = (hash << 4) + hash + (uint_t)Xc;	\
710Sstevel@tonic-gate 	}
720Sstevel@tonic-gate 
730Sstevel@tonic-gate void
740Sstevel@tonic-gate tmpfs_hash_init(void)
750Sstevel@tonic-gate {
760Sstevel@tonic-gate 	int	ix;
770Sstevel@tonic-gate 
780Sstevel@tonic-gate 	for (ix = 0; ix < T_MUTEX_SIZE; ix++)
790Sstevel@tonic-gate 		mutex_init(&t_hashmutex[ix], NULL, MUTEX_DEFAULT, NULL);
800Sstevel@tonic-gate }
810Sstevel@tonic-gate 
820Sstevel@tonic-gate /*
830Sstevel@tonic-gate  * This routine is where the rubber meets the road for identities.
840Sstevel@tonic-gate  */
850Sstevel@tonic-gate static void
860Sstevel@tonic-gate tmpfs_hash_in(struct tdirent *t)
870Sstevel@tonic-gate {
880Sstevel@tonic-gate 	uint_t		hash;
890Sstevel@tonic-gate 	struct tdirent	**prevpp;
900Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
910Sstevel@tonic-gate 
920Sstevel@tonic-gate 	TMPFS_HASH(t->td_parent, t->td_name, hash);
930Sstevel@tonic-gate 	t->td_hash = hash;
940Sstevel@tonic-gate 	prevpp = &t_hashtable[T_HASH_INDEX(hash)];
950Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
960Sstevel@tonic-gate 	mutex_enter(t_hmtx);
970Sstevel@tonic-gate 	t->td_link = *prevpp;
980Sstevel@tonic-gate 	*prevpp = t;
990Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1000Sstevel@tonic-gate }
1010Sstevel@tonic-gate 
1020Sstevel@tonic-gate /*
1030Sstevel@tonic-gate  * Remove tdirent *t from the hash list.
1040Sstevel@tonic-gate  */
1050Sstevel@tonic-gate static void
1060Sstevel@tonic-gate tmpfs_hash_out(struct tdirent *t)
1070Sstevel@tonic-gate {
1080Sstevel@tonic-gate 	uint_t		hash;
1090Sstevel@tonic-gate 	struct tdirent	**prevpp;
1100Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
1110Sstevel@tonic-gate 
1120Sstevel@tonic-gate 	hash = t->td_hash;
1130Sstevel@tonic-gate 	prevpp = &t_hashtable[T_HASH_INDEX(hash)];
1140Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
1150Sstevel@tonic-gate 	mutex_enter(t_hmtx);
1160Sstevel@tonic-gate 	while (*prevpp != t)
1170Sstevel@tonic-gate 		prevpp = &(*prevpp)->td_link;
1180Sstevel@tonic-gate 	*prevpp = t->td_link;
1190Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1200Sstevel@tonic-gate }
1210Sstevel@tonic-gate 
1220Sstevel@tonic-gate /*
1230Sstevel@tonic-gate  * Currently called by tdirrename() only.
1240Sstevel@tonic-gate  * rename operation needs to be done with lock held, to ensure that
1250Sstevel@tonic-gate  * no other operations can access the tmpnode at the same instance.
1260Sstevel@tonic-gate  */
1270Sstevel@tonic-gate static void
1280Sstevel@tonic-gate tmpfs_hash_change(struct tdirent *tdp, struct tmpnode *fromtp)
1290Sstevel@tonic-gate {
1300Sstevel@tonic-gate 	uint_t		hash;
1310Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
1320Sstevel@tonic-gate 
1330Sstevel@tonic-gate 	hash = tdp->td_hash;
1340Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
1350Sstevel@tonic-gate 	mutex_enter(t_hmtx);
1360Sstevel@tonic-gate 	tdp->td_tmpnode = fromtp;
1370Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1380Sstevel@tonic-gate }
1390Sstevel@tonic-gate 
1400Sstevel@tonic-gate static struct tdirent *
1410Sstevel@tonic-gate tmpfs_hash_lookup(char *name, struct tmpnode *parent, uint_t hold,
1420Sstevel@tonic-gate 	struct tmpnode **found)
1430Sstevel@tonic-gate {
1440Sstevel@tonic-gate 	struct tdirent	*l;
1450Sstevel@tonic-gate 	uint_t		hash;
1460Sstevel@tonic-gate 	kmutex_t	*t_hmtx;
1470Sstevel@tonic-gate 	struct tmpnode	*tnp;
1480Sstevel@tonic-gate 
1490Sstevel@tonic-gate 	TMPFS_HASH(parent, name, hash);
1500Sstevel@tonic-gate 	t_hmtx = &t_hashmutex[T_MUTEX_INDEX(hash)];
1510Sstevel@tonic-gate 	mutex_enter(t_hmtx);
1520Sstevel@tonic-gate 	l = t_hashtable[T_HASH_INDEX(hash)];
1530Sstevel@tonic-gate 	while (l) {
1540Sstevel@tonic-gate 		if ((l->td_hash == hash) &&
1550Sstevel@tonic-gate 		    (l->td_parent == parent) &&
1560Sstevel@tonic-gate 		    (strcmp(l->td_name, name) == 0)) {
1570Sstevel@tonic-gate 			/*
1580Sstevel@tonic-gate 			 * We need to make sure that the tmpnode that
1590Sstevel@tonic-gate 			 * we put a hold on is the same one that we pass back.
1600Sstevel@tonic-gate 			 * Hence, temporary variable tnp is necessary.
1610Sstevel@tonic-gate 			 */
1620Sstevel@tonic-gate 			tnp = l->td_tmpnode;
1630Sstevel@tonic-gate 			if (hold) {
1640Sstevel@tonic-gate 				ASSERT(tnp);
1650Sstevel@tonic-gate 				tmpnode_hold(tnp);
1660Sstevel@tonic-gate 			}
1670Sstevel@tonic-gate 			if (found)
1680Sstevel@tonic-gate 				*found = tnp;
1690Sstevel@tonic-gate 			mutex_exit(t_hmtx);
1700Sstevel@tonic-gate 			return (l);
1710Sstevel@tonic-gate 		} else {
1720Sstevel@tonic-gate 			l = l->td_link;
1730Sstevel@tonic-gate 		}
1740Sstevel@tonic-gate 	}
1750Sstevel@tonic-gate 	mutex_exit(t_hmtx);
1760Sstevel@tonic-gate 	return (NULL);
1770Sstevel@tonic-gate }
1780Sstevel@tonic-gate 
1790Sstevel@tonic-gate /*
1800Sstevel@tonic-gate  * Search directory 'parent' for entry 'name'.
1810Sstevel@tonic-gate  *
1820Sstevel@tonic-gate  * The calling thread can't hold the write version
1830Sstevel@tonic-gate  * of the rwlock for the directory being searched
1840Sstevel@tonic-gate  *
1850Sstevel@tonic-gate  * 0 is returned on success and *foundtp points
1860Sstevel@tonic-gate  * to the found tmpnode with its vnode held.
1870Sstevel@tonic-gate  */
1880Sstevel@tonic-gate int
1890Sstevel@tonic-gate tdirlookup(
1900Sstevel@tonic-gate 	struct tmpnode *parent,
1910Sstevel@tonic-gate 	char *name,
1920Sstevel@tonic-gate 	struct tmpnode **foundtp,
1930Sstevel@tonic-gate 	struct cred *cred)
1940Sstevel@tonic-gate {
1950Sstevel@tonic-gate 	int error;
1960Sstevel@tonic-gate 
1970Sstevel@tonic-gate 	*foundtp = NULL;
1980Sstevel@tonic-gate 	if (parent->tn_type != VDIR)
1990Sstevel@tonic-gate 		return (ENOTDIR);
2000Sstevel@tonic-gate 
2010Sstevel@tonic-gate 	if ((error = tmp_taccess(parent, VEXEC, cred)))
2020Sstevel@tonic-gate 		return (error);
2030Sstevel@tonic-gate 
2040Sstevel@tonic-gate 	if (*name == '\0') {
2050Sstevel@tonic-gate 		tmpnode_hold(parent);
2060Sstevel@tonic-gate 		*foundtp = parent;
2070Sstevel@tonic-gate 		return (0);
2080Sstevel@tonic-gate 	}
2090Sstevel@tonic-gate 
2100Sstevel@tonic-gate 	/*
2110Sstevel@tonic-gate 	 * Search the directory for the matching name
2120Sstevel@tonic-gate 	 * We need the lock protecting the tn_dir list
2130Sstevel@tonic-gate 	 * so that it doesn't change out from underneath us.
2140Sstevel@tonic-gate 	 * tmpfs_hash_lookup() will pass back the tmpnode
2150Sstevel@tonic-gate 	 * with a hold on it.
2160Sstevel@tonic-gate 	 */
2170Sstevel@tonic-gate 
2180Sstevel@tonic-gate 	if (tmpfs_hash_lookup(name, parent, 1, foundtp) != NULL) {
2190Sstevel@tonic-gate 		ASSERT(*foundtp);
2200Sstevel@tonic-gate 		return (0);
2210Sstevel@tonic-gate 	}
2220Sstevel@tonic-gate 
2230Sstevel@tonic-gate 	return (ENOENT);
2240Sstevel@tonic-gate }
2250Sstevel@tonic-gate 
2260Sstevel@tonic-gate /*
2270Sstevel@tonic-gate  * Enter a directory entry for 'name' and 'tp' into directory 'dir'
2280Sstevel@tonic-gate  *
2290Sstevel@tonic-gate  * Returns 0 on success.
2300Sstevel@tonic-gate  */
2310Sstevel@tonic-gate int
2320Sstevel@tonic-gate tdirenter(
2330Sstevel@tonic-gate 	struct tmount	*tm,
2340Sstevel@tonic-gate 	struct tmpnode	*dir,		/* target directory to make entry in */
2350Sstevel@tonic-gate 	char		*name,		/* name of entry */
2360Sstevel@tonic-gate 	enum de_op	op,		/* entry operation */
2370Sstevel@tonic-gate 	struct tmpnode	*fromparent,	/* source directory if rename */
2380Sstevel@tonic-gate 	struct tmpnode	*tp,		/* source tmpnode, if link/rename */
2390Sstevel@tonic-gate 	struct vattr	*va,
2400Sstevel@tonic-gate 	struct tmpnode	**tpp,		/* return tmpnode, if create/mkdir */
2410Sstevel@tonic-gate 	struct cred	*cred)
2420Sstevel@tonic-gate {
2430Sstevel@tonic-gate 	struct tdirent *tdp;
2440Sstevel@tonic-gate 	struct tmpnode *found = NULL;
2450Sstevel@tonic-gate 	int error = 0;
2460Sstevel@tonic-gate 	char *s;
2470Sstevel@tonic-gate 
2480Sstevel@tonic-gate 	/*
2490Sstevel@tonic-gate 	 * tn_rwlock is held to serialize direnter and dirdeletes
2500Sstevel@tonic-gate 	 */
2510Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
2520Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
2530Sstevel@tonic-gate 
2540Sstevel@tonic-gate 	/*
2550Sstevel@tonic-gate 	 * Don't allow '/' characters in pathname component
2560Sstevel@tonic-gate 	 * (thus in ufs_direnter()).
2570Sstevel@tonic-gate 	 */
2580Sstevel@tonic-gate 	for (s = name; *s; s++)
2590Sstevel@tonic-gate 		if (*s == '/')
2600Sstevel@tonic-gate 			return (EACCES);
2610Sstevel@tonic-gate 
2620Sstevel@tonic-gate 	if (name[0] == '\0')
2630Sstevel@tonic-gate 		panic("tdirenter: NULL name");
2640Sstevel@tonic-gate 
2650Sstevel@tonic-gate 	/*
2660Sstevel@tonic-gate 	 * For link and rename lock the source entry and check the link count
2670Sstevel@tonic-gate 	 * to see if it has been removed while it was unlocked.
2680Sstevel@tonic-gate 	 */
2690Sstevel@tonic-gate 	if (op == DE_LINK || op == DE_RENAME) {
2700Sstevel@tonic-gate 		if (tp != dir)
2710Sstevel@tonic-gate 			rw_enter(&tp->tn_rwlock, RW_WRITER);
2720Sstevel@tonic-gate 		mutex_enter(&tp->tn_tlock);
2730Sstevel@tonic-gate 		if (tp->tn_nlink == 0) {
2740Sstevel@tonic-gate 			mutex_exit(&tp->tn_tlock);
2750Sstevel@tonic-gate 			if (tp != dir)
2760Sstevel@tonic-gate 				rw_exit(&tp->tn_rwlock);
2770Sstevel@tonic-gate 			return (ENOENT);
2780Sstevel@tonic-gate 		}
2790Sstevel@tonic-gate 
2800Sstevel@tonic-gate 		if (tp->tn_nlink == MAXLINK) {
2810Sstevel@tonic-gate 			mutex_exit(&tp->tn_tlock);
2820Sstevel@tonic-gate 			if (tp != dir)
2830Sstevel@tonic-gate 				rw_exit(&tp->tn_rwlock);
2840Sstevel@tonic-gate 			return (EMLINK);
2850Sstevel@tonic-gate 		}
2860Sstevel@tonic-gate 		tp->tn_nlink++;
2870Sstevel@tonic-gate 		gethrestime(&tp->tn_ctime);
2880Sstevel@tonic-gate 		mutex_exit(&tp->tn_tlock);
2890Sstevel@tonic-gate 		if (tp != dir)
2900Sstevel@tonic-gate 			rw_exit(&tp->tn_rwlock);
2910Sstevel@tonic-gate 	}
2920Sstevel@tonic-gate 
2930Sstevel@tonic-gate 	/*
2940Sstevel@tonic-gate 	 * This might be a "dangling detached directory".
2950Sstevel@tonic-gate 	 * it could have been removed, but a reference
2960Sstevel@tonic-gate 	 * to it kept in u_cwd.  don't bother searching
2970Sstevel@tonic-gate 	 * it, and with any luck the user will get tired
2980Sstevel@tonic-gate 	 * of dealing with us and cd to some absolute
2990Sstevel@tonic-gate 	 * pathway.  *sigh*, thus in ufs, too.
3000Sstevel@tonic-gate 	 */
3010Sstevel@tonic-gate 	if (dir->tn_nlink == 0) {
3020Sstevel@tonic-gate 		error = ENOENT;
3030Sstevel@tonic-gate 		goto out;
3040Sstevel@tonic-gate 	}
3050Sstevel@tonic-gate 
3060Sstevel@tonic-gate 	/*
3070Sstevel@tonic-gate 	 * If this is a rename of a directory and the parent is
3080Sstevel@tonic-gate 	 * different (".." must be changed), then the source
3090Sstevel@tonic-gate 	 * directory must not be in the directory hierarchy
3100Sstevel@tonic-gate 	 * above the target, as this would orphan everything
3110Sstevel@tonic-gate 	 * below the source directory.
3120Sstevel@tonic-gate 	 */
3130Sstevel@tonic-gate 	if (op == DE_RENAME) {
3140Sstevel@tonic-gate 		if (tp == dir) {
3150Sstevel@tonic-gate 			error = EINVAL;
3160Sstevel@tonic-gate 			goto out;
3170Sstevel@tonic-gate 		}
3180Sstevel@tonic-gate 		if (tp->tn_type == VDIR) {
3190Sstevel@tonic-gate 			if ((fromparent != dir) &&
3200Sstevel@tonic-gate 			    (error = tdircheckpath(tp, dir, cred))) {
3210Sstevel@tonic-gate 				goto out;
3220Sstevel@tonic-gate 			}
3230Sstevel@tonic-gate 		}
3240Sstevel@tonic-gate 	}
3250Sstevel@tonic-gate 
3260Sstevel@tonic-gate 	/*
3270Sstevel@tonic-gate 	 * Search for the entry.  Return "found" if it exists.
3280Sstevel@tonic-gate 	 */
3290Sstevel@tonic-gate 	tdp = tmpfs_hash_lookup(name, dir, 1, &found);
3300Sstevel@tonic-gate 
3310Sstevel@tonic-gate 	if (tdp) {
3320Sstevel@tonic-gate 		ASSERT(found);
3330Sstevel@tonic-gate 		switch (op) {
3340Sstevel@tonic-gate 		case DE_CREATE:
3350Sstevel@tonic-gate 		case DE_MKDIR:
3360Sstevel@tonic-gate 			if (tpp) {
3370Sstevel@tonic-gate 				*tpp = found;
3380Sstevel@tonic-gate 				error = EEXIST;
3390Sstevel@tonic-gate 			} else {
3400Sstevel@tonic-gate 				tmpnode_rele(found);
3410Sstevel@tonic-gate 			}
3420Sstevel@tonic-gate 			break;
3430Sstevel@tonic-gate 
3440Sstevel@tonic-gate 		case DE_RENAME:
3450Sstevel@tonic-gate 			error = tdirrename(fromparent, tp,
3460Sstevel@tonic-gate 			    dir, name, found, tdp, cred);
3470Sstevel@tonic-gate 			if (error == 0) {
3480Sstevel@tonic-gate 				vnevent_rename_dest(TNTOV(found));
3490Sstevel@tonic-gate 			}
3500Sstevel@tonic-gate 			tmpnode_rele(found);
3510Sstevel@tonic-gate 			break;
3520Sstevel@tonic-gate 
3530Sstevel@tonic-gate 		case DE_LINK:
3540Sstevel@tonic-gate 			/*
3550Sstevel@tonic-gate 			 * Can't link to an existing file.
3560Sstevel@tonic-gate 			 */
3570Sstevel@tonic-gate 			error = EEXIST;
3580Sstevel@tonic-gate 			tmpnode_rele(found);
3590Sstevel@tonic-gate 			break;
3600Sstevel@tonic-gate 		}
3610Sstevel@tonic-gate 	} else {
3620Sstevel@tonic-gate 
3630Sstevel@tonic-gate 		/*
3640Sstevel@tonic-gate 		 * The entry does not exist. Check write permission in
3650Sstevel@tonic-gate 		 * directory to see if entry can be created.
3660Sstevel@tonic-gate 		 */
3670Sstevel@tonic-gate 		if (error = tmp_taccess(dir, VWRITE, cred))
3680Sstevel@tonic-gate 			goto out;
3690Sstevel@tonic-gate 		if (op == DE_CREATE || op == DE_MKDIR) {
3700Sstevel@tonic-gate 			/*
3710Sstevel@tonic-gate 			 * Make new tmpnode and directory entry as required.
3720Sstevel@tonic-gate 			 */
3730Sstevel@tonic-gate 			error = tdirmaketnode(dir, tm, va, op, &tp, cred);
3740Sstevel@tonic-gate 			if (error)
3750Sstevel@tonic-gate 				goto out;
3760Sstevel@tonic-gate 		}
3770Sstevel@tonic-gate 		if (error = tdiraddentry(dir, tp, name, op, fromparent)) {
3780Sstevel@tonic-gate 			if (op == DE_CREATE || op == DE_MKDIR) {
3790Sstevel@tonic-gate 				/*
3800Sstevel@tonic-gate 				 * Unmake the inode we just made.
3810Sstevel@tonic-gate 				 */
3820Sstevel@tonic-gate 				rw_enter(&tp->tn_rwlock, RW_WRITER);
3830Sstevel@tonic-gate 				if ((tp->tn_type) == VDIR) {
3840Sstevel@tonic-gate 					ASSERT(tdp == NULL);
3850Sstevel@tonic-gate 					/*
3860Sstevel@tonic-gate 					 * cleanup allocs made by tdirinit()
3870Sstevel@tonic-gate 					 */
3880Sstevel@tonic-gate 					tdirtrunc(tp);
3890Sstevel@tonic-gate 				}
3900Sstevel@tonic-gate 				mutex_enter(&tp->tn_tlock);
3910Sstevel@tonic-gate 				tp->tn_nlink = 0;
3920Sstevel@tonic-gate 				mutex_exit(&tp->tn_tlock);
3930Sstevel@tonic-gate 				gethrestime(&tp->tn_ctime);
3940Sstevel@tonic-gate 				rw_exit(&tp->tn_rwlock);
3950Sstevel@tonic-gate 				tmpnode_rele(tp);
3960Sstevel@tonic-gate 				tp = NULL;
3970Sstevel@tonic-gate 			}
3980Sstevel@tonic-gate 		} else if (tpp) {
3990Sstevel@tonic-gate 			*tpp = tp;
4000Sstevel@tonic-gate 		} else if (op == DE_CREATE || op == DE_MKDIR) {
4010Sstevel@tonic-gate 			tmpnode_rele(tp);
4020Sstevel@tonic-gate 		}
4030Sstevel@tonic-gate 	}
4040Sstevel@tonic-gate 
4050Sstevel@tonic-gate 	if ((op == DE_RENAME) && (error == 0)) {
4060Sstevel@tonic-gate 		vnevent_rename_src(TNTOV(tp));
4070Sstevel@tonic-gate 	}
4080Sstevel@tonic-gate out:
4090Sstevel@tonic-gate 	if (error && (op == DE_LINK || op == DE_RENAME)) {
4100Sstevel@tonic-gate 		/*
4110Sstevel@tonic-gate 		 * Undo bumped link count.
4120Sstevel@tonic-gate 		 */
4130Sstevel@tonic-gate 		DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
4140Sstevel@tonic-gate 		gethrestime(&tp->tn_ctime);
4150Sstevel@tonic-gate 	}
4160Sstevel@tonic-gate 	return (error);
4170Sstevel@tonic-gate }
4180Sstevel@tonic-gate 
4190Sstevel@tonic-gate /*
4200Sstevel@tonic-gate  * Delete entry tp of name "nm" from dir.
4210Sstevel@tonic-gate  * Free dir entry space and decrement link count on tmpnode(s).
4220Sstevel@tonic-gate  *
4230Sstevel@tonic-gate  * Return 0 on success.
4240Sstevel@tonic-gate  */
4250Sstevel@tonic-gate int
4260Sstevel@tonic-gate tdirdelete(
4270Sstevel@tonic-gate 	struct tmpnode *dir,
4280Sstevel@tonic-gate 	struct tmpnode *tp,
4290Sstevel@tonic-gate 	char *nm,
4300Sstevel@tonic-gate 	enum dr_op op,
4310Sstevel@tonic-gate 	struct cred *cred)
4320Sstevel@tonic-gate {
4330Sstevel@tonic-gate 	struct tdirent *tpdp;
4340Sstevel@tonic-gate 	int error;
4350Sstevel@tonic-gate 	size_t namelen;
4360Sstevel@tonic-gate 	struct tmpnode *tnp;
4370Sstevel@tonic-gate 	timestruc_t now;
4380Sstevel@tonic-gate 
4390Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
4400Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&tp->tn_rwlock));
4410Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
4420Sstevel@tonic-gate 
4430Sstevel@tonic-gate 	if (nm[0] == '\0')
4440Sstevel@tonic-gate 		panic("tdirdelete: NULL name for %p", (void *)tp);
4450Sstevel@tonic-gate 
4460Sstevel@tonic-gate 	/*
4470Sstevel@tonic-gate 	 * return error when removing . and ..
4480Sstevel@tonic-gate 	 */
4490Sstevel@tonic-gate 	if (nm[0] == '.') {
4500Sstevel@tonic-gate 		if (nm[1] == '\0')
4510Sstevel@tonic-gate 			return (EINVAL);
4520Sstevel@tonic-gate 		if (nm[1] == '.' && nm[2] == '\0')
4530Sstevel@tonic-gate 			return (EEXIST); /* thus in ufs */
4540Sstevel@tonic-gate 	}
4550Sstevel@tonic-gate 
4560Sstevel@tonic-gate 	if (error = tmp_taccess(dir, VEXEC|VWRITE, cred))
4570Sstevel@tonic-gate 		return (error);
4580Sstevel@tonic-gate 
4590Sstevel@tonic-gate 	/*
4600Sstevel@tonic-gate 	 * If the parent directory is "sticky", then the user must
4610Sstevel@tonic-gate 	 * own the parent directory or the file in it, or else must
4620Sstevel@tonic-gate 	 * have permission to write the file.  Otherwise it may not
4630Sstevel@tonic-gate 	 * be deleted (except by privileged users).
4640Sstevel@tonic-gate 	 * Same as ufs_dirremove.
4650Sstevel@tonic-gate 	 */
4660Sstevel@tonic-gate 	if ((error = tmp_sticky_remove_access(dir, tp, cred)) != 0)
4670Sstevel@tonic-gate 		return (error);
4680Sstevel@tonic-gate 
4690Sstevel@tonic-gate 	if (dir->tn_dir == NULL)
4700Sstevel@tonic-gate 		return (ENOENT);
4710Sstevel@tonic-gate 
4720Sstevel@tonic-gate 	tpdp = tmpfs_hash_lookup(nm, dir, 0, &tnp);
4730Sstevel@tonic-gate 	if (tpdp == NULL) {
4740Sstevel@tonic-gate 		/*
4750Sstevel@tonic-gate 		 * If it is gone, some other thread got here first!
4760Sstevel@tonic-gate 		 * Return error ENOENT.
4770Sstevel@tonic-gate 		 */
4780Sstevel@tonic-gate 		return (ENOENT);
4790Sstevel@tonic-gate 	}
4800Sstevel@tonic-gate 
4810Sstevel@tonic-gate 	/*
4820Sstevel@tonic-gate 	 * If the tmpnode in the tdirent changed, we were probably
4830Sstevel@tonic-gate 	 * the victim of a concurrent rename operation.  The original
4840Sstevel@tonic-gate 	 * is gone, so return that status (same as UFS).
4850Sstevel@tonic-gate 	 */
4860Sstevel@tonic-gate 	if (tp != tnp)
4870Sstevel@tonic-gate 		return (ENOENT);
4880Sstevel@tonic-gate 
4890Sstevel@tonic-gate 	tmpfs_hash_out(tpdp);
4900Sstevel@tonic-gate 
4910Sstevel@tonic-gate 	/*
4920Sstevel@tonic-gate 	 * Take tpdp out of the directory list.
4930Sstevel@tonic-gate 	 */
4940Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
4950Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
4960Sstevel@tonic-gate 	if (tpdp->td_prev) {
4970Sstevel@tonic-gate 		tpdp->td_prev->td_next = tpdp->td_next;
4980Sstevel@tonic-gate 	}
4990Sstevel@tonic-gate 	if (tpdp->td_next) {
5000Sstevel@tonic-gate 		tpdp->td_next->td_prev = tpdp->td_prev;
5010Sstevel@tonic-gate 	}
5020Sstevel@tonic-gate 
5030Sstevel@tonic-gate 	/*
5040Sstevel@tonic-gate 	 * If the roving slot pointer happens to match tpdp,
5050Sstevel@tonic-gate 	 * point it at the previous dirent.
5060Sstevel@tonic-gate 	 */
5070Sstevel@tonic-gate 	if (dir->tn_dir->td_prev == tpdp) {
5080Sstevel@tonic-gate 		dir->tn_dir->td_prev = tpdp->td_prev;
5090Sstevel@tonic-gate 	}
5100Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
5110Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
5120Sstevel@tonic-gate 
5130Sstevel@tonic-gate 	/*
5140Sstevel@tonic-gate 	 * tpdp points to the correct directory entry
5150Sstevel@tonic-gate 	 */
5160Sstevel@tonic-gate 	namelen = strlen(tpdp->td_name) + 1;
5170Sstevel@tonic-gate 
5180Sstevel@tonic-gate 	tmp_memfree(tpdp, sizeof (struct tdirent) + namelen);
5190Sstevel@tonic-gate 	dir->tn_size -= (sizeof (struct tdirent) + namelen);
5200Sstevel@tonic-gate 	dir->tn_dirents--;
5210Sstevel@tonic-gate 
5220Sstevel@tonic-gate 	gethrestime(&now);
5230Sstevel@tonic-gate 	dir->tn_mtime = now;
5240Sstevel@tonic-gate 	dir->tn_ctime = now;
5250Sstevel@tonic-gate 	tp->tn_ctime = now;
5260Sstevel@tonic-gate 
5270Sstevel@tonic-gate 	ASSERT(tp->tn_nlink > 0);
5280Sstevel@tonic-gate 	DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
5290Sstevel@tonic-gate 	if (op == DR_RMDIR && tp->tn_type == VDIR) {
5300Sstevel@tonic-gate 		tdirtrunc(tp);
5310Sstevel@tonic-gate 		ASSERT(tp->tn_nlink == 0);
5320Sstevel@tonic-gate 	}
5330Sstevel@tonic-gate 	return (0);
5340Sstevel@tonic-gate }
5350Sstevel@tonic-gate 
5360Sstevel@tonic-gate /*
5370Sstevel@tonic-gate  * tdirinit is used internally to initialize a directory (dir)
5380Sstevel@tonic-gate  * with '.' and '..' entries without checking permissions and locking
5390Sstevel@tonic-gate  */
5400Sstevel@tonic-gate void
5410Sstevel@tonic-gate tdirinit(
5420Sstevel@tonic-gate 	struct tmpnode *parent,		/* parent of directory to initialize */
5430Sstevel@tonic-gate 	struct tmpnode *dir)		/* the new directory */
5440Sstevel@tonic-gate {
5450Sstevel@tonic-gate 	struct tdirent *dot, *dotdot;
5460Sstevel@tonic-gate 	timestruc_t now;
5470Sstevel@tonic-gate 
5480Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&parent->tn_rwlock));
5490Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
5500Sstevel@tonic-gate 
5510Sstevel@tonic-gate 	dot = tmp_memalloc(sizeof (struct tdirent) + 2, TMP_MUSTHAVE);
5520Sstevel@tonic-gate 	dotdot = tmp_memalloc(sizeof (struct tdirent) + 3, TMP_MUSTHAVE);
5530Sstevel@tonic-gate 
5540Sstevel@tonic-gate 	/*
5550Sstevel@tonic-gate 	 * Initialize the entries
5560Sstevel@tonic-gate 	 */
5570Sstevel@tonic-gate 	dot->td_tmpnode = dir;
5580Sstevel@tonic-gate 	dot->td_offset = 0;
5590Sstevel@tonic-gate 	dot->td_name = (char *)dot + sizeof (struct tdirent);
5600Sstevel@tonic-gate 	dot->td_name[0] = '.';
5610Sstevel@tonic-gate 	dot->td_parent = dir;
5620Sstevel@tonic-gate 	tmpfs_hash_in(dot);
5630Sstevel@tonic-gate 
5640Sstevel@tonic-gate 	dotdot->td_tmpnode = parent;
5650Sstevel@tonic-gate 	dotdot->td_offset = 1;
5660Sstevel@tonic-gate 	dotdot->td_name = (char *)dotdot + sizeof (struct tdirent);
5670Sstevel@tonic-gate 	dotdot->td_name[0] = '.';
5680Sstevel@tonic-gate 	dotdot->td_name[1] = '.';
5690Sstevel@tonic-gate 	dotdot->td_parent = dir;
5700Sstevel@tonic-gate 	tmpfs_hash_in(dotdot);
5710Sstevel@tonic-gate 
5720Sstevel@tonic-gate 	/*
5730Sstevel@tonic-gate 	 * Initialize directory entry list.
5740Sstevel@tonic-gate 	 */
5750Sstevel@tonic-gate 	dot->td_next = dotdot;
5760Sstevel@tonic-gate 	dot->td_prev = dotdot;	/* dot's td_prev holds roving slot pointer */
5770Sstevel@tonic-gate 	dotdot->td_next = NULL;
5780Sstevel@tonic-gate 	dotdot->td_prev = dot;
5790Sstevel@tonic-gate 
5800Sstevel@tonic-gate 	gethrestime(&now);
5810Sstevel@tonic-gate 	dir->tn_mtime = now;
5820Sstevel@tonic-gate 	dir->tn_ctime = now;
5830Sstevel@tonic-gate 
5840Sstevel@tonic-gate 	/*
5850Sstevel@tonic-gate 	 * Link counts are special for the hidden attribute directory.
5860Sstevel@tonic-gate 	 * The only explicit reference in the name space is "." and
5870Sstevel@tonic-gate 	 * the reference through ".." is not counted on the parent
5880Sstevel@tonic-gate 	 * file. The attrdir is created as a side effect to lookup,
5890Sstevel@tonic-gate 	 * so don't change the ctime of the parent.
5900Sstevel@tonic-gate 	 * Since tdirinit is called with both dir and parent being the
5910Sstevel@tonic-gate 	 * same for the root vnode, we need to increment this before we set
5920Sstevel@tonic-gate 	 * tn_nlink = 2 below.
5930Sstevel@tonic-gate 	 */
5940Sstevel@tonic-gate 	if (!(dir->tn_vnode->v_flag & V_XATTRDIR)) {
5950Sstevel@tonic-gate 		INCR_COUNT(&parent->tn_nlink, &parent->tn_tlock);
5960Sstevel@tonic-gate 		parent->tn_ctime = now;
5970Sstevel@tonic-gate 	}
5980Sstevel@tonic-gate 
5990Sstevel@tonic-gate 	dir->tn_dir = dot;
6000Sstevel@tonic-gate 	dir->tn_size = 2 * sizeof (struct tdirent) + 5;	/* dot and dotdot */
6010Sstevel@tonic-gate 	dir->tn_dirents = 2;
6020Sstevel@tonic-gate 	dir->tn_nlink = 2;
6030Sstevel@tonic-gate }
6040Sstevel@tonic-gate 
6050Sstevel@tonic-gate 
6060Sstevel@tonic-gate /*
6070Sstevel@tonic-gate  * tdirtrunc is called to remove all directory entries under this directory.
6080Sstevel@tonic-gate  */
6090Sstevel@tonic-gate void
6100Sstevel@tonic-gate tdirtrunc(struct tmpnode *dir)
6110Sstevel@tonic-gate {
6120Sstevel@tonic-gate 	struct tdirent *tdp;
6130Sstevel@tonic-gate 	struct tmpnode *tp;
6140Sstevel@tonic-gate 	size_t namelen;
6150Sstevel@tonic-gate 	timestruc_t now;
6160Sstevel@tonic-gate 	int isvattrdir, isdotdot, skip_decr;
6170Sstevel@tonic-gate 
6180Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
6190Sstevel@tonic-gate 	ASSERT(dir->tn_type == VDIR);
6200Sstevel@tonic-gate 
6210Sstevel@tonic-gate 	isvattrdir = (dir->tn_vnode->v_flag & V_XATTRDIR) ? 1 : 0;
6220Sstevel@tonic-gate 	for (tdp = dir->tn_dir; tdp; tdp = dir->tn_dir) {
6230Sstevel@tonic-gate 		ASSERT(tdp->td_next != tdp);
6240Sstevel@tonic-gate 		ASSERT(tdp->td_prev != tdp);
6250Sstevel@tonic-gate 		ASSERT(tdp->td_tmpnode);
6260Sstevel@tonic-gate 
6270Sstevel@tonic-gate 		dir->tn_dir = tdp->td_next;
6280Sstevel@tonic-gate 		namelen = strlen(tdp->td_name) + 1;
6290Sstevel@tonic-gate 
6300Sstevel@tonic-gate 		/*
6310Sstevel@tonic-gate 		 * Adjust the link counts to account for this directory
6320Sstevel@tonic-gate 		 * entry removal. Hidden attribute directories may
6330Sstevel@tonic-gate 		 * not be empty as they may be truncated as a side-
6340Sstevel@tonic-gate 		 * effect of removing the parent. We do hold/rele
6350Sstevel@tonic-gate 		 * operations to free up these tmpnodes.
6360Sstevel@tonic-gate 		 *
6370Sstevel@tonic-gate 		 * Skip the link count adjustment for parents of
6380Sstevel@tonic-gate 		 * attribute directories as those link counts
6390Sstevel@tonic-gate 		 * do not include the ".." reference in the hidden
6400Sstevel@tonic-gate 		 * directories.
6410Sstevel@tonic-gate 		 */
6420Sstevel@tonic-gate 		tp = tdp->td_tmpnode;
6430Sstevel@tonic-gate 		isdotdot = (strcmp("..", tdp->td_name) == 0);
6440Sstevel@tonic-gate 		skip_decr = (isvattrdir && isdotdot);
6450Sstevel@tonic-gate 		if (!skip_decr) {
6460Sstevel@tonic-gate 			ASSERT(tp->tn_nlink > 0);
6470Sstevel@tonic-gate 			DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
6480Sstevel@tonic-gate 		}
6490Sstevel@tonic-gate 
6500Sstevel@tonic-gate 		tmpfs_hash_out(tdp);
6510Sstevel@tonic-gate 
6520Sstevel@tonic-gate 		tmp_memfree(tdp, sizeof (struct tdirent) + namelen);
6530Sstevel@tonic-gate 		dir->tn_size -= (sizeof (struct tdirent) + namelen);
6540Sstevel@tonic-gate 		dir->tn_dirents--;
6550Sstevel@tonic-gate 	}
6560Sstevel@tonic-gate 
6570Sstevel@tonic-gate 	gethrestime(&now);
6580Sstevel@tonic-gate 	dir->tn_mtime = now;
6590Sstevel@tonic-gate 	dir->tn_ctime = now;
6600Sstevel@tonic-gate 
6610Sstevel@tonic-gate 	ASSERT(dir->tn_dir == NULL);
6620Sstevel@tonic-gate 	ASSERT(dir->tn_size == 0);
6630Sstevel@tonic-gate 	ASSERT(dir->tn_dirents == 0);
6640Sstevel@tonic-gate }
6650Sstevel@tonic-gate 
6660Sstevel@tonic-gate /*
6670Sstevel@tonic-gate  * Check if the source directory is in the path of the target directory.
6680Sstevel@tonic-gate  * The target directory is locked by the caller.
6690Sstevel@tonic-gate  *
6700Sstevel@tonic-gate  * XXX - The source and target's should be different upon entry.
6710Sstevel@tonic-gate  */
6720Sstevel@tonic-gate static int
6730Sstevel@tonic-gate tdircheckpath(
6740Sstevel@tonic-gate 	struct tmpnode *fromtp,
6750Sstevel@tonic-gate 	struct tmpnode	*toparent,
6760Sstevel@tonic-gate 	struct cred	*cred)
6770Sstevel@tonic-gate {
6780Sstevel@tonic-gate 	int	error = 0;
6790Sstevel@tonic-gate 	struct tmpnode *dir, *dotdot;
6800Sstevel@tonic-gate 	struct tdirent *tdp;
6810Sstevel@tonic-gate 
6820Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&toparent->tn_rwlock));
6830Sstevel@tonic-gate 
6840Sstevel@tonic-gate 	tdp = tmpfs_hash_lookup("..", toparent, 1, &dotdot);
6850Sstevel@tonic-gate 	if (tdp == NULL)
6860Sstevel@tonic-gate 		return (ENOENT);
6870Sstevel@tonic-gate 
6880Sstevel@tonic-gate 	ASSERT(dotdot);
6890Sstevel@tonic-gate 
6900Sstevel@tonic-gate 	if (dotdot == toparent) {
6910Sstevel@tonic-gate 		/* root of fs.  search trivially satisfied. */
6920Sstevel@tonic-gate 		tmpnode_rele(dotdot);
6930Sstevel@tonic-gate 		return (0);
6940Sstevel@tonic-gate 	}
6950Sstevel@tonic-gate 	for (;;) {
6960Sstevel@tonic-gate 		/*
6970Sstevel@tonic-gate 		 * Return error for cases like "mv c c/d",
6980Sstevel@tonic-gate 		 * "mv c c/d/e" and so on.
6990Sstevel@tonic-gate 		 */
7000Sstevel@tonic-gate 		if (dotdot == fromtp) {
7010Sstevel@tonic-gate 			tmpnode_rele(dotdot);
7020Sstevel@tonic-gate 			error = EINVAL;
7030Sstevel@tonic-gate 			break;
7040Sstevel@tonic-gate 		}
7050Sstevel@tonic-gate 		dir = dotdot;
7060Sstevel@tonic-gate 		error = tdirlookup(dir, "..", &dotdot, cred);
7070Sstevel@tonic-gate 		if (error) {
7080Sstevel@tonic-gate 			tmpnode_rele(dir);
7090Sstevel@tonic-gate 			break;
7100Sstevel@tonic-gate 		}
7110Sstevel@tonic-gate 		/*
7120Sstevel@tonic-gate 		 * We're okay if we traverse the directory tree up to
7130Sstevel@tonic-gate 		 * the root directory and don't run into the
7140Sstevel@tonic-gate 		 * parent directory.
7150Sstevel@tonic-gate 		 */
7160Sstevel@tonic-gate 		if (dir == dotdot) {
7170Sstevel@tonic-gate 			tmpnode_rele(dir);
7180Sstevel@tonic-gate 			tmpnode_rele(dotdot);
7190Sstevel@tonic-gate 			break;
7200Sstevel@tonic-gate 		}
7210Sstevel@tonic-gate 		tmpnode_rele(dir);
7220Sstevel@tonic-gate 	}
7230Sstevel@tonic-gate 	return (error);
7240Sstevel@tonic-gate }
7250Sstevel@tonic-gate 
7260Sstevel@tonic-gate static int
7270Sstevel@tonic-gate tdirrename(
7280Sstevel@tonic-gate 	struct tmpnode *fromparent,	/* parent directory of source */
7290Sstevel@tonic-gate 	struct tmpnode *fromtp,		/* source tmpnode */
7300Sstevel@tonic-gate 	struct tmpnode *toparent,	/* parent directory of target */
7310Sstevel@tonic-gate 	char *nm,			/* entry we are trying to change */
7320Sstevel@tonic-gate 	struct tmpnode *to,		/* target tmpnode */
7330Sstevel@tonic-gate 	struct tdirent *where,		/* target tmpnode directory entry */
7340Sstevel@tonic-gate 	struct cred *cred)		/* credentials */
7350Sstevel@tonic-gate {
7360Sstevel@tonic-gate 	int error = 0;
7370Sstevel@tonic-gate 	int doingdirectory;
7380Sstevel@tonic-gate 	timestruc_t now;
7390Sstevel@tonic-gate 
7400Sstevel@tonic-gate #if defined(lint)
7410Sstevel@tonic-gate 	nm = nm;
7420Sstevel@tonic-gate #endif
7430Sstevel@tonic-gate 	ASSERT(RW_WRITE_HELD(&toparent->tn_rwlock));
7440Sstevel@tonic-gate 
7450Sstevel@tonic-gate 	/*
7460Sstevel@tonic-gate 	 * Short circuit rename of something to itself.
7470Sstevel@tonic-gate 	 */
7480Sstevel@tonic-gate 	if (fromtp == to)
7490Sstevel@tonic-gate 		return (ESAME);		/* special KLUDGE error code */
7500Sstevel@tonic-gate 
7510Sstevel@tonic-gate 	rw_enter(&fromtp->tn_rwlock, RW_READER);
7520Sstevel@tonic-gate 	rw_enter(&to->tn_rwlock, RW_READER);
7530Sstevel@tonic-gate 
7540Sstevel@tonic-gate 	/*
7550Sstevel@tonic-gate 	 * Check that everything is on the same filesystem.
7560Sstevel@tonic-gate 	 */
7570Sstevel@tonic-gate 	if (to->tn_vnode->v_vfsp != toparent->tn_vnode->v_vfsp ||
7580Sstevel@tonic-gate 	    to->tn_vnode->v_vfsp != fromtp->tn_vnode->v_vfsp) {
7590Sstevel@tonic-gate 		error = EXDEV;
7600Sstevel@tonic-gate 		goto out;
7610Sstevel@tonic-gate 	}
7620Sstevel@tonic-gate 
7630Sstevel@tonic-gate 	/*
7640Sstevel@tonic-gate 	 * Must have write permission to rewrite target entry.
7650Sstevel@tonic-gate 	 * Check for stickyness.
7660Sstevel@tonic-gate 	 */
7670Sstevel@tonic-gate 	if ((error = tmp_taccess(toparent, VWRITE, cred)) != 0 ||
7680Sstevel@tonic-gate 	    (error = tmp_sticky_remove_access(toparent, to, cred)) != 0)
7690Sstevel@tonic-gate 		goto out;
7700Sstevel@tonic-gate 
7710Sstevel@tonic-gate 	/*
7720Sstevel@tonic-gate 	 * Ensure source and target are compatible (both directories
7730Sstevel@tonic-gate 	 * or both not directories).  If target is a directory it must
7740Sstevel@tonic-gate 	 * be empty and have no links to it; in addition it must not
7750Sstevel@tonic-gate 	 * be a mount point, and both the source and target must be
7760Sstevel@tonic-gate 	 * writable.
7770Sstevel@tonic-gate 	 */
7780Sstevel@tonic-gate 	doingdirectory = (fromtp->tn_type == VDIR);
7790Sstevel@tonic-gate 	if (to->tn_type == VDIR) {
7800Sstevel@tonic-gate 		if (!doingdirectory) {
7810Sstevel@tonic-gate 			error = EISDIR;
7820Sstevel@tonic-gate 			goto out;
7830Sstevel@tonic-gate 		}
7840Sstevel@tonic-gate 		/*
785*569Sbatschul 		 * vn_vfswlock will prevent mounts from using the directory
7860Sstevel@tonic-gate 		 * until we are done.
7870Sstevel@tonic-gate 		 */
788*569Sbatschul 		if (vn_vfswlock(TNTOV(to))) {
7890Sstevel@tonic-gate 			error = EBUSY;
7900Sstevel@tonic-gate 			goto out;
7910Sstevel@tonic-gate 		}
7920Sstevel@tonic-gate 		if (vn_mountedvfs(TNTOV(to)) != NULL) {
7930Sstevel@tonic-gate 			vn_vfsunlock(TNTOV(to));
7940Sstevel@tonic-gate 			error = EBUSY;
7950Sstevel@tonic-gate 			goto out;
7960Sstevel@tonic-gate 		}
7970Sstevel@tonic-gate 
7980Sstevel@tonic-gate 		mutex_enter(&to->tn_tlock);
7990Sstevel@tonic-gate 		if (to->tn_dirents > 2 || to->tn_nlink > 2) {
8000Sstevel@tonic-gate 			mutex_exit(&to->tn_tlock);
8010Sstevel@tonic-gate 			vn_vfsunlock(TNTOV(to));
8020Sstevel@tonic-gate 			error = EEXIST; /* SIGH should be ENOTEMPTY */
8030Sstevel@tonic-gate 			/*
8040Sstevel@tonic-gate 			 * Update atime because checking tn_dirents is
8050Sstevel@tonic-gate 			 * logically equivalent to reading the directory
8060Sstevel@tonic-gate 			 */
8070Sstevel@tonic-gate 			gethrestime(&to->tn_atime);
8080Sstevel@tonic-gate 			goto out;
8090Sstevel@tonic-gate 		}
8100Sstevel@tonic-gate 		mutex_exit(&to->tn_tlock);
8110Sstevel@tonic-gate 	} else if (doingdirectory) {
8120Sstevel@tonic-gate 		error = ENOTDIR;
8130Sstevel@tonic-gate 		goto out;
8140Sstevel@tonic-gate 	}
8150Sstevel@tonic-gate 
8160Sstevel@tonic-gate 	tmpfs_hash_change(where, fromtp);
8170Sstevel@tonic-gate 	gethrestime(&now);
8180Sstevel@tonic-gate 	toparent->tn_mtime = now;
8190Sstevel@tonic-gate 	toparent->tn_ctime = now;
8200Sstevel@tonic-gate 
8210Sstevel@tonic-gate 	/*
8220Sstevel@tonic-gate 	 * Upgrade to write lock on "to" (i.e., the target tmpnode).
8230Sstevel@tonic-gate 	 */
8240Sstevel@tonic-gate 	rw_exit(&to->tn_rwlock);
8250Sstevel@tonic-gate 	rw_enter(&to->tn_rwlock, RW_WRITER);
8260Sstevel@tonic-gate 
8270Sstevel@tonic-gate 	/*
8280Sstevel@tonic-gate 	 * Decrement the link count of the target tmpnode.
8290Sstevel@tonic-gate 	 */
8300Sstevel@tonic-gate 	DECR_COUNT(&to->tn_nlink, &to->tn_tlock);
8310Sstevel@tonic-gate 	to->tn_ctime = now;
8320Sstevel@tonic-gate 
8330Sstevel@tonic-gate 	if (doingdirectory) {
8340Sstevel@tonic-gate 		/*
8350Sstevel@tonic-gate 		 * The entry for "to" no longer exists so release the vfslock.
8360Sstevel@tonic-gate 		 */
8370Sstevel@tonic-gate 		vn_vfsunlock(TNTOV(to));
8380Sstevel@tonic-gate 
8390Sstevel@tonic-gate 		/*
8400Sstevel@tonic-gate 		 * Decrement the target link count and delete all entires.
8410Sstevel@tonic-gate 		 */
8420Sstevel@tonic-gate 		tdirtrunc(to);
8430Sstevel@tonic-gate 		ASSERT(to->tn_nlink == 0);
8440Sstevel@tonic-gate 
8450Sstevel@tonic-gate 		/*
8460Sstevel@tonic-gate 		 * Renaming a directory with the parent different
8470Sstevel@tonic-gate 		 * requires that ".." be rewritten.  The window is
8480Sstevel@tonic-gate 		 * still there for ".." to be inconsistent, but this
8490Sstevel@tonic-gate 		 * is unavoidable, and a lot shorter than when it was
8500Sstevel@tonic-gate 		 * done in a user process.
8510Sstevel@tonic-gate 		 */
8520Sstevel@tonic-gate 		if (fromparent != toparent)
8530Sstevel@tonic-gate 			tdirfixdotdot(fromtp, fromparent, toparent);
8540Sstevel@tonic-gate 	}
8550Sstevel@tonic-gate out:
8560Sstevel@tonic-gate 	rw_exit(&to->tn_rwlock);
8570Sstevel@tonic-gate 	rw_exit(&fromtp->tn_rwlock);
8580Sstevel@tonic-gate 	return (error);
8590Sstevel@tonic-gate }
8600Sstevel@tonic-gate 
8610Sstevel@tonic-gate static void
8620Sstevel@tonic-gate tdirfixdotdot(
8630Sstevel@tonic-gate 	struct tmpnode	*fromtp,	/* child directory */
8640Sstevel@tonic-gate 	struct tmpnode	*fromparent,	/* old parent directory */
8650Sstevel@tonic-gate 	struct tmpnode	*toparent)	/* new parent directory */
8660Sstevel@tonic-gate {
8670Sstevel@tonic-gate 	struct tdirent	*dotdot;
8680Sstevel@tonic-gate 
8690Sstevel@tonic-gate 	ASSERT(RW_LOCK_HELD(&toparent->tn_rwlock));
8700Sstevel@tonic-gate 
8710Sstevel@tonic-gate 	/*
8720Sstevel@tonic-gate 	 * Increment the link count in the new parent tmpnode
8730Sstevel@tonic-gate 	 */
8740Sstevel@tonic-gate 	INCR_COUNT(&toparent->tn_nlink, &toparent->tn_tlock);
8750Sstevel@tonic-gate 	gethrestime(&toparent->tn_ctime);
8760Sstevel@tonic-gate 
8770Sstevel@tonic-gate 	dotdot = tmpfs_hash_lookup("..", fromtp, 0, NULL);
8780Sstevel@tonic-gate 
8790Sstevel@tonic-gate 	ASSERT(dotdot->td_tmpnode == fromparent);
8800Sstevel@tonic-gate 	dotdot->td_tmpnode = toparent;
8810Sstevel@tonic-gate 
8820Sstevel@tonic-gate 	/*
8830Sstevel@tonic-gate 	 * Decrement the link count of the old parent tmpnode.
8840Sstevel@tonic-gate 	 * If fromparent is NULL, then this is a new directory link;
8850Sstevel@tonic-gate 	 * it has no parent, so we need not do anything.
8860Sstevel@tonic-gate 	 */
8870Sstevel@tonic-gate 	if (fromparent != NULL) {
8880Sstevel@tonic-gate 		mutex_enter(&fromparent->tn_tlock);
8890Sstevel@tonic-gate 		if (fromparent->tn_nlink != 0) {
8900Sstevel@tonic-gate 			fromparent->tn_nlink--;
8910Sstevel@tonic-gate 			gethrestime(&fromparent->tn_ctime);
8920Sstevel@tonic-gate 		}
8930Sstevel@tonic-gate 		mutex_exit(&fromparent->tn_tlock);
8940Sstevel@tonic-gate 	}
8950Sstevel@tonic-gate }
8960Sstevel@tonic-gate 
8970Sstevel@tonic-gate static int
8980Sstevel@tonic-gate tdiraddentry(
8990Sstevel@tonic-gate 	struct tmpnode	*dir,	/* target directory to make entry in */
9000Sstevel@tonic-gate 	struct tmpnode	*tp,	/* new tmpnode */
9010Sstevel@tonic-gate 	char		*name,
9020Sstevel@tonic-gate 	enum de_op	op,
9030Sstevel@tonic-gate 	struct tmpnode	*fromtp)
9040Sstevel@tonic-gate {
9050Sstevel@tonic-gate 	struct tdirent *tdp, *tpdp;
9060Sstevel@tonic-gate 	size_t		namelen, alloc_size;
9070Sstevel@tonic-gate 	timestruc_t	now;
9080Sstevel@tonic-gate 
9090Sstevel@tonic-gate 	/*
9100Sstevel@tonic-gate 	 * Make sure the parent directory wasn't removed from
9110Sstevel@tonic-gate 	 * underneath the caller.
9120Sstevel@tonic-gate 	 */
9130Sstevel@tonic-gate 	if (dir->tn_dir == NULL)
9140Sstevel@tonic-gate 		return (ENOENT);
9150Sstevel@tonic-gate 
9160Sstevel@tonic-gate 	/*
9170Sstevel@tonic-gate 	 * Check that everything is on the same filesystem.
9180Sstevel@tonic-gate 	 */
9190Sstevel@tonic-gate 	if (tp->tn_vnode->v_vfsp != dir->tn_vnode->v_vfsp)
9200Sstevel@tonic-gate 		return (EXDEV);
9210Sstevel@tonic-gate 
9220Sstevel@tonic-gate 	/*
9230Sstevel@tonic-gate 	 * Allocate and initialize directory entry
9240Sstevel@tonic-gate 	 */
9250Sstevel@tonic-gate 	namelen = strlen(name) + 1;
9260Sstevel@tonic-gate 	alloc_size = namelen + sizeof (struct tdirent);
9270Sstevel@tonic-gate 	tdp = tmp_memalloc(alloc_size, 0);
9280Sstevel@tonic-gate 	if (tdp == NULL)
9290Sstevel@tonic-gate 		return (ENOSPC);
9300Sstevel@tonic-gate 
9310Sstevel@tonic-gate 	if ((op == DE_RENAME) && (tp->tn_type == VDIR))
9320Sstevel@tonic-gate 		tdirfixdotdot(tp, fromtp, dir);
9330Sstevel@tonic-gate 
9340Sstevel@tonic-gate 	dir->tn_size += alloc_size;
9350Sstevel@tonic-gate 	dir->tn_dirents++;
9360Sstevel@tonic-gate 	tdp->td_tmpnode = tp;
9370Sstevel@tonic-gate 	tdp->td_parent = dir;
9380Sstevel@tonic-gate 
9390Sstevel@tonic-gate 	/*
9400Sstevel@tonic-gate 	 * The directory entry and its name were allocated sequentially.
9410Sstevel@tonic-gate 	 */
9420Sstevel@tonic-gate 	tdp->td_name = (char *)tdp + sizeof (struct tdirent);
9430Sstevel@tonic-gate 	(void) strcpy(tdp->td_name, name);
9440Sstevel@tonic-gate 
9450Sstevel@tonic-gate 	tmpfs_hash_in(tdp);
9460Sstevel@tonic-gate 
9470Sstevel@tonic-gate 	/*
9480Sstevel@tonic-gate 	 * Some utilities expect the size of a directory to remain
9490Sstevel@tonic-gate 	 * somewhat static.  For example, a routine which unlinks
9500Sstevel@tonic-gate 	 * files between calls to readdir(); the size of the
9510Sstevel@tonic-gate 	 * directory changes from underneath it and so the real
9520Sstevel@tonic-gate 	 * directory offset in bytes is invalid.  To circumvent
9530Sstevel@tonic-gate 	 * this problem, we initialize a directory entry with an
9540Sstevel@tonic-gate 	 * phony offset, and use this offset to determine end of
9550Sstevel@tonic-gate 	 * file in tmp_readdir.
9560Sstevel@tonic-gate 	 */
9570Sstevel@tonic-gate 	tpdp = dir->tn_dir->td_prev;
9580Sstevel@tonic-gate 	/*
9590Sstevel@tonic-gate 	 * Install at first empty "slot" in directory list.
9600Sstevel@tonic-gate 	 */
9610Sstevel@tonic-gate 	while (tpdp->td_next != NULL && (tpdp->td_next->td_offset -
9620Sstevel@tonic-gate 	    tpdp->td_offset) <= 1) {
9630Sstevel@tonic-gate 		ASSERT(tpdp->td_next != tpdp);
9640Sstevel@tonic-gate 		ASSERT(tpdp->td_prev != tpdp);
9650Sstevel@tonic-gate 		ASSERT(tpdp->td_next->td_offset > tpdp->td_offset);
9660Sstevel@tonic-gate 		tpdp = tpdp->td_next;
9670Sstevel@tonic-gate 	}
9680Sstevel@tonic-gate 	tdp->td_offset = tpdp->td_offset + 1;
9690Sstevel@tonic-gate 
9700Sstevel@tonic-gate 	/*
9710Sstevel@tonic-gate 	 * If we're at the end of the dirent list and the offset (which
9720Sstevel@tonic-gate 	 * is necessarily the largest offset in this directory) is more
9730Sstevel@tonic-gate 	 * than twice the number of dirents, that means the directory is
9740Sstevel@tonic-gate 	 * 50% holes.  At this point we reset the slot pointer back to
9750Sstevel@tonic-gate 	 * the beginning of the directory so we start using the holes.
9760Sstevel@tonic-gate 	 * The idea is that if there are N dirents, there must also be
9770Sstevel@tonic-gate 	 * N holes, so we can satisfy the next N creates by walking at
9780Sstevel@tonic-gate 	 * most 2N entries; thus the average cost of a create is constant.
9790Sstevel@tonic-gate 	 * Note that we use the first dirent's td_prev as the roving
9800Sstevel@tonic-gate 	 * slot pointer; it's ugly, but it saves a word in every dirent.
9810Sstevel@tonic-gate 	 */
9820Sstevel@tonic-gate 	if (tpdp->td_next == NULL && tpdp->td_offset > 2 * dir->tn_dirents)
9830Sstevel@tonic-gate 		dir->tn_dir->td_prev = dir->tn_dir->td_next;
9840Sstevel@tonic-gate 	else
9850Sstevel@tonic-gate 		dir->tn_dir->td_prev = tdp;
9860Sstevel@tonic-gate 
9870Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
9880Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
9890Sstevel@tonic-gate 
9900Sstevel@tonic-gate 	tdp->td_next = tpdp->td_next;
9910Sstevel@tonic-gate 	if (tdp->td_next) {
9920Sstevel@tonic-gate 		tdp->td_next->td_prev = tdp;
9930Sstevel@tonic-gate 	}
9940Sstevel@tonic-gate 	tdp->td_prev = tpdp;
9950Sstevel@tonic-gate 	tpdp->td_next = tdp;
9960Sstevel@tonic-gate 
9970Sstevel@tonic-gate 	ASSERT(tdp->td_next != tdp);
9980Sstevel@tonic-gate 	ASSERT(tdp->td_prev != tdp);
9990Sstevel@tonic-gate 	ASSERT(tpdp->td_next != tpdp);
10000Sstevel@tonic-gate 	ASSERT(tpdp->td_prev != tpdp);
10010Sstevel@tonic-gate 
10020Sstevel@tonic-gate 	gethrestime(&now);
10030Sstevel@tonic-gate 	dir->tn_mtime = now;
10040Sstevel@tonic-gate 	dir->tn_ctime = now;
10050Sstevel@tonic-gate 
10060Sstevel@tonic-gate 	return (0);
10070Sstevel@tonic-gate }
10080Sstevel@tonic-gate 
10090Sstevel@tonic-gate static int
10100Sstevel@tonic-gate tdirmaketnode(
10110Sstevel@tonic-gate 	struct tmpnode *dir,
10120Sstevel@tonic-gate 	struct tmount	*tm,
10130Sstevel@tonic-gate 	struct vattr	*va,
10140Sstevel@tonic-gate 	enum	de_op	op,
10150Sstevel@tonic-gate 	struct tmpnode **newnode,
10160Sstevel@tonic-gate 	struct cred	*cred)
10170Sstevel@tonic-gate {
10180Sstevel@tonic-gate 	struct tmpnode *tp;
10190Sstevel@tonic-gate 	enum vtype	type;
10200Sstevel@tonic-gate 
10210Sstevel@tonic-gate 	ASSERT(va != NULL);
10220Sstevel@tonic-gate 	ASSERT(op == DE_CREATE || op == DE_MKDIR);
10230Sstevel@tonic-gate 	if (((va->va_mask & AT_ATIME) && TIMESPEC_OVERFLOW(&va->va_atime)) ||
10240Sstevel@tonic-gate 	    ((va->va_mask & AT_MTIME) && TIMESPEC_OVERFLOW(&va->va_mtime)))
10250Sstevel@tonic-gate 		return (EOVERFLOW);
10260Sstevel@tonic-gate 	type = va->va_type;
10270Sstevel@tonic-gate 	tp = tmp_memalloc(sizeof (struct tmpnode), TMP_MUSTHAVE);
10280Sstevel@tonic-gate 	tmpnode_init(tm, tp, va, cred);
10290Sstevel@tonic-gate 
10300Sstevel@tonic-gate 	/* setup normal file/dir's extended attribute directory */
10310Sstevel@tonic-gate 	if (dir->tn_flags & ISXATTR) {
10320Sstevel@tonic-gate 		/* parent dir is , mark file as xattr */
10330Sstevel@tonic-gate 		tp->tn_flags |= ISXATTR;
10340Sstevel@tonic-gate 	}
10350Sstevel@tonic-gate 
10360Sstevel@tonic-gate 
10370Sstevel@tonic-gate 	if (type == VBLK || type == VCHR) {
10380Sstevel@tonic-gate 		tp->tn_vnode->v_rdev = tp->tn_rdev = va->va_rdev;
10390Sstevel@tonic-gate 	} else {
10400Sstevel@tonic-gate 		tp->tn_vnode->v_rdev = tp->tn_rdev = NODEV;
10410Sstevel@tonic-gate 	}
10420Sstevel@tonic-gate 	tp->tn_vnode->v_type = type;
10430Sstevel@tonic-gate 	tp->tn_uid = crgetuid(cred);
10440Sstevel@tonic-gate 
10450Sstevel@tonic-gate 	/*
10460Sstevel@tonic-gate 	 * To determine the group-id of the created file:
10470Sstevel@tonic-gate 	 *   1) If the gid is set in the attribute list (non-Sun & pre-4.0
10480Sstevel@tonic-gate 	 *	clients are not likely to set the gid), then use it if
10490Sstevel@tonic-gate 	 *	the process is privileged, belongs to the target group,
10500Sstevel@tonic-gate 	 *	or the group is the same as the parent directory.
10510Sstevel@tonic-gate 	 *   2) If the filesystem was not mounted with the Old-BSD-compatible
10520Sstevel@tonic-gate 	 *	GRPID option, and the directory's set-gid bit is clear,
10530Sstevel@tonic-gate 	 *	then use the process's gid.
10540Sstevel@tonic-gate 	 *   3) Otherwise, set the group-id to the gid of the parent directory.
10550Sstevel@tonic-gate 	 */
10560Sstevel@tonic-gate 	if ((va->va_mask & AT_GID) &&
10570Sstevel@tonic-gate 	    ((va->va_gid == dir->tn_gid) || groupmember(va->va_gid, cred) ||
10580Sstevel@tonic-gate 	    secpolicy_vnode_create_gid(cred) == 0)) {
10590Sstevel@tonic-gate 		/*
10600Sstevel@tonic-gate 		 * XXX - is this only the case when a 4.0 NFS client, or a
10610Sstevel@tonic-gate 		 * client derived from that code, makes a call over the wire?
10620Sstevel@tonic-gate 		 */
10630Sstevel@tonic-gate 		tp->tn_gid = va->va_gid;
10640Sstevel@tonic-gate 	} else {
10650Sstevel@tonic-gate 		if (dir->tn_mode & VSGID)
10660Sstevel@tonic-gate 			tp->tn_gid = dir->tn_gid;
10670Sstevel@tonic-gate 		else
10680Sstevel@tonic-gate 			tp->tn_gid = crgetgid(cred);
10690Sstevel@tonic-gate 	}
10700Sstevel@tonic-gate 	/*
10710Sstevel@tonic-gate 	 * If we're creating a directory, and the parent directory has the
10720Sstevel@tonic-gate 	 * set-GID bit set, set it on the new directory.
10730Sstevel@tonic-gate 	 * Otherwise, if the user is neither privileged nor a member of the
10740Sstevel@tonic-gate 	 * file's new group, clear the file's set-GID bit.
10750Sstevel@tonic-gate 	 */
10760Sstevel@tonic-gate 	if (dir->tn_mode & VSGID && type == VDIR)
10770Sstevel@tonic-gate 		tp->tn_mode |= VSGID;
10780Sstevel@tonic-gate 	else {
10790Sstevel@tonic-gate 		if ((tp->tn_mode & VSGID) &&
10800Sstevel@tonic-gate 		    secpolicy_vnode_setids_setgids(cred, tp->tn_gid) != 0)
10810Sstevel@tonic-gate 			tp->tn_mode &= ~VSGID;
10820Sstevel@tonic-gate 	}
10830Sstevel@tonic-gate 
10840Sstevel@tonic-gate 	if (va->va_mask & AT_ATIME)
10850Sstevel@tonic-gate 		tp->tn_atime = va->va_atime;
10860Sstevel@tonic-gate 	if (va->va_mask & AT_MTIME)
10870Sstevel@tonic-gate 		tp->tn_mtime = va->va_mtime;
10880Sstevel@tonic-gate 
10890Sstevel@tonic-gate 	if (op == DE_MKDIR)
10900Sstevel@tonic-gate 		tdirinit(dir, tp);
10910Sstevel@tonic-gate 
10920Sstevel@tonic-gate 	*newnode = tp;
10930Sstevel@tonic-gate 	return (0);
10940Sstevel@tonic-gate }
1095