#include "sys/param.h"
#include "sys/user.h"
#include "sys/inode.h"
#include "sys/conf.h"
#include "sys/label.h"

#define	INOHSZ	63
/* INOHASH can't depend on fstype as long as unmount changes fstyp to err-fs */
#define	INOHASH(dev, ino)	(((dev)+(ino))%INOHSZ)
struct inode *inohash[INOHSZ];
struct inode *ifreel;

/*
 * Initialize hash links for inodes
 * and build inode free list.
 */
ihinit()
{
	register int i;
	register struct inode *ip = inode;

	ifreel = inode;
	for (i = 0; i < inodecnt-1; i++, ip++)
		ip->i_hlink = ip + 1;
	ip->i_hlink = NULL;
	for (i = 0; i < INOHSZ; i++)
		inohash[i] = NULL;
}

/*
 * Find an inode if it is incore.
 */
struct inode *
ifind(hp, ino)
register struct inode *hp;
register ino_t ino;
{
	register struct inode *ip;

	for (ip = inohash[INOHASH(hp->i_dev,ino)]; ip; ip = ip->i_hlink)
		if (ino==ip->i_number && hp->i_dev==ip->i_dev && hp->i_fstyp==ip->i_fstyp)
			return (ip);
	return ((struct inode *)0);
}

/*
 * see if there are any inodes save the given one
 * in the same filesystem
 * used by unmount routines to decide whether fs is busy
 * try to unmount anything mounted atop this fs
 * -- botch: deeply nested filesystems may run the system
 * out of stack and cause a crash
 */

int
ifsbusy(rip)
register struct inode *rip;
{
	register struct inode *ip;

	for (ip = inode; ip < inodeNINODE; ip++) {
		if (ip->i_count == 0)
			continue;
		if (ip->i_mpoint != rip->i_mpoint)
			continue;
		if (ip->i_mroot) {	/* something mounted here? */
			ip->i_count++;
			(*fstypsw[ip->i_mroot->i_fstyp]->t_mount)((struct inode *)NULL,
				ip, 0, 0, ip->i_mroot->i_fstyp);
			if (ip->i_count == 1) {	/* just our reference left? */
				idec(ip);
				continue;
			}
			idec(ip);
		}
		if (ip != rip)
			return (1);
	}
	return (0);
}

/*
 * mark inaccessible all i-nodes in this filesystem
 * used for forced unmounting
 * any further IO through those inodes will produce an error
 * -- it's annoying that ERRFS must be manifest
 */

#define	ERRFS	5
ifsinacc(rip)
register struct inode *rip;
{
	register struct inode *ip;

	rip = rip->i_mpoint;
	for (ip = inode; ip < inodeNINODE; ip++) {
		if (ip->i_count == 0)
			continue;
		if (ip->i_mpoint != rip)
			continue;
		ip->i_fstyp = ERRFS;	/* yes, always */
		ip->i_mpoint = rootdir;		/* hmm */
		if (ip->i_mroot)
			(*fstypsw[ip->i_mroot->i_fstyp]->t_mount)((struct inode *)NULL,
				ip, 0, 0, ip->i_mroot->i_fstyp);
	}
}

#ifndef plock		/* done inline */
/*
 * Lock an inode
 * If its already locked,
 * set the WANT bit and sleep.
 */
plock(ip)
register struct inode *ip;
{

	while(ip->i_flag&ILOCK) {
		ip->i_flag |= IWANT;
		sleep((caddr_t)ip, PINOD);
	}
	ip->i_flag |= ILOCK;
}

/*
 * Unlock an inode.
 * If WANT bit is on,
 * wakeup.
 */
prele(ip)
register struct inode *ip;
{
	ip->i_flag &= ~ILOCK;
	if(ip->i_flag&IWANT) {
		ip->i_flag &= ~IWANT;
		wakeup((caddr_t)ip);
	}
}
#endif


int idebt = 0;
int idebthigh = 0;
/*
 * Look up an inode by filsys, i-number.
 * filsys is denoted by some inode in that filesystem.
 * If the inode is mounted on, perform
 * the indicated indirection.
 * If it is in core (in the inode structure),
 * honor the locking protocol.
 * If it is not in core, fill in the fields that
 * signify the inode`s `name' (number, dev, filsys, fstyp)
 * and leave the rest blank, to be filled in by the caller.
 * A `blank' inode has i_count == 1.
 * In all cases, a pointer to a locked
 * inode structure is returned.
 */
struct inode *
iget(fip, dev, ino)
register struct inode *fip;
dev_t dev;
register ino_t ino;
{
	register struct inode *ip;
	register int slot;

loop:
	slot = INOHASH(dev, ino);
	for (ip = inohash[slot]; ip; ip = ip->i_hlink) {
		if(ino == ip->i_number && dev == ip->i_dev
			&& fip->i_fstyp == ip->i_fstyp) {
mloop:
			if((ip->i_flag&ILOCK) != 0) {
				ip->i_flag |= IWANT;
				sleep((caddr_t)ip, PINOD);
				goto loop;
			}
			if(ip->i_mroot != NULL) {
				ip = ip->i_mroot;
				goto mloop;
			}
			ip->i_count++;
			if(ip->i_count==0) {
				ip->i_count--;
				u.u_error = ENFILE;
				return(NULL);
			}
			ip->i_flag |= ILOCK;
			logpoison(ip);
			return(ip);
		}
	}
	if(ifreel == NULL) {
		tablefull("inode");
		u.u_error = ENFILE;
		return(NULL);
	}
	if(++idebt > idebthigh) {
		idebthigh = idebt;
		if(idebthigh%100 == 0) printf("ihigh=%d\n", idebthigh);
	}
	ip = ifreel;
	ifreel = ip->i_hlink;
	ip->i_hlink = inohash[slot];
	inohash[slot] = ip;
	ip->i_dev = dev;
	ip->i_fstyp = fip->i_fstyp;
	ip->i_number = ino;
	ip->i_flag = ILOCK;
	ip->i_count = 1;
	ip->i_sptr = NULL;
	ip->i_mroot = NULL;
	ip->i_fr = NULL;
	ip->i_pex = NULL;
	ip->i_pexflag = 0;
	ip->i_lab = labCPX(labelbot);	/* should be NO ? */
	ip->i_ceil = labDUP(fip->i_ceil?fip->i_ceil : labelyes);
	ip->i_mpoint = fip->i_mpoint;
	return (ip);
}

/*
 * Decrement reference count of
 * an inode structure.
 * On the last reference,
 * call closei to close the file.
 * Don't unlock unless we were the only reference.
 */
idec(ip)
register struct inode *ip;
{
	register int i;
	register struct inode *jp;

	if (ip->i_count == 0)
		panic("i_count==0");
	if (ip->i_count == 1) {
		ip->i_flag |= ILOCK;
		closei(ip);
		(*fstypsw[ip->i_fstyp]->t_put)(ip);
		i = INOHASH(ip->i_dev, ip->i_number);
		if (inohash[i] == ip)
			inohash[i] = ip->i_hlink;
		else {
			for (jp = inohash[i]; jp; jp = jp->i_hlink)
				if (jp->i_hlink == ip) {
					jp->i_hlink = ip->i_hlink;
					goto done;
				}
			panic("idec");
		}
		if(ip->i_fr != NULL)
			printf("idec fr %d\n", ip->i_number);
done:
		prele(ip);
		ip->i_flag = 0;
		ip->i_number = 0;
		ip->i_lab = jfree(ip->i_lab);
		if(ip->i_ceil)
			ip->i_ceil = jfree(ip->i_ceil);
		ip->i_hlink = ifreel;
		ifreel = ip;
		idebt--;
	}
	ip->i_count--;
}

/*
 * decrement the count for a locked inode.
 * unlock.
 * perhaps this should be a macro later
 */

iput(ip)
register struct inode *ip;
{
#if NOTDEF
	if ((ip->i_flag & ILOCK) == 0)
		panic("iput");
#endif
	if (ip->i_count == 0)
		panic("i_count==0");
	if (ip->i_count == 1)
		idec(ip);	/* will take care of lock too */
	else {
		ip->i_count--;
		prele(ip);
	}
}

/*
 * Check accessed and update flags on
 * an inode structure.
 * If any is on, update the inode
 * with the current time.
 * If waitfor is given, then must insure
 * i/o order so wait for write to complete.
 */
iupdat(ip, ta, tm, waitfor)
register struct inode *ip;
time_t *ta, *tm;
int waitfor;
{

	if((ip->i_flag&(IUPD|IACC|ICHG)) != 0)
		(*fstypsw[ip->i_fstyp]->t_updat)(ip, ta, tm, waitfor);
}

/*
 * allocate an in-core-only inode with no filesystem counterpart
 * used for pipes and similar things
 * a locked inode is returned, filled in and useable,
 * but not a member of any filesystem
 */
struct inode *
iuniq(fstyp)
	int fstyp;
{
	struct inode *ip;
	static ino_t ino=0;
	ino_t inostart;
	struct inode pi;		/* primer */

	pi.i_dev = NODEV;
	pi.i_fstyp = fstyp;
	pi.i_ceil = 0;
	pi.i_mpoint = NULL;
	for (inostart = ino++; ino != inostart; ino++) {
		if (ifind(&pi, ino))	/* already in use */
			continue;
		if ((ip = iget(&pi, NODEV, ino)) == NULL)
			return (NULL);	/* no inodes, or something */
		if (ip->i_count != 1) {	/* already in use -- safety */
			iput(ip);
			continue;
		}
		ip->i_mode = IFREG | (0666 & ~u.u_cmask);
		ip->i_uid = u.u_uid;
		ip->i_gid = u.u_gid;
		ip->i_nlink = 0;
		jl_setfix(ip->i_lab, F_LOOSE);
		return(ip);
	}
	tablefull("fake inode");
	u.u_error = ENFILE;
	return(NULL);
}
