xref: /csrg-svn/sys/ufs/ffs/ufs_lookup.c (revision 67266)
123401Smckusick /*
263376Sbostic  * Copyright (c) 1989, 1993
363376Sbostic  *	The Regents of the University of California.  All rights reserved.
465774Sbostic  * (c) UNIX System Laboratories, Inc.
565774Sbostic  * All or some portions of this file are derived from material licensed
665774Sbostic  * to the University of California by American Telephone and Telegraph
765774Sbostic  * Co. or Unix System Laboratories, Inc. and are reproduced herein with
865774Sbostic  * the permission of UNIX System Laboratories, Inc.
923401Smckusick  *
1044538Sbostic  * %sccs.include.redist.c%
1137739Smckusick  *
12*67266Smckusick  *	@(#)ufs_lookup.c	8.7 (Berkeley) 05/29/94
1323401Smckusick  */
1430Sbill 
1551511Sbostic #include <sys/param.h>
1651511Sbostic #include <sys/namei.h>
1751511Sbostic #include <sys/buf.h>
1851511Sbostic #include <sys/file.h>
1951511Sbostic #include <sys/mount.h>
2051511Sbostic #include <sys/vnode.h>
2130Sbill 
2251511Sbostic #include <ufs/ufs/quota.h>
2351511Sbostic #include <ufs/ufs/inode.h>
2451511Sbostic #include <ufs/ufs/dir.h>
2551511Sbostic #include <ufs/ufs/ufsmount.h>
2651511Sbostic #include <ufs/ufs/ufs_extern.h>
2747571Skarels 
2837739Smckusick struct	nchstats nchstats;
2948068Smckusick #ifdef DIAGNOSTIC
3037582Smarc int	dirchk = 1;
3148068Smckusick #else
3248068Smckusick int	dirchk = 0;
3348068Smckusick #endif
3415798Smckusick 
3554607Smckusick #define FSFMT(vp)	((vp)->v_mount->mnt_maxsymlinklen <= 0)
3654607Smckusick 
3730Sbill /*
3837739Smckusick  * Convert a component of a pathname into a pointer to a locked inode.
397534Sroot  * This is a very central and rather complicated routine.
4027268Smckusick  * If the file system is not maintained in a strict tree hierarchy,
4127268Smckusick  * this can result in a deadlock situation (see comments in code below).
4230Sbill  *
4353061Smckusick  * The cnp->cn_nameiop argument is LOOKUP, CREATE, RENAME, or DELETE depending
4453061Smckusick  * on whether the name is to be looked up, created, renamed, or deleted.
4537739Smckusick  * When CREATE, RENAME, or DELETE is specified, information usable in
4637739Smckusick  * creating, renaming, or deleting a directory entry may be calculated.
4737739Smckusick  * If flag has LOCKPARENT or'ed into it and the target of the pathname
4837739Smckusick  * exists, lookup returns both the target and its parent directory locked.
4937739Smckusick  * When creating or renaming and LOCKPARENT is specified, the target may
5037739Smckusick  * not be ".".  When deleting and LOCKPARENT is specified, the target may
5156803Smckusick  * be "."., but the caller must check to ensure it does an vrele and vput
5256803Smckusick  * instead of two vputs.
539166Ssam  *
5437739Smckusick  * Overall outline of ufs_lookup:
559166Ssam  *
567534Sroot  *	check accessibility of directory
5715798Smckusick  *	look for name in cache, if found, then if at end of path
5837739Smckusick  *	  and deleting or creating, drop it, else return name
597534Sroot  *	search for name in directory, to found or notfound
607534Sroot  * notfound:
6137739Smckusick  *	if creating, return locked directory, leaving info on available slots
627534Sroot  *	else return error
637534Sroot  * found:
647534Sroot  *	if at end of path and deleting, return information to allow delete
6537739Smckusick  *	if at end of path and rewriting (RENAME and LOCKPARENT), lock target
669166Ssam  *	  inode and return info to allow rewrite
6727268Smckusick  *	if not at end, add name to cache; if at end and neither creating
6827268Smckusick  *	  nor deleting, add name to cache
6930Sbill  */
7051511Sbostic int
ufs_lookup(ap)7154649Smckusick ufs_lookup(ap)
7254453Smckusick 	struct vop_lookup_args /* {
7354529Smckusick 		struct vnode *a_dvp;
7454529Smckusick 		struct vnode **a_vpp;
7554529Smckusick 		struct componentname *a_cnp;
7654453Smckusick 	} */ *ap;
7730Sbill {
7854529Smckusick 	register struct vnode *vdp;	/* vnode for directory being searched */
7954529Smckusick 	register struct inode *dp;	/* inode for directory being searched */
8051511Sbostic 	struct buf *bp;			/* a buffer of directory entries */
817534Sroot 	register struct direct *ep;	/* the current directory entry */
827534Sroot 	int entryoffsetinblock;		/* offset of ep in bp's buffer */
837534Sroot 	enum {NONE, COMPACT, FOUND} slotstatus;
8453061Smckusick 	doff_t slotoffset;		/* offset of area with free space */
857534Sroot 	int slotsize;			/* size of area at slotoffset */
867534Sroot 	int slotfreespace;		/* amount of space free in slot */
877534Sroot 	int slotneeded;			/* size of the entry we're seeking */
8815660Smckusick 	int numdirpasses;		/* strategy for directory search */
8953061Smckusick 	doff_t endsearch;		/* offset to end directory search */
9053061Smckusick 	doff_t prevoff;			/* prev entry dp->i_offset */
9156803Smckusick 	struct vnode *pdp;		/* saved dp during symlink work */
9254649Smckusick 	struct vnode *tdp;		/* returned by VFS_VGET */
9353061Smckusick 	doff_t enduseful;		/* pointer past last used dir slot */
9451511Sbostic 	u_long bmask;			/* block offset mask */
9537739Smckusick 	int lockparent;			/* 1 => lockparent flag is set */
9637739Smckusick 	int wantparent;			/* 1 => wantparent or lockparent flag */
9754607Smckusick 	int namlen, error;
9854529Smckusick 	struct vnode **vpp = ap->a_vpp;
9954529Smckusick 	struct componentname *cnp = ap->a_cnp;
10054529Smckusick 	struct ucred *cred = cnp->cn_cred;
10154529Smckusick 	int flags = cnp->cn_flags;
10254529Smckusick 	int nameiop = cnp->cn_nameiop;
10330Sbill 
10451511Sbostic 	bp = NULL;
10551511Sbostic 	slotoffset = -1;
10654529Smckusick 	*vpp = NULL;
10754529Smckusick 	vdp = ap->a_dvp;
10854529Smckusick 	dp = VTOI(vdp);
10954529Smckusick 	lockparent = flags & LOCKPARENT;
11054529Smckusick 	wantparent = flags & (LOCKPARENT|WANTPARENT);
1117534Sroot 
1125972Swnj 	/*
1137534Sroot 	 * Check accessiblity of directory.
11430Sbill 	 */
11552337Smckusick 	if ((dp->i_mode & IFMT) != IFDIR)
11637739Smckusick 		return (ENOTDIR);
11754529Smckusick 	if (error = VOP_ACCESS(vdp, VEXEC, cred, cnp->cn_proc))
11837739Smckusick 		return (error);
1197534Sroot 
1207534Sroot 	/*
12115798Smckusick 	 * We now have a segment name to search for, and a directory to search.
12215798Smckusick 	 *
12315798Smckusick 	 * Before tediously performing a linear scan of the directory,
12415798Smckusick 	 * check the name cache to see if the directory/name pair
12537739Smckusick 	 * we are looking for is known already.
12615798Smckusick 	 */
12754529Smckusick 	if (error = cache_lookup(vdp, vpp, cnp)) {
12838770Smckusick 		int vpid;	/* capability number of vnode */
12938770Smckusick 
13038770Smckusick 		if (error == ENOENT)
13138770Smckusick 			return (error);
13237739Smckusick 		/*
13337739Smckusick 		 * Get the next vnode in the path.
13438770Smckusick 		 * See comment below starting `Step through' for
13537739Smckusick 		 * an explaination of the locking protocol.
13637739Smckusick 		 */
13756803Smckusick 		pdp = vdp;
13854529Smckusick 		dp = VTOI(*vpp);
13954529Smckusick 		vdp = *vpp;
14054529Smckusick 		vpid = vdp->v_id;
14156803Smckusick 		if (pdp == vdp) {   /* lookup on "." */
14254529Smckusick 			VREF(vdp);
14339434Smckusick 			error = 0;
14454529Smckusick 		} else if (flags & ISDOTDOT) {
14556803Smckusick 			VOP_UNLOCK(pdp);
14665232Smckusick 			error = vget(vdp, 1);
14754529Smckusick 			if (!error && lockparent && (flags & ISLASTCN))
14856803Smckusick 				error = VOP_LOCK(pdp);
14915798Smckusick 		} else {
15065232Smckusick 			error = vget(vdp, 1);
15154529Smckusick 			if (!lockparent || error || !(flags & ISLASTCN))
15256803Smckusick 				VOP_UNLOCK(pdp);
15315798Smckusick 		}
15438770Smckusick 		/*
15538770Smckusick 		 * Check that the capability number did not change
15638770Smckusick 		 * while we were waiting for the lock.
15738770Smckusick 		 */
15839434Smckusick 		if (!error) {
15954529Smckusick 			if (vpid == vdp->v_id)
16039434Smckusick 				return (0);
16156803Smckusick 			vput(vdp);
16256803Smckusick 			if (lockparent && pdp != vdp && (flags & ISLASTCN))
16356803Smckusick 				VOP_UNLOCK(pdp);
16439434Smckusick 		}
16556803Smckusick 		if (error = VOP_LOCK(pdp))
16656803Smckusick 			return (error);
16756803Smckusick 		vdp = pdp;
16856803Smckusick 		dp = VTOI(pdp);
16954529Smckusick 		*vpp = NULL;
17015798Smckusick 	}
17115798Smckusick 
17215798Smckusick 	/*
1737534Sroot 	 * Suppress search for slots unless creating
1747534Sroot 	 * file and at end of pathname, in which case
1757534Sroot 	 * we watch for a place to put the new file in
1767534Sroot 	 * case it doesn't already exist.
1776571Smckusic 	 */
1787534Sroot 	slotstatus = FOUND;
17952950Smckusick 	slotfreespace = slotsize = slotneeded = 0;
18054529Smckusick 	if ((nameiop == CREATE || nameiop == RENAME) &&
18154529Smckusick 	    (flags & ISLASTCN)) {
1827534Sroot 		slotstatus = NONE;
18353061Smckusick 		slotneeded = (sizeof(struct direct) - MAXNAMLEN +
18454529Smckusick 			cnp->cn_namelen + 3) &~ 3;
1857534Sroot 	}
18637739Smckusick 
18715660Smckusick 	/*
18837739Smckusick 	 * If there is cached information on a previous search of
18937739Smckusick 	 * this directory, pick up where we last left off.
19015798Smckusick 	 * We cache only lookups as these are the most common
19115660Smckusick 	 * and have the greatest payoff. Caching CREATE has little
19215660Smckusick 	 * benefit as it usually must search the entire directory
19315660Smckusick 	 * to determine that the entry does not exist. Caching the
19437739Smckusick 	 * location of the last DELETE or RENAME has not reduced
19537739Smckusick 	 * profiling time and hence has been removed in the interest
19637739Smckusick 	 * of simplicity.
19715660Smckusick 	 */
19854529Smckusick 	bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
19954529Smckusick 	if (nameiop != LOOKUP || dp->i_diroff == 0 ||
20052337Smckusick 	    dp->i_diroff > dp->i_size) {
20153061Smckusick 		entryoffsetinblock = 0;
20253061Smckusick 		dp->i_offset = 0;
20315660Smckusick 		numdirpasses = 1;
20415660Smckusick 	} else {
20552337Smckusick 		dp->i_offset = dp->i_diroff;
20652337Smckusick 		if ((entryoffsetinblock = dp->i_offset & bmask) &&
20754529Smckusick 		    (error = VOP_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp)))
20851511Sbostic 			return (error);
20915660Smckusick 		numdirpasses = 2;
21015798Smckusick 		nchstats.ncs_2passes++;
21115660Smckusick 	}
21252950Smckusick 	prevoff = dp->i_offset;
21315660Smckusick 	endsearch = roundup(dp->i_size, DIRBLKSIZ);
21418027Smckusick 	enduseful = 0;
2157534Sroot 
21615660Smckusick searchloop:
21752337Smckusick 	while (dp->i_offset < endsearch) {
2185972Swnj 		/*
21959345Smckusick 		 * If necessary, get the next directory block.
2205972Swnj 		 */
22152337Smckusick 		if ((dp->i_offset & bmask) == 0) {
2225972Swnj 			if (bp != NULL)
2235972Swnj 				brelse(bp);
22453061Smckusick 			if (error =
22554529Smckusick 			    VOP_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp))
22637739Smckusick 				return (error);
2277534Sroot 			entryoffsetinblock = 0;
2285972Swnj 		}
2295972Swnj 		/*
2307534Sroot 		 * If still looking for a slot, and at a DIRBLKSIZE
23116657Smckusick 		 * boundary, have to start looking for free space again.
2326571Smckusic 		 */
2337534Sroot 		if (slotstatus == NONE &&
23437739Smckusick 		    (entryoffsetinblock & (DIRBLKSIZ - 1)) == 0) {
2357534Sroot 			slotoffset = -1;
2367534Sroot 			slotfreespace = 0;
2377534Sroot 		}
2387534Sroot 		/*
23916657Smckusick 		 * Get pointer to next entry.
24016657Smckusick 		 * Full validation checks are slow, so we only check
24116657Smckusick 		 * enough to insure forward progress through the
24216657Smckusick 		 * directory. Complete checks can be run by patching
24316657Smckusick 		 * "dirchk" to be true.
2447534Sroot 		 */
24564516Sbostic 		ep = (struct direct *)((char *)bp->b_data + entryoffsetinblock);
24626273Skarels 		if (ep->d_reclen == 0 ||
24754607Smckusick 		    dirchk && ufs_dirbadentry(vdp, ep, entryoffsetinblock)) {
24837739Smckusick 			int i;
24937739Smckusick 
25052337Smckusick 			ufs_dirbad(dp, dp->i_offset, "mangled entry");
25116657Smckusick 			i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
25252337Smckusick 			dp->i_offset += i;
2537534Sroot 			entryoffsetinblock += i;
2546571Smckusic 			continue;
2556571Smckusic 		}
2567534Sroot 
2576571Smckusic 		/*
2587534Sroot 		 * If an appropriate sized slot has not yet been found,
2596571Smckusic 		 * check to see if one is available. Also accumulate space
2606571Smckusic 		 * in the current block so that we can determine if
2616571Smckusic 		 * compaction is viable.
2626571Smckusic 		 */
2637534Sroot 		if (slotstatus != FOUND) {
2647534Sroot 			int size = ep->d_reclen;
2657534Sroot 
2666571Smckusic 			if (ep->d_ino != 0)
26754607Smckusick 				size -= DIRSIZ(FSFMT(vdp), ep);
2686571Smckusic 			if (size > 0) {
2697534Sroot 				if (size >= slotneeded) {
2707534Sroot 					slotstatus = FOUND;
27152337Smckusick 					slotoffset = dp->i_offset;
2727534Sroot 					slotsize = ep->d_reclen;
2737534Sroot 				} else if (slotstatus == NONE) {
2747534Sroot 					slotfreespace += size;
2757534Sroot 					if (slotoffset == -1)
27652337Smckusick 						slotoffset = dp->i_offset;
2777534Sroot 					if (slotfreespace >= slotneeded) {
2787534Sroot 						slotstatus = COMPACT;
27952337Smckusick 						slotsize = dp->i_offset +
28016688Smckusick 						      ep->d_reclen - slotoffset;
2817534Sroot 					}
2826571Smckusic 				}
2836571Smckusic 			}
2846571Smckusic 		}
2857534Sroot 
2866571Smckusic 		/*
2877534Sroot 		 * Check for a name match.
2885972Swnj 		 */
2897534Sroot 		if (ep->d_ino) {
29054607Smckusick #			if (BYTE_ORDER == LITTLE_ENDIAN)
29154607Smckusick 				if (vdp->v_mount->mnt_maxsymlinklen > 0)
29254607Smckusick 					namlen = ep->d_namlen;
29354607Smckusick 				else
29454607Smckusick 					namlen = ep->d_type;
29554607Smckusick #			else
29654607Smckusick 				namlen = ep->d_namlen;
29754607Smckusick #			endif
29854607Smckusick 			if (namlen == cnp->cn_namelen &&
29954529Smckusick 			    !bcmp(cnp->cn_nameptr, ep->d_name,
30054607Smckusick 				(unsigned)namlen)) {
30137739Smckusick 				/*
30237739Smckusick 				 * Save directory entry's inode number and
30349738Smckusick 				 * reclen in ndp->ni_ufs area, and release
30437739Smckusick 				 * directory buffer.
30537739Smckusick 				 */
30652337Smckusick 				dp->i_ino = ep->d_ino;
30752337Smckusick 				dp->i_reclen = ep->d_reclen;
30837739Smckusick 				brelse(bp);
3097534Sroot 				goto found;
31037739Smckusick 			}
3117534Sroot 		}
31252337Smckusick 		prevoff = dp->i_offset;
31352337Smckusick 		dp->i_offset += ep->d_reclen;
3147534Sroot 		entryoffsetinblock += ep->d_reclen;
31518027Smckusick 		if (ep->d_ino)
31652337Smckusick 			enduseful = dp->i_offset;
3177534Sroot 	}
31815798Smckusick /* notfound: */
31915660Smckusick 	/*
32015798Smckusick 	 * If we started in the middle of the directory and failed
32115660Smckusick 	 * to find our target, we must check the beginning as well.
32215660Smckusick 	 */
32315660Smckusick 	if (numdirpasses == 2) {
32415660Smckusick 		numdirpasses--;
32552337Smckusick 		dp->i_offset = 0;
32637739Smckusick 		endsearch = dp->i_diroff;
32715660Smckusick 		goto searchloop;
32815660Smckusick 	}
32937739Smckusick 	if (bp != NULL)
33037739Smckusick 		brelse(bp);
3317534Sroot 	/*
3327534Sroot 	 * If creating, and at end of pathname and current
3339166Ssam 	 * directory has not been removed, then can consider
3349166Ssam 	 * allowing file to be created.
3357534Sroot 	 */
33654529Smckusick 	if ((nameiop == CREATE || nameiop == RENAME) &&
33754529Smckusick 	    (flags & ISLASTCN) && dp->i_nlink != 0) {
3385972Swnj 		/*
3397534Sroot 		 * Access for write is interpreted as allowing
3407534Sroot 		 * creation of files in the directory.
3415972Swnj 		 */
34254529Smckusick 		if (error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc))
34337739Smckusick 			return (error);
3445972Swnj 		/*
3457534Sroot 		 * Return an indication of where the new directory
3467534Sroot 		 * entry should be put.  If we didn't find a slot,
34752337Smckusick 		 * then set dp->i_count to 0 indicating
34849738Smckusick 		 * that the new slot belongs at the end of the
34949738Smckusick 		 * directory. If we found a slot, then the new entry
35052337Smckusick 		 * can be put in the range from dp->i_offset to
35152337Smckusick 		 * dp->i_offset + dp->i_count.
3525972Swnj 		 */
35315660Smckusick 		if (slotstatus == NONE) {
35452337Smckusick 			dp->i_offset = roundup(dp->i_size, DIRBLKSIZ);
35552337Smckusick 			dp->i_count = 0;
35652337Smckusick 			enduseful = dp->i_offset;
35715660Smckusick 		} else {
35852337Smckusick 			dp->i_offset = slotoffset;
35952337Smckusick 			dp->i_count = slotsize;
36018027Smckusick 			if (enduseful < slotoffset + slotsize)
36118027Smckusick 				enduseful = slotoffset + slotsize;
3625972Swnj 		}
36352337Smckusick 		dp->i_endoff = roundup(enduseful, DIRBLKSIZ);
36464599Sbostic 		dp->i_flag |= IN_CHANGE | IN_UPDATE;
3655972Swnj 		/*
3667534Sroot 		 * We return with the directory locked, so that
3677534Sroot 		 * the parameters we set up above will still be
3687534Sroot 		 * valid if we actually decide to do a direnter().
36937739Smckusick 		 * We return ni_vp == NULL to indicate that the entry
37037739Smckusick 		 * does not currently exist; we leave a pointer to
37137739Smckusick 		 * the (locked) directory inode in ndp->ni_dvp.
37249738Smckusick 		 * The pathname buffer is saved so that the name
37349738Smckusick 		 * can be obtained later.
37437739Smckusick 		 *
37537739Smckusick 		 * NB - if the directory is unlocked, then this
37637739Smckusick 		 * information cannot be used.
3775972Swnj 		 */
37854529Smckusick 		cnp->cn_flags |= SAVENAME;
37937739Smckusick 		if (!lockparent)
38056803Smckusick 			VOP_UNLOCK(vdp);
38152799Smckusick 		return (EJUSTRETURN);
3827534Sroot 	}
38338770Smckusick 	/*
38438770Smckusick 	 * Insert name into cache (as non-existent) if appropriate.
38538770Smckusick 	 */
38655182Smckusick 	if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE)
38754529Smckusick 		cache_enter(vdp, *vpp, cnp);
38837739Smckusick 	return (ENOENT);
38937739Smckusick 
3907534Sroot found:
39115798Smckusick 	if (numdirpasses == 2)
39215798Smckusick 		nchstats.ncs_pass2++;
3937534Sroot 	/*
3947534Sroot 	 * Check that directory length properly reflects presence
3957534Sroot 	 * of this entry.
3967534Sroot 	 */
39754607Smckusick 	if (entryoffsetinblock + DIRSIZ(FSFMT(vdp), ep) > dp->i_size) {
39852337Smckusick 		ufs_dirbad(dp, dp->i_offset, "i_size too small");
39954607Smckusick 		dp->i_size = entryoffsetinblock + DIRSIZ(FSFMT(vdp), ep);
40064599Sbostic 		dp->i_flag |= IN_CHANGE | IN_UPDATE;
4017534Sroot 	}
4027534Sroot 
4037534Sroot 	/*
40415660Smckusick 	 * Found component in pathname.
40515798Smckusick 	 * If the final component of path name, save information
40615660Smckusick 	 * in the cache as to where the entry was found.
4077534Sroot 	 */
40854529Smckusick 	if ((flags & ISLASTCN) && nameiop == LOOKUP)
40952337Smckusick 		dp->i_diroff = dp->i_offset &~ (DIRBLKSIZ - 1);
4107534Sroot 
4117534Sroot 	/*
4127534Sroot 	 * If deleting, and at end of pathname, return
4137534Sroot 	 * parameters which can be used to remove file.
41437739Smckusick 	 * If the wantparent flag isn't set, we return only
41537739Smckusick 	 * the directory (in ndp->ni_dvp), otherwise we go
4169166Ssam 	 * on and lock the inode, being careful with ".".
4177534Sroot 	 */
41854529Smckusick 	if (nameiop == DELETE && (flags & ISLASTCN)) {
4197534Sroot 		/*
4207534Sroot 		 * Write access to directory required to delete files.
4217534Sroot 		 */
42254529Smckusick 		if (error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc))
42337739Smckusick 			return (error);
4247534Sroot 		/*
42552337Smckusick 		 * Return pointer to current entry in dp->i_offset,
4267534Sroot 		 * and distance past previous entry (if there
42752337Smckusick 		 * is a previous entry in this block) in dp->i_count.
42838770Smckusick 		 * Save directory inode pointer in ndp->ni_dvp for dirremove().
4297534Sroot 		 */
43052337Smckusick 		if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0)
43152337Smckusick 			dp->i_count = 0;
4327534Sroot 		else
43352337Smckusick 			dp->i_count = dp->i_offset - prevoff;
43452337Smckusick 		if (dp->i_number == dp->i_ino) {
43552337Smckusick 			VREF(vdp);
43654529Smckusick 			*vpp = vdp;
43741308Smckusick 			return (0);
4389166Ssam 		}
43954649Smckusick 		if (error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp))
44041308Smckusick 			return (error);
44141308Smckusick 		/*
44241308Smckusick 		 * If directory is "sticky", then user must own
44341308Smckusick 		 * the directory, or the file in it, else she
44441308Smckusick 		 * may not delete it (unless she's root). This
44541308Smckusick 		 * implements append-only directories.
44641308Smckusick 		 */
44741308Smckusick 		if ((dp->i_mode & ISVTX) &&
44854529Smckusick 		    cred->cr_uid != 0 &&
44954529Smckusick 		    cred->cr_uid != dp->i_uid &&
45054529Smckusick 		    VTOI(tdp)->i_uid != cred->cr_uid) {
45151549Smckusick 			vput(tdp);
45241308Smckusick 			return (EPERM);
45341308Smckusick 		}
45454529Smckusick 		*vpp = tdp;
45541308Smckusick 		if (!lockparent)
45656803Smckusick 			VOP_UNLOCK(vdp);
45737739Smckusick 		return (0);
4587534Sroot 	}
4597534Sroot 
4607534Sroot 	/*
46137739Smckusick 	 * If rewriting (RENAME), return the inode and the
4629166Ssam 	 * information required to rewrite the present directory
4639166Ssam 	 * Must get inode of directory entry to verify it's a
46437739Smckusick 	 * regular file, or empty directory.
4659166Ssam 	 */
46654529Smckusick 	if (nameiop == RENAME && wantparent &&
46754529Smckusick 	    (flags & ISLASTCN)) {
46854529Smckusick 		if (error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc))
46937739Smckusick 			return (error);
4709166Ssam 		/*
47137739Smckusick 		 * Careful about locking second inode.
47237739Smckusick 		 * This can only occur if the target is ".".
4739166Ssam 		 */
47452337Smckusick 		if (dp->i_number == dp->i_ino)
47537739Smckusick 			return (EISDIR);
47654649Smckusick 		if (error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp))
47737739Smckusick 			return (error);
47854529Smckusick 		*vpp = tdp;
47954529Smckusick 		cnp->cn_flags |= SAVENAME;
48037739Smckusick 		if (!lockparent)
48156803Smckusick 			VOP_UNLOCK(vdp);
48237739Smckusick 		return (0);
4839166Ssam 	}
4849166Ssam 
4859166Ssam 	/*
48656803Smckusick 	 * Step through the translation in the name.  We do not `vput' the
48737739Smckusick 	 * directory because we may need it again if a symbolic link
48812011Smckusick 	 * is relative to the current directory.  Instead we save it
48912011Smckusick 	 * unlocked as "pdp".  We must get the target inode before unlocking
49012011Smckusick 	 * the directory to insure that the inode will not be removed
49112011Smckusick 	 * before we get it.  We prevent deadlock by always fetching
49212011Smckusick 	 * inodes from the root, moving down the directory tree. Thus
49312011Smckusick 	 * when following backward pointers ".." we must unlock the
49412011Smckusick 	 * parent directory before getting the requested directory.
49512011Smckusick 	 * There is a potential race condition here if both the current
49666595Sbostic 	 * and parent directories are removed before the VFS_VGET for the
49712011Smckusick 	 * inode associated with ".." returns.  We hope that this occurs
49812011Smckusick 	 * infrequently since we cannot avoid this race condition without
49912492Ssam 	 * implementing a sophisticated deadlock detection algorithm.
50012011Smckusick 	 * Note also that this simple deadlock detection scheme will not
50112011Smckusick 	 * work if the file system has any hard links other than ".."
50212011Smckusick 	 * that point backwards in the directory structure.
5037534Sroot 	 */
50456803Smckusick 	pdp = vdp;
50554529Smckusick 	if (flags & ISDOTDOT) {
50656803Smckusick 		VOP_UNLOCK(pdp);	/* race to get the inode */
50754649Smckusick 		if (error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) {
50856803Smckusick 			VOP_LOCK(pdp);
50937739Smckusick 			return (error);
51037739Smckusick 		}
51156803Smckusick 		if (lockparent && (flags & ISLASTCN) &&
51256803Smckusick 		    (error = VOP_LOCK(pdp))) {
51356803Smckusick 			vput(tdp);
51456803Smckusick 			return (error);
51556803Smckusick 		}
51654529Smckusick 		*vpp = tdp;
51752337Smckusick 	} else if (dp->i_number == dp->i_ino) {
51854529Smckusick 		VREF(vdp);	/* we want ourself, ie "." */
51954529Smckusick 		*vpp = vdp;
52012011Smckusick 	} else {
52154649Smckusick 		if (error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp))
52237739Smckusick 			return (error);
52354529Smckusick 		if (!lockparent || !(flags & ISLASTCN))
52456803Smckusick 			VOP_UNLOCK(pdp);
52554529Smckusick 		*vpp = tdp;
52612011Smckusick 	}
52715798Smckusick 
52815798Smckusick 	/*
52926273Skarels 	 * Insert name into cache if appropriate.
53015798Smckusick 	 */
53155182Smckusick 	if (cnp->cn_flags & MAKEENTRY)
53254529Smckusick 		cache_enter(vdp, *vpp, cnp);
53337739Smckusick 	return (0);
53430Sbill }
53530Sbill 
53651511Sbostic void
ufs_dirbad(ip,offset,how)53751511Sbostic ufs_dirbad(ip, offset, how)
5387534Sroot 	struct inode *ip;
53953061Smckusick 	doff_t offset;
5407534Sroot 	char *how;
5417534Sroot {
54251511Sbostic 	struct mount *mp;
5437534Sroot 
54451511Sbostic 	mp = ITOV(ip)->v_mount;
54551511Sbostic 	(void)printf("%s: bad dir ino %d at offset %d: %s\n",
54651511Sbostic 	    mp->mnt_stat.f_mntonname, ip->i_number, offset, how);
54751511Sbostic 	if ((mp->mnt_stat.f_flags & MNT_RDONLY) == 0)
54848058Smckusick 		panic("bad dir");
5497534Sroot }
5507534Sroot 
55116657Smckusick /*
55216657Smckusick  * Do consistency checking on a directory entry:
55316657Smckusick  *	record length must be multiple of 4
55416657Smckusick  *	entry must fit in rest of its DIRBLKSIZ block
55516657Smckusick  *	record must be large enough to contain entry
55616657Smckusick  *	name is not longer than MAXNAMLEN
55716657Smckusick  *	name must be as long as advertised, and null terminated
55816657Smckusick  */
55951511Sbostic int
ufs_dirbadentry(dp,ep,entryoffsetinblock)56054607Smckusick ufs_dirbadentry(dp, ep, entryoffsetinblock)
56154607Smckusick 	struct vnode *dp;
5627534Sroot 	register struct direct *ep;
56316657Smckusick 	int entryoffsetinblock;
5647534Sroot {
5657534Sroot 	register int i;
56654607Smckusick 	int namlen;
5677534Sroot 
56854607Smckusick #	if (BYTE_ORDER == LITTLE_ENDIAN)
56954607Smckusick 		if (dp->v_mount->mnt_maxsymlinklen > 0)
57054607Smckusick 			namlen = ep->d_namlen;
57154607Smckusick 		else
57254607Smckusick 			namlen = ep->d_type;
57354607Smckusick #	else
57454607Smckusick 		namlen = ep->d_namlen;
57554607Smckusick #	endif
57626273Skarels 	if ((ep->d_reclen & 0x3) != 0 ||
57716657Smckusick 	    ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
57854607Smckusick 	    ep->d_reclen < DIRSIZ(FSFMT(dp), ep) || namlen > MAXNAMLEN) {
57951511Sbostic 		/*return (1); */
58051511Sbostic 		printf("First bad\n");
58151511Sbostic 		goto bad;
58251511Sbostic 	}
583*67266Smckusick 	if (ep->d_ino == 0)
584*67266Smckusick 		return (0);
58554607Smckusick 	for (i = 0; i < namlen; i++)
58651511Sbostic 		if (ep->d_name[i] == '\0') {
58751511Sbostic 			/*return (1); */
58851511Sbostic 			printf("Second bad\n");
58951511Sbostic 			goto bad;
59051511Sbostic 	}
59151511Sbostic 	if (ep->d_name[i])
59251511Sbostic 		goto bad;
5937534Sroot 	return (ep->d_name[i]);
59451511Sbostic bad:
59551511Sbostic 	return(1);
5967534Sroot }
5977534Sroot 
59830Sbill /*
5997534Sroot  * Write a directory entry after a call to namei, using the parameters
60049738Smckusick  * that it left in nameidata.  The argument ip is the inode which the new
60152337Smckusick  * directory entry will refer to.  Dvp is a pointer to the directory to
60252337Smckusick  * be written, which was left locked by namei. Remaining parameters
60352337Smckusick  * (dp->i_offset, dp->i_count) indicate how the space for the new
60452337Smckusick  * entry is to be obtained.
6057534Sroot  */
60651511Sbostic int
ufs_direnter(ip,dvp,cnp)60752292Sheideman ufs_direnter(ip, dvp, cnp)
6087534Sroot 	struct inode *ip;
60952232Sheideman 	struct vnode *dvp;
61052232Sheideman 	register struct componentname *cnp;
6115972Swnj {
6127534Sroot 	register struct direct *ep, *nep;
61351511Sbostic 	register struct inode *dp;
6147534Sroot 	struct buf *bp;
61551511Sbostic 	struct direct newdir;
61651511Sbostic 	struct iovec aiov;
61751511Sbostic 	struct uio auio;
6188631Sroot 	u_int dsize;
61951511Sbostic 	int error, loc, newentrysize, spacefree;
6207534Sroot 	char *dirbuf;
6215972Swnj 
62249738Smckusick #ifdef DIAGNOSTIC
62352232Sheideman 	if ((cnp->cn_flags & SAVENAME) == 0)
62449738Smckusick 		panic("direnter: missing name");
62549738Smckusick #endif
62651549Smckusick 	dp = VTOI(dvp);
62749738Smckusick 	newdir.d_ino = ip->i_number;
62852232Sheideman 	newdir.d_namlen = cnp->cn_namelen;
62952232Sheideman 	bcopy(cnp->cn_nameptr, newdir.d_name, (unsigned)cnp->cn_namelen + 1);
63054607Smckusick 	if (dvp->v_mount->mnt_maxsymlinklen > 0)
63154607Smckusick 		newdir.d_type = IFTODT(ip->i_mode);
63254607Smckusick 	else {
63354607Smckusick 		newdir.d_type = 0;
63454607Smckusick #		if (BYTE_ORDER == LITTLE_ENDIAN)
63554607Smckusick 			{ u_char tmp = newdir.d_namlen;
63654607Smckusick 			newdir.d_namlen = newdir.d_type;
63754607Smckusick 			newdir.d_type = tmp; }
63854607Smckusick #		endif
63954607Smckusick 	}
64054607Smckusick 	newentrysize = DIRSIZ(FSFMT(dvp), &newdir);
64152337Smckusick 	if (dp->i_count == 0) {
6427534Sroot 		/*
64352337Smckusick 		 * If dp->i_count is 0, then namei could find no
64452337Smckusick 		 * space in the directory. Here, dp->i_offset will
64549738Smckusick 		 * be on a directory block boundary and we will write the
64649738Smckusick 		 * new entry into a fresh block.
6477534Sroot 		 */
64852337Smckusick 		if (dp->i_offset & (DIRBLKSIZ - 1))
64964599Sbostic 			panic("ufs_direnter: newblk");
65052337Smckusick 		auio.uio_offset = dp->i_offset;
65149738Smckusick 		newdir.d_reclen = DIRBLKSIZ;
65249738Smckusick 		auio.uio_resid = newentrysize;
65349738Smckusick 		aiov.iov_len = newentrysize;
65449738Smckusick 		aiov.iov_base = (caddr_t)&newdir;
65549738Smckusick 		auio.uio_iov = &aiov;
65649738Smckusick 		auio.uio_iovcnt = 1;
65749738Smckusick 		auio.uio_rw = UIO_WRITE;
65849738Smckusick 		auio.uio_segflg = UIO_SYSSPACE;
65949738Smckusick 		auio.uio_procp = (struct proc *)0;
66052232Sheideman 		error = VOP_WRITE(dvp, &auio, IO_SYNC, cnp->cn_cred);
66151549Smckusick 		if (DIRBLKSIZ >
66251944Smckusick 		    VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_bsize)
66351511Sbostic 			/* XXX should grow with balloc() */
66451511Sbostic 			panic("ufs_direnter: frag size");
66551511Sbostic 		else if (!error) {
66618104Smckusick 			dp->i_size = roundup(dp->i_size, DIRBLKSIZ);
66764599Sbostic 			dp->i_flag |= IN_CHANGE;
66843288Smckusick 		}
66910849Ssam 		return (error);
6707534Sroot 	}
6717534Sroot 
6727534Sroot 	/*
67352337Smckusick 	 * If dp->i_count is non-zero, then namei found space
67452337Smckusick 	 * for the new entry in the range dp->i_offset to
67552337Smckusick 	 * dp->i_offset + dp->i_count in the directory.
67649738Smckusick 	 * To use this space, we may have to compact the entries located
67749738Smckusick 	 * there, by copying them together towards the beginning of the
67849738Smckusick 	 * block, leaving the free space in one usable chunk at the end.
6797534Sroot 	 */
6807534Sroot 
6817534Sroot 	/*
6827534Sroot 	 * Increase size of directory if entry eats into new space.
6837534Sroot 	 * This should never push the size past a new multiple of
6847534Sroot 	 * DIRBLKSIZE.
68518104Smckusick 	 *
68618104Smckusick 	 * N.B. - THIS IS AN ARTIFACT OF 4.2 AND SHOULD NEVER HAPPEN.
6877534Sroot 	 */
68852337Smckusick 	if (dp->i_offset + dp->i_count > dp->i_size)
68952337Smckusick 		dp->i_size = dp->i_offset + dp->i_count;
6907534Sroot 	/*
69137739Smckusick 	 * Get the block containing the space for the new directory entry.
6927534Sroot 	 */
69353061Smckusick 	if (error = VOP_BLKATOFF(dvp, (off_t)dp->i_offset, &dirbuf, &bp))
69437739Smckusick 		return (error);
6957534Sroot 	/*
69649738Smckusick 	 * Find space for the new entry. In the simple case, the entry at
69749738Smckusick 	 * offset base will have the space. If it does not, then namei
69852337Smckusick 	 * arranged that compacting the region dp->i_offset to
69952337Smckusick 	 * dp->i_offset + dp->i_count would yield the
70049738Smckusick 	 * space.
7017534Sroot 	 */
7027534Sroot 	ep = (struct direct *)dirbuf;
70354607Smckusick 	dsize = DIRSIZ(FSFMT(dvp), ep);
70411639Ssam 	spacefree = ep->d_reclen - dsize;
70552337Smckusick 	for (loc = ep->d_reclen; loc < dp->i_count; ) {
7067534Sroot 		nep = (struct direct *)(dirbuf + loc);
7077534Sroot 		if (ep->d_ino) {
7087534Sroot 			/* trim the existing slot */
7097534Sroot 			ep->d_reclen = dsize;
7107534Sroot 			ep = (struct direct *)((char *)ep + dsize);
7117534Sroot 		} else {
7127534Sroot 			/* overwrite; nothing there; header is ours */
71337739Smckusick 			spacefree += dsize;
7147534Sroot 		}
71554607Smckusick 		dsize = DIRSIZ(FSFMT(dvp), nep);
71611639Ssam 		spacefree += nep->d_reclen - dsize;
7177534Sroot 		loc += nep->d_reclen;
7187825Sroot 		bcopy((caddr_t)nep, (caddr_t)ep, dsize);
7197534Sroot 	}
7207534Sroot 	/*
7217534Sroot 	 * Update the pointer fields in the previous entry (if any),
7227534Sroot 	 * copy in the new entry, and write out the block.
7237534Sroot 	 */
7247534Sroot 	if (ep->d_ino == 0) {
72511639Ssam 		if (spacefree + dsize < newentrysize)
72664599Sbostic 			panic("ufs_direnter: compact1");
72749738Smckusick 		newdir.d_reclen = spacefree + dsize;
7287534Sroot 	} else {
72911639Ssam 		if (spacefree < newentrysize)
73064599Sbostic 			panic("ufs_direnter: compact2");
73149738Smckusick 		newdir.d_reclen = spacefree;
7327534Sroot 		ep->d_reclen = dsize;
7337534Sroot 		ep = (struct direct *)((char *)ep + dsize);
7347534Sroot 	}
73549738Smckusick 	bcopy((caddr_t)&newdir, (caddr_t)ep, (u_int)newentrysize);
73651549Smckusick 	error = VOP_BWRITE(bp);
73764599Sbostic 	dp->i_flag |= IN_CHANGE | IN_UPDATE;
73852337Smckusick 	if (!error && dp->i_endoff && dp->i_endoff < dp->i_size)
73954453Smckusick 		error = VOP_TRUNCATE(dvp, (off_t)dp->i_endoff, IO_SYNC,
74054453Smckusick 		    cnp->cn_cred, cnp->cn_proc);
74110849Ssam 	return (error);
7425972Swnj }
7436571Smckusic 
7449166Ssam /*
74538252Smckusick  * Remove a directory entry after a call to namei, using
74638252Smckusick  * the parameters which it left in nameidata. The entry
74752337Smckusick  * dp->i_offset contains the offset into the directory of the
74852337Smckusick  * entry to be eliminated.  The dp->i_count field contains the
7499166Ssam  * size of the previous record in the directory.  If this
7509166Ssam  * is 0, the first entry is being deleted, so we need only
7519166Ssam  * zero the inode number to mark the entry as free.  If the
75249738Smckusick  * entry is not the first in the directory, we must reclaim
7539166Ssam  * the space of the now empty record by adding the record size
7549166Ssam  * to the size of the previous entry.
7559166Ssam  */
75651511Sbostic int
ufs_dirremove(dvp,cnp)75752292Sheideman ufs_dirremove(dvp, cnp)
75852232Sheideman 	struct vnode *dvp;
75952232Sheideman 	struct componentname *cnp;
7606571Smckusic {
76151511Sbostic 	register struct inode *dp;
7627534Sroot 	struct direct *ep;
76337739Smckusick 	struct buf *bp;
76437739Smckusick 	int error;
7656571Smckusic 
76652232Sheideman 	dp = VTOI(dvp);
76752337Smckusick 	if (dp->i_count == 0) {
7687534Sroot 		/*
7697534Sroot 		 * First entry in block: set d_ino to zero.
7707534Sroot 		 */
77153061Smckusick 		if (error =
77253061Smckusick 		    VOP_BLKATOFF(dvp, (off_t)dp->i_offset, (char **)&ep, &bp))
77337739Smckusick 			return (error);
77449738Smckusick 		ep->d_ino = 0;
77551549Smckusick 		error = VOP_BWRITE(bp);
77664599Sbostic 		dp->i_flag |= IN_CHANGE | IN_UPDATE;
77749738Smckusick 		return (error);
7787534Sroot 	}
77949738Smckusick 	/*
78049738Smckusick 	 * Collapse new free space into previous entry.
78149738Smckusick 	 */
78253061Smckusick 	if (error = VOP_BLKATOFF(dvp, (off_t)(dp->i_offset - dp->i_count),
78353061Smckusick 	    (char **)&ep, &bp))
78449738Smckusick 		return (error);
78552337Smckusick 	ep->d_reclen += dp->i_reclen;
78651549Smckusick 	error = VOP_BWRITE(bp);
78764599Sbostic 	dp->i_flag |= IN_CHANGE | IN_UPDATE;
78837739Smckusick 	return (error);
7896571Smckusic }
7907534Sroot 
7917605Ssam /*
7929166Ssam  * Rewrite an existing directory entry to point at the inode
7939166Ssam  * supplied.  The parameters describing the directory entry are
7949166Ssam  * set up by a call to namei.
7959166Ssam  */
79651511Sbostic int
ufs_dirrewrite(dp,ip,cnp)79752232Sheideman ufs_dirrewrite(dp, ip, cnp)
7989166Ssam 	struct inode *dp, *ip;
79952232Sheideman 	struct componentname *cnp;
8009166Ssam {
80151511Sbostic 	struct buf *bp;
80249738Smckusick 	struct direct *ep;
80354607Smckusick 	struct vnode *vdp = ITOV(dp);
80449738Smckusick 	int error;
8059166Ssam 
80654607Smckusick 	if (error = VOP_BLKATOFF(vdp, (off_t)dp->i_offset, (char **)&ep, &bp))
80749738Smckusick 		return (error);
80849738Smckusick 	ep->d_ino = ip->i_number;
80954607Smckusick 	if (vdp->v_mount->mnt_maxsymlinklen > 0)
81054607Smckusick 		ep->d_type = IFTODT(ip->i_mode);
81151549Smckusick 	error = VOP_BWRITE(bp);
81264599Sbostic 	dp->i_flag |= IN_CHANGE | IN_UPDATE;
81349738Smckusick 	return (error);
8149166Ssam }
8159166Ssam 
8169166Ssam /*
8179166Ssam  * Check if a directory is empty or not.
8189166Ssam  * Inode supplied must be locked.
81912817Ssam  *
82012817Ssam  * Using a struct dirtemplate here is not precisely
82112817Ssam  * what we want, but better than using a struct direct.
82212817Ssam  *
82312817Ssam  * NB: does not handle corrupted directories.
8249166Ssam  */
82551511Sbostic int
ufs_dirempty(ip,parentino,cred)82651511Sbostic ufs_dirempty(ip, parentino, cred)
8279863Ssam 	register struct inode *ip;
82816777Smckusick 	ino_t parentino;
82937739Smckusick 	struct ucred *cred;
8309166Ssam {
8319166Ssam 	register off_t off;
83212817Ssam 	struct dirtemplate dbuf;
83312817Ssam 	register struct direct *dp = (struct direct *)&dbuf;
83454607Smckusick 	int error, count, namlen;
83512817Ssam #define	MINDIRSIZ (sizeof (struct dirtemplate) / 2)
8369166Ssam 
8379166Ssam 	for (off = 0; off < ip->i_size; off += dp->d_reclen) {
83848035Smckusick 		error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)dp, MINDIRSIZ, off,
83948035Smckusick 		   UIO_SYSSPACE, IO_NODELOCKED, cred, &count, (struct proc *)0);
84012817Ssam 		/*
84112817Ssam 		 * Since we read MINDIRSIZ, residual must
84212817Ssam 		 * be 0 unless we're at end of file.
84312817Ssam 		 */
84412817Ssam 		if (error || count != 0)
8459166Ssam 			return (0);
84624534Smckusick 		/* avoid infinite loops */
84726273Skarels 		if (dp->d_reclen == 0)
84824534Smckusick 			return (0);
84912817Ssam 		/* skip empty entries */
8509166Ssam 		if (dp->d_ino == 0)
8519166Ssam 			continue;
85212817Ssam 		/* accept only "." and ".." */
85354607Smckusick #		if (BYTE_ORDER == LITTLE_ENDIAN)
85454607Smckusick 			if (ITOV(ip)->v_mount->mnt_maxsymlinklen > 0)
85554607Smckusick 				namlen = dp->d_namlen;
85654607Smckusick 			else
85754607Smckusick 				namlen = dp->d_type;
85854607Smckusick #		else
85954607Smckusick 			namlen = dp->d_namlen;
86054607Smckusick #		endif
86154607Smckusick 		if (namlen > 2)
86212817Ssam 			return (0);
8639166Ssam 		if (dp->d_name[0] != '.')
8649166Ssam 			return (0);
86512817Ssam 		/*
86654607Smckusick 		 * At this point namlen must be 1 or 2.
86712817Ssam 		 * 1 implies ".", 2 implies ".." if second
86812817Ssam 		 * char is also "."
86912817Ssam 		 */
87054607Smckusick 		if (namlen == 1)
8719166Ssam 			continue;
87216777Smckusick 		if (dp->d_name[1] == '.' && dp->d_ino == parentino)
87316777Smckusick 			continue;
8749166Ssam 		return (0);
8759166Ssam 	}
8769166Ssam 	return (1);
8779166Ssam }
87812815Smckusick 
87912815Smckusick /*
88012815Smckusick  * Check if source directory is in the path of the target directory.
88112815Smckusick  * Target is supplied locked, source is unlocked.
88256803Smckusick  * The target is always vput before returning.
88312815Smckusick  */
88451511Sbostic int
ufs_checkpath(source,target,cred)88551511Sbostic ufs_checkpath(source, target, cred)
88612815Smckusick 	struct inode *source, *target;
88737739Smckusick 	struct ucred *cred;
88812815Smckusick {
88951549Smckusick 	struct vnode *vp;
89054607Smckusick 	int error, rootino, namlen;
89156803Smckusick 	struct dirtemplate dirbuf;
89212815Smckusick 
89356803Smckusick 	vp = ITOV(target);
89456803Smckusick 	if (target->i_number == source->i_number) {
89512815Smckusick 		error = EEXIST;
89612815Smckusick 		goto out;
89712815Smckusick 	}
89851511Sbostic 	rootino = ROOTINO;
89951511Sbostic 	error = 0;
90056803Smckusick 	if (target->i_number == rootino)
90112815Smckusick 		goto out;
90212815Smckusick 
90312815Smckusick 	for (;;) {
90456803Smckusick 		if (vp->v_type != VDIR) {
90512815Smckusick 			error = ENOTDIR;
90612815Smckusick 			break;
90712815Smckusick 		}
90851549Smckusick 		error = vn_rdwr(UIO_READ, vp, (caddr_t)&dirbuf,
90937739Smckusick 			sizeof (struct dirtemplate), (off_t)0, UIO_SYSSPACE,
91048035Smckusick 			IO_NODELOCKED, cred, (int *)0, (struct proc *)0);
91112815Smckusick 		if (error != 0)
91212815Smckusick 			break;
91354607Smckusick #		if (BYTE_ORDER == LITTLE_ENDIAN)
91454607Smckusick 			if (vp->v_mount->mnt_maxsymlinklen > 0)
91554607Smckusick 				namlen = dirbuf.dotdot_namlen;
91654607Smckusick 			else
91754607Smckusick 				namlen = dirbuf.dotdot_type;
91854607Smckusick #		else
91954607Smckusick 			namlen = dirbuf.dotdot_namlen;
92054607Smckusick #		endif
92154607Smckusick 		if (namlen != 2 ||
92216658Smckusick 		    dirbuf.dotdot_name[0] != '.' ||
92316658Smckusick 		    dirbuf.dotdot_name[1] != '.') {
92412815Smckusick 			error = ENOTDIR;
92512815Smckusick 			break;
92612815Smckusick 		}
92712815Smckusick 		if (dirbuf.dotdot_ino == source->i_number) {
92812815Smckusick 			error = EINVAL;
92912815Smckusick 			break;
93012815Smckusick 		}
93151511Sbostic 		if (dirbuf.dotdot_ino == rootino)
93212815Smckusick 			break;
93356803Smckusick 		vput(vp);
93456803Smckusick 		if (error = VFS_VGET(vp->v_mount, dirbuf.dotdot_ino, &vp)) {
93556803Smckusick 			vp = NULL;
93612815Smckusick 			break;
93756803Smckusick 		}
93812815Smckusick 	}
93912815Smckusick 
94012815Smckusick out:
94112815Smckusick 	if (error == ENOTDIR)
94212815Smckusick 		printf("checkpath: .. not a directory\n");
94356803Smckusick 	if (vp != NULL)
94456803Smckusick 		vput(vp);
94512815Smckusick 	return (error);
94612815Smckusick }
947