+ /*
+ * This can only be done by a session leader.
+ */
+ if (!SESS_LEADER(p, sessp)) {
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ session_rele(sessp);
+ tty_lock(tp);
+ error = EPERM;
+ goto out;
+ }
+ /*
+ * If this terminal is already the controlling terminal for the
+ * session, nothing to do here.
+ */
+ if (tp->t_session == sessp) {
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ session_rele(sessp);
+ tty_lock(tp);
+ error = 0;
+ goto out;
+ }
+ pg = proc_pgrp(p);
+ /*
+ * Deny if the terminal is already attached to another session or
+ * the session already has a terminal vnode.
+ */
+ session_lock(sessp);
+ if (sessp->s_ttyvp || tp->t_session) {
+ session_unlock(sessp);
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ if (pg != PGRP_NULL) {
+ pg_rele(pg);
+ }
+ session_rele(sessp);
+ tty_lock(tp);
+ error = EPERM;
+ goto out;
+ }
+ sessp->s_ttypgrpid = pg->pg_id;
+ oldtp = sessp->s_ttyp;
+ ttyhold(tp);
+ sessp->s_ttyp = tp;
+ session_unlock(sessp);
+ proc_list_lock();
+ oldsessp = tp->t_session;
+ oldpg = tp->t_pgrp;
+ if (oldsessp != SESSION_NULL) {
+ oldsessp->s_ttypgrpid = NO_PID;
+ }
+ /* do not drop refs on sessp and pg as tp holds them */
+ tp->t_session = sessp;
+ tp->t_pgrp = pg;
+ proc_list_unlock();
+ OSBitOrAtomic(P_CONTROLT, &p->p_flag);
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ /* drop the reference on prev session and pgrp */
+ if (oldsessp != SESSION_NULL) {
+ session_rele(oldsessp);
+ }
+ if (oldpg != PGRP_NULL) {
+ pg_rele(oldpg);
+ }
+ if (NULL != oldtp) {
+ ttyfree(oldtp);
+ }
+ tty_lock(tp);
+ break;
+
+ case TIOCSPGRP: { /* set pgrp of tty */
+ struct pgrp *pgrp = PGRP_NULL;
+
+ sessp = proc_session(p);
+ if (!isctty_sp(p, tp, sessp)) {
+ if (sessp != SESSION_NULL) {
+ session_rele(sessp);
+ }
+ error = ENOTTY;
+ goto out;
+ } else if ((pgrp = pgfind(*(int *)data)) == PGRP_NULL) {
+ if (sessp != SESSION_NULL) {
+ session_rele(sessp);
+ }
+ error = EINVAL;
+ goto out;
+ } else if (pgrp->pg_session != sessp) {
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ if (sessp != SESSION_NULL) {
+ session_rele(sessp);
+ }
+ pg_rele(pgrp);
+ tty_lock(tp);
+ error = EPERM;
+ goto out;
+ }
+
+ proc_list_lock();
+ oldpg = tp->t_pgrp;
+ tp->t_pgrp = pgrp;
+ sessp->s_ttypgrpid = pgrp->pg_id;
+ proc_list_unlock();
+
+ /*
+ * Wakeup readers to recheck if they are still the foreground
+ * process group.
+ *
+ * ttwakeup() isn't called because the readers aren't getting
+ * woken up because there is something to read but to force
+ * the re-evaluation of their foreground process group status.
+ *
+ * Ordinarily leaving these readers waiting wouldn't be an issue
+ * as launchd would send them a termination signal eventually
+ * (if nobody else does). But if this terminal happens to be
+ * /dev/console, launchd itself could get blocked forever behind
+ * a revoke of /dev/console and leave the system deadlocked.
+ */
+ wakeup(TSA_HUP_OR_INPUT(tp));
+
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ if (oldpg != PGRP_NULL) {
+ pg_rele(oldpg);
+ }
+ if (sessp != SESSION_NULL) {
+ session_rele(sessp);
+ }
+ tty_lock(tp);
+ break;
+ }
+ case TIOCSTAT: /* simulate control-T */
+ ttyinfo_locked(tp);
+ break;
+ case TIOCSWINSZ: /* set window size */
+ if (bcmp((caddr_t)&tp->t_winsize, data,
+ sizeof(struct winsize))) {
+ tp->t_winsize = *(struct winsize *)data;
+ /* SAFE: All callers drop the lock on return */
+ tty_unlock(tp);
+ tty_pgsignal(tp, SIGWINCH, 1);
+ tty_lock(tp);
+ }
+ break;
+ case TIOCSDRAINWAIT:
+ error = suser(kauth_cred_get(), &p->p_acflag);
+ if (error) {
+ goto out;
+ }
+ tp->t_timeout = *(int *)data * hz;
+ wakeup(TSA_OCOMPLETE(tp));
+ wakeup(TSA_OLOWAT(tp));
+ break;
+ case TIOCGDRAINWAIT:
+ *(int *)data = tp->t_timeout / hz;
+ break;
+ case TIOCREVOKE:
+ SET(tp->t_state, TS_REVOKE);
+ tp->t_gen++;
+ /*
+ * At this time, only this wait channel is woken up as only
+ * ttread has been problematic. It is possible we may need
+ * to add wake up other tty wait addresses as well.
+ */
+ wakeup(TSA_HUP_OR_INPUT(tp));
+ break;
+ case TIOCREVOKECLEAR:
+ CLR(tp->t_state, TS_REVOKE);
+ break;
+ default:
+ error = ttcompat(tp, cmd, data, flag, p);
+ goto out;
+ }
+
+ error = 0;
+out:
+ return error;
+}
+