/* $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: vfs.c 1.33 90/10/22 $"

/*
 * vfs.c
 *	Virtual File-System system calls.
 */

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

#include "sys/signal.h"
#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/mutex.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/uio.h"
#include "sys/file.h"
#include "sys/vfs.h"
#include "sys/statfs.h"
#include "sys/mount.h"
#include "sys/pathname.h"
#include "sys/vnode.h"
#include "sys/systm.h"		/* for unmount_mutex */
#include "sys/vmmeter.h"
#include "sys/ustat.h"
#include "sys/cmn_err.h"

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

/*
 * vfs global data.
 */
struct	vnode	*rootdir;		/* pointer to root vnode */

struct	vfs	*rootvfs;		/* pointer to root vfs. This is */
					/* also the head of the vfs list */

sema_t	sync_sema;			/* Sync if not already sync'ing */
void (**d_sync_routines)();		/* list of sync routines */
unsigned n_d_sync_routines;		/* # of d_sync_routines for wmemall */
unsigned max_n_d_sync_routines;		/* Highest space allocated */
bool_t d_sync_calloced;			/* Current table was calloced */
extern bool_t calloc_ok;

/*
 * System calls
 */

/*
 * mount system call.
 */
smount()
{
	register struct a {
		char	*fspec;
		char	*freg;
		int	flags;
		int	fstyp;
		char	*dataptr;
		int	datalen;
	} *uap = (struct a *)u.u_ap;
	struct pathname	pn;
	struct vnode	*vp;
	struct vfs	lvfs;
	int		fstyp;

	/*
	 * Must be super user
	 */
	if (!suser())
		return;

	/*
	 * Get the vnode to be covered.  After looking it up, purge
	 * directory-name cache of any entries referring to the
	 * desired vnode (e.g., need to remove any unnecessary ref's
	 * so that v_count can be == 1).
	 */
	u.u_error = lookupname(uap->freg, UIOSEG_USER, FOLLOW_LINK,
					(struct vnode **)0, &vp);
	if (u.u_error)
		return;
	dnlc_purge_vp(vp);
	if (vp->v_type != VDIR) {
		VN_PUT(vp);
		u.u_error = ENOTDIR;
		return;
	}
	if (vp->v_count != 1 || (vp->v_flag & VROOT)) {
		VN_PUT(vp);
		u.u_error = EBUSY;
		return;
	}
	u.u_error = pn_get(uap->freg, UIOSEG_USER, &pn);
	if (u.u_error) {
		VN_PUT(vp);
		return;
	}
	/*
	 * Backward compatibility: require the user program to
	 * supply a flag indicating a new-style mount, otherwise
	 * assume the fstyp of the root file system and zero values
	 * for dataptr and datalen.  MS_FSS indicates a 3.0 4-argument
	 * mount; MS_DATA is the preferred way and indicates a
	 * 6-argument mount.
	 *
	 * As far as I know, only svvs uses the old 3-argument style
	 * mount. For now, hard-code fstyp to be 1. Has to be a better
	 * way.
	 */
	fstyp = (uap->flags & (MS_DATA|MS_FSS)) ? uap->fstyp : 1;
	if ((fstyp <= 0) || (fstyp >= mount_ntypes) ||
	    vfssw[fstyp] == (struct vfsops *)0) {
		u.u_error = EINVAL;
		pn_free(&pn);		/* release pathname */
		VN_PUT(vp);
		return;
	}

	/*
	 * Initialize local vfs structure. The VFS_MOUNT() routine
	 * will enter the vfs into the vfs list.
	 *
	 * This differs from the SUN implementation.
	 */
	VFS_INIT(&lvfs, vfssw[fstyp], (caddr_t)0);
	u.u_error = vfs_filldata(vp, &lvfs, uap->flags);
	if (!u.u_error) {
		u.u_error = VFS_MOUNT(&lvfs, pn.pn_path, uap->fspec,
					uap->dataptr, uap->datalen);
	}
	pn_free(&pn);
	if (u.u_error) {
		VN_PUT(vp);
	} else {
		VN_UNLOCKNODE(vp);
	}
}

/*
 * Sync system call.
 *	Sync each vfs for fs specific items, then flush the buffer cache.
 *	Also, perform additional sync like actions for drivers.
 */
sync()
{
	register struct vfsops **vfstyp;

	/*
	 * If this fails, do nothing (sync() is heuristic update).
	 */
	if (!cp_sema(&sync_sema))
		return;

	/*
	 * Drivers syncs should be fast and won't block on reads, so do them
	 * first 
	 */
	drivers_sync();

	/*
	 * Call the VFS_SYNC routine for each filesystem TYPE rather than for
	 * each filesystem mounted.
	 */
	for (vfstyp = vfssw; vfstyp < &vfssw[mount_ntypes]; ++vfstyp) {
		if (*vfstyp != (struct vfsops *)0) {
			(*(*vfstyp)->vfs_sync)();
		}
	}

	/*
	 * For all devices and filesystems, force stale buffer cache
	 * information to be flushed.
	 */
	bflush(NULLVP);

	v_sema(&sync_sema);
}

/*
 * drivers_sync()
 *	Any driver can request that a specific routine be called at sync time.
 * 	This routine is the one that calls these sync routines.
 */
drivers_sync()
{
	register void (**d_sync)() = d_sync_routines;

	if ( d_sync )
		while (*d_sync) {
			(**d_sync)();
			d_sync++;
		}
	return;
}

/*
 * d_sync_add(d_sync)
 *	Add the given driver sync routine to those present.  No checking is
 *	done  to see if it is already present.  
 */
d_sync_add(d_sync)
	void (*d_sync)();
{
	(void)p_sema(&sync_sema, PSYNC);

	if ( !d_sync_routines ) {
		/* First time, set everything up */
		max_n_d_sync_routines = DSYNCPG;
		if (calloc_ok) {
			callocrnd(NBPG);
			d_sync_routines = (void (**)())calloc(NBPG);
			d_sync_calloced = 1;
		} else {
			d_sync_routines = (void (**)())wmemall(NBPG, 1);
			d_sync_calloced = 0;
		}
		bzero( (char *)d_sync_routines, NBPG);
		n_d_sync_routines = 0;
	}

	/* There is always at least one zero entry in the table. */
	d_sync_routines[n_d_sync_routines++] = d_sync;

	/* Be sure there's a zero entry to end the table. */
	if ( n_d_sync_routines == max_n_d_sync_routines ) {
		void (**new_d_sync)();
		
		max_n_d_sync_routines += DSYNCPG;
		if (calloc_ok) {
			callocrnd(NBPG);
			new_d_sync = (void (**)())
				calloc(max_n_d_sync_routines * DSYNCSZ);
		} else {
			new_d_sync = (void (**)())
				wmemall((int)(max_n_d_sync_routines*DSYNCSZ),
					1);
		}
		bcopy( (char *)d_sync_routines, (char *) new_d_sync,
		      n_d_sync_routines * DSYNCSZ);
		bzero( (char *)(new_d_sync + n_d_sync_routines), NBPG);
		if ( !d_sync_calloced)
			wmemfree((caddr_t)d_sync_routines,
				 (int)(n_d_sync_routines*DSYNCSZ));
		if (calloc_ok)
			d_sync_calloced = 1;
		else 	d_sync_calloced = 0;

		d_sync_routines = new_d_sync;
	}
	v_sema(&sync_sema);
}

/*
 * d_sync_remove(d_sync)
 *	Remove the given driver sync routine to those present.  No check is
 *	made to see if the driver is present more than once.  Returns -1 if the
 *	routine is not present, 0 if it was.
 */
d_sync_remove(d_sync)
	void (*d_sync)();
{
	register void (**table)();
	register int i;

	(void)p_sema(&sync_sema, PSYNC);
	for (i=0, table=d_sync_routines; i<n_d_sync_routines; i++, table++)
		if ( d_sync == *table ) {
			/* found it, remove it */
			ovbcopy( (char *)(table+1), (char *) table,
				(unsigned)(--n_d_sync_routines-i)*DSYNCSZ);
			d_sync_routines[n_d_sync_routines] = 0;
			v_sema(&sync_sema);
			return 0;
		}
	
	/* not found */
	v_sema(&sync_sema);
	return( -1 );
}
#ifdef lint
void
d_sync_ref()
{
	void (*dummy)() = d_sync_ref;

	/* The only reason for this routine is to generate references for
	 * d-sync_add and d_sync_remove
	 */
	
	d_sync_add( dummy );
	if ( ! d_sync_remove( dummy ) )
		d_sync_ref();
}
#endif /* lint */

/*
 * get filesystem statistics
 */
statfs()
{
	register struct a {
		char	*fname;
		struct	statfs *sb;
		int	len;
		int	fstyp;
	} *uap = (struct a *)u.u_ap;
	struct vnode *vp;

	u.u_error =
	    lookupname(uap->fname, UIOSEG_USER, FOLLOW_LINK,
		(struct vnode **)0, &vp);
	if (u.u_error)
		return;
	cstatfs(vp, uap->sb, uap->len, uap->fstyp);
	VN_PUT(vp);
}

fstatfs()
{
	register struct a {
		int	fd;
		struct	statfs *sb;
		int	len;
		int	fstyp;
	} *uap = (struct a *)u.u_ap;
	register struct vnode *vp;
	struct file *fp;

	u.u_error = getvnodefp(uap->fd, &fp);
	if (u.u_error)
		return;
	vp = fp->f_vnode;
	VN_LOCKNODE(vp);
	cstatfs(vp, uap->sb, uap->len, uap->fstyp);
	VN_UNLOCKNODE(vp);
}

cstatfs(vp, ubuf, len, fstyp)
	struct vnode *vp;
	struct statfs *ubuf;
	int len, fstyp;
{
	struct statfs sb;

	if (len < 0 || len > sizeof(sb) || fstyp < 0 || fstyp >= mount_ntypes) {
		u.u_error = EINVAL;
		return;
	}
	if (fstyp && vp->v_type != VCHR && vp->v_type != VBLK) {
		u.u_error = EINVAL;
		return;
	}
	if (fstyp) {
		u.u_error = (*(vfssw[fstyp])->vfs_statfs)((struct vfs *)0,
					vp, &sb);
	} else {
		/*
		 * Let's avoid dereferencing vp->v_vfsp if it's NULL.
		 * This happens when fstatfs() was called with an
		 * fd that was returned from a clone device.
		 */
		if (vp->v_vfsp == (struct vfs *)NULL) {
			u.u_error = EINVAL;
			return;
		}
		u.u_error = VFS_STATFS(vp->v_vfsp, (struct vnode *)0, &sb);
	}
	if (u.u_error)
		return;
	u.u_error = copyout((caddr_t)&sb, (caddr_t)ubuf, (unsigned int)len);
}

/*
 * vfs_ustat
 *	the guts of the ustat() system call.
 *	called indirectly from utssys()
 */
vfs_ustat(dev, ub)
dev_t	dev;
struct	ustat *ub;
{
	struct vfs	*vfsp;
	struct statfs	sb;
	int error;

	(void)p_sema(&vfs_mutex, PVFS);
	vfsp = rootvfs;
	while (vfsp != (struct vfs *)NULL) {
		if (vfsp->vfs_dev == dev)
			break;
		vfsp = vfsp->vfs_next;
	}
	if (vfsp == (struct vfs *)NULL) {
		v_sema(&vfs_mutex);
		return(EINVAL);
	}
	error = VFS_STATFS(vfsp, (struct vnode *)0, &sb);
	v_sema(&vfs_mutex);
	if (error == 0) {
		ub->f_tfree = sb.f_bfree;
		ub->f_tinode = sb.f_ffree; 
		bcopy(sb.f_fname, ub->f_fname, sizeof(ub->f_fname));
		bcopy(sb.f_fpack, ub->f_fpack, sizeof(ub->f_fpack));
	}
	return(error);
}


/*
 * Umount system call.
 *
 */
sumount()
{
	struct a {
		char	*file;		/* directory or block-special */
	} *uap = (struct a *)u.u_ap;
	register struct vfs *vfsp;
	register struct vnode *coveredvp;
	struct vnode *vp;
	extern void dnlc_purge_vfsp();

	if (!suser())
		return;

	(void)p_sema(&unmount_mutex, PVFS);	/* serialize unmounts */

	u.u_error = lookupname(uap->file, UIOSEG_USER, FOLLOW_LINK,
			(struct vnode **)0, &vp);
	if (u.u_error) {
		v_sema(&unmount_mutex);
		return;
	}

	/*
	 * Get pointer to vfs. If it's a directory, then
	 * ensure it's a root dir and get vfsp from the
 	 * vnode. Else, need to scan the vfs list for
 	 * a match on dev.
 	 */
	if (vp->v_type == VDIR) {
		if ((vp->v_flag & VROOT) == 0) {
			u.u_error = EINVAL;
			goto out;
		}
		vfsp = vp->v_vfsp;
	} else {
		(void)p_sema(&vfs_mutex, PVFS);
		vfsp = rootvfs;
		while (vfsp != (struct vfs *)NULL) {
			if (vfsp->vfs_dev == vp->v_rdev)
				break;
			vfsp = vfsp->vfs_next;
		}
		v_sema(&vfs_mutex);
		if (vfsp == (struct vfs *)NULL) {
			/* no match */
			u.u_error = EINVAL;
			goto out;
		}
	}

	VN_PUT(vp);
	vp = (struct vnode *)0;
	coveredvp = vfsp->vfs_vnodecovered;

	xumount(vfsp);		/* remove unused sticky files from text table */
	dnlc_purge_vfsp(vfsp);	/* purge dnlc entries for file system */
	VFS_SYNC(vfsp);

	u.u_error = VFS_UNMOUNT(vfsp);
	if (!u.u_error)
		VN_RELE(coveredvp);
out:
	if (vp)
		VN_PUT(vp);
	v_sema(&unmount_mutex);
}

/*
 * External routines
 */

/*
 * vfs_mountroot is called by main (init_main.c) to
 * mount the root filesystem.
 */

void
vfs_mountroot()
{
	register int error;
	extern int (*rootfsmount)();	/* pointer to root mounting routine */
					/* set by (auto)configuration */

	/*
	 * Rootfsmount is a pointer to the routine which will mount a specific
	 * filesystem as the root. It is setup by autoconfiguration.
	 * If error panic.
	 */
	error = (*rootfsmount)(1);
	if (error) {
		cmn_err(CE_CONT, "vfs_mountroot: mount error = %d\n", error);
		cmn_err(CE_PANIC, "vfs_mountroot cannot mount root");
		/*
		 *+ When the system was being booted,
		 *+ the kernel couldn't mount the root filesystem.
		 */
	}
	/*
	 * Get vnode for '/'.
	 * Setup rootdir, u.u_rdir and u.u_cdir to point to it.
	 * These are used by lookuppn so that it knows where
	 * to start from '/' or '.'.
	 */
	error = VFS_ROOT(rootvfs, &rootdir);
	VN_UNLOCKNODE(rootdir);
	ASSERT(!error, "rootmount: cannot find root vnode");
	/*
	 *+ When booting the system,
	 *+ the kernel couldn't find the root vnode of the root
	 *+ filesystem.
	 */
	u.u_cdir = rootdir;
	VN_HOLD(u.u_cdir);
	u.u_rdir = NULL;
}

/*
 * vfs_filldata
 * Fill out vfs_flag and vfs_vnodecovered fields.
 * coveredvp is NULLVP if this is the root.
 */
int
vfs_filldata(coveredvp, vfsp, mflag)
	struct vnode *coveredvp;
	register struct vfs *vfsp;
	int mflag;
{
	if (coveredvp != NULLVP) {
		/*
		 * Return EBUSY if the covered vp is already mounted on.
		 */
		if (coveredvp->v_vfsmountedhere != (struct vfs *)0)
			return (EBUSY);
	}
	vfsp->vfs_vnodecovered = coveredvp;
	if (mflag & MS_RDONLY) {
		vfsp->vfs_flag |= VFS_RDONLY;
	} else {
		vfsp->vfs_flag &= ~VFS_RDONLY;
	}
	if (mflag & MS_NOSUID) {
		vfsp->vfs_flag |= VFS_NOSUID;
	} else {
		vfsp->vfs_flag &= ~VFS_NOSUID;
	}
	return (0);
}

/*
 * vfs_add is called by a specific filesystem's mount routine to add
 * the new vfs into the vfs list and to cover the mounted on vnode.
 * The vfs is also locked so that lookuppn will not venture into the
 * covered vnodes subtree.
 *
 * Caller holds vfs_mutex sema to insure one at a time in here.
 */
void
vfs_add(vfsp, root)
	register struct vfs *vfsp;
	bool_t root;
{

	if (root) {
		/*
		 * This is the root of the whole world.
		 */
		rootvfs = vfsp;
		vfsp->vfs_next = (struct vfs *)0;
	} else {
		/*
		 * Put the new vfs on the vfs list after root.
		 * Point the covered vnode at the new vfs so lookuppn
		 * (vfs_lookup.c) can work its way into the new file system.
		 */
		vfsp->vfs_next = rootvfs->vfs_next;
		rootvfs->vfs_next = vfsp;
		vfsp->vfs_vnodecovered->v_vfsmountedhere = vfsp;
	}
}

/*
 * Remove a vfs from the vfs list, and destory pointers to it.
 * Should be called by filesystem implementation after it determines
 * that an unmount is legal but before it destroys the vfs.
 *
 * Caller holds vfs_mutex and mounted-on vnode locked.
 */
void
vfs_remove(vfsp)
register struct vfs *vfsp;
{
	register struct vfs *tvfsp;
	register struct vnode *vp;

	/*
	 * can't unmount root. Should never happen, because fs will be busy.
	 */
	ASSERT(vfsp != rootvfs, "vfs_remove: unmounting root");
	/*
	 *+ A call to unmount succeeded in unmounting the root
	 *+ filesystem.  This should normally fail because the
	 *+ root filesystem is always busy.
	 */

	tvfsp = rootvfs;
	for (; tvfsp != (struct vfs *)0; tvfsp = tvfsp->vfs_next) {
		if (tvfsp->vfs_next == vfsp) {
			/*
			 * remove vfs from list, unmount covered vp.
			 */
			tvfsp->vfs_next = vfsp->vfs_next;
			vp = vfsp->vfs_vnodecovered;
			vp->v_vfsmountedhere = (struct vfs *)0;
			return;
		}
	}
	/*
	 * can't find vfs to remove
	 */
	cmn_err(CE_PANIC, "vfs_remove: vfs not found");
	/*
	 *+ While the kernel was unmounting a filesystem, 
	 *+ it could not find the structure describing
	 *+ that mounted filesystem in its
	 *+ mount table.  This indicates a kernel software bug.
	 */
}
