/* * 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 2007 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 acquire 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 (acquired via tty_hold()) to be * released. Hence, we have the following for(;;) loop that will * acquire 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 acquire 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 convenience, 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 acquire 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 (acquired 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-acquire 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, NULL); 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; }