/* $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: vtoc.c 1.30 90/05/03 $"

/*
 * VTOC partitioning driver
 */

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

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/ioctl.h"
#include "sys/sysmacros.h"
#include "sys/param.h"
#include "sys/intctl.h"
#include "sys/errno.h"
#include "sys/buf.h"
#include "sys/uio.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/file.h"
#include "sys/mutex.h"
#include "sys/vtoc.h"
#include "sys/autoconf.h"
#include "sys/cmn_err.h"
#include "sys/debug.h"
#include "sys/map.h"
#include "sys/ufsfilsys.h"
#include "sys/genalloc.h"
#include "sys/systm.h"
#include "sys/vnode.h"

#define	PVTOC	(PZERO - 1)             /* not signalable */
#define SPLVTOC	SPLDEV

#define	VTOC_ALLOC_PRIV()	genalloc(&vtoc_privlist, 0)
#define VTOC_FREE_PRIV(P)	genfree(&vtoc_privlist, (P))

#define DEVTOVTOCPRIV(d)        (struct vtoc_private *)(d)->d_private

/*
 * ignore_part defines the class of partitions for which no device will
 * be created.
 */
#define ignore_part(T)		((T) == V_NOPART || (T) == V_RESERVED)

/*
 * list of partitions for action
 */
struct	plist {
	int		index;		/* index into the VTOC */
	struct dev	*dev;		/* dev handle for this partition */
	struct partition part;		/* partition data */
};

extern	int	vtoc_c_major;		/* major no of vtoc device */

static	sema_t	vtoc_sema;		/* exclusive access vtoc data strcts*/

/*
 * memory management for private data associated with devs
 */

static	struct	genallocator vtoc_privlist;

static	struct	dev   *dtab[V_NUMPAR];	/* new devs added with V_DESTROY */
static	struct	plist *modlist;		/* partitions whose data has changed */
static	struct	plist *addlist;		/* partitions which are being created */
static	struct	plist *remlist;		/* partitions which are going away */
static	struct	buf vtocbuf;		/* for reading/writing VTOC */

static	caddr_t			vtocall();

extern	struct dev	*fdtodev();

extern	int	vtocopen();
extern	int	vtocclose();
extern	int	vtocstrat();
extern	int	vtocsize();
extern	int	vtocread();
extern	int	vtocwrite();
extern	int	vtocioctl();
extern	int	vtoc_shutdown();
extern	int	vtocdiagstrat();
extern	struct devops	vtocops;

extern	int	vtoc_mem_init;
extern	int	vtoc_mem_click;
extern	int	mount_readonly;

/*
 * vtoc_init - initilize the driver
 * this device is represented by a single dev in devsw.
 */
/*ARGSUSED*/
void
vtoc_init(ndevs)
	int ndevs;
{
	struct dev *newdev;

	/*
	 * initialize the dev for /dev/vtoc.  This will be a common
	 * handle into the driver for initial partition setup.
	 */

	newdev = (struct dev *)calloc(sizeof(struct dev));
	newdev->d_dev = makedev(vtoc_c_major, 0);
	newdev->d_ops = &vtocops;
	if (devsw_add(newdev) != 0) {
		cmn_err(CE_WARN, "vtoc: problem adding dev to devsw");
		/*
		 *+ The kernel discovered a problem when it
		 *+ attempted to add the VTOC
		 *+ device driver to the system.  This is probably
		 *+ due to an error in driver code.
		 */
	}

	/*
	 * We need to manage the memory used for private data in vtoc
	 * devs.  Since the number of partitions per disk is unknown
	 * at boot time (and thus, the amount of memory needed for private
	 * data), we use genalloc routines to manage the memory.  Could
	 * as easily use the heap when it comes along.
	 */

	if (geninit(&vtoc_privlist, sizeof(struct vtoc_private), vtoc_mem_init,
		calloc, vtoc_mem_click, vtocall) != 0) {

		cmn_err(CE_WARN,"vtoc: problem initializing private data pool");
		/*
		 *+ The kernel encountered a problem when it tried to allocate memory
		 *+ for the VTOC driver's private data pool.
		 */
		(void)devsw_remove(newdev);
		return;
	}

	init_sema(&vtoc_sema, 1);
	bufinit(&vtocbuf);
}

/*
 * vtocall - memory allocator used to increment vtoc_privlist
 *
 * As we allocate memory for private data, we may exhaust the freelist.
 * If that's the case, call vtocall to allocate more.
 */
static caddr_t
vtocall(nbytes)
	int	nbytes;
{
	return(wmemall(nbytes, 1));
}

/*
 * vtocopen - open a partition (or /dev/vtoc).  If we are mapped to
 * a device, call its open routine.  Otherwise, we have yet to be
 * bound to a device, so return success.  Note that we honor "soft"
 * write-protect.
 */
vtocopen(dp, mode, type)
	struct dev *dp;
	int	mode;
	int	type;
{
	int	error;
	struct vtoc_private *dsp = DEVTOVTOCPRIV(dp);
	struct dev *dpp;

	/*
	 * Do we need to handle an interrupted syscall here?
	 */
	if (dsp != NULL && (dpp = dsp->vp_physdev)) {
		if ((mode & FWRITE) && (dsp->vp_flags & VP_WRPROTECT))
			return(EBUSY);
		error = DEV_OPEN(dpp, mode, type);
		return(error);
	}
	return(0);
}

/*
 * vtocclose - call device's close routine if we are bound to one
 */
vtocclose(dp, flag, type)
	struct dev *dp;
	int	flag;
	int	type;
{
	struct vtoc_private *dsp = DEVTOVTOCPRIV(dp);
	struct dev *dpp;

	if (dsp != NULL && (dpp = dsp->vp_physdev)) {
		DEV_CLOSE(dpp, flag, type);
	}
}

/*ARGSUSED*/
vtocread(dp, uio, flags)
        struct dev *dp;
        struct uio *uio;
{
        int err, diff;
        off_t lim;
	struct vtoc_private *dsp = DEVTOVTOCPRIV(dp);

	lim = dsp->vp_size;
	lim <<= DEV_BSHIFT;
	err = physck(lim, uio, B_READ, &diff);
	if (err != 0) {
		if (err == -1)  /* not an error, but request of 0 bytes */
			err = 0;
		return (err);
	}
	if (flags & IO_ASYNC) {
		err = aphysio(dp, B_READ, uio, diff, flags);
	} else {
		err = physio(dp, (struct buf *)0, B_READ, uio);
		uio->uio_resid += diff;
	}
	return (err);
}

/*ARGSUSED*/
vtocwrite(dp, uio, flags)
        struct dev *dp;
        struct uio *uio;
{
        int err, diff;
        off_t lim;
	struct vtoc_private *dsp = DEVTOVTOCPRIV(dp);

	lim = dsp->vp_size;
	lim <<= DEV_BSHIFT;
	err = physck(lim, uio, B_WRITE, &diff);
	if (err != 0) {
		if (err == -1)  /* not an error, but request of 0 bytes */
			err = 0;
		return (err);
	}
	if (flags & IO_ASYNC) {
		err = aphysio(dp, B_WRITE, uio, diff, flags);
	} else {
		err = physio(dp, (struct buf *)0, B_WRITE, uio);
		uio->uio_resid += diff;
	}
	return (err);
}
	
/*
 * vtocstrat - Do the actual partition checking/offsetting.
 * No action will be taken on I/O completion, so the buf header
 * passed as an argument can be safely reused.  Note that the b_blkno
 * must have its value preserved since the buf cache code expects it
 * to remain unchanged.
 */
vtocstrat(dp, bp, offset)
	struct	dev *dp;
	struct	buf *bp;
	daddr_t	    offset;
{
	struct vtoc_private *dsp = DEVTOVTOCPRIV(dp);
	daddr_t start = dsp->vp_start;

	bp->b_status = B_STATUS_OK;	  /* default status */
	if ((bp->b_blkno + offset + (bp->b_bcount >> DEV_BSHIFT))
							> dsp->vp_size) {
		bp->b_flags |= B_ERROR;
		bp->b_error = EINVAL;
		bp->b_resid = bp->b_bcount;
		biodone(bp);
		return;
	}

	DEV_STRATEGY(dsp->vp_physdev, bp, offset + start);
}

/*
 * vtocioctl - handle I/O control requests
 *
 * valid commands:
 *	V_BUILD  - construct a set of partitions from a disk and a VTOC
 *	V_DESTROY - deconstruct the partitions created in V_BUILD
 *	V_READ   - read the VTOC from the disk and return it
 *	V_WRITE  - write a new VTOC to disk and rearrange partitions to match
 *	V_WRITEPROTECT - soft write-protect prevents writes on raw device
 *	V_WRITEENABLE - reenable writes on raw device
 */
/*ARGSUSED*/
vtocioctl(dp, cmd, data, flag)
	struct	dev *dp;
	int	cmd;
	caddr_t	data;
	int	flag;
{
	struct	dev	*dpp;
	struct	vtoc	*pv;
	struct	vtoc_private *dsp;
	int	error;


	/*
	 * ioctls which may only be done by root
	 */
	if (!suser()) {
		switch(cmd) {
		case V_BUILD:
		case V_DESTROY:
		case V_WRITE:
		case V_WRITEPROTECT:
		case V_WRITEENABLE:
			return(EPERM);
		}
	}

	if (dp->d_private == NULL) {

		/*
		 * ioctls which must be done on a vtoc dev which is bound to
		 * a device.
		 */

		switch(cmd) {

#ifdef DEBUG
		case V_IOCTL:
#endif /* DEBUG */
		case V_READ:
		case V_WRITE:
		case V_WRITEPROTECT:
		case V_WRITEENABLE:
			return(EINVAL);
		}
	} else {

		/*
		 * ioctls which must be done on the vtoc dev which
		 * is not bound to any device.
		 */

		switch(cmd) {

		case V_BUILD:
		case V_DESTROY:
			return(EINVAL);
		}
	}

	switch(cmd) {

	case V_BUILD:
		dpp = fdtodev((int)data);
		if (!dpp)
			return(EINVAL);

		error = vtoc_vbuild(dp, dpp);
		if (error == 0) {
			fdclose((int)data);
			error = u.u_error;
		}

		return(error);

	case V_DESTROY:
		return(vtoc_vdestroy(dp, (int)data));

	case V_READ:

		/*
		 * read the VTOC off the disk and return it to
		 * the user.
		 */
		dsp = DEVTOVTOCPRIV(dp);
		pv = (struct vtoc *)wmemall(V_SIZE, 1);

		/*
		 * serialize via vtoc_sema to insure that the
		 * VTOC is not being changed out from under us.
		 */

		(void)p_sema(&vtoc_sema, PVTOC);
		error = read_vtoc(dsp->vp_physdev, pv);
		if (error) {
			wmemfree(pv, V_SIZE);
			v_sema(&vtoc_sema);
			return(error);
		}
		error = copyout((caddr_t)pv, data, sizeof(struct vtoc));
		wmemfree(pv, V_SIZE);
		v_sema(&vtoc_sema);
		return(error);

	case V_WRITE:
		return(vtoc_vwrite(dp, data));

	case V_WRITEPROTECT:
		
		if (mount_readonly == 0)
			return(0);
		/*
		 * disallow write-protecting the diag device
		 */
		if ((dp->d_dev & (DEV_DIAG_MASK|V_RAW_MINOR)) ==
		    (DEV_DIAG_MASK|V_RAW_MINOR))
			return(EINVAL);

		/*
		 * All further writes to the raw device will fail.
		 */
		dsp = DEVTOVTOCPRIV(dp);
		dsp->vp_flags |= VP_WRPROTECT;
		return(0);
		

	case V_WRITEENABLE:

		if (mount_readonly == 0)
			return(0);
		/*
		 * reenable writes to the raw device
		 */
		if ((dp->d_dev & (DEV_DIAG_MASK|V_RAW_MINOR)) ==
		    (DEV_DIAG_MASK|V_RAW_MINOR))
			return(EINVAL);

		dsp = DEVTOVTOCPRIV(dp);
		dsp->vp_flags &= ~VP_WRPROTECT;
		return(0);

#ifdef DEBUG
	case V_IOCTL:
		error = DEV_IOCTL( ((struct vtoc_private *)
				    dp->d_private)->vp_physdev,
				  data, (caddr_t) 0, flag);
		return (error);
#endif /* DEBUG */

	default:
		/*
		 * For raw device, pass ioctl on to real device
		 * driver.
		 */
		if ((dp->d_dev & (DEV_DIAG_MASK|V_RAW_MINOR)) ==
		    (DEV_DIAG_MASK|V_RAW_MINOR)) {
			error = DEV_IOCTL(((struct vtoc_private *)
					  dp->d_private)->vp_physdev,
					  cmd, (caddr_t)data, flag);
			return(error);
		} else
			return(EINVAL);
	}
	/*NOTREACHED*/
}


/*
 * vtoc_vbuild - do the real work of the V_BUILD operation.
 *
 * contruct a vtoc driver onto a disk.  Care must be taken to
 * be sure that we can back out of the operation if there is
 * a consistency problem.  Note that we do not do a dev_deref
 * on the "child" device dev, since we are just inheriting the
 * reference which was established in its open operation.
 */
vtoc_vbuild(dp, dpp)
	struct	dev *dp, *dpp;
{
	int	i;
	struct	partition *p;
	dev_t	newdevt;
	struct	dev *td;
	struct	devops *dops, *diagdops;
	struct	vtoc_private *ddsp;
	int	error;
	static	struct	vtoc *vtocp;

	if (dp == (struct dev *)NULL || dpp == (struct dev *)NULL) {
		return(EINVAL);
	}

	/*
	 * We're picky here about the minor device number
	 * we are partitioning - insist that the minor number
	 * be one of the unpartitioned devices.  Note that we
	 * diasallow the diag dev as well.
	 */
	if ((dpp->d_dev & (V_RAW_MINOR | DEV_DIAG_MASK)) != V_RAW_MINOR) {
		return(EINVAL);
	}

	/*
	 * try to remove the dev from devsw.  This will only
	 * work if we are the only reference to the dev.
	 * If others have active references, fail the operation.
	 */
	if (devsw_remove(dpp) != 0) {
		return(EBUSY);
	}
		
	/*
	 * minor numbers start with a low byte of 0.
	 */
	newdevt = (dpp->d_dev & ~(V_RAW_MINOR));

	/*
	 * Access is serialize through vtoc_sema to keep the VTOC from
	 * changing out from under us.
	 */

	(void)p_sema(&vtoc_sema, PVTOC);

	if (!vtocp) {
		vtocp = (struct vtoc *)wmemall(V_SIZE, 1);
	}

	/*
	 * read and verify the VTOC
	 */

	error = read_vtoc(dpp, vtocp);
	if (error) {
		v_sema(&vtoc_sema);
		(void)devsw_add(dpp);
		return(error);
	}

	/*
	 * for each possible new dev, there may be an existing
	 * dev already present.  This will cause the operation
	 * to fail.
	 */

	for (i = 0; i < vtocp->v_nparts; i++) {
		p = &vtocp->v_part[i];
		if (!ignore_part(p->p_type)) {
			if ((td = dev_ref(newdevt + i)) != (struct dev *)NULL){
				dev_deref(td);
				(void)devsw_add(dpp);
				v_sema(&vtoc_sema);
				return(EBUSY);
			}
		}
	}

	/*
	 * allocate the devops structure for normal partitions
	 */

	dops = DEVOPS_ALLOC();

	*dops = *dpp->d_ops;
	dops->d_open = vtocopen;
	dops->d_close = vtocclose;
	dops->d_strategy = vtocstrat;
	dops->d_read = vtocread;
	dops->d_write = vtocwrite;
	dops->d_size = vtocsize;
	dops->d_ioctl = vtocioctl;
	dops->d_shutdown = vtoc_shutdown;

	/*
	 * Now allocate the devs and add them to devsw.
	 */

	for (i=0; i < vtocp->v_nparts; i++) {
		p = &vtocp->v_part[i];
		if (!ignore_part(p->p_type)) {
			td = DEV_ALLOC();
			td->d_private = (caddr_t)VTOC_ALLOC_PRIV();
			td->d_ops = dops;
			vtoc_newdev(td, dpp, newdevt + i, &vtocp->v_part[i]);
			(void)devsw_add(td);
			dev_deref(td);
		}
	}

	/*
	 * allocate an initialize a devops struct for the diag device.
	 */

	diagdops = DEVOPS_ALLOC();

	*diagdops = *dpp->d_ops;
	diagdops->d_open = vtocopen;
	diagdops->d_close = vtocclose;
	diagdops->d_strategy = vtocdiagstrat;
	diagdops->d_read = vtocread;
	diagdops->d_write = vtocwrite;
	diagdops->d_size = 0;
	diagdops->d_ioctl = vtocioctl;
	diagdops->d_shutdown = 0;

	/*
	 * allocate the diag dev and initialize it.  Note that the diag dev
	 * saves a copy of the devops struct used by "normal" partitions,
	 * so that it can be deallocated when the device gets destroyed.
	 */

	td = DEV_ALLOC();
	td->d_private = (caddr_t)VTOC_ALLOC_PRIV();
	td->d_ops = diagdops;
	td->d_flags = dpp->d_flags;
	td->d_dev = newdevt | DEV_DIAG_MASK | V_RAW_MINOR;
	ddsp = DEVTOVTOCPRIV(td);
	ddsp->vp_driver_id = vtoc_c_major;
	ddsp->vp_start = 0;
	ddsp->vp_size = vtocp->v_capacity;
	ddsp->vp_physdev = dpp;
	ddsp->vp_devops = dops;
	(void)devsw_add(td);
	dev_deref(td);

	/*
	 * physical dev's parent pointer is set to point to the diag dev.
	 * This allows physical drivers to keep a pointer to their
	 * unpartitioned dev around to use in passing up V_REMDEV interrupts. 
	 * This needs to be undone in V_DESTROY/V_REMDEV operations.
	 */

	dpp->d_parent = td;
	v_sema(&vtoc_sema);
	return(0);
}

				
/*
 * vtoc_newdev
 *
 *	initialize a dev.  Most characteristics inherited from base device.
 */
static
vtoc_newdev(newdev, dp, newdevt, p)
	struct	dev *newdev, *dp;
	dev_t	newdevt;
	struct	partition *p;
{
	struct vtoc_private *dsp;

	newdev->d_flags = dp->d_flags;
	newdev->d_dev = newdevt;
	dsp = DEVTOVTOCPRIV(newdev);
	dsp->vp_driver_id = vtoc_c_major;
	dsp->vp_size = p->p_size;
	dsp->vp_start = p->p_start;
	dsp->vp_physdev = dp;
}

/*
 * vtoc_vdestroy
 *	remove the vtoc driver, leaving just the raw disk.
 *
 * Note on memory management: The V_BUILD ioctl will allocate
 * memory for two devops structs: one for the diag dev, and
 * one which is shared amongst all of the partition devs.
 * The pointer to this second devops struct is stored in
 * the diagnostic dev's private data field, as an aid to freeing it.
 *
 * This ioctl should only work if applied to /dev/vtoc, with
 * any part of the device to be destroyed as the argument.
 */
static
vtoc_vdestroy(dp, data)
	struct	dev	*dp;
	int		data;
{
	int	i;
	struct	partition *part;
	struct	vtoc	*pv;
	dev_t	devno;
	struct	dev *dpp, *argdp, *diagdp;
	int	error, nparts, argi = -1;
	struct	vtoc_private *dsp;


	if (major(dp->d_dev) != vtoc_c_major) {
		return(EINVAL);
	}

	/*
	 * argdp points to dev which is behind the fd passed
	 * in as argument to ioctl.
	 */

	if (data < 0 || !(argdp = fdtodev(data))) {
		return(EINVAL);
	}

	dsp = DEVTOVTOCPRIV(argdp);

	/*
	 * Confirm that this is a reasonable dev to destroy.  Must
	 * have "Private" dev data, and must also have the
	 * proper driver id in its private data.
	 */

	if (!dsp || dsp->vp_driver_id != vtoc_c_major) {
		return(EINVAL);
	}

	dpp = dsp->vp_physdev;

	/*
	 * Serialize through vtoc_sema to protect access to
	 * dtab.
	 */

	(void)p_sema(&vtoc_sema, PVTOC);
	bzero((caddr_t)dtab, sizeof(dtab));
	pv = (struct vtoc *)wmemall(V_SIZE, 1);

	/*
	 * read the VTOC off the disk to verify which partitions
	 * to remove.
	 */
	error = read_vtoc(dpp, pv);
	if (error) {
		wmemfree(pv, V_SIZE);
		v_sema(&vtoc_sema);
		return(error);
	}

	nparts = pv->v_nparts;
	/*
	 * clear any dev_t bits related to partition number or whether
	 * this is a diagnostic device
	 */
	devno = argdp->d_dev & ~V_PART_MASK;
	devno &= ~DEV_DIAG_MASK;

	/*
	 * loop through all possible partitions, grabbing
	 * references to them if they exist.
	 */
	for (i=0; i < nparts; i++) {
		part = &pv->v_part[i];
		if (ignore_part(part->p_type))
			continue;

		dtab[i] = dev_ref(devno + i);

		/*
		 * If a ref fails, this dev is being used in
		 * another mapping - fail the DECONS.
		 */
		if (dtab[i] == (struct dev *)NULL) {
			error = EBUSY;
			goto failpop;
		}

		/*
		 * The dev which was handed in as argument needs
		 * to be handled specially.  Decrement the
		 * ref count and attempt to remove it.  If
		 * this fails, zero out the entry in d[], so
		 * it does not get the ref count decreased
		 * again (ufs_close() takes care of this.
		 */

		if (dtab[i] == argdp) {
			dev_deref(dtab[i]);
			if (devsw_remove(argdp) != 0) {
				dtab[i] = (struct dev *)NULL;
				error = EBUSY;
				goto failpop;
			} else {
				argi = i;
			}
		} else if (devsw_remove(dtab[i]) != 0) {

			/*
			 * If we can't remove a dev from devsw, it is
			 * currently in use.  Decrement its ref count,
			 * and zero out the entry in d[], so we don't
			 * try to add it back to devsw.
			 */

			dev_deref(dtab[i]);
			dtab[i] = (struct dev *)NULL;
			error = EBUSY;
			goto failpop;
		}
	}

	/*
	 * clean up the diagnostics device.
	 * Note that since fdtodev() doesn't bump the
	 * ref cnt, we don't need to dev_deref() it (unless
	 * we are trying to V_DESTROY through the diag device).
	 */

	devno |= DEV_DIAG_MASK | V_RAW_MINOR;
	diagdp = dev_ref(devno);

	/*
	 * Sanity-check for existance of diag device.  Should
	 * be no way for the device to go away.
	 */

	if (!diagdp) {
		cmn_err(CE_WARN, "vtoc missing diagnostics device");
		/*
		 *+ A V_DESTROY operation on the vtoc device failed
		 *+ because the diagnostic device was discovered
		 *+ to be missing.  This can indicate that a
		 *+ driver is removing the wrong device
		 *+ objects.
		 */

		error = EINVAL;
		goto failpop;
	} else {
		if (diagdp == argdp) {
			dev_deref(argdp);
		}

		if (devsw_remove(diagdp) != 0) {
			if (diagdp != argdp) {
				dev_deref(diagdp);
			}
			error = EBUSY;
			goto failpop;
		}
	}

	/*
	 * The fd passed in as an argument to the ioctl must have
	 * a close() simulated on it before recovering the memory
	 * associated with its dev.  Note that for the inode to
	 * get closed, the ref count must be > 0
	 */

	fdclose(data);

	/*
	 * recover memory used by removed devs.  
	 */

	for (i=0; i < nparts; i++) {
		if (dtab[i]) {
			VTOC_FREE_PRIV(DEVTOVTOCPRIV(dtab[i]));
			DEV_FREE(dtab[i]);
		}
	}

	dsp = DEVTOVTOCPRIV(diagdp);
	DEVOPS_FREE(dsp->vp_devops);
	DEVOPS_FREE(diagdp->d_ops);
	VTOC_FREE_PRIV(DEVTOVTOCPRIV(diagdp));
	DEV_FREE(diagdp);

	/*
	 * clean up the physical disk device
	 */

	dpp->d_parent = (struct dev *)NULL;
	(void)devsw_add(dpp);
	dev_deref(dpp);

	wmemfree(pv, V_SIZE);
	v_sema(&vtoc_sema);
	return(0);

failpop:
	/*
	 * Add back devs which were removed from devsw.  Need to
	 * be careful to release references to these devs.  Note
	 * that we don't decrement the ref count on the dev
	 * passed in as an argument, since ufs_close() handles this.
	 */

	for (i = 0; i < nparts; i++) {
		if (dtab[i]) {
			(void)devsw_add(dtab[i]);
			if (i != argi) {
				dev_deref(dtab[i]);
			}
		}
	}
	wmemfree(pv, V_SIZE);
	v_sema(&vtoc_sema);
	return(error);
}

/*
 * vtoc_vwrite
 *	implement the V_WRITE ioctl
 *
 * given a new VTOC as an argument, do the following:
 *	. confirm that the partitions make sense,
 *	  that they don't overlap, etc.
 *	. seperate the partitions into four catagories:
 *		- partitions to be changed
 *		- partitions to be added
 *		- partitions to be deleted
 *		- partitions to be left alone
 *	. make sure each group of partitions
 *	  can be changed in the manner requested
 *	. write the new VTOC to disk
 *	. run through each catagory, doing the operations.
 * on error, we restore things to the way they were, and
 * restore the old VTOC to disk.
 *
 * Locking: We cannot hold devsw_lck while we do this
 * operation, since we have to write the VTOC to disk
 * midway through it.  However, we want to make sure that
 * no new refs to devs happen.  Note: devsw_remove(d) will
 * not work if d has a reference to it outstanding, so
 * it is sufficient to remove all affected devs from devsw
 * before acting on them.  Thus, no lock needs to be
 * held through the write of the VTOC.
 */
static
vtoc_vwrite(dp, data)
	struct	dev	*dp;
	caddr_t		data;
{
	int	i, j;
	struct	vtoc *v, *vo;
	struct	partition *p, *po, *p1;
	struct	vtoc_private *dsp;
	struct	dev *dd, *dpp, *dtp;
	struct	devops *dops;
	int	error, nparts;
	int	totsec, first;
	int	nmods, nadds, nrems;
	dev_t	base;

	nmods = nadds = nrems = 0;

	base = dp->d_dev & ~(V_PART_MASK | DEV_DIAG_MASK);
	dsp = DEVTOVTOCPRIV(dp);
	dpp = dsp->vp_physdev;
	(void)p_sema(&vtoc_sema, PVTOC);

	/*
	 * first - allocate space for the old and new VTOC.  Note
	 * that we allocate enough for the whole VTOC region of
	 * the disk, to allow disk driver strategy routines to
	 * optimize the IO.
	 */

	v = (struct vtoc *)wmemall(V_SIZE, 1);
	vo = (struct vtoc *)wmemall(V_SIZE, 1);

	error = copyin(data, v, sizeof(struct vtoc));
	if (error) {
		wmemfree(v, V_SIZE); 
		wmemfree(vo, V_SIZE); 
		v_sema(&vtoc_sema);
		return(error);
	}
	error = read_vtoc(dpp, vo);
	if (error) {
		wmemfree(v, V_SIZE); 
		wmemfree(vo, V_SIZE); 
		v_sema(&vtoc_sema);
		return(error);
	}

	/*
	 * Check basic sanity for the new VTOC.
	 */

	if (v->v_size <= 0 || v->v_size > V_SIZE ||
	    v->v_nparts > V_NUMPAR ||
	    v->v_secsize <= 0 ||
	    v->v_ntracks <= 0 ||
	    v->v_nsectors <= 0 ||
	    v->v_ncylinders <= 0 ||
	    v->v_rpm <= 0 ||
	    v->v_capacity <= 0 ||
	    v->v_nseccyl <= 0) {
		wmemfree(v, V_SIZE); 
		wmemfree(vo, V_SIZE); 
		v_sema(&vtoc_sema);
		return(EINVAL);
	}

	/*
	 * allocate memory for partition lists.
	 */

	modlist =
	    (struct plist *)wmemall(V_NUMPAR * sizeof(struct plist), 1);
	addlist =
	    (struct plist *)wmemall(V_NUMPAR * sizeof(struct plist), 1);
	remlist =
	    (struct plist *)wmemall(V_NUMPAR * sizeof(struct plist), 1);

	nparts = MAX(v->v_nparts, vo->v_nparts);
	totsec = v->v_capacity;
	first = DEV_FIRSTSECT(dpp);
	p = &v->v_part[0];
	po = &vo->v_part[0];

	/*
	 * Gather the lists of partitions to be acted upon
	 */
	for (i = 0; i < nparts; i++, p++, po++) {

		/*
		 * Basic partition sanity.
		 */
		
		if (p->p_type != V_NOPART && i < v->v_nparts) {
			daddr_t start;
			long	size;

			size = p->p_size;
			start = p->p_start;

			if (start > totsec ||
			    start + size > totsec ||
			    start < 0 || size <= 0 ||
			    start + size < start ||
			    p->p_type < 0) {
				error = EINVAL;
				goto fail;
			}

			/*
			 * Don't allow a partition to overlap
			 * the VTOC if:
			 *	. partition is not of type V_RESERVED and
			 *	. partition STARTS in the VTOC area OR
			 *	. partition END in the VTOC area OR
			 *	. partition has VTOC area in its MIDDLE.
			 */

			if (p->p_type != V_RESERVED &&
			     ((start >= V_VTOCSEC + first &&
			     start < V_VTOCSEC + btodb(V_SIZE) + first)
			     || (start+size > V_VTOCSEC + first &&
			     start+size <= V_VTOCSEC + btodb(V_SIZE) + first)
			     || (start < V_VTOCSEC + first &&
			     start+size > V_VTOCSEC + btodb(V_SIZE) + first))) {
				error = EINVAL;
				goto fail;
			}
		}

		/*
		 * Allow no overlap between any two partitions
		 */
		for (p1 = &v->v_part[i + 1], j = i + 1;
		     j < v->v_nparts; j++, p1++) {
			
			if (p1->p_type == V_NOPART)
				continue;

			if (V_OVLAP(p, p1)) {
				error = EINVAL;
				goto fail;
			}
		}

		/*
		 * Partition Catagory 1: only partition data has
		 * changed.  Remove the dev from devsw - if we are
		 * unable to remove it, it cannot be modified.
		 */
		
		if (!ignore_part(p->p_type) && !ignore_part(po->p_type)
			&& i < v->v_nparts && i < vo->v_nparts) {

			/*
			 * make sure there really is a difference.
			 */
			if (bcmp(p, po, sizeof(struct partition)) == 0)
				continue;

			dd = dev_ref(base + i);

			/*
			 * If can't get a reference, the dev has been
			 * removed by another driver.
			 */

			if (!dd) {
				error = EBUSY;
				goto fail;
			}

			/*
			 * If can't remove the dev, some other
			 * outstanding reference exists.
			 */

			if (devsw_remove(dd) != 0) {
				dev_deref(dd);
				error = EBUSY;
				goto fail;
			}

			modlist[nmods].dev = dd;
			modlist[nmods].part = *p;
			modlist[nmods++].index = i;
			continue;
		}

		/*
		 * Partition Catagory 2: Partitions to be added.
		 * This includes partitions which are valid in new
		 * VTOC and not in the old as well as all valid
		 * partitions in new VTOC when we are beyond the
		 * edge of the old VTOC's limit (vo->v_nparts).
		 * Simple sanity-check to be sure there is
		 * no dev here by this name.  If there is,
		 * fail everything.
		 */
		
		if ((!ignore_part(p->p_type) &&
		      ignore_part(po->p_type) &&
		      i < vo->v_nparts &&
		      i < v->v_nparts)
		   || (!ignore_part(p->p_type) &&
		       i > (int)vo->v_nparts - 1)) {

			/*
			 * Sanity - some dev exists with this dev
			 * number.
			 */

			if ((dd = dev_ref(base + i))
						!= (struct dev *)NULL) {
				dev_deref(dd);
				error = EINVAL;
				goto fail;
			}

			addlist[nadds].part = *p;
			addlist[nadds++].index = i;
			continue;
		}

		/*
		 * Partition Catagory 3: Partitions to be removed.
		 * This includes partitions which are valid in old
		 * VTOC and not in the new as well as all valid
		 * partitions in old VTOC when we are beyond the
		 * edge of the new VTOC's limit (vo->v_nparts).
		 * Simply remove them from devsw now.
		 */

		if ((!ignore_part(po->p_type) &&
		      ignore_part(p->p_type) &&
		      i < vo->v_nparts &&
		      i < v->v_nparts)
		   || (!ignore_part(po->p_type) &&
		      i > (int)v->v_nparts - 1)) {

			dd = dev_ref(base + i);

			if (!dd) {
				error = EBUSY;
				goto fail;
			}
			if (devsw_remove(dd) != 0) {
				dev_deref(dd);
				error = EBUSY;
				goto fail;
			}

			remlist[nrems].dev = dd;
			remlist[nrems].part = *po;
			remlist[nrems++].index = i;
			continue;
		}
	}

	/*
	 * write the new VTOC to disk.  If the write fails, we
	 * may have hosed the VTOC by writing only part of it.
	 * However, this should fail the checksum when it
	 * is reread.
	 */

	error = write_vtoc(dpp, v);
	if (error) {
		goto fail;
	}

	/*
	 * We're committed.  None of these actions should be
	 * able to fail now.
	 */

	/*
	 * Modify partitions which are changing.
	 */

	for (i = 0; i < nmods; i++) {
		p = &modlist[i].part;
		dd = modlist[i].dev;
		dsp = DEVTOVTOCPRIV(dd);
		dsp->vp_size = p->p_size;
		dsp->vp_start = p->p_start;
		(void)devsw_add(dd);
		dev_deref(dd);
	}

	/*
	 * Add partitions which are now appearing
	 */
	
	dtp = dev_ref(base | DEV_DIAG_MASK | V_RAW_MINOR);
	dsp = DEVTOVTOCPRIV(dtp);
	dops = dsp->vp_devops;
	dev_deref(dtp);

	for (i = 0; i < nadds; i++) {
		p = &addlist[i].part;
		dd = DEV_ALLOC();
		dd->d_private = (caddr_t)VTOC_ALLOC_PRIV();
		dd->d_ops = dops;
		vtoc_newdev(dd, dpp, base + addlist[i].index, p);
		(void)devsw_add(dd);
		dev_deref(dd);
	}

	/*
	 * Free memory associated with partitions which are
	 * being removed
	 */
	
	for (i = 0; i < nrems; i++) {
		dd = remlist[i].dev;
		dsp = DEVTOVTOCPRIV(dd);
		VTOC_FREE_PRIV(dsp);
		DEV_FREE(dd);
	}

	wmemfree(v, V_SIZE);
	wmemfree(vo, V_SIZE);
	vtoc_freelists();
	v_sema(&vtoc_sema);
	return(0);

fail:
	wmemfree(v, V_SIZE);
	wmemfree(vo, V_SIZE);
	for (i = 0; i < nmods; i++) {
		(void)devsw_add(modlist[i].dev);
		dev_deref(modlist[i].dev);
	}

	for (i = 0; i < nrems; i++) {
		(void)devsw_add(remlist[i].dev);
		dev_deref(remlist[i].dev);
	}
	vtoc_freelists();
	v_sema(&vtoc_sema);
	return(error);
}

/*
 * vtoc_freelists
 *	free the lists of partition changes allocated for V_WRITE
 *
 * The V_WRITE ioctl handler allocates some lists of partitions
 * to be acted on.  vtoc_freelists() frees these lists after
 * we have finished processing the ioctl.
 */
static
vtoc_freelists()
{
	
	wmemfree(modlist, V_NUMPAR * sizeof(struct plist));
	wmemfree(addlist, V_NUMPAR * sizeof(struct plist));
	wmemfree(remlist, V_NUMPAR * sizeof(struct plist));
}

/*
 * read_vtoc - read and verify a VTOC from the disk.
 *
 * assumes that vtoc_sema has been set, but devsw_lck is clear
 * Arguments: pointer to the physical device's dev and a pointer to
 * space which can be filled with a VTOC.
 */
static int
read_vtoc(dp, v)
	struct	dev *dp;
	struct	vtoc *v;
{
	struct	buf *bp = &vtocbuf;
	ulong	cksum;
	daddr_t	offset;

	/*
	 * first discover if the disk has a "firstsect" - ie, an
	 * offset which must be added to get around reserved space
	 * at the front of the disk.
	 */
	offset = DEV_FIRSTSECT(dp);
	if (offset < 0) {
		return(ENXIO);
	}

	bp->b_un.b_addr = (caddr_t)v;
	bp->b_blkno = offset + V_VTOCSEC;
	bp->b_bcount = V_SIZE;
	clrbuf(bp);
	bp->b_devp = dp;
	bp->b_flags = B_READ;
	bp->b_error = 0;
	bp->b_proc = u.u_procp;
	bp->b_iotype = B_FILIO;
	BIODONE(bp) = 0;

	DEV_STRATEGY(dp, bp, (daddr_t)0);
	biowait(bp);
	if (bp->b_flags & B_ERROR) {
		return(bp->b_error);
	}

	cksum = v->v_cksum;
	v->v_cksum = 0;
	if (v->v_sanity != VTOC_SANE) {
		return(EINVAL);
	}

	if (v->v_version != V_VERSION_1) {
		return(EINVAL);
	}

	if (vtoc_get_cksum(v) != cksum) {
		return(EINVAL);
	}


	return(0);
}

/*
 * write_vtoc - set the appropriate checksum and write the VTOC.
 *
 * returns any IO errors.
 * assumes that vtoc_sema has been set, but devsw_lck is clear
 * Arguments: pointer to the physical device's dev and a pointer to
 * space which contains a VTOC to be written to disk.
 */
static int
write_vtoc(dp, v)
	struct	dev *dp;
	struct	vtoc *v;
{
	struct	buf *bp = &vtocbuf;
	daddr_t	offset;

	/*
	 * first discover if the disk has a "firstsect" - ie, an
	 * offset which must be added to get around reserved space
	 * at the front of the disk.
	 */
	offset = DEV_FIRSTSECT(dp);
	if (offset < 0) {
		return(ENXIO);
	}
		
	v->v_sanity = VTOC_SANE;
	v->v_version = V_VERSION_1;
	v->v_cksum = 0;
	v->v_cksum = vtoc_get_cksum(v);

	bp->b_un.b_addr = (caddr_t)v;
	bp->b_blkno = offset + V_VTOCSEC;
	bp->b_bcount = V_SIZE;
	bp->b_devp = dp;
	bp->b_flags = B_WRITE;
	bp->b_error = 0;
	bp->b_proc = u.u_procp;
	bp->b_iotype = B_FILIO;
	BIODONE(bp) = 0;

	DEV_STRATEGY(dp, bp, (daddr_t)0);
	biowait(bp);
	if (bp->b_flags & B_ERROR) {
		return(bp->b_error);
	}

	return(0);
}

/*
 * vtoc_get_cksum()
 * 	return a checksum for the VTOC
 */
static
vtoc_get_cksum(v)
	struct	vtoc *v;
{
	register long sum;
	register int  nelem = sizeof(struct vtoc) / sizeof(long);
	register long *lptr = (long *)v;

	sum = 0;
	while (nelem-- > 0) {
		sum += *lptr;
		++lptr;
	}
	return (sum);
}

/*
 * vtoc_shutdown()
 *	Called by child when the child breaks.  The second argument is a
 * pointer to the struct dev of the child.  The first argument should be
 * a pointer to a vtoc dev
 */
vtoc_shutdown(dp, cdp)
	struct	dev *dp, *cdp;
{
	struct	dev *dpp;
	struct	vtoc_private *dsp;
	dev_t	devno;
	int	i;

	/*
	 * remove the vtoc-to-physdev mapping.  Usually called
	 * due to exceptional conditions like the disk going
	 * offline.  Called with devsw_lck set.
	 */
	devno = dp->d_dev & ~(V_PART_MASK | DEV_DIAG_MASK);
	for (i=0; i < V_NUMPAR; i++) {
		dpp = dev_ref(devno + i);
		if (!dpp)
			continue;
		
		if (dpp->d_parent) {
			DEV_SHUTDOWN(dpp->d_parent, dpp);
		}
		
		(void)devsw_remove(dpp);
		dsp = DEVTOVTOCPRIV(dpp);
		VTOC_FREE_PRIV(dsp);
		DEV_FREE(dpp);
	}
	
	/*
	 * clean up vtoc diag device
	 */
	
	devno |= DEV_DIAG_MASK | V_RAW_MINOR;
	
	if ((dpp = dev_ref(devno)) != (struct dev *)NULL) {
		dsp = DEVTOVTOCPRIV(dpp);
		DEVOPS_FREE(dsp->vp_devops);
		DEVOPS_FREE(dpp->d_ops);
		VTOC_FREE_PRIV(dsp);
		DEV_FREE(dpp);
	}
	
	/*
	 * Now make child dev available again, and break its
	 * relationship with the vtoc driver.
	 */
	
	(void)devsw_add(cdp);
	dev_deref(cdp);
	cdp->d_parent = (struct dev *)NULL;

	return(0);
}

/*
 * vtocsize()
 *	Used for swap-space partition calculation.
 */
vtocsize(dp)
	register struct dev *dp;
{
	struct vtoc_private *dsp;

	dsp = DEVTOVTOCPRIV(dp);
	if (dsp->vp_size <= 0 || !(dsp->vp_physdev))
		return(-1);
	return(dsp->vp_size);
}

/*
 * vtocdiagstrategy
 *	strategy routine for the diagnostic vtoc device
 *
 * Acts as a filter for I/O requests going through the diagnostic
 * device.  This routine provides read-only access to the entire
 * disk drive.  This access is provided for performance-measuring
 * utilities which need direct access to the whole disk.  Thus,
 * no partition checking is done at this level.
 */
vtocdiagstrat(dp, bp, offset)
	struct	dev *dp;
	struct	buf *bp;
	daddr_t	    offset;
{
	struct vtoc_private *dsp = DEVTOVTOCPRIV(dp);

	bp->b_status = B_STATUS_OK;	  /* Default return status */

	if ((bp->b_flags & B_READ) != B_READ) {
		bp->b_flags |= B_ERROR;
		bp->b_error = EINVAL;
		bp->b_resid = bp->b_bcount;
		biodone(bp);
		return;
	}

	DEV_STRATEGY(dsp->vp_physdev, bp, offset);
}
