/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: ufs_subr.c 1.23 90/04/24 $"

/*
 * ufs_subr.c
 *	Various file-system related subroutines.  Some shared with utilities.
 */

/* $Log:	ufs_subr.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/conf.h"
#include "sys/buf.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/ufsinode.h"
#include "sys/ufsfilsys.h"
#include "sys/ufsmount.h"
#include "sys/vmmeter.h"
#include "sys/var.h"
#include "sys/cmn_err.h"

#include "sys/vmparam.h"
#include "sys/pte.h"
#include "sys/gate.h"
#include "sys/intctl.h"
#include "sys/plocal.h"

/*
 * update()
 *	Update inodes, super-blocks to disks.
 *
 * Update is the internal name of 'sync'. It goes through the 
 * mount table to initiate the writing of the modified super blocks
 * and it goes through the inodes to write modified nodes.
 * The upper level code is responsible for flushing the disk buffer cache.
 */

update()
{
	register struct inode *ip;
	register struct mount *mp;
	register struct fs *fs;

	/*
	 * If vfs_mutex available, Write back modified superblocks.
	 * Note that we turn off "fmod", then unlock the file-sys;
	 * this allows mods to occur whilst we're updating. This is
	 * ok, since unmount does an explicit sbupdate().
	 */
	if (cp_sema(&vfs_mutex)) {
		for (mp = mounttab; mp < v.ve_mount; mp++) {
			if (mp->m_bufp == NULL ||
			    mp->m_devvp == (struct vnode *)0)
				continue;
			fs = mp->m_bufp->b_un.b_fs;
			if (fs->fs_fmod == 0)		/* ok no lock */
				continue;
			if (fs->fs_ronly) {
				cmn_err(CE_CONT, "fs = %s\n", fs->fs_fsmnt);
				cmn_err(CE_PANIC, "update: rofs mod");
				/*
				 *+ When synching the disks, the kernel found
				 *+ that a filesystem mounted read-only had
				 *+ been modified.
				 */
			}
			FSLOCK(fs);
			fs->fs_fmod = 0;
			fs->fs_time = time.tv_sec;
			FSUNLOCK(fs);
			sbupdate(mp->m_devvp, fs);
		}
		v_sema(&vfs_mutex);
	}

	/*
	 * Write back each (modified) inode.  Ignore busy inodes.
	 *
	 * For each inode, if it's modified and available (not locked),
	 * *then* lock inode hash+free list and update the inode if it's
	 * still available.  This avoids list locking for uninteresting
	 * inodes.  Loosing a race is ok (we don't modify anything),
	 * since this is heuristic update.
	 */
	for (ip = inode; ip < v.ve_inode; ip++) {
		if ((ip->i_flag&(IUPD|IACC|ICHG)) == 0		/* not mod'd, */
		||  !RWSEMA_IDLE(&ip->i_mutex))			/* or locked */
			continue;				/* ==> ignore */
		if (igrab(ip)) {				/* try for it */
			iupdat(ip, 0);				/* update it */
			IPUT(ip);				/* done */
		}
	}
}

/*
 * syncip()
 *	Flush all the blocks associated with an inode.
 *
 * Note that we make a more stringent check of
 * writing out any block in the buffer pool that may
 * overlap the inode. This brings the inode up to
 * date with recent mods to the cooked device.
 *
 * This can be nasty on a huge sparse file.
 *
 * Called with locked inode.
 */

syncip(ip)
	register struct inode *ip;
{
	register struct fs *fs;
	register long	lbn;
	register long	lastlbn;
	register daddr_t blkno;

	ASSERT_DEBUG(RWSEMA_WRBUSY(&ip->i_mutex), "syncip: unlocked");
	fs = ip->i_fs;
	lastlbn = howmany(ip->i_size, fs->fs_bsize);
	for (lbn = 0; lbn < lastlbn; lbn++) {
		blkno = bmap(ip, lbn, B_READ);
		if (blkno != (daddr_t)-1)
			blkflush(ip->i_devvp, fsbtodb(fs, blkno),
				blksize(fs, ip, lbn));
	}
	ip->i_pflags &= ~IP_EXECMOD;		/* file now sync'd */
	IMARK(ip, ICHG);
	iupdat(ip, 1);
}

/*
 * fragacct()
 *	Update the frsum fields to reflect addition or deletion 
 *	of some frags.
 *
 * fraglist is part of a cylinder-group; caller locked this.
 */

extern	int around[];
extern	int inside[];
extern	unchar *fragtbl[];

fragacct(fs, fragmap, fraglist, cnt)
	register struct fs *fs;
	int	fragmap;
	long	fraglist[];
	int	cnt;
{
	register int field;
	register int subfield;
	register int siz;
	register int pos;
	int	inblk;
	int	mask;

	inblk = (int)(fragtbl[fs->fs_frag][fragmap]) << 1;
	fragmap <<= 1;
	mask = 1 << (1 + (fs->fs_frag % NBBY));
	for (siz = 1; siz < fs->fs_frag; siz++, mask <<= 1) {
		if ((inblk & mask) == 0)
			continue;
		field = around[siz];
		subfield = inside[siz];
		for (pos = siz; pos <= fs->fs_frag; pos++) {
			if ((fragmap & field) == subfield) {
				fraglist[siz] += cnt;
				pos += siz;
				field <<= siz;
				subfield <<= siz;
			}
			field <<= 1;
			subfield <<= 1;
		}
	}
}

/*
 * badblock()
 *	Check that a specified block number is in range.
 */

badblock(fs, bn)
	struct fs *fs;
	daddr_t bn;
{
	if ((unsigned)bn >= fs->fs_size) {
		cmn_err(CE_WARN, "bad block %d, ", bn);
		/*
		 *+ A bad block number was encountered while the kernel was 
		 *+ doing a filesystem block allocation or
		 *+ freeing a filesystem block.  This
		 *+ probably indicates a corrupted filesystem.
		 *+ Corrective action:  run fsck on the filesystem.
		 */
		fserr(fs, "bad block");
		return (1);
	}
	return (0);
}

/*
 * isblock()
 *	check if a block is available
 */

isblock(fs, cp, h)
	struct fs *fs;
	unsigned char *cp;
	daddr_t h;
{
	unsigned char mask;

	switch (fs->fs_frag) {
	case 8:
		return (cp[h] == 0xff);
	case 4:
		mask = 0x0f << ((h & 0x1) << 2);
		return ((cp[h >> 1] & mask) == mask);
	case 2:
		mask = 0x03 << ((h & 0x3) << 1);
		return ((cp[h >> 2] & mask) == mask);
	case 1:
		mask = 0x01 << (h & 0x7);
		return ((cp[h >> 3] & mask) == mask);
	default:
		cmn_err(CE_PANIC, "isblock");
		/*
		 *+ On attempting to allocate a fileystem block, the kernel
		 *+ discovered that the superblock contained an illegal
		 *+ value for the number of fragments in a block.
		 */
		return (NULL);
	}
}

/*
 * clrblock()
 *	take a block out of the map
 */

clrblock(fs, cp, h)
	struct fs *fs;
	unchar *cp;
	daddr_t h;
{

	switch ((fs)->fs_frag) {
	case 8:
		cp[h] = 0;
		return;
	case 4:
		cp[h >> 1] &= ~(0x0f << ((h & 0x1) << 2));
		return;
	case 2:
		cp[h >> 2] &= ~(0x03 << ((h & 0x3) << 1));
		return;
	case 1:
		cp[h >> 3] &= ~(0x01 << (h & 0x7));
		return;
	default:
		cmn_err(CE_PANIC, "clrblock");
		/*
		 *+ On attempting to allocate a fileystem block, the kernel
		 *+ discovered that the superblock contained an illegal
		 *+ value for the number of fragments in a block.
		 */
	}
}

/*
 * setblock()
 *	put a block into the map
 */

setblock(fs, cp, h)
	struct fs *fs;
	unsigned char *cp;
	daddr_t h;
{

	switch (fs->fs_frag) {

	case 8:
		cp[h] = 0xff;
		return;
	case 4:
		cp[h >> 1] |= (0x0f << ((h & 0x1) << 2));
		return;
	case 2:
		cp[h >> 2] |= (0x03 << ((h & 0x3) << 1));
		return;
	case 1:
		cp[h >> 3] |= (0x01 << (h & 0x7));
		return;
	default:
		cmn_err(CE_PANIC, "setblock");
		/*
		 *+ On attempting to free a fileystem block, the kernel
		 *+ discovered that the superblock contained an illegal
		 *+ value for the number of fragments in a block.
		 */
	}
}

struct mount *
getmp(devvp)
	struct vnode *devvp;
{
	register struct mount *mp;
	register struct fs *fs;

	for (mp = &mounttab[0]; mp < v.ve_mount; mp++) {
		if (mp->m_bufp == NULL || mp->m_devvp != devvp)
			continue;
		fs = mp->m_bufp->b_un.b_fs;
		if (fs->fs_magic != FS_MAGIC) {
			cmn_err(CE_CONT, "dev = 0x%x, fs = %s\n",
					  devvp->v_rdev, fs->fs_fsmnt);
			cmn_err(CE_PANIC, "getmp: bad magic");
			/*
			 *+ The kernel found an in-core superblock with an invalid
			 *+ magic number.
			 */
		}

		return (mp);
	}
	return (NULL);
}

/*
 * getmpbydev()
 *	Identical to getmp() but searches by dev_t.
 */

struct mount *
getmpbydev(dev)
	dev_t dev;
{
	register struct mount *mp;
	register struct fs *fs;

	for (mp = &mounttab[0]; mp < v.ve_mount; mp++) {
		if (mp->m_bufp == NULL || mp->m_dev != dev)
			continue;
		fs = mp->m_bufp->b_un.b_fs;
		if (fs->fs_magic != FS_MAGIC) {
			cmn_err(CE_CONT, "dev = 0x%x, fs = %s\n", dev, fs->fs_fsmnt);
			cmn_err(CE_PANIC, "getmpbydev: bad magic");
			/*
			 *+ The kernel found an in-core superblock with an invalid
			 *+ magic number.
			 */
		}

		return (mp);
	}
	return (NULL);
}

/*
 * nfsgetmpbydev()
 *	Identical to getmpbydev() but does handshake with ufs_unmount().
 *
 * Used by NFS's fhtovp().
 * ***Warning: this is EXCEEDINGLY UGLY and will be replaced.*** It is
 * only exceptable because fhtovp() currently assumes ufs is the only
 * type of local file system. This will be fixed ala NFS3.2.
 */

struct mount *
nfsgetmpbydev(dev)
	dev_t dev;
{
	register struct mount *mp;
	register struct fs *fs;
	spl_t	s_ipl;

#ifdef	lint
	/*
	 * The base kernel does not use nfsgetmp().
	 * This keeps lint from complaining.
	 */
	(void) nfsgetmpbydev(dev);
#endif	/* lint */

	for (mp = &mounttab[0]; mp < v.ve_mount; mp++) {
		if (mp->m_bufp == NULL || mp->m_dev != dev)
			continue;

		s_ipl = p_lock(&mp->m_lock, SPLFS);
		/*
		 * Now check again in case we have lost a race with unmount().
		 */
		if (mp->m_bufp == NULL || mp->m_dev != dev) {
			/*
			 * lost the race.
			 */
			v_lock(&mp->m_lock, s_ipl);
			return (NULL);
		}

		fs = mp->m_bufp->b_un.b_fs;
		if (fs->fs_magic != FS_MAGIC) {
			v_lock(&mp->m_lock, s_ipl);
			cmn_err(CE_CONT, "dev = 0x%x, fs = %s\n", dev,
				fs->fs_fsmnt);
			cmn_err(CE_PANIC, "nfsgetmp: bad magic");
			/*
			 *+ A filesystem with a bad filesystem magic number
			 *+ was found in the mounted filesystem table.
			 *+ This indicates a kernel software problem.
			 */
		}

		/*
		 * Add a reference so a concurrent ufs_unmount() 
		 * will return EBUSY.
		 */
		mp->m_count++;
		v_lock(&mp->m_lock, s_ipl);
		return (mp);
	}
	return (NULL);
}
