#ifndef lint
static	char sccsid[] = "@(#)pc_vfsops.c 1.1 94/10/31 SMI";
#endif

/*
 * Copyright (c) 1989 by Sun Microsystems, Inc.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/user.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/conf.h>
#undef NFSCLIENT
#include <sys/mount.h>
#include <pcfs/pc_label.h>
#include <pcfs/pc_fs.h>
#include <pcfs/pc_dir.h>
#include <pcfs/pc_node.h>

/*
 * pcfs vfs operations.
 */
static int pcfs_mount();
static int pcfs_unmount();
static int pcfs_root();
static int pcfs_statfs();
static int pcfs_sync();
#ifdef notdef
static int pcfs_vget();
#endif notdef
extern int pcfs_invalop();
extern int pcfs_badop();

struct vfsops pcfs_vfsops = {
	pcfs_mount,
	pcfs_unmount,
	pcfs_root,
	pcfs_statfs,
	pcfs_sync,
	pcfs_badop,
	pcfs_invalop,	/* vfs_mountroot */
	pcfs_invalop,	/* vfs_swapvp */
};

/*
 * Default device to mount on.
 */

int pcfsdebuglevel = 0;
static int pcinit = 0;

/*
 * pc_mount system call
 */
static int
pcfs_mount(vfsp, path, data)
	struct vfs *vfsp;
	char *path;
	caddr_t data;
{
	int error;
	dev_t dev;
	struct vnode *vp;
	struct pc_args args;
PCFSDEBUG(6)
printf("pcfs_mount ");
	/*
	 * Get arguments
	 */
	error = copyin(data, (caddr_t)&args, sizeof (struct pc_args));
	if (error) {
		return (error);
	}

	/*
	 * Get the device to be mounted on
	 */
	error = lookupname(args.fspec, UIO_USERSPACE, FOLLOW_LINK,
	    (struct vnode **)0, &vp);
	if (error) {
		if (u.u_error == ENOENT)
			return (ENODEV);	/* needs translation */
		return (error);
	}
	if (vp->v_type != VBLK) {
		VN_RELE(vp);
		return (ENOTBLK);
	}
	dev = vp->v_rdev;
	VN_RELE(vp);
	if (major(dev) >= nblkdev) {
		return (ENXIO);
	}
	/*
	 * Mount the filesystem
	 */
	error = pcfs_mountfs(dev, path, vfsp);
	return (error);
}

static struct pcfs *pc_mounttab = NULL;

/*ARGSUSED*/
static int
pcfs_mountfs(dev, path, vfsp)
	dev_t dev;
	char *path;
	struct vfs *vfsp;
{
	register struct pcfs *fsp;
	int error;

	for (fsp = pc_mounttab; fsp != NULL; fsp = fsp->pcfs_nxt) {
		if (fsp->pcfs_devvp->v_rdev == dev) {
			return (EBUSY);
		}
	}
	if (!pcinit) {
		pcinit++;
		pc_init();
	}

	fsp = (struct pcfs *)kmem_alloc((u_int)sizeof (struct pcfs));
	bzero((caddr_t)fsp, sizeof (struct pcfs));
	fsp->pcfs_devvp = bdevvp(dev);
	fsp->pcfs_vfs = vfsp;
	error = pc_getfat(fsp);

	if (error) {
		VN_RELE(fsp->pcfs_devvp);
		kmem_free((caddr_t)fsp, (u_int) sizeof (struct pcfs));
		return (error);
	}
	fsp->pcfs_nxt = pc_mounttab;
	pc_mounttab = fsp;
	vfsp->vfs_fsid.val[0] = (long)dev;
	vfsp->vfs_fsid.val[1] = MOUNT_PC;
	vfsp->vfs_bsize = fsp->pcfs_clsize;
	vfsp->vfs_data = (caddr_t)fsp;
	return (0);
}

/*
 * vfs operations
 */

static int
pcfs_unmount(vfsp)
	register struct vfs *vfsp;
{
	register struct pcfs *fsp, *fsp1;

PCFSDEBUG(6)
printf("pc_unmount\n");
	fsp = VFSTOPCFS(vfsp);
	if (fsp->pcfs_nrefs)
		return (EBUSY);

	/* now there should be no pcp node on pcfhead or pcdhead. */

	if (fsp->pcfs_fatp != (u_char *)0) /* ?? has the fat been synced */
		pc_invalfat(fsp);
	else
		binval(fsp->pcfs_devvp);	/* ?? do we need this */

	if (fsp == pc_mounttab) {
		pc_mounttab = fsp->pcfs_nxt;
	} else {
		for (fsp1 = pc_mounttab; fsp1 != NULL; fsp1 = fsp1->pcfs_nxt)
			if (fsp1->pcfs_nxt == fsp)
				fsp1->pcfs_nxt = fsp->pcfs_nxt;
	}

	VN_RELE(fsp->pcfs_devvp);
	kmem_free((caddr_t)fsp, (u_int) sizeof (struct pcfs));
	return (0);
}

/*
 * find root of pcfs
 */
static int
pcfs_root(vfsp, vpp)
	struct vfs *vfsp;
	struct vnode **vpp;
{
	register struct pcfs *fsp;
	struct pcnode *pcp;

	fsp = VFSTOPCFS(vfsp);
	PC_LOCKFS(fsp);
	pcp = pc_getnode(fsp, (daddr_t)0, 0, (struct pcdir *)0);
PCFSDEBUG(6)
printf("pcfs_root(0x%x) pcp= 0x%x\n", vfsp, pcp);
	PC_UNLOCKFS(fsp);
	*vpp = PCTOV(pcp);
	pcp->pc_flags |= PC_EXTERNAL;
	return (0);
}

/*
 * Get file system statistics.
 */
static int
pcfs_statfs(vfsp, sbp)
	register struct vfs *vfsp;
	struct statfs *sbp;
{
	register struct pcfs *fsp;
	int error;

	fsp = VFSTOPCFS(vfsp);
	error = pc_getfat(fsp);
	if (error)
		return (error);
	sbp->f_bsize = fsp->pcfs_clsize;
	sbp->f_blocks = fsp->pcfs_ncluster;
	sbp->f_bavail = sbp->f_bfree = pc_freeclusters(fsp);
	bzero((caddr_t)&sbp->f_fsid, sizeof (fsid_t));
#ifdef notdef
	sbp->f_fsid.val[0] = (long)fsp->pcfs_devvp->v_rdev;
#endif notdef
	sbp->f_fsid = vfsp->vfs_fsid;
	return (0);
}

/*
 * Flush any pending I/O.
 */
static int
pcfs_sync(vfsp)
register struct vfs *vfsp;
{
	struct pcfs * fsp;
	static int updlock = 0;
	int error = 0;

	if ((updlock) || (!pcinit)) {
		return (0);
	}
	updlock = 1;

	if (vfsp != NULL) {
		error = pc_syncfsnodes(VFSTOPCFS(vfsp));
	} else {
		fsp = pc_mounttab;
		while (fsp != NULL) {
			error = pc_syncfsnodes(fsp);

			if (error) break;
			fsp = fsp -> pcfs_nxt;
		}
	}
	updlock = 0;
	return (error);
}

static int
pc_syncfsnodes(fsp)
register struct pcfs * fsp;
{
	register struct pchead * hp;
	register struct pcnode * pcp;
	int error;

	PC_LOCKFS(fsp);
	if (! (error = pc_syncfat(fsp))) {
		hp = pcfhead;
		while (hp < & pcfhead [ NPCHASH ]) {
			pcp = hp->pch_forw;
			while (pcp != (struct pcnode *) hp) {
				if (VFSTOPCFS (PCTOV (pcp) -> v_vfsp) == fsp)
					if (error = pc_nodesync (pcp))
						break;
				pcp = pcp -> pc_forw;
			}
			if (error)
				break;
			hp ++;
		}
	}
	PC_UNLOCKFS (fsp);
	return (error);
}

int
pc_lockfs(fsp)
	register struct pcfs *fsp;
{
	int error;

	PC_LOCKFS(fsp);
PCFSDEBUG(6)
printf("pcfs_lockfs(0x%x) locked\n", fsp);
	error = pc_getfat(fsp);
	if (error) {
PCFSDEBUG(6)
printf("pcfs_lockfs(0x%x) getfat error\n", fsp);
		pc_unlockfs(fsp);
	}
	return (error);
}

void
pc_unlockfs(fsp)
	register struct pcfs *fsp;
{

	if (!(fsp->pcfs_flags & PCFS_LOCKED))
		panic("pc_unlockfs");
PCFSDEBUG(6)
printf("pcfs_unlockfs(0x%x)\n", fsp);
	PC_UNLOCKFS(fsp);
}

struct bootsec {
	char	instr[3];
	char	version[8];
	u_char	bps[2];			/* bytes per sector */
	u_char	spcl;			/* sectors per alloction unit */
	u_char	res_sec[2];		/* reserved sectors, starting at 0 */
	u_char	nfat;			/* number of FATs */
	u_char	rdirents[2];		/* number of root directory entries */
	u_char	totalsec[2];		/* total sectors in logical image */
	char	mediadesriptor;		/* media descriptor byte */
	u_short	fatsec;			/* number of sectors per FAT */
	u_short	spt;			/* sectors per track */
	u_short nhead;			/* number of heads */
	u_short hiddensec;		/* number of hidden sectors */
};

/*
 * Get the file allocation table.
 * If there is an old one, invalidate it.
 */
int
pc_getfat(fsp)
	register struct pcfs *fsp;
{
	register struct buf *bp = 0;
	register struct buf *tp = 0;
	register u_char *fatp;
	struct bootsec *bootp;
	struct vnode *devvp;
	int error;

PCFSDEBUG(3)
printf("pc_getfat\n");
	devvp = fsp->pcfs_devvp;
	if (fsp->pcfs_fatp) {
		/*
		 * There is a fat in core.
		 * If there are open file pcnodes or we have modified it or
		 * it hasn't timed out yet use the in core fat.
		 * Otherwise invalidate it and get a new one
		 */
		if (fsp->pcfs_frefs ||
		    (fsp->pcfs_flags & PCFS_FATMOD) ||
		    timercmp(&time, &fsp->pcfs_fattime, <)) {
PCFSDEBUG(3)
printf("pc_getfat: use same fat\n");
			return  (0);
		} else {
			pc_invalfat(fsp);
		}
	}
	/*
	 * Open block device mounted on.
	 */
	error = VOP_OPEN(&devvp,
		(PCFSTOVFS(fsp)->vfs_flag & VFS_RDONLY) ? FREAD : FREAD|FWRITE,
		u.u_cred);
	if (error)
		/* return (error); */
		return (EIO);
	/*
	 * Get fat and check it for validity
	 */
	tp = bread(devvp, (daddr_t)0, (PC_FATBLOCK+PC_MAXFATSEC) * PC_SECSIZE);
	if (tp->b_flags & (B_ERROR | B_INVAL)) {
PCFSDEBUG(1)
printf("pc_getfat: ");
		if (tp->b_flags & B_ERROR)
			pc_diskchanged (fsp);
		else
			brelse (tp);
		return (EIO);
	}
	tp->b_flags |= B_INVAL;
	bootp = (struct bootsec *)tp->b_un.b_addr;
	fatp = (u_char *)(tp->b_un.b_addr + PC_FATBLOCK*PC_SECSIZE);
	if (fatp[1] != 0xFF || fatp[2] != 0xFF ||
		ltohs(bootp->res_sec[0]) != 1) {
		/* we don't support disks with reserved sectors. */
		error = EINVAL;
		goto out;
	}
	switch (fatp[0]) {
	case SS8SPT:
	case DS8SPT:
	case SS9SPT:
	case DS9SPT:
	case DS18SPT:
	case DS9_15SPT:
		fsp->pcfs_spcl = (int)bootp->spcl;
		fsp->pcfs_fatsec = (int)ltohs(bootp->fatsec);
		fsp->pcfs_spt = (int)ltohs(bootp->spt);
		fsp->pcfs_rdirsec = (int)ltohs(bootp->rdirents[0])
					* sizeof (struct pcdir) / PC_SECSIZE;
		fsp->pcfs_clsize = fsp->pcfs_spcl * PC_SECSIZE;
		fsp->pcfs_rdirstart = PC_FATBLOCK +
					(bootp->nfat * fsp->pcfs_fatsec);
		fsp->pcfs_datastart = fsp->pcfs_rdirstart + fsp->pcfs_rdirsec;
		fsp->pcfs_ncluster = ((int)ltohs(bootp->totalsec[0]) -
					fsp->pcfs_datastart) / fsp->pcfs_spcl;
		break;
	default:
		error = EINVAL;
		goto out;
	}

	/*
	 * Copy the fat into a buffer in its native size.
	 */
	bp = geteblk(fsp->pcfs_fatsec * PC_SECSIZE);
	bcopy((caddr_t)fatp, (caddr_t)bp->b_un.b_addr,
		(u_int)(fsp->pcfs_fatsec * PC_SECSIZE));
	brelse(tp);

	fsp->pcfs_fatbp = bp;
	fsp->pcfs_fatp = (u_char *)bp->b_un.b_addr;
	fsp->pcfs_fattime = time;
	fsp->pcfs_fattime.tv_sec += PCFS_DISKTIMEOUT;
	return (0);
out:
PCFSDEBUG(1)
printf("pc_getfat: illegal disk format\n");
	if (tp)
		brelse(tp);

	(void) VOP_CLOSE(devvp, (PCFSTOVFS(fsp)->vfs_flag & VFS_RDONLY) ?
		FREAD : FREAD|FWRITE, 1, u.u_cred);
	return (error);
}

int
pc_syncfat(fsp)
	register struct pcfs *fsp;
{
	register struct buf *bp;
	register int fatsize;

PCFSDEBUG(6)
printf("pcfs_syncfat\n");
	if ((fsp->pcfs_fatp == (u_char *)0) || !(fsp->pcfs_flags & PCFS_FATMOD))
		return (0);
	/*
	 * write out fat
	 */
	fsp->pcfs_flags &= ~PCFS_FATMOD;
	fsp->pcfs_fattime = time;
	fsp->pcfs_fattime.tv_sec += PCFS_DISKTIMEOUT;
	fatsize = fsp->pcfs_fatbp->b_bcount;
	bp = getblk(fsp->pcfs_devvp, (daddr_t)PC_FATBLOCK, fatsize);
	bp->b_flags |= B_NOCACHE;		/* don't cache */
	bcopy((caddr_t)fsp->pcfs_fatp, bp->b_un.b_addr, (u_int)fatsize);
	bwrite(bp);
	if (bp -> b_flags & B_ERROR) {
		pc_diskchanged (fsp);
		return (EIO);
	}
	/*
	 * write out alternate fat
	 */
	bp = getblk(fsp->pcfs_devvp,
		(daddr_t)(PC_FATBLOCK + fsp->pcfs_fatsec), fatsize);
	bp->b_flags |= B_NOCACHE;		/* don't cache */
	bcopy((caddr_t)fsp->pcfs_fatp, bp->b_un.b_addr, (u_int)fatsize);
	bawrite(bp);
PCFSDEBUG(6)
printf("pcfs_syncfat: wrote out fat\n");
	return (0);
}

void
pc_invalfat(fsp)
	register struct pcfs *fsp;
{

PCFSDEBUG(6)
printf("pc_invalfat\n");
	if (fsp->pcfs_fatp == (u_char *)0 || fsp->pcfs_frefs)
		panic("pc_invalfat");
	/*
	 * Release old fat, invalidate all the blocks associated
	 * with the device.
	 */
	brelse(fsp->pcfs_fatbp);
	fsp->pcfs_fatbp = (struct buf *)0;
	fsp->pcfs_fatp = (u_char *)0;
	binval(fsp->pcfs_devvp);
	/* ?? Also have to invalidate all the vnode pages. */
	/*
	 * close mounted device
	 */
	(void) VOP_CLOSE(fsp->pcfs_devvp,
			(PCFSTOVFS(fsp)->vfs_flag & VFS_RDONLY) ?
			FREAD : FREAD|FWRITE, 1, u.u_cred);
}

pc_badfs(fsp)
	struct pcfs *fsp;
{

	uprintf("corrupted PC file system on dev 0x%x\n",
		fsp->pcfs_devvp->v_rdev);
}

#ifdef notdef
/*
 * The problem with supporting NFS mount PCFS filesystem is that there
 * is no good place to keep the generation number. The only possible
 * place is inside a directory entry. (There are a few words that are
 * not used.) But directory entries come and go. That is, if a
 * directory is removed completely, its directory blocks are freed
 * and the generation numbers are lost. Whereas in ufs, inode blocks
 * are dedicated for inodes, so the generation numbers are permanently
 * kept on the disk.
 */
static int
pcfs_vget(vfsp, vpp, fidp)
	struct vfs *vfsp;
	struct vnode **vpp;
	struct fid *fidp;
{
	struct pcnode *pcp;
	struct pcdir *ep;
	struct pcfid *pcfid;
	struct pcfs *fsp;
	long eblkno;
	int eoffset;
	struct buf *bp;
	int error;

	pcfid = (struct pcfid *)fidp;
	fsp = VFSTOPCFS(vfsp);

	if (pcfid->pcfid_fileno == -1) {
		pcp = pc_getnode(fsp, (daddr_t)0, 0, (struct pcdir *)0);
		pcp -> pc_flags |= PC_EXTERNAL;
		*vpp = PCTOV(pcp);
		return (0);
	}
	if (pcfid->pcfid_fileno < 0) {
		eblkno = pc_cldaddr(fsp, -pcfid->pcfid_fileno - 1);
		eoffset = 0;
	} else {
		eblkno = pcfid->pcfid_fileno / ENTPS;
		eoffset = (pcfid->pcfid_fileno % ENTPS) * sizeof (struct pcdir);
	}
	error = pc_lockfs(fsp);
	if (error) {
		*vpp = NULL;
		return (error);
	}

	if (eblkno >= fsp->pcfs_datastart || (eblkno-fsp->pcfs_rdirstart)
	    < (fsp->pcfs_rdirsec & ~(fsp->pcfs_spcl - 1))) {
		bp = bread(fsp->pcfs_devvp, eblkno, fsp->pcfs_clsize);
	} else {
		bp = bread(fsp->pcfs_devvp, eblkno,
			(int) (fsp->pcfs_datastart - eblkno)*PC_SECSIZE);
	}
	if ((bp)->b_flags & (B_ERROR | B_INVAL)) {
		if (bp -> b_flags & B_ERROR)
			pc_diskchanged (fsp);
		else
			brelse (bp);
		*vpp = NULL;
		pc_unlockfs(fsp);
		return (EIO);
	}
	ep = (struct pcdir *)(bp->b_un.b_addr + eoffset);
	if ((ep->pcd_gen == pcfid->pcfid_gen) &&
		pc_validchar(ep->pcd_filename[0])) {
		pcp = pc_getnode(fsp, eblkno, eoffset, ep);
		*vpp = PCTOV(pcp);
		pcp -> pc_flags |= PC_EXTERNAL;
	} else
		*vpp = NULL;
	pc_unlockfs(fsp);
	bp->b_flags |= B_NOCACHE;
	brelse(bp);
	return (0);
}
#endif notdef
