/* $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.
 */

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

#ident "$Header: spc_vnodeops.c 1.46 90/09/07 $"

/* @(#)spec_vnodeops.c	1.2 86/12/23 NFSSRC */

/*
 * spc_vnodeops.c
 *	specfs device file system vnode ops
 */

/* $Log:	spc_vnodeops.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/buf.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/timer.h"
#include "sys/conf.h"
#include "sys/uio.h"
#include "sys/file.h"
#include "sys/snode.h"
#include "sys/vmmeter.h"
#include "sys/vmparam.h"
#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/plocal.h"
#include "sys/cmn_err.h"
#include "sys/ufsinode.h"	/* for var.h */
#include "sys/ufsmount.h"
#include "sys/var.h"
#include "sys/open.h"
#include "sys/fcntl.h"
#include "sys/flock.h"
#include "sys/session.h"
#include "sys/poll.h"
#include "sys/stropts.h"
#include "sys/stream.h"
#include "sys/proc.h"
#include "sys/dio.h"

static int spec_open();
static int spec_close();
static int spec_rdwr();
static int spec_ioctl();
static int spec_getattr();
int spec_setattr();
int spec_access();
int spec_link();
static int spec_inactive();
static int spec_badop();
static int spec_noop();
int spec_fsync();
int spec_fdclose();
int spec_vhangup();
static int spec_strmsgio();
static int spec_strpoll();
int spec_relctty();
int spec_fcntl();
int spec_realvp();
static int spec_strategy();
static int spec_minphys();
#ifdef	notyet
int spec_lockctl();
int spec_fid();
#endif /*notyet*/

struct vnodeops spec_vnodeops = {
	spec_open,	/* vn_open     */
	spec_close,	/* vn_close    */
	spec_rdwr,	/* vn_rdwr     */
	spec_ioctl,	/* vn_ioctl    */
	spec_getattr,	/* vn_geattr   */
	spec_setattr,	/* vn_setattr  */
	spec_access,	/* vn_access   */
	spec_noop,	/* vn_lookup   */
	spec_noop,	/* vn_creat    */
	spec_noop,	/* vn_remove   */
	spec_link,	/* vn_link     */
	spec_noop,	/* vn_rename   */
	spec_noop,	/* vn_mkdir    */
	spec_noop,	/* vn_rmdir    */
	spec_noop,	/* vn_readdir  */
	spec_noop,	/* vn_symlink  */
	spec_noop,	/* vn_readlink */
	spec_fsync,	/* vn_fsync    */
	spec_inactive,	/* vn_inactive */
	spec_badop,	/* vn_bmap     */
	spec_strategy,	/* vn_strategy */
	spec_badop,	/* vn_bread    */
	spec_badop,	/* vn_brelse   */
	spec_minphys,	/* vn_minphys  */
	spec_badop,	/* vn_getdents */
	spec_fdclose,	/* vn_fdclose  */
	spec_fcntl,	/* vn_fcntl    */
	spec_realvp,	/* vn_realvp   */
	spec_vhangup,	/* vn_vhangup  */
	spec_strmsgio,	/* vn_strmsgio */
	spec_strpoll	/* vn_strpoll  */
};

/*
 * CHECKSPTAPE()
 *	If snode is a tape device, let bdwrite() know to do a bawrite().
 *
 * Snode is not locked at call/return.
 *
 * Used by spec_open() once dev_ref has been established.
 */

#define CHECKSPTAPE(sp) { \
	ASSERT_DEBUG((sp)->s_devp != NULL, "CHECKSPTAPE"); \
\
	if (DEV_FLAGS((sp)->s_devp) & D_TAPE) { \
		SPECPRINTF5("CHECKSPTAPE: setting VBTAPE\n"); \
\
		VN_LOCKNODE(STOV(sp)); \
		STOV(sp)->v_flag |= VBTAPE; \
		VN_UNLOCKNODE(STOV(sp)); \
	} \
}


/*
 * spec_open()
 *	Open a special file (device).
 *
 * Caller passes *vpp unlocked.  It remains unlocked at exit.
 *
 * We do the dev_ref() here rather than in specvp(), bdevvp(), ... since
 * it is at VOP_OPEN rather than VOP_LOOKUP when we assert that the device
 * is actually available.  (If VOP_LOOKUP failed because dev_ref found the
 * device was not currently configured, you couldn't do, e.g., vn_link()
 * on an device inode for a device which was not currently configured.)
 *
 * Something tricky to note is that, in the case of opening a STREAMS device,
 * the device may be the clone device in which case cloneopen() can deallocate
 * our snode and allocate a new one.  But we don't get to know that this has
 * happend or what the new snode is.  Therefore, we must not reference the
 * snode after a STREAMS open.
 */
/*ARGSUSED*/
static int
spec_open(vpp, flag, cred)
	struct vnode **vpp;
	int flag;
	struct ucred *cred;
{
	register struct snode *sp;
	dev_t dev;
	register struct dev *dp;
	int error;
	label_t saved_qsave;

	SPECPRINTF5("spec_open: begin vp 0x%x, major %d, minor %d, type %d\n",
		(*vpp), major((*vpp)->v_rdev), minor((*vpp)->v_rdev),
		(*vpp)->v_type);
	/*
	 * Setjmp in case open is interrupted.
	 * If it is, close and return error.
	 * But first, let's save the current u_qsave on the stack
	 * (to be restored before returning from spec_open).
	 */

	saved_qsave = u.u_qsave;
	if (setjmp(&u.u_qsave)) {
		/*
		 * Restore the saved u_qsave area.
		 */
	  	u.u_qsave = saved_qsave;

		SPECPRINTF1("spec_open: vp 0x%x, major %d, minor %d\n, type %d EINTR\n",
			(*vpp), major((*vpp)->v_rdev), minor((*vpp)->v_rdev),
			(*vpp)->v_type);

		error = EINTR;
		(void) spec_close(*vpp, flag & FMASK, cred);
		return(error);
	}
	sp = VTOS(*vpp);

	/*
	 * Do open protocol for special type.
	 */

	dev = sp->s_dev;

	switch ((*vpp)->v_type) {

	case VCHR:
		dp = dev_ref(dev);
		if (!dp) {
			SPECPRINTF1("spec_open: vp 0x%x, major %d, minor %d, type %d FAILED dev_ref\n",
				(*vpp), major((*vpp)->v_rdev),
				minor((*vpp)->v_rdev), (*vpp)->v_type);
			error = ENXIO;
			goto out;
		}
		sp->s_devp = STOV(sp)->v_rdevp = dp;
		CHECKSPTAPE(sp);

		if (DEV_STREAMTAB(dp)) {
			SPECPRINTF5("spec_open: calling VCHR stropen\n");
			ASSERT(u.u_clonedev == (struct dev *)NULL,
			       "non-null u_clonedev field");
			/*
			 *+ The u_clonedev field of the uarea should be
			 *+ clear at this point.  If it is not, the system
			 *+ has returned from an open on a clone device
			 *+ without resetting u_clonedev to NULL (a
			 *+ seemingly impossible scenario).
			 */
			error = stropen(sp, flag);
		} else {
			SPECPRINTF5("spec_open: calling VCHR DEV_OPEN\n");
			error = DEV_OPEN(dp, flag, OTYP_CHR);
		}
		if (error != 0) {
			/*
			 * If we have just done a cloneopen(), make
			 * sure we don't dev_deref() the wrong devp.
			 * This is a temporary workaround and should
			 * be removed when mailbug #31990 is addressed.
			 */
		  	if (u.u_clonedev != (struct dev *)NULL)
				dev_deref(u.u_clonedev);
			else
				dev_deref(dp);
		}
		u.u_clonedev = (struct dev *)NULL;
		break;

	case VBLK:
		dp = dev_ref(dev);
		if (!dp) {
			SPECPRINTF1("spec_open: vp 0x%x, major %d, minor %d, type %d FAILED dev_ref\n",
				(*vpp), major((*vpp)->v_rdev),
				minor((*vpp)->v_rdev), (*vpp)->v_type);
			error = ENXIO;
			goto out;
		}
		/*
		 * Set the dev pointer in the snode and the copy in 
		 * the snode/vnode.
		 *
		 * Also set it for the underlying "common" VBLK snode
		 * (which may be the same as this snode).  We do this
		 * since the "common" VBLK snode may not be explicitly
		 * opened (by spec_open) but it will be used for I/O.
		 * (However, it may be explicitly opened, which is ok 
		 * since the same dev pointer should be set in either case.)
		 * Notice that, in the case where the "common" VBLK snode is
		 * not explicitly opened, it shares the dev pointer reference
		 * count(s) of the VBLK snode(s) which point to it.  In the
		 * case where it is explicitly opened, it has a dev pointer 
		 * reference count of its own which is decremented when it
		 * is closed.
		 */

		ASSERT_DEBUG(!(VTOS(sp->s_bdevvp)->s_devp) || VTOS(sp->s_bdevvp)->s_devp==dp,
			"spec_open: common VBLK dev pointer mismatch");

		sp->s_devp = STOV(sp)->v_rdevp = dp;
		VTOS(sp->s_bdevvp)->s_devp = sp->s_bdevvp->v_rdevp = dp;

		CHECKSPTAPE(sp);
		CHECKSPTAPE(VTOS(sp->s_bdevvp));

		SPECPRINTF5("spec_open: calling VBLK DEV_OPEN\n");

		error = DEV_OPEN(dp, flag, OTYP_BLK);
		if (error != 0)
			dev_deref(dp);
		break;

	case VFIFO:
#ifdef DEBUG
		cmn_err(CE_PANIC, "spec_open: got a VFIFO???");
#endif
		error = ENXIO;
		break;
	
	default:
		cmn_err(CE_PANIC, "spec_open: invalid v_type");
		/*
		 *+ An attempt was made to open an invalid specnode type.
		 *+ This indicates a kernel software bug.
		 */
#ifdef notdef
		OLD CODE below removed because this "can't happen".
		error = 0;
		break;
#endif	/* notdef */
	}

	SPECPRINTF1("spec_open: vp 0x%x, major %d, minor %d, type %d, s_devp 0x%x, %s = %d\n",
		(*vpp), major((*vpp)->v_rdev), minor((*vpp)->v_rdev),
		(*vpp)->v_type, sp->s_devp, error ? "ERROR" : "error", error);

out:
	/*
	 * Restore the saved u_qsave area before returning.
	 */
	u.u_qsave = saved_qsave;
	return (error);
}

/*
 * spec_close()
 *	Close a special node.
 *
 * For VBLK and VCHR special-file nodes, call the close
 * procedure *unconditionally* (i.e., "every open, every close").
 * This implies no special checks for other opens of the device,
 * no scan of file-table.
 *
 * Caller passes vp unlocked.  It remains unlocked at exit.
 */
/*ARGSUSED*/
static int
spec_close(vp, flag, cred)
	struct vnode *vp;
	int flag;
	struct ucred *cred;
{
	register struct snode *sp;
	register struct	dev *dp;
	register struct mount *mp;
	register int error=0;
	label_t saved_qsave;

	SPECPRINTF5("spec_close: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	/*
	 * setjmp in case close is interrupted.
	 * But first, let's save the current u_qsave on the stack
	 * (to be restored before returning from spec_close).
	 */

	saved_qsave = u.u_qsave;
	if (setjmp(&u.u_qsave)) {
		/*
		 * Restore the saved u_qsave area before returning.
		 */
		u.u_qsave = saved_qsave;

		SPECPRINTF1("spec_close: vp 0x%x, major %d, minor %d, type %d EINTR\n",
			vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);
		error = EINTR;
		return (error);
	}

	sp = VTOS(vp);
	dp = sp->s_devp;
	ASSERT_DEBUG(sp->s_devp, "spec_close: s_devp is null");

	switch(vp->v_type) {

	case VCHR:
		if (DEV_STREAMTAB(dp))
			strclose(sp, flag);
		else {
			/*
			 * Make sure there is no pending asynchronous
			 * I/O for this process
			 */
			if (u.u_procp->p_aio) {
				aio_pend_wait(u.u_procp->p_aio, u.u_procp, 0);
			}
			DEV_CLOSE(dp, flag, OTYP_CHR);
		}
		dev_deref(dp);
		break;

	case VBLK:
		/*
		 * If device is mounted, no need to flush and inval
		 * (other use exists).  If not, need to bflush() and
		 * binval() to clean up.
		 *
		 * Note: low probability (non-harmful) race with
		 * concurrent mount or unmount (worst case is redundant
		 * flush/inval).
		 */
		for (mp = mounttab; mp < v.ve_mount; mp++) {
			if (mp->m_bufp != NULL && sp->s_bdevvp == mp->m_devvp)
				break;
		}
		if (mp == v.ve_mount) {		/* ie, not found */
			SPECPRINTF5("spec_close: VBLK not mounted: bflush\n");
			bflush(sp->s_bdevvp);
			binval(sp->s_bdevvp);
		} else
			SPECPRINTF5("spec_close: VBLK mounted: skip bflush\n");
		DEV_CLOSE(dp, flag, OTYP_BLK);
		dev_deref(dp);
		break;

	case VFIFO:
#ifdef DEBUG
		cmn_err(CE_PANIC, "spec_close: got a VFIFO???");
#endif
		break;

	default:
		break;
	}

	SPECPRINTF1("spec_close: closed vp 0x%x, major %d, minor %d, type %d, s_devp 0x%x\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type,
		sp->s_devp);

	/*
	 * Restore the saved u_qsave area before returning.
	 */
	u.u_qsave = saved_qsave;
	return (error);
}

/*
 * spec_rdwr()
 *	Read or write a vnode.
 *
 * Caller passes vp unlocked.
 */

/*ARGSUSED*/
static int
spec_rdwr(vp, uiop, rw, ioflag, cred)
	struct vnode *vp;
	struct uio *uiop;
	enum uio_rw rw;
	int ioflag;
	struct ucred *cred;
{
	register int n;
	register int on;
	register enum vtype type;
	register struct snode *sp;
	struct	dev *dev;
	struct vnode *devvp;	/* "common" vp of VBLK device */
	struct buf *bp;
	daddr_t	lbn, bn;
	int	size;
	long	bsize;
	int	error = 0;

	ASSERT(rw == UIO_READ || rw == UIO_WRITE, "spec_rdwr: Illegal operation");
	/*
	 *+ The kernel called the function that reads/writes special files, 
	 *+ but the operation requested was neither a read or a write.
	 */

	SPECPRINTF5("spec_rdwr: begin %s vp 0x%x, major %d, minor %d, type %d\n",
		(rw == UIO_READ) ? "READ" : "WRITE",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);
	SPECPRINTF5("spec_rdwr: uio_offset %d, uio_resid %d\n",
			uiop->uio_offset, uiop->uio_resid);
	SPECPRINTF5("spec_rdwr: s_realvp 0x%x\n", VTOS(vp)->s_realvp);

	sp = VTOS(vp);
	dev = sp->s_devp;

	if (rw == UIO_READ && uiop->uio_resid == 0)
		return (0);
	if (uiop->uio_offset < 0 || (uiop->uio_offset + uiop->uio_resid) < 0) {
		if (!vp->v_rdevp || !(vp->v_rdevp->d_flags & D_UNSOFF))
			return (EINVAL);
	}
	if (rw == UIO_READ)
		SMARK(sp, SACC);
	type = vp->v_type;

	ASSERT(type == VBLK || type == VCHR, "spec_rdwr: Illegal file type");
	/*
	 *+ The kernel called the function that reads/writes special files, 
	 *+ but the file was not a special file.
	 */

	if (type == VCHR) {
		if (rw == UIO_READ) {
			if (DEV_STREAMTAB(dev)) {
				error = strread(sp, uiop, ioflag);
			} else {
				error = DEV_READ(dev, uiop, ioflag);
			}
		} else {
			SMARK(sp, SUPD|SCHG);
			if (DEV_STREAMTAB(dev)) {
				error = strwrite(sp, uiop, ioflag);
			} else {
				error = DEV_WRITE(dev, uiop, ioflag);
			}
		}

		SPECPRINTF1("spec_rdwr: READ vp 0x%x, major %d, minor %d, type %d, s_devp 0x%x, %s = %d\n",
			vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type,
			sp->s_devp, error ? "ERROR" : "error", error);

		return (error);
	}
	/*
	 * Only VBLK left now.
	 */
	if (uiop->uio_resid == 0)
		return (0);

	devvp = sp->s_bdevvp;
	bsize = BLKDEV_IOSIZE;
	u.u_error = 0;

	ASSERT_DEBUG(devvp->v_type == VBLK, "spec_rdwr: s_bdevvp not VBLK");

	do {
		lbn = uiop->uio_offset / bsize;
		on = uiop->uio_offset % bsize;
		n = MIN((unsigned)(bsize - on), uiop->uio_resid);
		bn = lbn * (BLKDEV_IOSIZE/DEV_BSIZE);
		l.rablock = bn + (BLKDEV_IOSIZE/DEV_BSIZE);
		l.rasize = size = bsize;
		if (rw == UIO_READ) {
			if ((long)bn<0) {
				bp = geteblk((int)size);
				clrbuf(bp);
			} else if (sp->s_lastr + 1 == lbn)
				bp=breada(devvp, bn, (int)size, 
					l.rablock, l.rasize);
			else
				bp = bread(devvp, bn, (int)size);
			sp->s_lastr = lbn;
		} else {
			if (n == bsize) 
				bp = getblk(devvp, bn, size, 0);
			else
				bp = bread(devvp, bn, (int)size);
		}

		if (bp->b_flags & B_ERROR) {
			error = EIO;
			brelse(bp);
			goto bad;
		}
		if (rw == UIO_READ) {
			n = MIN(n, size - bp->b_resid);
			u.u_error = uiomove(bp->b_un.b_addr+on, n, rw, uiop);
			if (n + on == bsize)
				bp->b_flags |= B_AGE;
			brelse(bp);
		} else {
			if (bp->b_vp->v_flag & VBTAPE) 
				/* Clear the unused end portion of the buffer */
				bzero(bp->b_un.b_addr+on+n, 
					(unsigned)(size - (on + n)));
			else
				n = MIN(n, size - bp->b_resid);

			u.u_error = uiomove(bp->b_un.b_addr+on, n, rw, uiop);
			if (ioflag & IO_SYNC)
				bwrite(bp);
			else if (n + on == bsize) {
				bp->b_flags |= B_AGE;
				bawrite(bp);
			} else
				bdwrite(bp);
			SMARK(sp, SUPD|SCHG);
		}
	} while (u.u_error == 0 && uiop->uio_resid > 0 && n != 0);

	if (error == 0)				/* XXX */
		error = u.u_error;		/* XXX */
bad:

	SPECPRINTF1("spec_rdwr: %s vp 0x%x, major %d, minor %d, type %d, %s = %d\n",
		(rw == UIO_READ) ? "READ" : "WRITE",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type,
		error ? "ERROR" : "error", error);

	return (error);
}

/*
 * spec_ioctl()
 *	Do an IO-control operation on a special file.
 *
 * Caller passes vp unlocked.  It remains unlocked at exit.
 */

/*ARGSUSED*/
static int
spec_ioctl(vp, cmd, arg, flag, cred)
	struct vnode *vp;
	int cmd;
	caddr_t arg;
	int flag;
	struct ucred *cred;
{
	register struct snode *sp;
	register struct dev *dev;

	SPECPRINTF1("spec_ioctl: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);
	SPECPRINTF1("spec_ioctl: calling driver cmd 0x%x, arg 0x%x, flag 0x%x\n",
		cmd, arg, flag);

	sp = VTOS(vp);
	dev = sp->s_devp;

	ASSERT(vp->v_type == VCHR, "spec_ioctl: not VCHR");
	/*
	 *+ The kernel attempted to do an ioctl on an snode that was
	 *+ not a character special file.
	 */

	ASSERT(dev != NULL, "spec_ioctl: NULL s_devp");
	/*
	 *+ The kernel attempted to do an ioctl on an snode that did
	 *+ not have the s_devp field initialized correctly.
	 */

	if (DEV_STREAMTAB(dev))
		return(strioctl(sp, cmd, (int) arg, flag));
	else
		return(DEV_IOCTL(dev, cmd, arg, flag));
}


/*
 * spec_inactive()
 *	Vnode is no longer referenced.
 *
 * We are called with the vnode locked.
 * The snode itself MAY be locked, as indicated by the flag==NODEISLOCKED.
 *
 * There's a very careful design to avoid races.
 * See specvp() for more information.
 *
 *	Note DYNIX doesn't pass a cred to VOP_INACTIVE.
 *	So pass a random cred to spec_fsync, since ufs_fsync doesn't
 *	use the cred.
 *
 *	On entry, vnode may or may not be locked
 *	spin lock in vnode is asserted.
 *	sunsave() removes spec vnode from spec_list, zaps
 *	fields in the snode to indicate it's deallocated, and
 *	VN_RELE's the s_realvp and the s_bdevvp, if any.
 */

/*ARGSUSED*/
static int
spec_inactive(vp, flag)
	struct vnode *vp;
	register flag;
{
	SPECPRINTF1("spec_inactive: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	if ((flag == NODEISLOCKED) || VN_TRYLOCKNODE(vp)) {

		VN_UNLOCK(vp);

		/* If a "shadow vnode" exists, it must be "sync'd" */

		if (VTOS(vp)->s_realvp != NULL) {
			char saveuerror = u.u_error;

			SPECPRINTF5("spec_inactive: fsync s_realvp...\n");

			(void)spec_fsync(vp, u.u_cred);
			/*
			 * spec_fsync does a xxx_setattr which may set 
			 * u.u_error. 			XXX Blech
			 */
			u.u_error = saveuerror;
		}

		/*
		 * This snode must be removed from the snode hash list.
		 *
		 * In the event sunsave loses a race with a lookup
		 * we do NOT want to free this snode.  In this
		 * case, sunsave returns a "failure" indication.
		 *
		 * Otherwise, free the snode. sunsave ALWAYS
		 * VN_UNLOCKNODE's the snode's vnode semaphore.  
		 * If the snode is to be freed, sunsave also
		 * VN_RELE's any shadow vnodes.
		 */
		if (sunsave(VTOS(vp))) {
			spec_free(VTOS(vp));
		}

	} else {
		SPECPRINTF5("spec_inactive: couldn't lock vnode\n");
		VN_UNLOCK(vp);

	}
	SPECPRINTF5("spec_inactive: done\n");
	return (0);
}

/*
 * spec_getattr()
 *	Return attributes of specfs file-system vnode.
 *
 * Caller passes vp locked.  It remains locked at exit.
 */

dev_t specfs_devt = makedev(67, 0);	/* dev_t assigned to specfs */

/*ARGSUSED*/
static int
spec_getattr(vp, vap, cred)
	struct vnode *vp;
	struct vattr *vap;
	struct ucred *cred;
{
	int error;
	register struct snode *sp;
	register struct vnode *realvp;
	enum vtype type;

	SPECPRINTF1("spec_getattr: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	if ((realvp = sp->s_realvp) == NULL) {
		SPECPRINTF5("spec_getattr: NULL s_realvp\n");

		/*
		 * No real vnode behind this one.
		 * (Either clone open VCHR or common VBLK).
		 * Pickup info from snode.
		 * Set blocksize based on type in the unreal vnode.
		 */
		bzero((caddr_t)vap, sizeof (*vap));

		type = vp->v_type;

		vap->va_rdev   = sp->s_dev;
		vap->va_mode   = sp->s_mode;
		vap->va_uid    = sp->s_uid;
		vap->va_gid    = sp->s_gid;
		vap->va_nodeid = sp->s_nodeid;
		vap->va_fsid   = specfs_devt;	/* dev_t assigned to specfs */
		vap->va_nlink  = 0;
		vap->va_size   = 0;
	} else {
		SPECPRINTF5("spec_getattr: VOP_GETATTR of s_realvp 0x%x\n",
								realvp);
		VN_LOCKNODE(realvp);
		error = VOP_GETATTR(realvp, vap, cred);
		VN_UNLOCKNODE(realvp);
		if (error != 0)
			return (error);
		type = vap->va_type;
	}

	/* set current times from snode, even if older than vnode */
	vap->va_atime = sp->s_atime;
	vap->va_mtime = sp->s_mtime;
	vap->va_ctime = sp->s_ctime;

	/* set device-dependent blocksizes */
	switch (type) {
	case VBLK:
		vap->va_blocksize = BLKDEV_IOSIZE;
		break;

	case VCHR:
		vap->va_blocksize = MAXBSIZE;
		break;
	}

	SPECPRINTF5("spec_getattr: done\n");

	return (0);
}

/*
 * spec_setattr()
 *	Set attributes of specfs file-system vnode.
 *
 * Caller passes vp locked.  It remains locked at exit.
 *
 * VOP_SETATTR expects vnode argument lock.
 * There's NO deadlock in holding snode's vnode locked and asserting
 * the real vnode.
 */
int
spec_setattr(vp, vap, cred)
	struct vnode *vp;
	register struct vattr *vap;
	struct ucred *cred;
{
	struct vattr vattrbuf;
	struct snode *sp;
	int error;
	register int chtime;

	SPECPRINTF5("spec_setattr: begin vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	if (sp->s_realvp == NULL) {
		/*
		 * "Can't happen" since all syscalls that do this require
		 * a path and there is no path to an snode without a real vp.
		 */
#ifdef DEBUG
		cmn_err(CE_WARN, "spec_setattr: no real vnode to update\n");
#endif /* DEBUG */
		error = 0;			/* no real vnode to update */
	} else {
		chtime = 0;
		if (vap->va_atime.tv_sec != -1 || vap->va_mtime.tv_sec != -1)
			chtime = 1;
		VN_LOCKNODE(sp->s_realvp);
		error = VOP_SETATTR(sp->s_realvp, vap, cred);
		if (error == 0 && chtime)
			error = VOP_GETATTR(sp->s_realvp, &vattrbuf, cred);
		VN_UNLOCKNODE(sp->s_realvp);
		if (error == 0 && chtime) {
			/*
			 *  If the times were changed, update the snode
			 *  to the times of the realvp.  This code is written				 *  this way to handle both the cases of setting the
			 *  times to explicit values, and the case of when 
			 *  utime() says to set the mtime and atime values to
			 *  the current time....
			 */
			sp->s_atime = vattrbuf.va_atime;
			sp->s_mtime = vattrbuf.va_mtime;
			sp->s_ctime = vattrbuf.va_ctime;
		}
	}

	SPECPRINTF1("spec_setattr: vp 0x%x, major %d, minor %d, type %d, %s = %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type,
		error ? "ERROR" : "error", error);

	return (error);
}

/*
 * spec_access()
 *	See if given access mode is allowed.
 *
 * Return 0 for access allowed, or error number.
 * Caller passes vp locked.  It remains locked at exit.
 *
 * VOP_ACCESS expects vnode argument lock.
 * There's NO deadlock in holding snode's vnode locked and asserting
 * the real vnode.
 */
int
spec_access(vp, mode, cred)
	struct vnode *vp;
	int mode;
	struct ucred *cred;
{
	int error;

	SPECPRINTF1("spec_access: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	if (VTOS(vp)->s_realvp == NULL)
		return (0);	/* allow all access */

	VN_LOCKNODE((VTOS(vp)->s_realvp));
	error = VOP_ACCESS(VTOS(vp)->s_realvp, mode, cred);
	VN_UNLOCKNODE((VTOS(vp)->s_realvp));

	SPECPRINTF1("spec_access: mode o%o, %s = %d\n",
		mode, error ? "ERROR" : "error", error);

	return(error);
}

/*
 * spec_link()
 *	Link a file or a directory.
 *
 * If source is a directory, must be superuser.
 *
 * Caller passes vp, tdvp unlocked.  They remain unlocked at exit.
 * vp has a reference held.
 * VOP_LINK expects vnode argument unlocked.
 */
int
spec_link(vp, tdvp, tnm, cred)
	struct vnode *vp;
	struct vnode *tdvp;
	char *tnm;
	struct ucred *cred;
{
	SPECPRINTF1("spec_link: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	if (VTOS(vp)->s_realvp == NULL)
		return (ENOENT);    /* can't link to something non-existent */
	return(VOP_LINK(VTOS(vp)->s_realvp, tdvp, tnm, cred));
}

/*
 * spec_fsync()
 *	Synch-out an snode.
 *
 * Caller passes vp locked.  It remains locked at exit.
 *
 * In order to sync out the snode times without multi-client problems,
 * make sure the times written out are never earlier than the times
 * already set in the vnode.
 *
 * VOP_SETATTR, VOP_GETATTR, and VOP_FSYNC expect vnode arguments locked.
 * There's NO deadlock in holding snode's vnode locked and asserting
 * the real vnode.
 */
int
spec_fsync(vp, cred)
	struct vnode *vp;
	struct ucred *cred;
{
	register int error;
	register struct snode *sp;
	struct vattr vap;
	struct vattr vatmp;

	SPECPRINTF1("spec_fsync: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	/*
	 * If times didn't change, don't flush anything.
	 *
	 * NFS4.0 excepted VBLK here and always did the fsync operation.
	 * This seems unnecessary and Sun said it appeared that the VBLK
	 * exception was SunOS VM specific code and not necessary in general.
	 */
	if ((sp->s_flag & (SACC|SUPD|SCHG)) == 0)
		return (0);

	sp->s_flag &= ~(SACC|SUPD|SCHG);

	/*
	 * If no real vnode to update, don't flush anything
	 */
	if (sp->s_realvp == NULL)
		return (0);

	SPECPRINTF5("spec_fsync: locking s_realvp 0x%x...\n", sp->s_realvp);

	VN_LOCKNODE(sp->s_realvp);
	error = VOP_GETATTR(sp->s_realvp, &vatmp, cred);
	if (!error) {
		int setattr = 0;

		vattr_null(&vap);
		if (timercmp(&sp->s_atime, &vatmp.va_atime, >)) {
			setattr = 1;
			vap.va_atime = sp->s_atime;
		}
		if (timercmp(&sp->s_mtime, &vatmp.va_mtime, >)) {
			setattr = 1;
			vap.va_mtime = sp->s_mtime;
		}
		if (setattr)
			VOP_SETATTR(sp->s_realvp, &vap, cred);
	}
	(void) VOP_FSYNC(sp->s_realvp, cred);
	VN_UNLOCKNODE(sp->s_realvp);

	SPECPRINTF5("spec_fsync: done\n");

	return (0);
}

/*
 * spec_fdclose()
 *	vop which gets called for every file descriptor close.
 *
 *	Cleanup STREAMS, then call the VOP for the realvp.
 */
spec_fdclose(vp)
struct vnode *vp;
{
	struct snode *sp;

	SPECPRINTF1("spec_fdclose: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	SPECPRINTF5("spec_fdclose: calling cleanlocks...\n");

	cleanlocks(vp, USE_PID);
	sp = VTOS(vp);
	if (sp->s_sptr) {
		SPECPRINTF5("spec_fdclose: calling strclean...\n");
		strclean(sp);
	}
	SPECPRINTF5("spec_fdclose: done\n");
}

/*
 * spec_fcntl()
 *	vop for fcntl operations.. particularly those related to record locking.
 *
 * The only operations supported are for advisory file locking.
 *
 *	The flags passed in here are from the file descriptor.
 *	This is not right.  But for now.....
 */
spec_fcntl(vp, cmd, arg, flag, offset)
	struct vnode *vp;
	int cmd, arg, flag;
	off_t offset;
{
	SPECPRINTF1("spec_fcntl: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);
	SPECPRINTF1("spec_fcntl: cmd 0x%x, arg 0x%x, flag 0x%x, offset 0x%x\n",
		cmd, arg, flag, offset);

	/*
	 * lck_fcntl() will return EINVAL if the cmd is not a file locking cmd.
	 *
	 * Note we specify that the file size is zero (so locking offsets
	 * relative to the end of the file are the same as from the beginning
	 * of the file) and that we specify no mandatory file locking.
	 */
	return(lck_fcntl(vp, cmd, arg, flag, offset, 0, 0));
}

/*
 * spec_realvp()
 *	Return the actual vnode, if any, represented by this snode.
 *
 * Caller passes vp unlocked.  vp and vpp are unlocked at exit.
 */

int
spec_realvp(vp, vpp)
	struct vnode *vp;
	struct vnode **vpp;
{
	extern struct vnodeops fifo_vnodeops;
	struct vnode *rvp;

	ASSERT_DEBUG(vp, "spec_realvp: null vnode");
	ASSERT_DEBUG(vp->v_op == &spec_vnodeops || vp->v_op == &fifo_vnodeops,
			"spec_realvp: vp of incorrect type");

	SPECPRINTF1("spec_realvp: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);
	
	if (VTOS(vp)->s_realvp) {
		vp = VTOS(vp)->s_realvp;

		SPECPRINTF5("spec_realvp: VOP_REALVP of s_realvp 0x%x\n", vp);

		if (VOP_REALVP(vp, &rvp) == 0) {
			vp = rvp;
		}
	}
	*vpp = vp;

	SPECPRINTF5("spec_realvp: done\n");

	return (0);
}

/*
 * spec_strategy()
 *	Call strategy routine fo the device associated with the vnode
 *	associated with the buffer.
 */

static int
spec_strategy(bp)
	struct buf *bp;
{
	SPECPRINTF1("spec_strategy: vp 0x%x, major %d, minor %d, type %d\n",
		bp->b_vp, major(bp->b_vp->v_rdev), minor(bp->b_vp->v_rdev),
		bp->b_vp->v_type);

	ASSERT_DEBUG(bp->b_vp->v_rdevp, "spec_strategy: device not opened");
	
	DEV_STRATEGY(bp->b_vp->v_rdevp, bp, (daddr_t)0);
	return (0);
}

/*
 * spec_minphys()
 *	Call minphys routine fo the device associated with the vnode
 *	associated with the buffer.
 */

static int
spec_minphys(bp)
	struct buf *bp;
{
	SPECPRINTF1("spec_minphys: vp 0x%x, major %d, minor %d, type %d\n",
		bp->b_vp, major(bp->b_vp->v_rdev), minor(bp->b_vp->v_rdev),
		bp->b_vp->v_type);
	
	DEV_MINPHYS(bp->b_devp, bp);
	return (0);
}

static int
spec_badop()
{
	cmn_err(CE_PANIC, "spec_badop");
	/*
	 *+ An invalid operation on a specnode was requested.
	 *+ This indicates a kernel software bug.
	 */
}

static int
spec_noop()
{
	return (EINVAL);
}

/*
 * spec_vhangup()
 *	Vhangup a special node.
 *
 * For VCHR special-file nodes, call the vhangup procedure.
 * Caller passes vp unlocked.  It remains unlocked at exit.
 */
/*ARGSUSED*/
static
spec_vhangup(vp)
	struct vnode *vp;
{
	register struct snode *sp;

	SPECPRINTF5("spec_vhangup: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	ASSERT_DEBUG(sp->s_devp, "spec_vhangup: s_devp is null");
	ASSERT_DEBUG((vp->v_type == VCHR), "spec_vhangup not VCHR");

	if (sp->s_sptr)
		strvhangup(sp);
#ifdef DEV_VHANGUP
	else {
		DEV_VHANGUP(sp->s_devp, flag, OTYP_CHR);
	}
#endif
	SPECPRINTF1("spec_vhangup: hangup vp 0x%x, major %d, minor %d, type %d, s_devp 0x%x\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type,
		sp->s_devp);
}

/*
 * spec_strmsgio()
 *	Do a streams {put,get}msg operation on the target vnode.
 *
 * Caller passes vp unlocked.  It remains unlocked at exit.
 */
static int
spec_strmsgio(vp, mctl, mdata, rw, uflag, ioflag)
	struct vnode *vp;
	struct strbuf *mctl;
	struct strbuf *mdata;
	enum uio_rw rw;
	int uflag;
	int ioflag;
{
	register struct snode *sp;
	register struct dev *dev;

	SPECPRINTF1("spec_strmsgio: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	if (vp->v_type != VCHR)
		return (ENOSTR);

	dev = sp->s_devp;
	ASSERT(dev != NULL, "spec_strmsgio: NULL s_devp");
	/*
	 *+ The kernel attempted a streams message I/O on an
	 *+ snode that did not have the s_devp field initialized
	 *+ correctly.
	 */

	if (DEV_STREAMTAB(dev)) {
		if (rw == UIO_READ) {
			return(strgetmsg(sp, mctl, mdata, uflag, ioflag));
		}
		/* UIO_WRITE */
		return(strputmsg(sp, mctl, mdata, uflag, ioflag));
	}
	return(DEV_STRMSGIO(dev, mctl, mdata, rw, uflag, ioflag));
}

/*
 * spec_strpoll()
 *	Do a streams poll operation on the target vnode.
 *
 * Caller passes vp unlocked.  It remains unlocked at exit.
 */
static int
spec_strpoll(vp, events, anyyet)
	struct vnode *vp;
	short events;
	int anyyet;
{
	register struct snode *sp;
	register struct dev *dev;

	SPECPRINTF1("spec_strpoll: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	if (vp->v_type != VCHR)
		return (POLLNVAL);

	dev = sp->s_devp;
	ASSERT(dev != NULL, "spec_strpoll: NULL s_devp");
	/*
	 *+ The kernel attempted a streams poll operation on an
	 *+ snode that did not have the s_devp field initialized
	 *+ correctly.
	 */

	if (DEV_STREAMTAB(dev)) {
		return(strpoll(sp->s_sptr, events, anyyet));
	}
	return(DEV_STRPOLL(dev, events, anyyet));
}

/*
 * spec_relctty()
 *	Release a special node as a controlling tty.
 *
 * For VCHR special-file nodes, call the appropriate release ctty procedure.
 * Caller passes vp unlocked.  It remains unlocked at exit.
 */

/*should be declared static if this is ever turned into a real VOP*/
spec_relctty(vp)
	struct vnode *vp;
{
	register struct snode *sp;

	SPECPRINTF5("spec_relctty: vp 0x%x, major %d, minor %d, type %d\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type);

	sp = VTOS(vp);
	ASSERT_DEBUG(sp->s_devp, "spec_relctty: s_devp is null");
	ASSERT_DEBUG((vp->v_type == VCHR), "spec_relctty not VCHR");

	if (sp->s_sptr)
		strrelctty(sp);
#ifdef DEV_RELCTTY
	else {
		DEV_RELCTTY(sp->s_devp, OTYP_CHR);
	}
#endif
	SPECPRINTF1("spec_relctty: relctty vp 0x%x, major %d, minor %d, type %d, s_devp 0x%x\n",
		vp, major(vp->v_rdev), minor(vp->v_rdev), vp->v_type,
		sp->s_devp);
}

#ifdef lint
static dummy()
{
	dummy();
	spec_vnodeops.vn_open();
}
#endif /*lint*/
