#include "../h/local.h"

#ifdef  SCCS_ID
static char SCCS_ID [] = "%Z%%M%    	%I%	%Y% %U% - %E% ";
#endif  SCCS_ID

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/mount.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/inode.h"
#include "../h/ino.h"
#include "../h/filsys.h"
#include "../h/conf.h"
#include "../h/buf.h"
#ifdef  UCB_QUOTAS
#include "../h/quota.h"
#endif  UCB_QUOTAS

#ifdef  UCB_IHASH
#define INOHSZ  64              /* must be multiple of two */
#define INOHASH(dev,ino)        (((dev) + (ino)) & (INOHSZ - 1))
struct  inode   *ihash[INOHSZ] = { 0 };
struct  inode   *ifreelist;

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

	ifreelist = &inode[0];
	for(ip = inode; ip < &inodeNINODE[-1]; ip++)
		ip->i_link = ip + 1;
	ip->i_link = (struct inode *) NULL;
	for(i = 0; i < INOHSZ; i++)
		ihash[i] = (struct inode *) NULL;
}

/*
 * Find an inode if it is incore.
 * This is the equivalent, for inodes,
 * of ``incore'' in bio.c or ``pfind'' in subr.c.
 */
struct inode *
ifind(dev, ino)
dev_t dev;
ino_t ino;
{
	register struct inode *ip;

	for (ip = ihash[INOHASH(dev,ino)]; ip != NULL;
	    ip = ip->i_link)
#ifdef  USTEST
		if (USTOI(ino)==USTOI(ip->i_number) && dev==ip->i_dev)
#else   USTEST
		if (ino==ip->i_number && dev==ip->i_dev)
#endif  USTEST
			return (ip);
	return ((struct inode *)0);
}
#endif  UCB_IHASH

/*
 * Look up an inode by device,inumber.
 * If it is in core (in the inode structure),
 * honor the locking protocol.
 * If it is not in core, read it in from the
 * specified device.
 * If the inode is mounted on, perform
 * the indicated indirection.
 * In all cases, a pointer to a locked
 * inode structure is returned.
 *
 * printf warning: no inodes -- if the inode
 *	structure is full
 * panic: no imt -- if the mounted file
 *	system is not in the mount table.
 *	"cannot happen"
 */
struct inode *
iget(dev, ino)
dev_t dev;
ino_t ino;
{
	register struct inode *ip;
	register struct mount *mp;
#ifdef  UCB_IHASH
	register int slot;
#else   UCB_IHASH
	register struct inode *oip;
#endif  UCB_IHASH
	register struct buf *bp;
	register struct dinode *dp;

trace(0x08, "iget", (dev<<16)|ino);
loop:
	slot = INOHASH(dev, ino);
	ip = ihash[slot];
	while (ip != NULL) {
		if (USTOI(ino)==USTOI(ip->i_number) && dev==ip->i_dev) {
	again:
			if((ip->i_flag&ILOCK) != 0) {
				ip->i_flag |= IWANT;
				sleep((caddr_t)ip, PINOD);
				if (   USTOI(ino) == USTOI(ip->i_number)
				    && dev == ip->i_dev)
					goto again;
				goto loop;
			}
			if((ip->i_flag&IMOUNT) != 0) {
				for(mp = &mount[0]; mp < mountNMOUNT; mp++)
				if(mp->m_inodp == ip) {
					dev = mp->m_dev;
					ino = ROOTINO;
					goto loop;
				}
				panic("no imt");
			}
			ip->i_count++;
			ip->i_flag |= ILOCK;
trace(0x08, "ip=", ip);
			return(ip);
		}
		ip = ip->i_link;
	}
	if (ifreelist == NULL) {
		printf("Inode table overflow\n");
		u.u_error = ENFILE;
trace(0x08, "ip=", NULL);
		return(NULL);
	}
	ip = ifreelist;
	ifreelist = ip->i_link;
	ip->i_link = ihash[slot];
	ihash[slot] = ip;
	ip->i_dev = dev;
	ip->i_number = ino;
	ip->i_flag = ILOCK;
	ip->i_count++;
	ip->i_un.i_lastr = 0;
	bp = bread(dev, itod(ino));
	/*
	 * Check I/O errors
	 */
	if((bp->b_flags&B_ERROR) != 0) {
		brelse(bp);
		iput(ip);
trace(0x08, "ip=", NULL);
		return(NULL);
	}
	dp = bp->b_un.b_dino;
	dp += itoo(ino);
	iexpand(ip, dp);
	brelse(bp);
trace(0x08, "ip=", ip);
	return(ip);
}

iexpand(ip, dp)
register struct inode *ip;
register struct dinode *dp;
{
	register char *p1;
	char *p2;
	int i;

#ifdef V6
	if (dp->di_mode == 0)
		ip->i_mode = 0;
	else {
		ip->i_mode = dp->di_mode & ~(DIALLOC|DILARG);
		if ((dp->di_mode&DIFMT) == 0)
			ip->i_mode |= IFREG;
		if (dp->di_mode&DILARG)
			ip->i_mode |= ILARG;
	}
#else
	ip->i_mode = dp->di_mode;
#endif
	ip->i_nlink = dp->di_nlink;
	ip->i_uid = dp->di_uid;
	ip->i_gid = dp->di_gid;
#ifdef V6
	ip->i_size = dp->di_size1;
	{ register int *q1, *q2;
		q1 = ip->i_un.i_addr;
		q2 = dp->di_addr;
		for (i=0; i<NADDR; i++)
			*q1++ = *q2++;
	}
#else
	ip->i_size = dp->di_size;
	p1 = (char *)ip->i_un.i_addr;
	p2 = (char *)dp->di_addr;
	for(i=0; i<NADDR; i++) {
#ifdef DEC
		*p1++ = *p2++;
		*p1++ = 0;
#else  DEC
		*p1++ = 0;
		*p1++ = *p2++;
#endif DEC
		*p1++ = *p2++;
		*p1++ = *p2++;
	}
#endif
}

/*
 * Decrement reference count of
 * an inode structure.
 * On the last reference,
 * write the inode out and if necessary,
 * truncate and deallocate the file.
 */
iput(ip)
register struct inode *ip;
{
#ifdef  UCB_QUOTAS
	register struct inode *qp;
#endif  UCB_QUOTAS
#ifdef  UCB_IHASH
	register struct inode *jp;
	register int i;
#endif  UCB_IHASH

#ifdef  UCB_QUOTAS

	/*
	 * Berkeley's quota system is hierarchical
	 * so this loop is necessary to de-reference all
	 * quota nodes still hanging around
	 */
    do {
	qp = NULL;
#endif  UCB_QUOTAS

trace(0x10, "iput", ip);
	if(ip->i_count == 1) {
		ip->i_flag |= ILOCK;
		if(ip->i_nlink <= 0) {
			itrunc(ip);
			ip->i_mode = 0;
			ip->i_flag |= IUPD|ICHG;
			ifree(ip->i_dev, ip->i_number);
		}
#ifdef  UCB_QUOTAS
		/*
		 * EECS, UCB
		 * Berkeley's quota system
		 */
		qp = ip->i_quot;
		if (qp != NULL) {
			ip->i_quot = NULL;
			qprint(01)("IPUT: qp = %d, ip = %d, i_flag = 0%o\n", qp->i_number, ip->i_number, ip->i_flag);
		}
#endif  UCB_QUOTAS
#ifdef  UCB_NODUPS
		iupdat(ip, &time, &time, 0);
#else   UCB_NODUPS
		iupdat(ip, &time, &time);
#endif  UCB_NODUPS
		prele(ip);
#ifdef  UCB_IHASH
		i = INOHASH(ip->i_dev, ip->i_number);
		if (ihash[i] == ip)
			ihash[i] = ip->i_link;
		else {
			for (jp = ihash[i]; jp != NULL; jp = jp->i_link)
				if (jp->i_link == ip) {
					jp->i_link = ip->i_link;
					goto done;
				}
			panic("iput");
		}
done:
		ip->i_link = ifreelist;
		ifreelist = ip;
#endif  UCB_IHASH
		ip->i_flag = 0;
		ip->i_number = 0;
	}
#ifdef  UCB_IHASH
	else
		prele(ip);
	ip->i_count--;
#else   UCB_IHASH
	ip->i_count--;
	prele(ip);
#endif  UCB_IHASH
#ifdef  UCB_QUOTAS
	if (isquot(ip))
		qprint(01)("IPUT: decremented count of quota %d\n", ip->i_number);
    } while ((ip = qp) != NULL);
#endif  UCB_QUOTAS
}

/*
 * Check accessed and update flags on
 * an inode structure.
 * If any are on, update the inode
 * with the current time.
#ifdef  UCB_NODUPS
 * If waitfor set, then must insure
 * i/o order by waiting for the write
 * to complete.
#endif  UCB_NODUPS
 */
#ifdef  UCB_NODUPS
iupdat(ip, ta, tm, waitfor)
#else   UCB_NODUPS
iupdat(ip, ta, tm)
#endif  UCB_NODUPS
register struct inode *ip;
time_t *ta, *tm;
#ifdef  UCB_NODUPS
int waitfor;
#endif  UCB_NODUPS
{
	register struct buf *bp;
	struct dinode *dp;
	register char *p1;
	char *p2;
	int i;

	if((ip->i_flag&(IUPD|IACC|ICHG)) != 0) {
		if(getfs(ip->i_dev)->s_ronly)
			return;
		bp = bread(ip->i_dev, itod(ip->i_number));
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			return;
		}
		dp = bp->b_un.b_dino;
		dp += itoo(ip->i_number);
#ifdef V6
	dp->di_mode = ip->i_mode & ~(ILARG|DIALLOC|DILARG);
	if (ip->i_mode != 0)
		dp->di_mode |= DIALLOC;
	if (ip->i_mode&ILARG)
		dp->di_mode |= DILARG;
#else
		dp->di_mode = ip->i_mode;
#endif
		dp->di_nlink = ip->i_nlink;
		dp->di_uid = ip->i_uid;
		dp->di_gid = ip->i_gid;
#ifdef  V6
		dp->di_size1 = ip->i_size;
		{ register int *q1, *q2;
			q1 = dp->di_addr;
			q2 = ip->i_un.i_addr;
			for (i=0; i<NADDR; i++)
				*q1++ = *q2++;
		}
		if(ip->i_flag&IACC)
			dp->di_atime = *ta;
		if(ip->i_flag&IUPD)
			dp->di_mtime = *tm;
#else   V6
		dp->di_size = ip->i_size;
		p1 = (char *)dp->di_addr;
		p2 = (char *)ip->i_un.i_addr;
		for(i=0; i<NADDR; i++) {
#ifdef  DEC
			*p1++ = *p2++;
			if(*p2++ != 0 && (ip->i_mode&IFMT)!=IFMPC
			   && (ip->i_mode&IFMT)!=IFMPB)
#ifdef  UCB_DEVERR
				printf("iaddress[%d] > 2^24(%D), i_number = %d, i_dev = %d\n", i, ip->i_un.i_addr[i], ip->i_number, ip->i_dev);
#else   UCB_DEVERR
				printf("iaddress > 2^24\n");
#endif  UCB_DEVERR
#else   DEC
			if(*p2++ != 0 && (ip->i_mode&IFMT)!=IFMPC
			   && (ip->i_mode&IFMT)!=IFMPB)
#ifdef  UCB_DEVERR
				printf("iaddress[%d] > 2^24(%D), i_number = %d, i_dev = %d\n", i, ip->i_un.i_addr[i], ip->i_number, ip->i_dev);
#else   UCB_DEVERR
				printf("iaddress > 2^24\n");
#endif  UCB_DEVERR
			*p1++ = *p2++;
#endif  DEC
			*p1++ = *p2++;
			*p1++ = *p2++;
		}
		if(ip->i_flag&IACC)
			dp->di_atime = *ta;
		if(ip->i_flag&IUPD)
			dp->di_mtime = *tm;
		if(ip->i_flag&ICHG)
			dp->di_ctime = time;
#endif  V6
		ip->i_flag &= ~(IUPD|IACC|ICHG);
#ifdef  UCB_NODUPS
		if (waitfor)
			bwrite(bp);
		else
			bdwrite(bp);
#else   UCB_NODUPS
		bdwrite(bp);
#endif  UCB_NODUPS
	}
}

/*
 * Free all the disk blocks associated
 * with the specified inode structure.
 * The blocks of the file are removed
 * in reverse order. This FILO
 * algorithm will tend to maintain
 * a contiguous free list much longer
 * than FIFO.
 */
#ifdef V6
itrunc(rp)
register struct inode *rp;
{
	register *ip, *bp, *cp;
	register *dp, *ep;
	int	i;

	i = rp->i_mode & IFMT;
	if (i!=IFREG && i!=IFDIR)
		return;
	for(ip = &rp->i_un.i_addr[7]; ip >= &rp->i_un.i_addr[0]; ip--)
	if(*ip) {
		if((rp->i_mode&ILARG) != 0) {
			bp = bread(rp->i_dev, *ip);
			for(cp = bp->b_un.b_addr+(BSIZE-NBPW); cp >= bp->b_un.b_addr; cp--)
			if(*cp) {
				if(ip == &rp->i_un.i_addr[7]) {
					dp = bread(rp->i_dev, *cp);
					for(ep = dp->b_un.b_addr+(BSIZE-NBPW); ep >= dp->b_un.b_addr; ep--)
					if(*ep)
						free(rp->i_dev, *ep);
					brelse(dp);
				}
				free(rp->i_dev, *cp);
			}
			brelse(bp);
		}
		free(rp->i_dev, *ip);
		*ip = 0;
	}
	rp->i_mode =& ~ILARG;
	rp->i_size = 0;
	rp->i_flag =| IUPD;
}
#else   V6
itrunc(ip)
register struct inode *ip;
{
	register i;
	dev_t dev;
	daddr_t bn;
#ifdef  UCB_NODUPS
	struct inode itmp;
#endif  UCB_NODUPS

	i = ip->i_mode & IFMT;
	if (i!=IFREG && i!=IFDIR)
		return;

#ifdef  UCB_NODUPS
	/*
	 * Clean inode on disk before freeing blocks
	 * to insure no duplicates if system crashes.
	 */
	itmp = *ip;
	itmp.i_size = 0;
	for (i = 0; i < NADDR; i++)
		itmp.i_un.i_addr[i] = 0;
	itmp.i_flag |= ICHG|IUPD;
	iupdat(&itmp, &time, &time, 1);
	ip->i_flag &= ~(IUPD|IACC|ICHG);

	/*
	 * Now return blocks to free list... if machine
	 * crashes, they will be harmless MISSING blocks.
	 */
#endif  UCB_NODUPS
	dev = ip->i_dev;
	for(i=NADDR-1; i>=0; i--) {
		bn = ip->i_un.i_addr[i];
		if(bn == (daddr_t)0)
			continue;
		ip->i_un.i_addr[i] = (daddr_t)0;
		switch(i) {

		default:
#ifdef  UCB_QUOTAS
			qfree(ip, bn);
#else   UCB_QUOTAS
			free(dev, bn);
#endif  UCB_QUOTAS
			break;

		case NADDR-3:
#ifdef  UCB_QUOTAS
			tloop(dev, bn, 0, 0, ip);
#else   UCB_QUOTAS
			tloop(dev, bn, 0, 0);
#endif  UCB_QUOTAS
			break;

		case NADDR-2:
#ifdef  UCB_QUOTAS
			tloop(dev, bn, 1, 0, ip);
#else   UCB_QUOTAS
			tloop(dev, bn, 1, 0);
#endif  UCB_QUOTAS
			break;

		case NADDR-1:
#ifdef  UCB_QUOTAS
			tloop(dev, bn, 1, 1, ip);
#else   UCB_QUOTAS
			tloop(dev, bn, 1, 1);
#endif  UCB_QUOTAS
		}
	}
	ip->i_size = 0;
#ifndef UCB_NODUPS
	ip->i_flag |= ICHG|IUPD;
#else   UCB_NODUPS
	/*
	 * Inode was written and flags updated above.
	 * No need to modify flags here.
	 */
#endif  UCB_NODUPS
}

#ifdef  UCB_QUOTAS
tloop(dev, bn, f1, f2, ip)
#else   UCB_QUOTAS
tloop(dev, bn, f1, f2)
#endif  UCB_QUOTAS
dev_t dev;
daddr_t bn;
#ifdef  UCB_QUOTAS
struct inode    *ip;
#endif  UCB_QUOTAS
{
	register i;
	register struct buf *bp;
	register daddr_t *bap;
	daddr_t nb;

	bp = NULL;
	for(i=NINDIR-1; i>=0; i--) {
		if(bp == NULL) {
			bp = bread(dev, bn);
			if (bp->b_flags & B_ERROR) {
				brelse(bp);
				return;
			}
			bap = bp->b_un.b_daddr;
		}
		nb = bap[i];
		if(nb == (daddr_t)0)
			continue;
		if(f1) {
			brelse(bp);
			bp = NULL;
#ifdef  UCB_QUOTAS
			tloop(dev, nb, f2, 0, ip);
#else   UCB_QUOTAS
			tloop(dev, nb, f2, 0);
#endif  UCB_QUOTAS
		} else
#ifdef  UCB_QUOTAS
			qfree(ip, nb);
#else   UCB_QUOTAS
			free(dev, nb);
#endif  UCB_QUOTAS
	}
	if(bp != NULL)
		brelse(bp);
#ifdef  UCB_QUOTAS
	qfree(ip, bn);
#else   UCB_QUOTAS
	free(dev, bn);
#endif  UCB_QUOTAS
}
#endif  V6

/*
 * Make a new file.
 */
struct inode *
maknode(mode)
{
	register struct inode *ip;

	ip = ialloc(u.u_pdir->i_dev);
	if(ip == NULL) {
		iput(u.u_pdir);
		return(NULL);
	}
	ip->i_flag |= IACC|IUPD|ICHG;
	if((mode&IFMT) == 0)
		mode |= IFREG;
	ip->i_mode = mode & ~u.u_cmask;
	ip->i_nlink = 1;
	ip->i_uid = u.u_uid;
	ip->i_gid = u.u_gid;

#ifdef  UCB_NODUPS
	/*
	 * Make sure inode goes to disk before directory entry.
	 */
	iupdat(ip, &time, &time, 1);
#endif  UCB_NODUPS

	wdir(ip);
	return(ip);
}

/*
 * Write a directory entry with
 * parameters left as side effects
 * to a call to namei.
 */
wdir(ip)
struct inode *ip;
{

	if (u.u_pdir->i_nlink <= 0) {
		u.u_error = ENOTDIR;
		goto out;
	}
	u.u_dent.d_ino = ip->i_number;
	bcopy((caddr_t)u.u_dbuf, (caddr_t)u.u_dent.d_name, DIRSIZ);
	u.u_count = sizeof(struct direct);
	u.u_segflg = 1;
	u.u_base = (caddr_t)&u.u_dent;
	writei(u.u_pdir);
#ifdef  UCB_QUOTAS
	/*
	 * Copy quota for new file
	 */
	if (!u.u_error)
		qcopy(u.u_pdir, ip);
#endif  UCB_QUOTAS
out:
	iput(u.u_pdir);
}
