/* $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_vfsops.c 1.48 89/11/17 $"

/*
 * ufs_vfsops.c
 *	UNIX file-system virtual-file system operations (mount, unmount,
 *	sync, etc).
 */

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

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/sysmacros.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/buf.h"
#include "sys/pathname.h"
#include "sys/vfs.h"
#include "sys/statfs.h"
#include "sys/vnode.h"
#include "sys/file.h"
#include "sys/uio.h"
#include "sys/conf.h"
#include "sys/ufsinode.h"
#include "sys/ufsfilsys.h"
#include "sys/mount.h"
#include "sys/ufsmount.h"
#include "sys/vmmeter.h"
#include "sys/var.h"
#include "sys/cmn_err.h"
#include "sys/vtoc.h"
#include "sys/open.h"
#include "sys/snode.h"
#include "sys/swap.h"

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

/*
 * ufs vfs operations.
 */

extern	int	ufs_mount();
extern	int	ufs_unmount();
extern	int	ufs_root();
extern	int	ufs_statfs();
extern	int	ufs_sync();

struct	vfsops	ufs_vfsops = {
	ufs_mount,
	ufs_unmount,
	ufs_root,
	ufs_statfs,
	ufs_sync,
};

/*
 * this is the default filesystem type.
 * this should be setup by the configurator
 */

extern	int	ufs_mountroot(), ufs_unmountroot();
int		(*rootfsmount)() = ufs_mountroot;
int		(*rootfsunmount)() = ufs_unmountroot;

/*
 * Default device to mount on.
 */
extern	dev_t	rootdev;

/*
 * ufs mount vfs operation
 */
/*ARGSUSED*/
static
ufs_mount(vfsp, path, fspec, data, datalen)
	struct vfs *vfsp;
	char	*path;
	char	*fspec;
	char	*data;		/* unused */
	int	datalen;	/* unused */
{
	int error;
	dev_t dev;
	struct vnode *devvp;
	struct vnode *vp;

	SPECPRINTF5("ufs_mount: begin\n");

	/*
	 * Get the device to be mounted on. First unlock coveredvp, in case
	 * device to be mounted on has the coveredvp in its pathname. This is
	 * to avoid deadlock in lookupname().
	 */
	vfsp->vfs_vnodecovered->v_flag |= VMOUNTING;
	VN_UNLOCKNODE(vfsp->vfs_vnodecovered);
	error = lookupname(fspec, UIOSEG_USER, FOLLOW_LINK,
					(struct vnode **)0, &vp);
	if (error) {
		VN_LOCKNODE(vfsp->vfs_vnodecovered);
		vfsp->vfs_vnodecovered->v_flag &= ~VMOUNTING;
		return (error);
	}

	if (vp->v_type != VBLK) {
		VN_PUT(vp);
		VN_LOCKNODE(vfsp->vfs_vnodecovered);
		vfsp->vfs_vnodecovered->v_flag &= ~VMOUNTING;
		return (ENOTBLK);
	}
	dev = vp->v_rdev;
	VN_PUT(vp);

	VN_LOCKNODE(vfsp->vfs_vnodecovered);
	/*
	 * Window shut again. Remove VMOUNTING bit.
	 */
	vfsp->vfs_vnodecovered->v_flag &= ~VMOUNTING;

	/*
	 * make a special (device) vnode for the filesystem
	 */
	devvp = bdevvp(dev);

	/*
	 * Mount the filesystem
	 */
	error = mountfs(&devvp, path, vfsp, (struct vfs **)0);
	if (error) {
                VN_RELE(devvp);
	} else {
		spec_writeprotect(devvp, 1);
	}
	SPECPRINTF5("ufs_mount: return error = %d\n", error);
	return (error);
}

/*
 * Called by vfs_mountroot when ufs is going to be mounted as root.
 * iflg is 1 if called from vfs_mountroot, 0 if from uadmin/remount.
 */
static
ufs_mountroot(iflg)
	int iflg;
{
	register struct fs *fsp;
	register int error;
	struct vfs *nvfsp;		/* new vfs entry on vfs list */
	struct vfs lvfs;
	struct vnode *devvp;

	SPECPRINTF5("ufs_mountroot: begin\n");

	if (iflg)
		init_mounttab();	/* see comment for init_mounttab() */

	VFS_INIT(&lvfs, &ufs_vfsops, (caddr_t)0);
	(void) vfs_filldata(NULLVP, &lvfs, 0);
	/*
	 * make a special (device) vnode for the filesystem
	 */
	devvp = bdevvp(rootdev);

	/*
	 * Mount the filesystem
	 */
	error = mountfs(&devvp, "/", &lvfs, &nvfsp);
	if (error) {
                VN_RELE(devvp);
		SPECPRINTF5("ufs_mountroot: return ERROR = %d\n", error);
		return (error);
	}
	spec_writeprotect(devvp, 1);
	fsp = ((struct mount *)(nvfsp->vfs_data))->m_bufp->b_un.b_fs;
	if (iflg)
		FW_INITTODR(fsp->fs_time);

	SPECPRINTF5("ufs_mountroot: done, no error\n");

	return (0);
}

/*
 * init_mounttab()
 *	Initialize the m_lock field in the mount table
 *
 * This is called only once. We kludge for now and call it from
 * ufs_mountroot() because we know it is called once and before any other
 * mounts are possible. This will go away when fhtovp() is reworked to
 * be independent of the local file system type.
 */

static
init_mounttab()
{
	register struct mount *mp;

	for (mp = &mounttab[0]; mp < v.ve_mount; mp++) {
		init_lock(&mp->m_lock);
	}
}

/*
 * mountfs()
 *	Do the work of a mount.
 *
 * The use of wmemall()/wmemfree() has potential problems with TLB flushes;
 * if the memory space was recently used by some other processor, it could
 * have stale TLB data and *then* try to do a file-sys thing.  This can
 * be avoided if each processor context switches *after* the mount and
 * *before* accessing this file-sys.  This is typical, and cases where this
 * doesn't occur are hard to force.  The law of large numbers says we
 * ignore the problem.
 */
static int
mountfs(devvpp, path, vfsp, vfspp)
	struct vnode **devvpp;
	char *path;
	struct vfs *vfsp;		/* vfs to enter into vfs list */
	struct vfs **vfspp;		/* new entry in vfs list */
{
	register struct fs	*fsp;
	register struct mount	*mp;
	register struct buf	*bp = 0;
	struct	buf	*tp = 0;
	int	error;
	int	blks;
	caddr_t	space;
	caddr_t	sspace = 0;
	int	i;
	int	size;
	spl_t	s_ipl;
	char	*strncpy();

	/*
	 * Open block device mounted on.
	 */

	SPECPRINTF5("mountfs: calling VOP_OPEN of vp 0x%x\n", *devvpp);

	error = VOP_OPEN(devvpp,
		(vfsp->vfs_flag & VFS_RDONLY) ? FREAD : FREAD|FWRITE, u.u_cred);

	if (error) {
		SPECPRINTF5("mountfs: VOP_OPEN ERROR = %d\n", error);
		return (error);
	}

	/*
	 * The primary swap device is not removed from devsw (so dumps
	 * can be read from it).  So, we need to be sure we can protect
	 * from mounting it here.  Note that miniroot runs with root & swap
	 * on same dev, so special-case "/".
	 */
	if (strcmp(path, "/") != 0 &&
	    VTOS(*devvpp)->s_devp == swdevt[0].sw_devp) {
		error = ENXIO;
		goto out;
	}

	/*
	 * Read in superblock.
	 */
	tp = bread(*devvpp, SBLOCK, SBSIZE);
	if (tp->b_flags & B_ERROR) {
		error = EIO;
		goto out;
	}
	/*
	 * Verify the super block is reasonable.
	 */
	fsp = tp->b_un.b_fs;
	if (fsp->fs_magic != FS_MAGIC || fsp->fs_bsize > MAXBSIZE) {
		error = EINVAL;
		goto out;
	}
	if (vfsp->vfs_flag & VFS_RDONLY) {
		fsp->fs_fmod = 0;
		fsp->fs_ronly = 1;
	} else {
		fsp->fs_fmod = 1;
		fsp->fs_ronly = 0;
		if ((fsstate(fsp) + (long)fsp->fs_time) == FS_OKAY) {
			if (fsp->fs_postblformat == FS_42POSTBLFMT) 
				fsp->fs_un.fs_dsp.dfs_state = FS_ACTIVE;
			else
				fsp->fs_state = FS_ACTIVE;
		} else {
			/* allow if mounting the root fs */
			if ((*devvpp)->v_rdev == rootdev)
				if (fsp->fs_postblformat == FS_42POSTBLFMT)  {
					fsp->fs_un.fs_dsp.dfs_state = FS_BAD;
				} else {
					fsp->fs_state = FS_BAD;
				}
			else {
				error = ENOSPC;
				/*
				 * Rather than "goto out" here, fall through
				 * to eventually check whether the reason this
				 * filesystem is not FS_OKAY is because it is
				 * already mounted.  If so, we want to return
				 * EBUSY instead.  
				 *
				 * After determining that the filesystem is 
				 * not already mounted, check "error" and 
				 * fail if set.
				 */
			}
		}
	}
	/*
	 * Copy the super block into a buffer in its native size
	 * and update copy on disk.
	 */
	bp = geteblk((int)fsp->fs_sbsize);
	bcopy((caddr_t)tp->b_un.b_addr, (caddr_t)bp->b_un.b_addr,
		(uint)fsp->fs_sbsize);
	if (fsp->fs_ronly)
		brelse(tp);
	else
		bwrite(tp);
	tp = 0;
	fsp = bp->b_un.b_fs;
	vfsp->vfs_bsize = fsp->fs_bsize;
	if (fsp->fs_postblformat == FS_42POSTBLFMT) {
		/*
		 * save OLD 4.2 "spares".
		 */
		fsp->fs_id[0] = fsp->fs_un.fs_dsp.dfs_id[0];
		fsp->fs_id[1] = fsp->fs_un.fs_dsp.dfs_id[1];
		fsp->fs_state = fsp->fs_un.fs_dsp.dfs_state;
		/*
		 * construct new tahoe-style "spares".
		 */
		fsp->fs_nrpos = 8;
		fsp->fs_optim = FS_OPTSPACE;
		fsp->fs_npsect = fsp->fs_nsect;
		fsp->fs_interleave = 1;
		fsp->fs_trackskew = 0;
		fsp->fs_headswitch = 0;
		fsp->fs_trkseek = 0;
	}
	/*
	 * Sanity checks 
	 */
	fsp->fs_npsect = MAX(fsp->fs_npsect, fsp->fs_nsect);	/* XXX */
	fsp->fs_interleave = MAX(fsp->fs_interleave, 1);	/* XXX */
	/*
	 * Read in cyl group info.
	 */
	blks = howmany(fsp->fs_cssize, fsp->fs_fsize);
	sspace = space = wmemall((int)fsp->fs_cssize, 1);
	for (i = 0; i < blks; i += fsp->fs_frag) {
		size = fsp->fs_bsize;
		if (i + fsp->fs_frag > blks)
			size = (blks - i) * fsp->fs_fsize;
		tp = bread(*devvpp, fsbtodb(fsp, fsp->fs_csaddr + i), size);
		if (tp->b_flags & B_ERROR) {
			error = EIO;
			goto out;
		}
		bcopy((caddr_t)tp->b_un.b_addr, space, (uint)size);
		fsp->fs_csp[i / fsp->fs_frag] = (struct csum *)space;
		space += size;
		brelse(tp);
		tp = 0;
	}
	(void) strncpy(fsp->fs_fsmnt, path, sizeof(fsp->fs_fsmnt));

	/*
	 * NOW find mounttab slot and insert into vfs list.
	 */
	(void)p_sema(&vfs_mutex, PVFS);	/* Exclude vfs mount/unmount activity */

	/*
	 * Check for dev already mounted on.
	 */
	for (mp = &mounttab[0]; mp < v.ve_mount; mp++) {
		if (mp->m_bufp != 0 && *devvpp == mp->m_devvp) {
			v_sema(&vfs_mutex);
			error = EBUSY;
			goto out;
		}
	}
	/*
	 * "error" may have been set to ENOSPC above because the filesystem
	 * was not FS_OKAY.  Now we've determined that the reason this
	 * filesystem was not FS_OKAY was not because it was already mounted,
	 * so we return the ENOSPC error now.
	 */
	if (error) {
		v_sema(&vfs_mutex);
		goto out;
	}
	/*
	 * Find empty mount table entry.
	 */
	for (mp = &mounttab[0]; mp < v.ve_mount; mp++) {
		if (mp->m_bufp == 0) {
			/*
			 * Add vfs to vfs list.
			 * Copy local to mount table slot.
			 * Lock entry to avoid races with nfsgetmp().
			 */
			s_ipl = p_lock(&mp->m_lock, SPLFS);
			mp->m_devvp     = *devvpp;
			vfsp->vfs_devvp = *devvpp;
			mp->m_bufp = bp;
			vfsp->vfs_data = (caddr_t)mp;
			mp->m_vfs = *vfsp;
			vfs_add(&mp->m_vfs, mp->m_dev == rootdev);
			if (vfspp != (struct vfs **)0)
				*vfspp = &mp->m_vfs;	/* see ufs_mountroot */
			v_lock(&mp->m_lock, s_ipl);
			v_sema(&vfs_mutex);

			SPECPRINTF5("mountfs: done, no error\n");

			return (0);
		}
	}
	v_sema(&vfs_mutex);
	error = EBUSY;
out:
	if (sspace)
		wmemfree(sspace, (int)fsp->fs_cssize);
	if (bp)
		brelse(bp);
	if (tp)
		brelse(tp);
	/*
	 * Need to close the device, since we had a successful open above.
	 * bflush() first (paranoia).
	 */
	bflush(*devvpp);
	binval(*devvpp);
	(void) VOP_CLOSE(*devvpp, (vfsp->vfs_flag & VFS_RDONLY) ?
			FREAD : FREAD|FWRITE, u.u_cred);

	SPECPRINTF5("mountfs: return ERROR = %d\n", error);

	return (error);
}

/*
 * vfs operations
 */

/*
 * ufs_unmount()
 *	Unmount a UNIX file-system.
 *
 * Called from unmount() while holding unmount_mutex.
.
 */
static
ufs_unmount(vfsp)
	struct vfs *vfsp;
{
	return (unmount1(vfsp, 0));
}

/*
 * unmount1()
 *	Unmount a UNIX file-system.
 *
 * Currently, no calls pass forcibly == 1; this would be *very* hard
 * to do:  probably run the file-table and mark entries as "bogus",
 * somehow force users of these to wakeup and close them.  Alternatives:
 * kill all processes with open files on the device (mean!), or yank
 * those inodes we can and bogusize the rest of the file-desc's (probably
 * racey)...
 */

/*ARGSUSED*/
static
unmount1(vfsp, forcibly)
	register struct	vfs	*vfsp;
	int	forcibly;
{
	register struct	vnode	*vp;
	register struct	mount	*mp;
	register struct	fs	*fs;
	register struct	vnode	*dev_vp;
	struct	buf		*bufp;		/* pointer to superblock */
	spl_t	s;
	int	flag;

	mp = (struct mount *)vfsp->vfs_data;
	dev_vp = mp->m_devvp;

	vp = vfsp->vfs_vnodecovered;
	/*
	 * If NULLVP then root, disallow unmount.
	 */
	if ((vp == NULLVP) || !VN_TRYLOCKNODE(vp))
		return (EBUSY);

	/*
	 * Lock inode lists so iflush() can tell us if anything is still in use.
	 * If nothing in use and covered vnode isn't held, do the unmount.
	 * We call vfs_remove with the covered vnode still locked,
	 * to ensure that no one enters the fs being unmounted until 
	 * vp->vfsmountedhere is cleared, after which the filesystem
	 * is inaccesible. Holding the covered vnode locked and holding
	 * vfs_mutex is sufficient to ensure that no other processes could
	 * venture into the fs being unmounted.
	 *
	 * This relies on dirlook() holding a ref to an inode until
	 * it holds a ref on inode being looked up, and inode allocation
	 * holding a ref to the parent directory until the inode is allocated
	 * and held.  These rules ==> if iflush() passes and the covered
	 * vnode is only held once (by the mount), then the file-sys
	 * may be safely unmounted (there are no in-use entries and none
	 * being concurrently created).
	 */

	(void)p_sema(&vfs_mutex, PVFS);
	s = p_lock(&mp->m_lock, SPLFS);
	(void) p_lock(&ino_list, SPLFS);
	if (!iflush(dev_vp)) {
		v_lock(&ino_list, SPLFS);
		v_lock(&mp->m_lock, s);
		v_sema(&vfs_mutex);
		VN_UNLOCKNODE(vp);
		return (EBUSY);
	}
	v_lock(&ino_list, SPLFS);

	/*
	 * locked vnode count not needed here: if count==1, it could only grow
	 * via an iget(), and that can't happen since we have node locked.
	 * If v_count > 1, lookuppn() could be redirecting to the mounted root
	 * as we speak.
	 */
	if (vp->v_count > 1) {
		v_lock(&mp->m_lock, s);
		v_sema(&vfs_mutex);
		VN_UNLOCKNODE(vp);
		return (EBUSY);
	}

	/*
	 * Check for race with fhtovp(). If a nfsd is about to do an
	 * iget() (or is doing iget()), then fail because of being BUSY.
	 */
	if (mp->m_count != 0) {
		v_lock(&mp->m_lock, s);
		v_sema(&vfs_mutex);
		VN_UNLOCKNODE(vp);
		return (EBUSY);
	}

	/*
	 * vfs_remove clears vp->vfsmountedhere to prevent lookuppn()
	 * from indirecting to a mounted vfs when it reaches this vnode.
	 */

	vfs_remove(vfsp);
	VN_UNLOCKNODE(vp);

#ifdef QUOTA
	if (mp->m_qinod != NULL) {
		v_lock(&mp->m_lock, s);
		(void)closedq(mp);
		/*
		 * Here we have to iflush again to get rid of the quota
		 * inode.  A drag, but it would be ugly to cheat, & this
		 * doesn't happen often.
		 */
		(void) p_lock(&mp->m_lock, SPLFS);
		(void) p_lock(&ino_list, SPLFS);
		(void)iflush(dev_vp);
		v_lock(&ino_list, SPLFS);
	}
#endif
	/*
	 * Now vp->vfsmountedhere is cleared. Save various items into locals
	 * so that we can release the mp->m_lock. Then we can put away the
	 * filesystem at our leisure.
	 */

	fs = mp->m_bufp->b_un.b_fs;
	flag = !fs->fs_ronly;
	bufp = mp->m_bufp;
	mp->m_bufp = 0;
	mp->m_devvp = (struct vnode *)0;
	v_lock(&mp->m_lock, s);

	if (flag) {				/* insure clean update */
		fs->fs_fmod = 0;
		fs->fs_time = time.tv_sec;
		fs->fs_state = FS_OKAY - (long)fs->fs_time;
		sbupdate(dev_vp, fs);
	}
	wmemfree((caddr_t)fs->fs_csp[0], (int)fs->fs_cssize);
	brelse(bufp);
	v_sema(&vfs_mutex);

	/*
	 * write-enable the device, to be sure it's cool.
	 */
	spec_writeprotect(dev_vp, 0);

	/*
	 * Must close and release vnode, since we opened it at mount time.
	 * Do a bflush(),binval() even though close should do this anyway.
	 */
	bflush(dev_vp);
	binval(dev_vp);
	(void) VOP_CLOSE(dev_vp, flag, u.u_cred);
	VN_RELE(dev_vp);

	return (0);
}

static
ufs_unmountroot()
{
	register struct mount *mp;
	register struct fs *fsp;

	mp = (struct mount *)rootvfs->vfs_data;
	fsp = mp->m_bufp->b_un.b_fs;
	fsp->fs_time = time.tv_sec;
	fsp->fs_state = FS_OKAY - (long)fsp->fs_time;
	sbupdate(mp->m_devvp, fsp);
}

/*
 * ufs_root()
 * 	find root of ufs
 *
 * Return with locked vnode (*vpp).
 */

static
ufs_root(vfsp, vpp)
	struct vfs *vfsp;
	struct vnode **vpp;
{
	register struct mount *mp;
	register struct inode *ip;

	mp = (struct mount *)vfsp->vfs_data;
	ip = iget(mp->m_devvp, mp->m_bufp->b_un.b_fs, (ino_t)ROOTINO);
	if (ip == (struct inode *)0) {
		return (u.u_error);
	}
	*vpp = ITOV(ip);
	return (0);
}

/*
 * Flush any pending I/O.
 */

/*
 * Get file system statistics.
 */

static
ufs_statfs(vfsp, vp, sbp)
	struct vfs *vfsp;
	struct vnode *vp;
	struct statfs *sbp;
{
	register struct fs *fsp;
	struct buf *bp;
	struct vnode *dev_vp;
	int error = 0;

	/*
	 * ufs_statfs returns file system statistics for both mounted
	 * and unmounted file systems. A non-zero vfsp indicates that
	 * the file system is mounted and vfsp points to its vfs
	 * structure. If vp is non-zero, it points to a special file
	 * and we read the superblock off that device. Note that 
	 * if vp is zero, vfsp must be non-zero and vice-versa.
	 */
	if (vp) {
		/*
	 	 * Open block device mounted on.
	 	 */

		if (vp->v_type != VBLK) {
			return (ENOTBLK);
		}

		dev_vp = bdevvp(vp->v_rdev);
		error = VOP_OPEN(&dev_vp, FREAD, u.u_cred);
		if (error) {
			VN_RELE(dev_vp);
			return (error);
		}

		/*
	 	* Read in superblock.
	 	*/
		bp = bread(dev_vp, SBLOCK, SBSIZE);
		if (bp->b_flags & B_ERROR) {
			error = EIO;
			goto out;
		}
		fsp = bp->b_un.b_fs;
		if (fsp->fs_magic != FS_MAGIC) {
			error = EINVAL;
			goto out;
		}
	} else {
		fsp = ((struct mount *)vfsp->vfs_data)->m_bufp->b_un.b_fs;
		ASSERT(fsp->fs_magic == FS_MAGIC, "ufs_statfs");
		/*
		 *+ When doing a statfs on a mounted filesystem, the kernel
		 *+ found that the in-core superblock had a bad magic number.
		 */
	}
	sbp->f_fstyp = MOUNT_UFS;
	sbp->f_bsize = fsp->fs_bsize;
	sbp->f_frsize = fsp->fs_fsize;
	sbp->f_blocks = fsbtodb(fsp, fsp->fs_size);
	sbp->f_bfree = fsbtodb(fsp, fsp->fs_cstotal.cs_nbfree * fsp->fs_frag +
			fsp->fs_cstotal.cs_nffree);
	/*
	 * avail = MAX(max_avail - used, 0)
	 */
	sbp->f_bavail = (fsbtodb(fsp, fsp->fs_dsize) *
			(100 - fsp->fs_minfree) / 100) -
		        (fsbtodb(fsp, fsp->fs_dsize) - sbp->f_bfree);
	sbp->f_bavail = MAX(sbp->f_bavail, 0);
	/*
	 * inodes
	 */
	sbp->f_files = fsp->fs_ncg * fsp->fs_ipg;
	sbp->f_ffree = fsp->fs_cstotal.cs_nifree;
	bcopy(fsp->fs_fname, sbp->f_fname, sizeof(sbp->f_fname));
	bcopy(fsp->fs_fpack, sbp->f_fpack, sizeof(sbp->f_fpack));
out:
	if (vp) {
		brelse(bp);
		/*
	 	* Need to close device, since we had a successful open above.
	 	* bflush() first (paranoia).
	 	*/
		bflush(dev_vp);
		binval(dev_vp);
		(void) VOP_CLOSE(dev_vp, FREAD, u.u_cred);
                VN_RELE(dev_vp);
	}
	return (error);
}

static
ufs_sync()
{
	update();
	return (0);
}

/*
 * sbupdate()
 *	Update superblocks on disk.
 *
 * Called from update() and unmount1(). Note change in interface from 4.2.
 */

sbupdate(dev_vp, fs)
	struct vnode *dev_vp;
	register struct fs *fs;
{
	register struct buf *bp;
	int blks;
	caddr_t space;
	int i, size;

	bp = getblk(dev_vp, SBLOCK, (int)fs->fs_sbsize, 0);
	bcopy((caddr_t)fs, bp->b_un.b_addr, (uint)fs->fs_sbsize);
	/* Restore compatibility to old file systems.		   XXX */
	if (fs->fs_postblformat == FS_42POSTBLFMT) {
		/*
		 * Restore OLD 4.2 "spares".
		 */
		bp->b_un.b_fs->fs_un.fs_dsp.dfs_id[0] = fs->fs_id[0];
		bp->b_un.b_fs->fs_un.fs_dsp.dfs_id[1] = fs->fs_id[1];
		bp->b_un.b_fs->fs_un.fs_dsp.dfs_state = fs->fs_state;
		bp->b_un.b_fs->fs_un.fs_dsp.dfs_sparecon[0] = 0;
		bp->b_un.b_fs->fs_un.fs_dsp.dfs_sparecon[1] = 0;
		bp->b_un.b_fs->fs_un.fs_dsp.dfs_sparecon[2] = 0;
		/*
		 * Restore OLD 4.2 fs_postbl[][]
		 */
		bp->b_un.b_fs->fs_nrpos = -1;	
		bp->b_un.b_fs->fs_id[0] = -1;	
		bp->b_un.b_fs->fs_id[1] = -1;	
		bp->b_un.b_fs->fs_state = -1;	
	}
	bwrite(bp);
	blks = howmany(fs->fs_cssize, fs->fs_fsize);
	space = (caddr_t)fs->fs_csp[0];
	for (i = 0; i < blks; i += fs->fs_frag) {
		size = fs->fs_bsize;
		if (i + fs->fs_frag > blks)
			size = (blks - i) * fs->fs_fsize;
		bp = getblk(dev_vp, fsbtodb(fs, fs->fs_csaddr + i), size, 0);
		bcopy(space, bp->b_un.b_addr, (uint)size);
		space += size;
		bwrite(bp);
	}
}
