/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/file.h>
#include <sys/vnode.h>
#include <sys/errno.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/policy.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/proc.h>
#include <sys/session.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/strsubr.h>
#include <sys/fs/snode.h>

sess_t session0 = {
	&pid0,		/* s_sidp */
	{0},		/* s_lock */
	1,		/* s_ref */
	B_FALSE,	/* s_sighuped */
	B_FALSE,	/* s_exit */
	0,		/* s_exit_cv */
	0,		/* s_cnt */
	0,		/* s_cnt_cv */
	NODEV,		/* s_dev */
	NULL,		/* s_vp */
	NULL		/* s_cred */
};

void
sess_hold(proc_t *p)
{
	ASSERT(MUTEX_HELD(&pidlock) || MUTEX_HELD(&p->p_splock));
	mutex_enter(&p->p_sessp->s_lock);
	p->p_sessp->s_ref++;
	mutex_exit(&p->p_sessp->s_lock);
}

void
sess_rele(sess_t *sp, boolean_t pidlock_held)
{
	ASSERT(MUTEX_HELD(&pidlock) || !pidlock_held);

	mutex_enter(&sp->s_lock);

	ASSERT(sp->s_ref != 0);
	if (--sp->s_ref > 0) {
		mutex_exit(&sp->s_lock);
		return;
	}
	ASSERT(sp->s_ref == 0);

	/*
	 * It's ok to free this session structure now because we know
	 * that no one else can have a pointer to it.  We know this
	 * to be true because the only time that s_ref can possibly
	 * be incremented is when pidlock or p_splock is held AND there
	 * is a proc_t that points to that session structure.  In that
	 * case we are guaranteed that the s_ref is at least 1 since there
	 * is a proc_t that points to it.  So when s_ref finally drops to
	 * zero then no one else has a reference (and hence pointer) to
	 * this session structure and there is no valid proc_t pointing
	 * to this session structure anymore so, no one can acquire a
	 * reference (and pointer) to this session structure so it's
	 * ok to free it here.
	 */

	if (sp == &session0)
		panic("sp == &session0");

	/* make sure there are no outstanding holds */
	ASSERT(sp->s_cnt == 0);

	/* make sure there is no exit in progress */
	ASSERT(!sp->s_exit);

	/* make sure someone already freed any ctty */
	ASSERT(sp->s_vp == NULL);
	ASSERT(sp->s_dev == NODEV);

	if (!pidlock_held)
		mutex_enter(&pidlock);
	PID_RELE(sp->s_sidp);
	if (!pidlock_held)
		mutex_exit(&pidlock);

	mutex_destroy(&sp->s_lock);
	cv_destroy(&sp->s_cnt_cv);
	kmem_free(sp, sizeof (sess_t));
}

sess_t *
tty_hold(void)
{
	proc_t		*p = curproc;
	sess_t		*sp;
	boolean_t	got_sig = B_FALSE;

	/* make sure the caller isn't holding locks they shouldn't */
	ASSERT(MUTEX_NOT_HELD(&pidlock));

	for (;;) {
		mutex_enter(&p->p_splock);	/* protect p->p_sessp */
		sp = p->p_sessp;
		mutex_enter(&sp->s_lock);	/* protect sp->* */

		/* make sure the caller isn't holding locks they shouldn't */
		ASSERT((sp->s_vp == NULL) ||
		    MUTEX_NOT_HELD(&sp->s_vp->v_stream->sd_lock));

		/*
		 * If the session leader process is not exiting (and hence
		 * not trying to release the session's ctty) then we can
		 * safely grab a hold on the current session structure
		 * and return it.  If on the other hand the session leader
		 * process is exiting and clearing the ctty then we'll
		 * wait till it's done before we loop around and grab a
		 * hold on the session structure.
		 */
		if (!sp->s_exit)
			break;

		/* need to hold the session so it can't be freed */
		sp->s_ref++;
		mutex_exit(&p->p_splock);

		/* Wait till the session leader is done */
		if (!cv_wait_sig(&sp->s_exit_cv, &sp->s_lock))
			got_sig = B_TRUE;

		/*
		 * Now we need to drop our hold on the session structure,
		 * but we can't hold any locks when we do this because
		 * sess_rele() may need to aquire pidlock.
		 */
		mutex_exit(&sp->s_lock);
		sess_rele(sp, B_FALSE);

		if (got_sig)
			return (NULL);
	}

	/* whew, we finally got a hold */
	sp->s_cnt++;
	sp->s_ref++;
	mutex_exit(&sp->s_lock);
	mutex_exit(&p->p_splock);
	return (sp);
}

void
tty_rele(sess_t *sp)
{
	/* make sure the caller isn't holding locks they shouldn't */
	ASSERT(MUTEX_NOT_HELD(&pidlock));

	mutex_enter(&sp->s_lock);
	if ((--sp->s_cnt) == 0)
		cv_broadcast(&sp->s_cnt_cv);
	mutex_exit(&sp->s_lock);

	sess_rele(sp, B_FALSE);
}

void
sess_create(void)
{
	proc_t *p = curproc;
	sess_t *sp, *old_sp;

	sp = kmem_zalloc(sizeof (sess_t), KM_SLEEP);

	mutex_init(&sp->s_lock, NULL, MUTEX_DEFAULT, NULL);
	cv_init(&sp->s_cnt_cv, NULL, CV_DEFAULT, NULL);

	/*
	 * we need to grap p_lock to protect p_pgidp because
	 * /proc looks at p_pgidp while holding only p_lock.
	 *
	 * we don't need to hold p->p_sessp->s_lock or get a hold on the
	 * session structure since we're not actually updating any of
	 * the contents of the old session structure.
	 */
	mutex_enter(&pidlock);
	mutex_enter(&p->p_lock);
	mutex_enter(&p->p_splock);

	pgexit(p);

	sp->s_sidp = p->p_pidp;
	sp->s_ref = 1;
	sp->s_dev = NODEV;

	old_sp = p->p_sessp;
	p->p_sessp = sp;

	pgjoin(p, p->p_pidp);
	PID_HOLD(p->p_pidp);

	mutex_exit(&p->p_splock);
	mutex_exit(&p->p_lock);
	mutex_exit(&pidlock);

	sess_rele(old_sp, B_FALSE);
}

/*
 * Note that sess_ctty_clear() resets all the fields in the session
 * structure but doesn't release any holds or free any objects
 * that the session structure might currently point to.  it is the
 * callers responsibility to do this.
 */
static void
sess_ctty_clear(sess_t *sp, stdata_t *stp)
{
	/*
	 * Assert that we hold all the necessary locks.  We also need
	 * to be holding proc_t->p_splock for the process associated
	 * with this session, but since we don't have a proc pointer
	 * passed in we can't assert this here.
	 */
	ASSERT(MUTEX_HELD(&stp->sd_lock) && MUTEX_HELD(&pidlock) &&
	    MUTEX_HELD(&sp->s_lock));

	/* reset the session structure members to defaults */
	sp->s_sighuped = B_FALSE;
	sp->s_dev = NODEV;
	sp->s_vp = NULL;
	sp->s_cred = NULL;

	/* reset the stream session and group pointers */
	stp->sd_pgidp = NULL;
	stp->sd_sidp = NULL;
}

static void
sess_ctty_set(proc_t *p, sess_t *sp, stdata_t *stp)
{
	cred_t	*crp;

	/* Assert that we hold all the necessary locks. */
	ASSERT(MUTEX_HELD(&stp->sd_lock) && MUTEX_HELD(&pidlock) &&
	    MUTEX_HELD(&p->p_splock) && MUTEX_HELD(&sp->s_lock));

	/* get holds on structures */
	mutex_enter(&p->p_crlock);
	crhold(crp = p->p_cred);
	mutex_exit(&p->p_crlock);
	PID_HOLD(sp->s_sidp);	/* requires pidlock */
	PID_HOLD(sp->s_sidp);	/* requires pidlock */

	/* update the session structure members */
	sp->s_vp = makectty(stp->sd_vnode);
	sp->s_dev = sp->s_vp->v_rdev;
	sp->s_cred = crp;

	/* update the stream emebers */
	stp->sd_flag |= STRISTTY;	/* just to be sure */
	stp->sd_sidp = sp->s_sidp;
	stp->sd_pgidp = sp->s_sidp;
}

int
strctty(stdata_t *stp)
{
	sess_t		*sp;
	proc_t		*p = curproc;
	boolean_t	got_sig = B_FALSE;

	/*
	 * We are going to try to make stp the default ctty for the session
	 * associated with curproc.  Not only does this require holding a
	 * bunch of locks but it also requires waiting for any outstanding
	 * holds on the session structure (aquired via tty_hold()) to be
	 * released.  Hence, we have the following for(;;) loop that will
	 * aquire our locks, do some sanity checks, and wait for the hold
	 * count on the session structure to hit zero.  If we get a signal
	 * while waiting for outstanding holds to be released then we abort
	 * the operation and return.
	 */
	for (;;) {
		mutex_enter(&stp->sd_lock);	/* protects sd_pgidp/sd_sidp */
		mutex_enter(&pidlock);		/* protects p_pidp */
		mutex_enter(&p->p_splock);	/* protects p_sessp */
		sp = p->p_sessp;
		mutex_enter(&sp->s_lock);	/* protects sp->* */

		if (((stp->sd_flag & (STRHUP|STRDERR|STWRERR|STPLEX)) != 0) ||
		    (stp->sd_sidp != NULL) ||		/* stp already ctty? */
		    (p->p_pidp != sp->s_sidp) ||	/* we're not leader? */
		    (sp->s_vp != NULL)) {		/* session has ctty? */
			mutex_exit(&sp->s_lock);
			mutex_exit(&p->p_splock);
			mutex_exit(&pidlock);
			mutex_exit(&stp->sd_lock);
			return (ENOTTY);
		}

		/* sanity check.  we can't be exiting right now */
		ASSERT(!sp->s_exit);

		/*
		 * If no one else has a hold on this session structure
		 * then we now have exclusive access to it, so break out
		 * of this loop and update the session structure.
		 */
		if (sp->s_cnt == 0)
			break;

		/* need to hold the session so it can't be freed */
		sp->s_ref++;

		/* ain't locking order fun? */
		mutex_exit(&p->p_splock);
		mutex_exit(&pidlock);
		mutex_exit(&stp->sd_lock);

		if (!cv_wait_sig(&sp->s_cnt_cv, &sp->s_lock))
			got_sig = B_TRUE;
		mutex_exit(&sp->s_lock);
		sess_rele(sp, B_FALSE);

		if (got_sig)
			return (EINTR);
	}

	/* set the session ctty bindings */
	sess_ctty_set(p, sp, stp);

	mutex_exit(&sp->s_lock);
	mutex_exit(&p->p_splock);
	mutex_exit(&pidlock);
	mutex_exit(&stp->sd_lock);
	return (0);
}

/*
 * freectty_lock() attempts to aquire the army of locks required to free
 * the ctty associated with a given session leader process.  If it returns
 * successfully the following locks will be held:
 *	sd_lock, pidlock, p_splock, s_lock
 *
 * as a secondary bit of convience, freectty_lock() will also return
 * pointers to the session, ctty, and ctty stream associated with the
 * specified session leader process.
 */
static boolean_t
freectty_lock(proc_t *p, sess_t **spp, vnode_t **vpp, stdata_t **stpp,
    boolean_t at_exit)
{
	sess_t		*sp;
	vnode_t		*vp;
	stdata_t	*stp;

	mutex_enter(&pidlock);			/* protect p_pidp */
	mutex_enter(&p->p_splock);		/* protect p->p_sessp */
	sp = p->p_sessp;
	mutex_enter(&sp->s_lock);		/* protect sp->* */

	if ((sp->s_sidp != p->p_pidp) ||	/* we're not leader? */
	    (sp->s_vp == NULL)) {		/* no ctty? */
		mutex_exit(&sp->s_lock);
		mutex_exit(&p->p_splock);
		mutex_exit(&pidlock);
		return (B_FALSE);
	}

	vp = sp->s_vp;
	stp = sp->s_vp->v_stream;

	if (at_exit) {
		/* stop anyone else calling tty_hold() */
		sp->s_exit = B_TRUE;
	} else {
		/*
		 * due to locking order we have to grab stp->sd_lock before
		 * grabbing all the other proc/session locks.  but after we
		 * drop all our current locks it's possible that someone
		 * could come in and change our current session or close
		 * the current ctty (vp) there by making sp or stp invalid.
		 * (a VN_HOLD on vp won't protect stp because that only
		 * prevents the vnode from being freed not closed.)  so
		 * to prevent this we bump s_ref and s_cnt here.
		 *
		 * course this doesn't matter if we're the last thread in
		 * an exiting process that is the session leader, since no
		 * one else can change our session or free our ctty.
		 */
		sp->s_ref++;	/* hold the session structure */
		sp->s_cnt++;	/* protect vp and stp */
	}

	/* drop our session locks */
	mutex_exit(&sp->s_lock);
	mutex_exit(&p->p_splock);
	mutex_exit(&pidlock);

	/* grab locks in the right order */
	mutex_enter(&stp->sd_lock);		/* protects sd_pgidp/sd_sidp */
	mutex_enter(&pidlock);			/* protect p_pidp */
	mutex_enter(&p->p_splock);		/* protects p->p_sessp */
	mutex_enter(&sp->s_lock);		/* protects sp->* */

	/* if the session has changed, abort mission */
	if (sp != p->p_sessp) {
		/*
		 * this can't happen during process exit since we're the
		 * only thread in the process and we sure didn't change
		 * our own session at this point.
		 */
		ASSERT(!at_exit);

		/* release our locks and holds */
		mutex_exit(&sp->s_lock);
		mutex_exit(&p->p_splock);
		mutex_exit(&pidlock);
		mutex_exit(&stp->sd_lock);
		tty_rele(sp);
		return (B_FALSE);
	}

	/*
	 * sanity checks.  none of this should have changed since we had
	 * holds on the current ctty.
	 */
	ASSERT(sp->s_sidp == p->p_pidp);	/* we're the leader */
	ASSERT(sp->s_vp != NULL);		/* a ctty exists */
	ASSERT(vp == sp->s_vp);
	ASSERT(stp == sp->s_vp->v_stream);

	/* release our holds */
	if (!at_exit) {
		if ((--(sp)->s_cnt) == 0)
			cv_broadcast(&sp->s_cnt_cv);
		sp->s_ref--;
		ASSERT(sp->s_ref > 0);
	}

	/* return our pointers */
	*spp = sp;
	*vpp = vp;
	*stpp = stp;

	return (B_TRUE);
}

/*
 * Returns B_FALSE if no signal is sent to the process group associated with
 * this ctty.  Returns B_TRUE if a signal is sent to the process group.
 * If it return B_TRUE it also means that all the locks we were holding
 * were dropped so that we could send the signal.
 */
static boolean_t
freectty_signal(proc_t *p, sess_t *sp, stdata_t *stp, boolean_t at_exit)
{
	/* Assert that we hold all the necessary locks. */
	ASSERT(MUTEX_HELD(&stp->sd_lock) && MUTEX_HELD(&pidlock) &&
	    MUTEX_HELD(&p->p_splock) && MUTEX_HELD(&sp->s_lock));

	/* check if we already signaled this group */
	if (sp->s_sighuped)
		return (B_FALSE);

	sp->s_sighuped = B_TRUE;

	if (!at_exit) {
		/*
		 * once again, we're about to drop our army of locks and we
		 * don't want sp or stp to be freed.  (see the comment in
		 * freectty_lock())
		 */
		sp->s_ref++;	/* hold the session structure */
		sp->s_cnt++;	/* protect vp and stp */
	}

	/* can't hold these locks while calling pgsignal() */
	mutex_exit(&sp->s_lock);
	mutex_exit(&p->p_splock);
	mutex_exit(&pidlock);

	/* signal anyone in the foreground process group */
	pgsignal(stp->sd_pgidp, SIGHUP);

	/* signal anyone blocked in poll on this stream */
	if (!(stp->sd_flag & STRHUP))
		strhup(stp);

	mutex_exit(&stp->sd_lock);

	/* release our holds */
	if (!at_exit)
		tty_rele(sp);

	return (B_TRUE);
}

int
freectty(boolean_t at_exit)
{
	proc_t		*p = curproc;
	stdata_t	*stp;
	vnode_t		*vp;
	cred_t		*cred;
	sess_t		*sp;
	struct pid	*pgidp, *sidp;
	boolean_t	got_sig = B_FALSE;

	/*
	 * If the current process is a session leader we are going to
	 * try to release the ctty associated our current session.  To
	 * do this we need to aquire a bunch of locks, signal any
	 * processes in the forground that are associated with the ctty,
	 * and make sure no one has any outstanding holds on the current
	 * session * structure (aquired via tty_hold()).  Hence, we have
	 * the following for(;;) loop that will do all this work for
	 * us and break out when the hold count on the session structure
	 * hits zero.
	 */
	for (;;) {
		if (!freectty_lock(p, &sp, &vp, &stp, at_exit))
			return (EIO);

		if (freectty_signal(p, sp, stp, at_exit)) {
			/* loop around to re-aquire locks */
			continue;
		}

		/*
		 * Only a session leader process can free a ctty.  So if
		 * we've made it here we know we're a session leader and
		 * if we're not actively exiting it impossible for another
		 * thread in this process to be exiting.  (Because that
		 * thread would have already stopped all other threads
		 * in the current process.)
		 */
		ASSERT(at_exit || !sp->s_exit);

		/*
		 * If no one else has a hold on this session structure
		 * then we now have exclusive access to it, so break out
		 * of this loop and update the session structure.
		 */
		if (sp->s_cnt == 0)
			break;

		if (!at_exit) {
			/* need to hold the session so it can't be freed */
			sp->s_ref++;
		}

		/* ain't locking order fun? */
		mutex_exit(&p->p_splock);
		mutex_exit(&pidlock);
		mutex_exit(&stp->sd_lock);

		if (at_exit) {
			/*
			 * if we're exiting then we can't allow this operation
			 * to fail so we do a cw_wait() instead of a
			 * cv_wait_sig().  if there are threads with active
			 * holds on this ctty that are blocked, then
			 * they should only be blocked in a cv_wait_sig()
			 * and hopefully they were in the foreground process
			 * group and recieved the SIGHUP we sent above.  of
			 * course it's possible that they weren't in the
			 * foreground process group and didn't get our
			 * signal (or they could be stopped by job control
			 * in which case our signal wouldn't matter until
			 * they are restarted).  in this case we won't
			 * exit until someone else sends them a signal.
			 */
			cv_wait(&sp->s_cnt_cv, &sp->s_lock);
			mutex_exit(&sp->s_lock);
			continue;
		}

		if (!cv_wait_sig(&sp->s_cnt_cv, &sp->s_lock)) {
			got_sig = B_TRUE;
		}

		mutex_exit(&sp->s_lock);
		sess_rele(sp, B_FALSE);

		if (got_sig)
			return (EINTR);
	}
	ASSERT(sp->s_cnt == 0);

	/* save some pointers for later */
	cred = sp->s_cred;
	pgidp = stp->sd_pgidp;
	sidp = stp->sd_sidp;

	/* clear the session ctty bindings */
	sess_ctty_clear(sp, stp);

	/* wake up anyone blocked in tty_hold() */
	if (at_exit) {
		ASSERT(sp->s_exit);
		sp->s_exit = B_FALSE;
		cv_broadcast(&sp->s_exit_cv);
	}

	/* we can drop these locks now */
	mutex_exit(&sp->s_lock);
	mutex_exit(&p->p_splock);
	mutex_exit(&pidlock);
	mutex_exit(&stp->sd_lock);

	/* This is the only remaining thread with access to this vnode */
	(void) VOP_CLOSE(vp, 0, 1, (offset_t)0, cred);
	VN_RELE(vp);
	crfree(cred);

	/* release our holds on assorted structures and return */
	mutex_enter(&pidlock);
	PID_RELE(pgidp);
	PID_RELE(sidp);
	mutex_exit(&pidlock);

	return (1);
}

/*
 *	++++++++++++++++++++++++
 *	++  SunOS4.1 Buyback  ++
 *	++++++++++++++++++++++++
 *
 * vhangup: Revoke access of the current tty by all processes
 * Used by privileged users to give a "clean" terminal at login
 */
int
vhangup(void)
{
	if (secpolicy_sys_config(CRED(), B_FALSE) != 0)
		return (set_errno(EPERM));
	/*
	 * This routine used to call freectty() under a condition that
	 * could never happen.  So this code has never actually done
	 * anything, and evidently nobody has ever noticed.
	 */
	return (0);
}

dev_t
cttydev(proc_t *pp)
{
	sess_t	*sp;
	dev_t	dev;

	mutex_enter(&pp->p_splock);	/* protects p->p_sessp */
	sp = pp->p_sessp;

#ifdef DEBUG
	mutex_enter(&sp->s_lock);	/* protects sp->* */
	if (sp->s_vp == NULL)
		ASSERT(sp->s_dev == NODEV);
	else
		ASSERT(sp->s_dev != NODEV);
	mutex_exit(&sp->s_lock);
#endif /* DEBUG */

	dev = sp->s_dev;
	mutex_exit(&pp->p_splock);
	return (dev);
}

void
ctty_clear_sighuped(void)
{
	ASSERT(MUTEX_HELD(&pidlock) || MUTEX_HELD(&curproc->p_splock));
	curproc->p_sessp->s_sighuped = B_FALSE;
}