/*
 * namei code for fs filesystem
 */

#include "sys/param.h"
#include "sys/systm.h"
#include "sys/inode.h"
#include "sys/buf.h"
#include "sys/filsys.h"
#include "sys/dir.h"
#include "sys/user.h"
#include "sys/log.h"
#include "sys/proc.h"
#include "sys/label.h"

#define isblind(ip) (((ip)->i_mode&(IFMT|ICCTYP))==(IFDIR|IBLIND))

struct dent {
	struct direct dir;
	off_t	off;
	int	nentry;
};

fsnami(p, flagp, follow)
struct nx *p;
register struct argnamei *flagp;
{
	register struct inode *dp;
	register char *cp;
	register struct buf *bp;
	register struct inode *dip;
	register int i;
	struct dent dent;
	struct inode *domkfile();
	ino_t dsearch();

	cp = p->cp;
	dp = p->dp;

	/*
	 * dp is the inode to search; cp is the pathname to find.
	 * dp must be a directory, with execute permissions.
	 * (this test is done too early; it forbids a filesystem
	 * consisting of a single file with i-number ROOTINO)
	 */

/* dirloop: secure unix.  see outmore below. */
	if((dp->i_mode&IFMT) == IFDIR) {
		if(isblind(dp)==0 && labRchk(dp)==0)
			goto outnull;
	} else {
		u.u_error = ENOTDIR;
		goto outnull;
	}
	if (access(dp, IEXEC))
		goto outnull;
	for (i=0; *cp!='\0' && *cp!='/'; i++) {
		if (i >= DIRSIZ) {
			u.u_error = ENOENT;
			goto outnull;
		}
		dent.dir.d_name[i] = *cp++;
	}
	while (i < DIRSIZ)
		dent.dir.d_name[i++] = '\0';
	while (*cp == '/')
		cp++;
	if (dent.dir.d_name[0] == '\0') {	/* null name, e.g. "/" or "" */
		if (flagp->flag != NI_SEARCH) {
			u.u_error = ENOENT;
			goto outnull;
		}
		/*
		 * should pass NI_CREAT too, but dp is known to be a directory
		 */
		goto out;
	}
	u.u_segflg = SEGSYS;	/* for various routines that write directories */
	if (dsearch(dp, &dent) == 0) {
		if (u.u_error)
			goto outnull;
		/*
		 * name not found
		 * if we wanted to create or link, that's OK
		 */
		if (*cp == 0) switch (flagp->flag) {
		case NI_CREAT:
		case NI_NXCREAT:
		case NI_MKDIR:
		case NI_LINK:
			dip = dp;
			dp = domkfile(dip, flagp, &dent);
			iput(dip);
			goto out;
		}
		u.u_error = ENOENT;
		goto outnull;
	}
	/*
	 * name found
	 * did we want to remove it?
	 * (do before ".." check)
	 */
	if(flagp->flag == NI_DEL && *cp == 0) {
		dormfile(dp, &dent);
		goto outnull;
	}
	if(flagp->flag == NI_RMDIR && *cp == 0) {
		dormdir(dp, &dent);
		goto outnull;
	}
	/*
	 * special code for ".." in a root directory
	 */
	if (dent.dir.d_name[0] == '.'
	&&  dent.dir.d_name[1] == '.'
	&&  dent.dir.d_name[2] == '\0') {
		if (dp == rootdir || dp == u.u_rdir)
			goto dirloop;
		if (dent.dir.d_ino == ROOTINO && dp->i_number == ROOTINO) {
			dip = dp->i_mpoint;
			i = dp->i_fstyp;
			iput(dp);
			dp = dip;
			plock(dp);
			dp->i_count++;
			while (*--cp == '/')
				;
			cp--;		/* skip over ".." and / */
			if (dp->i_fstyp != i)
				goto outmore;
			goto dirloop;
		}
	}
	/*
	 * fetch the inode for the filename we found
	 */
	dip = dp;
	prele(dip);
	dp = iget(dip, dip->i_dev, dent.dir.d_ino);
	if (dp == NULL) {
		idec(dip);
		goto out;	/* sic */
	}
	if (dp->i_count == 1 && fsiread(dip, dp) < 0) {
		idec(dip);
		dp = NULL;	/* fsiread iput it */
		goto out;
	}
	if(dip->i_fstyp != dp->i_fstyp) {
		idec(dip);
		goto outmore;
	}
	/*
	 * symlink?  If so and if wanted, follow.
	 */
	if ((dp->i_mode&IFMT)==IFLNK && (follow || *cp!='\0')) {
		char *ocp;

		ocp = cp;
		while (*cp++)
			;
		i = cp - ocp;		/* strlen(ocp) + 1 for NUL */
		if (dp->i_size > BSIZE(dp->i_dev)
		||  dp->i_size + 1 + i > p->nlen	/* +1 for '/' */
		||  ++p->nlink>8) {
			u.u_error = ELOOP;
			idec(dip);
			goto outnull;
		}
		cp = p->nbuf;
		if (i == 1)	/* empty pathname, just put NUL there */
			cp[dp->i_size] = 0;
		else {
			bcopy(ocp, cp + dp->i_size + 1, i);	/* remaining pathname */
			cp[dp->i_size] = '/';
		}
		bp = bread(dp->i_dev, bmap(dp, (daddr_t)0, B_READ));
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			idec(dip);
			goto outnull;
		}
		bcopy(bp->b_un.b_addr, cp, dp->i_size);	/* new prefix */
		brelse(bp);
		i = dp->i_fstyp;
		iput(dp);
		if (*cp != '/')
			dp = dip;
		else {
			idec(dip);
			while (*cp == '/')
				cp++;
			if ((dp = u.u_rdir) == NULL)
				dp = rootdir;
			plock(dp);
			dp->i_count++;
		}
		if(loggable(LN)) 
			seclog(LOG_NAMEI, 10, "Is", dp, cp);
		if (i != dp->i_fstyp)
			goto outmore;
		goto dirloop;
	}
	idec(dip);
	/*
	 * more pathname to walk?
	 */
	if (*cp)
		goto dirloop;
	/*
	 * final checks before returning:
	 * -- some operations are in error if filename exists
	 * -- save last piece of filename for accounting
	 */
	if (flagp->flag == NI_LINK || flagp->flag == NI_NXCREAT
	||  flagp->flag == NI_MKDIR) {
		u.u_error = EEXIST;
		goto outnull;
	}
	if(flagp->flag == NI_SEARCH && flagp->un.buf)
		bcopy(dent.dir.d_name, flagp->un.buf,
			MIN(flagp->len, sizeof(dent.dir.d_name)));

	/*
	 * here to return the inode in dp
	 */
out:
	p->dp = dp;
	return(0);

	/*
	 * here to discard dp and return NULL
	 */
outnull:
	iput(dp);
	p->dp = NULL;
	return (0);

	/*
	 * here if we crossed into another filesystem's domain:
	 * return dp (the first inode in that filesystem)
	 * and cp (the remainder of the pathname)
	 * and value 1, so namei knows it should continue
	 */
outmore:
	/*
	 * also get here in secure unix: force revisit of
	 * main sec lab checker in namei() loop, once per
	 * path name component.
	 */
dirloop:
	p->cp = cp;
	p->dp = dp;
	return (1);
}

/*
 * create a new file of some sort in directory dp
 *   name is in dent->d_name
 *   offset is in dent->d_off
 *   return inode if it is needed
 */
struct inode *
domkfile(dp, flagp, dent)
register struct inode *dp;
struct argnamei *flagp;
struct dent *dent;
{
	register struct inode *dip;
	struct direct x[2];
	register i;

	if(isblind(dp)==0 && labWchk(dp) ==0)
		return(NULL);
	if(access(dp, IWRITE))
		return(NULL);
	if (dp->i_nlink == 0) {
		u.u_error = EINVAL;
		return (NULL);
	}
	u.u_count = sizeof(struct direct);
	u.u_base = (caddr_t)&dent->dir;
	u.u_offset = ltoL(dent->off);
	switch(flagp->flag) {

	case NI_CREAT:	/* create a new file */
	case NI_NXCREAT:
		dip = ialloc(dp);
		if(dip == NULL)
			return(NULL);
		jl_setfix(dip->i_lab, F_LOOSE);
		if(!labWchk(dip))
			goto elab;
		dip->i_flag |= IACC|IUPD|ICHG;
		dip->i_mode = flagp->un.mode;
		if((dip->i_mode & IFMT) == 0)
			dip->i_mode |= IFREG;
		dip->i_nlink = 1;
		dip->i_uid = u.u_uid;
		dip->i_gid = dp->i_gid;
		if (u.u_uid && u.u_gid != dip->i_gid && (ICONC&~dip->i_mode ))
			dip->i_mode &= ~ISGID;
		fsiupdat(dip, &time, &time, 1);
		dent->dir.d_ino = dip->i_number;
		flagp->un.mode = ~flagp->un.mode;
		writei(dp);
		return(dip);

	case NI_MKDIR:	/* make a new directory */
		dip = ialloc(dp);
		if(dip == NULL)
			return(NULL);
		jl_setfix(dip->i_lab, F_LOOSE);
		if(!labWchk(dip))
			goto elab;
		dent->dir.d_ino = dip->i_number;
		dip->i_mode = flagp->un.mode;
		dip->i_nlink = 1;
		dip->i_uid = u.u_uid;
		dip->i_gid = dp->i_gid;
		dip->i_flag |= IACC|IUPD|ICHG;
		x[0].d_ino = dip->i_number;
		x[1].d_ino = dp->i_number;
		for(i = 0; i < DIRSIZ; i++)
			x[0].d_name[i] = x[1].d_name[i] = 0;
		x[0].d_name[0] = x[1].d_name[0] = x[1].d_name[1] = '.';
		u.u_count = sizeof(x);
		u.u_base = (caddr_t)x;
		u.u_offset = ltoL(0);
		u.u_segflg = SEGSYS;
		writei(dip);
		if (u.u_error) {
			dip->i_nlink--;
			iput(dip);
			return(NULL);
		}
		dip->i_nlink++;
		dp->i_nlink++;
		fsiupdat(dip, &time, &time, 1);
		u.u_count = sizeof(struct direct);
		u.u_base = (caddr_t)&dent->dir;
		u.u_offset = ltoL(dent->off);
		writei(dp);
		iput(dip);
		return(NULL);

	case NI_LINK:	/* make a link */
		if(dp->i_dev != flagp->un.il->i_dev) {
			u.u_error = EXDEV;
			return(NULL);
		}
		dent->dir.d_ino = flagp->un.il->i_number;
		writei(dp);
		return(NULL);
	}
	panic("domkfile");
elab:
	u.u_error = ELAB;
	dip->i_nlink = 0;
	iput(dip);
	return NULL;
}

extern short nilpofile;
struct direct niludent = {0};		/* for clearing directory ents */ 
/*
 * delete a non-directory file
 */
dormfile(dp, dent)
register struct inode *dp;
register struct dent *dent;
{
	register struct inode *dip;
	register b;

	if((b=isblind(dp)) == 0 && labWchk(dp) == 0)
		return;
	if(access(dp, IWRITE))
		return;
	if(dp->i_number == dent->dir.d_ino) {	/* for '.' */
		dip = dp;
		dp->i_count++;
	} else {
		dip = iget(dp, dp->i_dev, dent->dir.d_ino);
		if (dip == NULL)
			return;
		if (dip->i_count == 1 && fsiread(dp, dip) < 0)
			return;
	}
	if(dip->i_mpoint != dp->i_mpoint) {
		u.u_error = EBUSY;
		iput(dip);
		return;
	}
	if(labrmchk(dip,b)==0||((dip->i_mode&IFMT)==IFDIR && !suser(0))) {
		iput(dip);
		return;
	}
	if(dip->i_flag&ITEXT)
		xrele(dip);		/* free busy text */
	u.u_base = (caddr_t)&dent->dir;
	u.u_count = sizeof(struct direct);
	u.u_offset = ltoL(dent->off);
	dent->dir = niludent;
	writei(dp);		/* segflg already set*/
	dip->i_nlink--;
	dip->i_flag |= ICHG;
	iput(dip);
}

/*
 * remove a directory (fsnami)
 * dp is inode of containing directory
 * dent->dir is the entry (which exists)
 * u.u_offset is the offset in containing directory of this entry
 */
dormdir(dp, dent)
register struct inode *dp;
register struct dent *dent;
{
	register struct inode *dip;
	register int nentry;
	struct dent tdent;
	ino_t dotino, dotdotino;
	ino_t dsearch();
	int b;

	if((b=isblind(dp)) == 0 && labWchk(dp) == 0)
		return;
	if(access(dp, IWRITE))
		return;
	if(dp->i_number == dent->dir.d_ino
	 || strncmp(dent->dir.d_name, "..", DIRSIZ)==0) { /* gets "." and "" */
		u.u_error = EINVAL;
		return;
	}
	if((dip = iget(dp, dp->i_dev, dent->dir.d_ino)) == NULL)
		return;
	if(dip->i_number == ROOTINO) {
		u.u_error = EINVAL;
		iput(dip);
		return;
	}
	if(dip->i_mpoint != dp->i_mpoint) {
		u.u_error = EBUSY;
		iput(dip);
		return;
	}
	if (dip->i_count == 1 && fsiread(dp, dip) < 0)
		return;
	if(labrmchk(dip, b) == 0) {
		iput(dip);
		return;
	}
	if((dip->i_mode & IFMT) != IFDIR) {
		u.u_error = ENOTDIR;
		iput(dip);
		return;
	}
	/* search for ., .., other entries in dir */
	cpdirent("/", &tdent);		/* cannot be found; just count entries */
	dsearch(dip, &tdent);
	nentry = tdent.nentry;
	tdent.dir.d_name[0] = '.';
	if (dotino = dsearch(dip, &tdent))
		nentry--;
	tdent.dir.d_name[1] = '.';
	if(dotdotino = dsearch(dip, &tdent))
		nentry--;
	if (nentry > 0) {
		u.u_error = EHASF;	/* removing nonempty directory */
		iput(dip);
		return;
	}
	if (dotino) {
		if (dip->i_number == dotino)
			dip->i_nlink--;
		/* else error? */
	}
	if (dotdotino) {
		if (dp->i_number == dotdotino)
			dp->i_nlink--;
		/* else error? */
	}
	u.u_base = (caddr_t)&dent->dir;
	u.u_count = sizeof(struct direct);
	u.u_offset = ltoL(dent->off);
	dent->dir = niludent;
	writei(dp);
	dp->i_flag |= ICHG;
	fsiupdat(dp, &time, &time, 1);
	dip->i_nlink--;
	dip->i_flag |= ICHG;
	iput(dip);
}

cpdirent(s, dent)
register char *s;
register struct dent *dent;
{
	register char *dp = dent->dir.d_name;

	while (dp < &dent->dir.d_name[DIRSIZ]) {
		*dp++ = *s;
		if (*s)
			s++;
	}
}

/*
 * search directory ip for entry dent->dir.d_name
 *  success: return ino, leave dent->dir with copy of entry,
 *           dent->off pointing at entry
 *	     dent->nentry with count of entries
 *  fail:    return 0, leave dent->off pointing at empty slot
 */
ino_t
dsearch(ip, dent)
struct inode *ip;
struct dent *dent;
{
	register struct direct *dp, *dpe;
	register char *nm;
	register off_t off;
	struct buf *bp;
	register int bsize;
	register daddr_t n, nblock;

	dent->dir.d_ino = 0;
	nm = dent->dir.d_name;
	bsize = BSIZE(ip->i_dev);
	dent->nentry = 0;
	bp = NULL;
	dent->off = -1;
	nblock = (ip->i_size+bsize-1) / bsize;

	for (n=0, off=0; n<nblock; n++) {
		if (bp)
			brelse(bp);
		bp = bread(ip->i_dev, bmap(ip, n, B_READ));
		if (bp->b_flags & B_ERROR) {
			u.u_error = EIO;
			goto out;
		}
		dp = (struct direct *)bp->b_un.b_addr;
		dpe = &dp[min(bsize, ip->i_size-off) / sizeof(struct direct)];
		for (; dp < dpe; dp++, off+=sizeof(struct direct)) {
			if (dp->d_ino == 0) {
				if (dent->off<0)
					dent->off = off;
				continue;
			}
			dent->nentry++;
			if (nm[2]==dp->d_name[2]	/* hash */
			 && strncmp(nm, dp->d_name, DIRSIZ) == 0) {
				dent->off = off;
				dent->dir.d_ino = dp->d_ino;
				goto out;
			}
		}
	}
out:
	if (dent->off<0)
		dent->off = off;
	if (bp)
		brelse(bp);
	return(dent->dir.d_ino);
}

