/* $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: gentty.c 1.41 90/04/11 $"

/*
 *	indirect driver for controlling tty.
 */

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

#include "sys/types.h"
#include "sys/param.h"
#include "sys/conf.h"
#include "sys/devsw.h"
#include "sys/errno.h"
#include "sys/signal.h"
#include "sys/timer.h"
#include "sys/file.h"
#include "sys/vnode.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/session.h"
#include "sys/uio.h"
#include "sys/sysmacros.h"
#include "sys/cmn_err.h"
#include "sys/autoconf.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/debug.h"
#include "sys/stropts.h"
#include "sys/poll.h"

#define	TRUE	1
#define	FALSE	0

struct	dev sydev;

extern int sy_c_major;

extern int syopen();
extern int syread();
extern int sywrite();
extern int syioctl();
extern int systrmsgio();
extern int systrpoll();
extern int nulldev();
extern struct vnode *sycheck();

#ifdef DEBUG		
extern	int	jc_debug;
#endif

extern	struct	devops	sydevops;

/*ARGSUSED*/
void
sy_init(driver)
	struct driver_module *driver;
{
	sydev.d_dev = makedev(sy_c_major, 0);
	sydev.d_ops = &sydevops;
	if (devsw_add(&sydev) != 0) {
		cmn_err(CE_NOTE, "sy: problem adding gentty dev to devsw\n");
		/*
		 *+ A problem occurred when the kernel tried to add the generic tty
		 *+ device to the system.  This is probably due to a driver
		 *+ that has added a device with the same major/minor number.
		 */
	}
}


/* ARGSUSED */
syopen(dp, flag)
	struct dev *dp;
{
	int retval;

	/*
	 * Don't need to do anything.  Note this open does not increase
	 * the ref count on the controlling terminal. If it were to
	 * last close, then further I/O is not possible using this
	 * file discriptor.
	 */
#ifdef DEBUG		
	if (jc_debug)
		printf("syopen\n");
#endif
	(void) sycheck(u.u_procp->p_session, FALSE, &retval);
	return(retval);
}


/* ARGSUSED */
syread(dp, uiop, flag)
	struct dev *dp;
	struct uio *uiop;
{
	register struct session *s;	/* session of calling process */
	register struct vnode *vp;
	int retval;

#ifdef DEBUG		
	if (jc_debug)
		printf("syread\n");
#endif
	s = u.u_procp->p_session;
	if ((vp = sycheck(s, TRUE, &retval)) != NULL) {
		/*
		 * WARNING:
		 *  We must use the returned pointer of 'vp' only.  The vnode
		 *  (and any associated stream) originally referenced by 's_vp'
		 *  cannot be "put" until we have released our 's_ctty_count'
		 *  reference (created by sycheck()), but 's_vp' could now be
		 *  NULL (since our session is not locked, and the last file
		 *  descriptor referring to 'vp' might be getting closed).
		 */
		/* The controlling tty had better be a character special */
		ASSERT_DEBUG((vp->v_type==VCHR),
			     "syread not from a character special");
		retval = VOP_RDWR(vp, uiop, UIO_READ, flag|IO_GENTTY,
				  u.u_cred);
		syrelease(s, FALSE);
	}
	return(retval);
}


/* ARGSUSED */
sywrite(dp, uiop, flag)
	struct dev *dp;
	struct uio *uiop;
{
	register struct session *s;	/* session of calling process */
	register struct vnode *vp;
	int retval;

#ifdef DEBUG		
	if (jc_debug)
		printf("sywrite\n");
#endif
	s = u.u_procp->p_session;
	if ((vp = sycheck(s, TRUE, &retval)) != NULL) {
		/*
		 * WARNING:
		 *  We must use the returned pointer of 'vp' only.  The vnode
		 *  (and any associated stream) originally referenced by 's_vp'
		 *  cannot be "put" until we have released our 's_ctty_count'
		 *  reference (created by sycheck()), but 's_vp' could now be
		 *  NULL (since our session is not locked, and the last file
		 *  descriptor referring to 'vp' might be getting closed).
		 */
		/* The controlling tty had better be a character special */
		ASSERT_DEBUG((vp->v_type==VCHR),
			     "sywrite not from a character special");
		retval = VOP_RDWR(vp, uiop, UIO_WRITE, flag|IO_GENTTY,
				  u.u_cred);
		syrelease(s, FALSE);
	}
	return(retval);
}


/* ARGSUSED */
syioctl(dp, cmd, arg, mode)
	struct dev *dp;
{
	register struct session *s;	/* session of calling process */
	register struct vnode *vp;
	int retval;

#ifdef DEBUG		
	if (jc_debug)
		printf("syioctl\n");
#endif
	s = u.u_procp->p_session;
	if ((vp = sycheck(s, TRUE, &retval)) != NULL) {
		/*
		 * WARNING:
		 *  We must use the returned pointer of 'vp' only.  The vnode
		 *  (and any associated stream) originally referenced by 's_vp'
		 *  cannot be "put" until we have released our 's_ctty_count'
		 *  reference (created by sycheck()), but 's_vp' could now be
		 *  NULL (since our session is not locked, and the last file
		 *  descriptor referring to 'vp' might be getting closed).
		 */
		/* The controlling tty had better be a character special */
		ASSERT_DEBUG((vp->v_type==VCHR),
			     "syioctl not from a character special");
		retval = VOP_IOCTL(vp, cmd, arg, mode|FGENTTY, u.u_cred);
		syrelease(s, FALSE);
	}
	return(retval);
}


/* ARGSUSED */
systrmsgio(dp, mctl, mdata, rw, uflag, ioflag)
	struct dev *dp;
	struct strbuf *mctl;
	struct strbuf *mdata;
	enum uio_rw rw;
	int uflag;			/* user's I/O flags */
	int ioflag;			/* stream I/O flags */
{
	register struct session *s;	/* session of calling process */
	register struct vnode *vp;
	int retval;

#ifdef DEBUG		
	if (jc_debug)
		printf("systrmsgio\n");
#endif
	s = u.u_procp->p_session;
	if ((vp = sycheck(s, TRUE, &retval)) != NULL) {
		/*
		 * WARNING:
		 *  We must use the returned pointer of 'vp' only.  The vnode
		 *  (and any associated stream) originally referenced by 's_vp'
		 *  cannot be "put" until we have released our 's_ctty_count'
		 *  reference (created by sycheck()), but 's_vp' could now be
		 *  NULL (since our session is not locked, and the last file
		 *  descriptor referring to 'vp' might be getting closed).
		 */
		/* The controlling tty had better be a character special */
		ASSERT_DEBUG((vp->v_type==VCHR),
			     "systrmsgio not from a character special");
		retval = VOP_STRMSGIO(vp, mctl, mdata, rw, uflag,
				      ioflag|IO_GENTTY);
		syrelease(s, FALSE);
	}
	return(retval);
}


/* ARGSUSED */
systrpoll(dp, events, anyyet)
	struct dev *dp;
	short events;
	int anyyet;
{
	register struct session *s;	/* session of calling process */
	register struct vnode *vp;
	int retval;

#ifdef DEBUG		
	if (jc_debug)
		printf("systrpoll\n");
#endif
	s = u.u_procp->p_session;
	if ((vp = sycheck(s, TRUE, &retval)) != NULL) {
		/*
		 * WARNING:
		 *  We must use the returned pointer of 'vp' only.  The vnode
		 *  (and any associated stream) originally referenced by 's_vp'
		 *  cannot be "put" until we have released our 's_ctty_count'
		 *  reference (created by sycheck()), but 's_vp' could now be
		 *  NULL (since our session is not locked, and the last file
		 *  descriptor referring to 'vp' might be getting closed).
		 */
		/* The controlling tty had better be a character special */
		ASSERT_DEBUG((vp->v_type==VCHR),
			     "systrpoll not from a character special");
		retval = VOP_STRPOLL(vp, events, anyyet);
		syrelease(s, FALSE);
	} else
		retval = POLLNVAL;
	return(retval);
}


/*
 * Check to see if this process has a controlling terminal or still has
 * access rights to a controlling terminal.  If it does and 'hold' is
 * TRUE, then increment the in-use count for interlock with: acct(),
 * exit(), setpgrp(), setsid(), strclose(), and strvhangup().
 */
struct vnode *
sycheck(s, hold, retvalp)
register struct session *s;		/* session of calling process */
bool_t hold;				/* Bump s_ctty_count if TRUE */
int *retvalp;				/* place to return error */
{
	register spl_t spl;
	register struct vnode *rvp;

	*retvalp = 0;
	rvp = NULL;
	spl = p_lock(&s->s_lock, SPLSESS);
	if (s->s_ctty == NULL) {
		/*
		 * The controlling terminal has never been established.
		 */
#ifdef DEBUG		
		if (jc_debug)
			printf("sycheck() ENXIO session %d\n", s->s_sid);
#endif
		*retvalp = ENXIO;
	} else if ((s->s_ctty->c_sid != u.u_procp->p_sid) ||
		   ((rvp = s->s_vp) == NULL)) {
		/*
		 * The controlling terminal has been relinquished.
		 */
#ifdef DEBUG		
		if (jc_debug)
			printf("sycheck() EIO\n");
#endif
		*retvalp = EIO;
	} else if (hold) {
		/*
		 *  This process has a controlling tty, and wishes to use
		 *  it now.  Increment the usage count.
		 */
		s->s_ctty_count++;
	}
	v_lock(&s->s_lock, spl);
	return(rvp);
}


/*
 * Release the controlling tty.
 * On last reference, cv_sema the semaphore for interlock with
 * acct(), exit(), setpgrp(), setsid(), strclose(), and strvhangup().
 */
syrelease(s, islocked)
register struct session *s;		/* session of calling process */
bool_t islocked;			/* TRUE if session locked by caller */
{
	register spl_t spl;

	if (!islocked)
		spl = p_lock(&s->s_lock, SPLSESS);
	s->s_ctty_count--;
	if (s->s_vp == NULL) {
		/*
		 *  The controlling tty is currently being relinquished.
		 *  An extra reference (s_ctty_count++) has been made
		 *  by the process relinquishing the tty.  We must
		 *  wake up that process when the reference count goes
		 *  to one (see relinquish_ctty()).
		 */
		if (s->s_ctty_count == 1)
			(void)cv_sema(&s->s_sema);
	}
	if (!islocked)
		v_lock(&s->s_lock, spl);
}


/*
 * Reacquire access to the controlling ctty, after having slept.
 * This interlocks with acct(), exit(), setpgrp(), setsid(), strclose(),
 * and strvhangup().
 */
int
syreacquire(s, success_ret)
register struct session *s;		/* session of calling process */
int success_ret;			/* value to return on success */
{
	register spl_t spl;

	spl = p_lock(&s->s_lock, SPLSESS);
	if (s->s_vp == NULL) {
		v_lock(&s->s_lock, spl);
		u.u_error = EIO;
		return(CTTY_ERR);
	}
	s->s_ctty_count++;
	v_lock(&s->s_lock, spl);
	return(success_ret);
}


/*
 * Set a new controlling terminal.
 * Only the session leader can do this
 * and only he can change p_session so don't lock sess_list.
 * Others can change the pgrp list pointers so lock this session.
 * The ctty structure must be locked externally.
 */
new_ctty(p, vp, ctty)
	proc_t	*p;
	struct vnode	*vp;
	struct ctty	*ctty;
{
	struct	session	*s;
	spl_t	spl;

	s = p->p_session;
#ifdef DEBUG		
	if (jc_debug)
		printf("new_ctty()\n");
#endif
	spl = p_lock(&s->s_lock, SPLSESS);
	/*
	 * must be a session leader and the controlling tty must
	 * not already be established or belong to someone else.
	 */
	if (!s->s_ctty && (s->s_sid == p->p_pid) && !s->s_vp &&
						(ctty->c_sid == 0)) {
		ASSERT_DEBUG((s->s_ctty_count == 0),
			     "Non-zero ctty use count in new_ctty()");
		s->s_ctty = ctty;
		ctty->c_pgrp = p->p_pgrp;
		ctty->c_sid = p->p_sid;
		ctty->c_session = s;
		s->s_vp = vp;
#ifdef DEBUG		
		if (jc_debug)
			printf("new_ctty() for sid %d fgid %d vp is at 0x%x dev_t0x%x &ctty:0x%x\n",
				p->p_pgrp, p->p_sid, vp, vp->v_rdev, ctty);
#endif
	}
	v_lock(&s->s_lock, spl);
}


/*
 * Relinquish the controlling tty set up by new_ctty().
 * This function must be called with the session structure (s) already
 * locked.
 *
 * Upon return, the session lock is released, and the spl level is
 * set to SPL0.
 */
#define	VOP_RELCTTY(VP)	spec_relctty(VP)

relinquish_ctty(session)
	register struct session	*session;
{
	register struct vnode *vp;
	extern struct vnodeops spec_vnodeops;

#ifdef DEBUG		
	if (jc_debug) {
		if ((vp = session->s_vp) != NULL) {
			printf("relinquish_ctty#1 sid:%d(sess:0x%x) rdev:%d\n", 
				session->s_sid, (int)session, vp->v_rdev);
		} else {
			printf("relinquish_ctty#1 sid:%d(sess:0x%x)\n",
				session->s_sid, (int)session);
		}
	}
#endif
	/*
	 * After s_vp is NULLed out, sycheck() will fail since
	 * session->s_ctty is still non-null.
	 */
	if ((vp = session->s_vp) != NULL) {	/* we got here first */
		/*
		 * We won the race between exit(), strclose(), and
		 * strvhangup() in calling this function.
		 *
		 * To keep other processes that "lose the race" at a barrier
		 * below until we are done, we increment s_ctty_count before
		 * we drop the session lock.  The code in syrelease() knows
		 * of this, and wakes up the winner of this race when the
		 * reference count (s_ctty_count reaches 1).
		 */
		session->s_vp = NULL;
		session->s_ctty_count++;
		/*
		 * Inform the (streams) device that it is to no longer be
		 * associated with any particular session.
		 */
#ifdef DEBUG		
		if (jc_debug) {
			printf("relinquish_ctty#2 sid:%d(sess:0x%x) rdev:%d\n", 
				session->s_sid, (int)session, vp->v_rdev);
		}
#endif
		ASSERT_DEBUG((vp->v_op = &spec_vnodeops),
			     "relinquish_ctty: s_vp must be specfs");
		VOP_RELCTTY(vp);
	} else {
		/*
		 * We lost the race.
		 */
		while (session->s_ctty_count > 0) {
			/*
			 * We must wait for all on-going I/O operations on
			 * the controlling tty via s_vp to complete.
			 */
#ifdef DEBUG		
			if (jc_debug) {
				printf("relinquish_ctty#3 sid:%d(sess:0x%x\n",
					session->s_sid, (int)session);
			}
#endif
			(void) p_sema_v_lock(&session->s_sema, PZERO,
					     &session->s_lock);
			(void) p_lock(&session->s_lock, SPLSESS);
		}
		v_lock(&session->s_lock, SPL0);
	}
#ifdef DEBUG		
	if (jc_debug) {
		printf("relinquish_ctty#4 sid:%d(sess:0x%x)\n",
			session->s_sid, (int)session);
	}
#endif
}


/*
 * finish_relctty()
 *
 * Finish disassociating the given (stream) device from its session.
 * This function must be called with the session structure (s) already
 * locked.
 *
 * The lock pointer: lock2hold must refer to the lock that must be acquired
 * at spl level: spl prior to invoking: wakeupfn(wakeupfnarg).  (This same
 * lock must also govern access to the ctty structure associated with the
 * device.)  The "wakeup" function is used to fail the I/O of the other
 * processes doing I/O on the real device via /dev/tty.
 *
 * Upon return, all locks are released, and
 * the spl level is set to SPL0.
 *
 * NOTES
 *  The locking in this function is a little complicated because of the
 *  need to avoid acquiring the (streams) device lock governing access to
 *  both the (streams) device and its associated ctty structure, and THEN
 *  acquiring the session lock.  This would yield the potential for deadlock,
 *  since throughout the rest of the kernel, whenever both locks need to be
 *  held at the same time, the order of locking is always:
 *
 *	1.  (streams) device lock,
 *	2.  session lock.
 *
 *  This function must only be called from the (streams) device function
 *  responsible for disassociating the (streams) device from its current
 *  session as a controlling terminal device.
 *
 *  Lastly, the "spl" parameter must be <= SPLSESS.
 */

finish_relctty(s, lock2hold, spl, wakeupfn, wakeupfnarg)
	register struct session *s;
	register lock_t *lock2hold;
	spl_t spl;
	void (*wakeupfn)();
	long wakeupfnarg;
{
	register struct ctty *ctty;

	ASSERT_DEBUG((spl <= SPLSESS),
			"finish_relctty: spl must be <= SPLSESS");
	/* The session structure must be locked by the caller */
	ctty = s->s_ctty;
#ifdef DEBUG		
	if (jc_debug) {
		printf("finish_relctty#1 sid:%d(sess:0x%x) lock2hold:0x%x\n",
			s->s_sid, (int)s, (int)lock2hold);
	}
#endif
	if (s->s_ctty_count > 1) {
		v_lock(&s->s_lock, SPL0);	/* unlock session */
		(void)p_lock(lock2hold, spl);	/* it is held now */
		(*wakeupfn)(wakeupfnarg);	/* wake up /dev/tty I/O */
		v_lock(lock2hold, SPL0);	/* unlock device; note spl */
		(void)p_lock(&s->s_lock, SPLSESS);
	}
	/*
	 * Wait for all on-going (streams) device spawned from
	 * /dev/tty to fail.
	 */
	if (s->s_ctty_count > 1) {
#ifdef DEBUG		
		if (jc_debug) {
			printf("finish_relctty#2 sid:%d(sess:0x%x)\n",
				s->s_sid, (int)s);
		}
#endif
		/*
		 *  Only one wakeup possible here, so no loop.
		 */
		(void)p_sema_v_lock(&s->s_sema, PZERO, &s->s_lock);
	} else
		v_lock(&s->s_lock, SPL0);
	/*
	 * All I/O activity via /dev/tty has ceased for the session.
	 */
	(void)p_lock(lock2hold, spl);
	ctty->c_sid = 0;
	ctty->c_pgrp = 0;
	ctty->c_session = NULL;
	v_lock(lock2hold, SPL0);
	(void)p_lock(&s->s_lock, SPLSESS);
	s->s_ctty_count = 0;
	vall_sema(&s->s_sema);
	v_lock(&s->s_lock, SPL0);
}


/*
 * ctty_read check if the process will generate SIGTTIN signals.
 * POSIX IEEE std 1003.1-1988 7.1.1.4
 * Return CTTY_OK if ok to read.
 * return CTTY_SIG if signaled
 * return CTTY_SLEPT if blocked and now awakened.
 * return CTTY_ERR on error.
 */

int
ctty_read(ctty, held_lock, ioflag)
	struct	ctty	*ctty;
	lock_t		*held_lock;
	int		ioflag;			/* IO_GENTTY */
{
	register spl_t	spl;
	proc_t		*p;
	struct session	*s;
	int		retval;

	p = u.u_procp;
	s = p->p_session;

#ifdef DEBUG
	if (jc_debug == 2)
		printf("ctty_read(0x%x) ioflag:0x%x pid:%d pgrp:%d c_pgrp:%d\n",
			ctty, ioflag, p->p_pid, p->p_pgrp, ctty->c_pgrp);
#endif
	if (s->s_ctty != ctty) {
		/*
		 * Since the session pointer is not the pointer to this
		 * device, this is not the controlling terminal for this
		 * process.
		 */
		return (CTTY_OK);
	}
	if (s->s_vp == NULL) {
		/*
		 * No controlling terminal exists.  If we were called via
		 * /dev/tty, then the controlling tty has been recently
		 * taken away.
		 */
		if (ioflag & IO_GENTTY) {
			u.u_error = EIO;
			return (CTTY_ERR);
		}
		return (CTTY_OK);
	}
	/*
	 * This is or was our ctty.
	 */
	if (s->s_sid != ctty->c_sid) {
		/*
		 * since the sids don't match then the ctty has been
		 * deallocated from the process.
		 *
		 */
#ifdef DEBUG
		if (jc_debug == 2)
			printf("ctty_read(): s_sid=%d c_sid=%d\n",
				s->s_sid, ctty->c_sid);
#endif
		u.u_error = EINVAL;
		return (CTTY_ERR);
	}

	if (p->p_pgrp == ctty->c_pgrp) {
		/*
		 * POSIX IEEE std 1003.1-1988 7.1.1.4
		 * If a process is in the foreground process 
		 * group of its controlling terminal, read
		 * operation shall be allowed.
		 */
		 return (CTTY_OK);
	}
#ifdef DEBUG
	if (jc_debug == 2)
		printf("ctty_read(): in background\n");
#endif

	/*
	 * we only get here if reading ffrom the ctty and in
	 * a background process group.
	 */
	if (SIGM_ISMEMBER(p->p_sigignore, sigmask(SIGTTIN)) ||
	     SIGM_ISMEMBER(p->p_sigmask, sigmask(SIGTTIN)) || 
						(p->p_flag & SORPH)) {
		/*
		 * POSIX IEEE std 1003.1-1988 7.1.1.4
		 * If the reading process is ignoring or blocking
		 * the SIGTTIN signal or the process group is orphaned.
		 * read() returns -1 errno is set to EIO and no signal 
		 * is sent.
		 */
#ifdef DEBUG
		if (jc_debug == 2)
			printf("ctty_read(): blocked background orphan\n");
#endif
		 u.u_error = EIO;
		 return (CTTY_ERR);
	}
	/*
	 * POSIX IEEE std 1003.1-1988 7.1.1.4
	 * any attempts by a process in a background process group
	 * to read from its controlling terminal shall cause its process
	 * group to be sent a SIGTTIN signal.
	 */
	spl = p_lock(&s->s_lock, SPLSESS);
	gsignal(p, SIGTTIN);
	/*
	 * If we were called via /dev/tty, then release the reference on
	 * controlling terminal.
	 */
	if (ioflag & IO_GENTTY) {
		syrelease(s, TRUE);
	}
	v_lock(&s->s_lock, spl);
	/*
	 * Now take the stop signal that we just sent to ourselves.
	 */
#ifdef DEBUG
	if (jc_debug > 1)
		printf("ctty_read blocking\n");
#endif
	spl = p_lock(&p->p_state, SPLHI);
	switch (issig(held_lock)) {
	case STOPPED:
		v_lock(&p->p_state, spl);
		/*
		 * If we were called via /dev/tty, then reacquire the reference
		 * on controlling terminal.
		 */
		if (ioflag & IO_GENTTY) {
			retval = syreacquire(s, CTTY_SLEPT);
		} else {
			retval = CTTY_SLEPT;
		}
		/* The lock was dropped; reacquire it again */
		(void) p_lock(held_lock, spl);
		return (retval);
	case STPGOTSIG:
		v_lock(&p->p_state, spl);
		/*
		 * If we were called via /dev/tty, then reacquire the reference
		 * on controlling terminal.
		 */
		if (ioflag & IO_GENTTY) {
			retval = syreacquire(s, CTTY_SIG);
		} else {
			retval = CTTY_SIG;
		}
		/* The lock was dropped; reacquire it again */
		(void) p_lock(held_lock, spl);
		return (retval);
	default:
		v_lock(&p->p_state, spl);
		/*
		 * If we were called via /dev/tty, then reacquire the reference
		 * on controlling terminal.
		 */
		if (ioflag & IO_GENTTY) {
			retval = syreacquire(s, CTTY_SIG);
		} else {
			retval = CTTY_SIG;
		}
		return (retval);
	}
}


/*
 * ctty_write check if the process will genetrate SIGTTOU signals.
 * POSIX IEEE std 1003.1-1988 7.1.1.4
 * Return CTTY_OK if ok to read.
 * return CTTY_SIG if SIGTTIN and has blocked and has been signaled.
 * return CTTY_SLEPT if blocked and now awakened.
 * return CTTY_ERR on error.
 */
int
ctty_write(ctty, tostop, held_lock, ioflag)
	struct	ctty	*ctty;
	unsigned char	tostop;
	lock_t		*held_lock;
	int		ioflag;			/* IO_GENTTY */
{
	register spl_t	spl;
	proc_t		*p;
	struct session	*s;
	int		blockig;
	int		retval;

	p = u.u_procp;
	s = p->p_session;

#ifdef DEBUG
	if (jc_debug == 2)
		printf("ctty_write(0x%x) tostop:%d ioflag:0x%x pid:%d pgrp:%d c_pgrp:%d\n",
			ctty, tostop, ioflag, p->p_pid, p->p_pgrp, ctty->c_pgrp);
#endif
	if (s->s_ctty != ctty) {
		/*
		 * Since the session pointer is not the pointer to this
		 * device, this is not the controlling terminal for this
		 * process.
		 */
		return (CTTY_OK);
	}
	if (s->s_vp == NULL) {
		/*
		 * No controlling terminal exists.  If we were called via
		 * /dev/tty, then the controlling tty has been recently
		 * taken away.
		 */
		if (ioflag & IO_GENTTY) {
			u.u_error = EIO;
			return (CTTY_ERR);
		}
		return (CTTY_OK);
	}
	/*
	 * This is or was our ctty.
	 */
	if (s->s_sid != ctty->c_sid) {
#ifdef DEBUG
		if (jc_debug == 2)
			printf("ctty_write(): sid=%d c_sid=%d\n", 
					s->s_sid, ctty->c_sid);
#endif
		/*
		 * since the sids don't match then the ctty has been
		 * deallocated from the process.
		 *
		 */
		u.u_error = EINVAL;
		return (CTTY_ERR);
	}

	if (p->p_pgrp == ctty->c_pgrp) {
		/*
		 * POSIX IEEE std 1003.1-1988 7.1.1.4
		 * If a process is in the foreground process 
		 * group of its controlling terminal, write
		 * operations shall be allowed.
		 */
		 return (CTTY_OK);
	}

#ifdef DEBUG
	if (jc_debug == 2)
		printf("ctty_write(): in background\n");
#endif
	/*
	 * we only get here if writing to the ctty and in
	 * a background process group.
	 */
	blockig = SIGM_ISMEMBER(p->p_sigignore, sigmask(SIGTTOU)) ||
			  SIGM_ISMEMBER(p->p_sigmask, sigmask(SIGTTOU));
	if (!tostop || blockig) {
		/*
		 * POSIX IEEE std 1003.1-1988 7.1.1.4
		 * If TOSTOP is not set, or if TOSTOP is set and the 
		 * process is ignoring or blocking the SIGTTOU signal,
		 * the process is allowed to write to the terminal
		 * and the SIGTTOU signal is not sent.
		 */
		 return (CTTY_OK);
	} else if (p->p_flag & SORPH) {
		/*
		 * POSIX IEEE std 1003.1-1988 7.1.1.4
		 * If TOSTOP is set and the process group of the
		 * writting process is orphaned, and the writing 
		 * process is not ignoring or blocking SIGTTOU, 
		 * the write() returns -1 with errno set to EIO and 
		 * no signal is sent.
		 */
#ifdef DEBUG
		if (jc_debug == 2)
			printf("ctty_write(): blocked background orphan EIO\n");
#endif
		 u.u_error = EIO;
		 return (CTTY_ERR);
	}
	/*
	 * POSIX IEEE std 1003.1-1988 7.1.1.4
	 * any attempts by a process in a background process group
	 * to write to its controllong terminal shall cause its process
	 * group to be sent a SIGTTOU signal.
	 */
	spl = p_lock(&s->s_lock, SPLSESS);
	gsignal(p, SIGTTOU);
	/*
	 * If we were called via /dev/tty, then release the reference on
	 * controlling terminal.
	 */
	if (ioflag & IO_GENTTY) {
		syrelease(s, TRUE);
	}
	v_lock(&s->s_lock, spl);
	/*
	 * Now take the stop signal that we just sent to ourselves.
	 */
#ifdef DEBUG
	if (jc_debug > 1)
		printf("ctty_write blocking\n");
#endif
	spl = p_lock(&p->p_state, SPLHI);
	switch (issig(held_lock)) {
	case STOPPED:
		v_lock(&p->p_state, spl);
		/*
		 * If we were called via /dev/tty, then reacquire the reference
		 * on controlling terminal.
		 */
		if (ioflag & IO_GENTTY) {
			retval = syreacquire(s, CTTY_SLEPT);
		} else {
			retval = CTTY_SLEPT;
		}
		/* The lock was dropped; reacquire it again */
		(void) p_lock(held_lock, spl);
		return (retval);
	case STPGOTSIG:
		v_lock(&p->p_state, spl);
		/*
		 * If we were called via /dev/tty, then reacquire the reference
		 * on controlling terminal.
		 */
		if (ioflag & IO_GENTTY) {
			retval = syreacquire(s, CTTY_SIG);
		} else {
			retval = CTTY_SIG;
		}
		/* The lock was dropped; reacquire it again */
		(void) p_lock(held_lock, spl);
		return (retval);
	default:
		v_lock(&p->p_state, spl);
		/*
		 * If we were called via /dev/tty, then reacquire the reference
		 * on controlling terminal.
		 */
		if (ioflag & IO_GENTTY) {
			retval = syreacquire(s, CTTY_SIG);
		} else {
			retval = CTTY_SIG;
		}
		return (retval);
	}
}
