]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/tty.c
xnu-4903.241.1.tar.gz
[apple/xnu.git] / bsd / kern / tty.c
index b863e27ff805183219b96e09c8599ca9f6c36d5c..ed7e7965820295c04f6079338897c2b896c34e96 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997-2007 Apple Inc. All rights reserved.
+ * Copyright (c) 1997-2017 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
 #include <dev/kmreg_com.h>
 #include <machine/cons.h>
 #include <sys/resource.h>      /* averunnable */
-
-/*
- * Debugging assertions for tty locks
- */
-#define TTY_DEBUG 1
-#if TTY_DEBUG
-#define        TTY_LOCK_OWNED(tp) do {lck_mtx_assert(&tp->t_lock, LCK_MTX_ASSERT_OWNED); } while (0)
-#define        TTY_LOCK_NOTOWNED(tp) do {lck_mtx_assert(&tp->t_lock, LCK_MTX_ASSERT_NOTOWNED); } while (0)
-#else
-#define TTY_LOCK_OWNED(tp)
-#define TTY_LOCK_NOTOWNED(tp)
-#endif
+#include <kern/waitq.h>
+#include <libkern/section_keywords.h>
 
 static lck_grp_t       *tty_lck_grp;
 static lck_grp_attr_t  *tty_lck_grp_attr;
@@ -142,7 +132,7 @@ static void ttyunblock(struct tty *tp);
 static int     ttywflush(struct tty *tp);
 static int     proc_compare(proc_t p1, proc_t p2);
 
-static void    ttyhold(struct tty *tp);
+void ttyhold(struct tty *tp);
 static void    ttydeallocate(struct tty *tp);
 
 static int isctty(proc_t p, struct tty  *tp);
@@ -330,7 +320,6 @@ tty_unlock(struct tty *tp)
        lck_mtx_unlock(&tp->t_lock);
 }
 
-
 /*
  * ttyopen (LDISC)
  *
@@ -372,6 +361,7 @@ ttyclose(struct tty *tp)
 {
        struct pgrp * oldpg;
        struct session * oldsessp;
+       struct knote *kn;
 
        TTY_LOCK_OWNED(tp);     /* debug assert */
 
@@ -408,8 +398,15 @@ ttyclose(struct tty *tp)
                pg_rele(oldpg);
        tty_lock(tp);
        tp->t_state = 0;
+       SLIST_FOREACH(kn, &tp->t_wsel.si_note, kn_selnext) {
+               KNOTE_DETACH(&tp->t_wsel.si_note, kn);
+       }
        selthreadclear(&tp->t_wsel);
+       SLIST_FOREACH(kn, &tp->t_rsel.si_note, kn_selnext) {
+               KNOTE_DETACH(&tp->t_rsel.si_note, kn);
+       }
        selthreadclear(&tp->t_rsel);
+
        return (0);
 }
 
@@ -916,6 +913,20 @@ ttysetpgrphup(struct tty *tp)
 {
        TTY_LOCK_OWNED(tp);     /* debug assert */
        SET(tp->t_state, TS_PGRPHUP);
+       /*
+        * Also wake up sleeping readers which may or may not belong to the
+        * current foreground process group.
+        *
+        * This forces any non-fg readers (which entered read when
+        * that process group was in the fg) to return with EIO (if they're
+        * catching SIGTTIN or with SIGTTIN). The ones which do belong to the fg
+        * process group will promptly go back to sleep and get a SIGHUP shortly
+        * This would normally happen as part of the close in revoke but if
+        * there is a sleeping reader from a non-fg process group we never get
+        * to the close because the sleeping reader holds an iocount on the
+        * vnode of the terminal which is going to get revoked->reclaimed.
+        */
+       wakeup(TSA_HUP_OR_INPUT(tp));
 }
 
 /*
@@ -1457,6 +1468,9 @@ ttioctl_locked(struct tty *tp, u_long cmd, caddr_t data, int flag, proc_t p)
                 * case.
                 */
                if (ISSET(tp->t_state, TS_PGRPHUP)) {
+                       if (sessp != SESSION_NULL)
+                               session_rele(sessp);
+                       pg_rele(pgrp);
                        error = EPERM;
                        goto out;
                }
@@ -1465,6 +1479,23 @@ ttioctl_locked(struct tty *tp, u_long cmd, caddr_t data, int flag, proc_t p)
                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 becuse 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)
@@ -1517,18 +1548,27 @@ int
 ttyselect(struct tty *tp, int rw, void *wql, proc_t p)
 {
        int retval = 0;
+       /*
+        * Attaching knotes to TTYs needs to call selrecord in order to hook
+        * up the waitq to the selinfo, regardless of data being ready.  See
+        * filt_ttyattach.
+        */
+       bool needs_selrecord = rw & FMARK;
+       rw &= ~FMARK;
 
-       if (tp == NULL)
-               return (ENXIO);
+       if (tp == NULL) {
+               return ENXIO;
+       }
 
-       TTY_LOCK_OWNED(tp);     /* debug assert */
+       TTY_LOCK_OWNED(tp);
+
+       if (tp->t_state & TS_ZOMBIE) {
+               retval = 1;
+               goto out;
+       }
 
        switch (rw) {
        case FREAD:
-               if (ISSET(tp->t_state, TS_ZOMBIE)) {
-                       return(1);
-               }
-
                retval = ttnread(tp);
                if (retval > 0) {
                        break;
@@ -1537,12 +1577,8 @@ ttyselect(struct tty *tp, int rw, void *wql, proc_t p)
                selrecord(p, &tp->t_rsel, wql);
                break;
        case FWRITE:
-               if (ISSET(tp->t_state, TS_ZOMBIE)) {
-                       return(1);
-               }
-
                if ((tp->t_outq.c_cc <= tp->t_lowat) &&
-                               ISSET(tp->t_state, TS_CONNECTED)) {
+                   (tp->t_state & TS_CONNECTED)) {
                        retval = tp->t_hiwat - tp->t_outq.c_cc;
                        break;
                }
@@ -1550,6 +1586,19 @@ ttyselect(struct tty *tp, int rw, void *wql, proc_t p)
                selrecord(p, &tp->t_wsel, wql);
                break;
        }
+
+out:
+       if (retval > 0 && needs_selrecord) {
+               switch (rw) {
+               case FREAD:
+                       selrecord(p, &tp->t_rsel, wql);
+                       break;
+               case FWRITE:
+                       selrecord(p, &tp->t_wsel, wql);
+                       break;
+               }
+       }
+
        return retval;
 }
 
@@ -2426,7 +2475,9 @@ loop:
                        i = b_to_q((u_char *)cp, ce, &tp->t_outq);
                        ce -= i;
                        tp->t_column += ce;
-                       cp += ce, cc -= ce, tk_nout += ce;
+                       cp += ce;
+                       cc -= ce;
+                       tk_nout += ce;
                        tp->t_outcc += ce;
                        if (i > 0) {
                                /* out of space */
@@ -3064,7 +3115,7 @@ ttymalloc(void)
 /*
  * Increment the reference count on a tty.
  */
-static void
+void
 ttyhold(struct tty *tp)
 {
        TTY_LOCK_OWNED(tp);
@@ -3144,3 +3195,391 @@ isctty_sp(proc_t p, struct tty  *tp, struct session *sessp)
        return(sessp == tp->t_session && p->p_flag & P_CONTROLT);
 
 }
+
+
+static int  filt_ttyattach(struct knote *kn, struct kevent_internal_s *kev);
+static void filt_ttydetach(struct knote *kn);
+static int  filt_ttyevent(struct knote *kn, long hint);
+static int  filt_ttytouch(struct knote *kn, struct kevent_internal_s *kev);
+static int  filt_ttyprocess(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev);
+
+SECURITY_READ_ONLY_EARLY(struct filterops) tty_filtops = {
+       .f_isfd    = 1,
+       .f_attach  = filt_ttyattach,
+       .f_detach  = filt_ttydetach,
+       .f_event   = filt_ttyevent,
+       .f_touch   = filt_ttytouch,
+       .f_process = filt_ttyprocess
+};
+
+/*
+ * Called with struct tty locked. Returns non-zero if there is data to be read
+ * or written.
+ */
+static int
+filt_tty_common(struct knote *kn, struct tty *tp)
+{
+       int retval = 0;
+
+       TTY_LOCK_OWNED(tp); /* debug assert */
+
+       if (tp->t_state & TS_ZOMBIE) {
+               kn->kn_flags |= EV_EOF;
+               return 1;
+       }
+
+       switch (knote_get_seltype(kn)) {
+       case FREAD:
+               retval = ttnread(tp);
+               break;
+       case FWRITE:
+               if ((tp->t_outq.c_cc <= tp->t_lowat) &&
+                   (tp->t_state & TS_CONNECTED)) {
+                       retval = tp->t_hiwat - tp->t_outq.c_cc;
+               }
+               break;
+       }
+
+       kn->kn_data = retval;
+
+       /*
+        * TODO(mwidmann, jandrus): For native knote low watermark support,
+        * check the kn_sfflags for NOTE_LOWAT and check against kn_sdata.
+        *
+        * res = ((kn->kn_sfflags & NOTE_LOWAT) != 0) ?
+        *        (kn->kn_data >= kn->kn_sdata) : kn->kn_data;
+        */
+
+       return retval;
+}
+
+/*
+ * Find the struct tty from a waitq, which is a member of one of the two struct
+ * selinfos inside the struct tty.  Use the seltype to determine which selinfo.
+ */
+static struct tty *
+tty_from_waitq(struct waitq *wq, int seltype)
+{
+       struct selinfo *si;
+       struct tty *tp = NULL;
+
+       /*
+        * The waitq is part of the selinfo structure managed by the driver. For
+        * certain drivers, we want to hook the knote into the selinfo
+        * structure's si_note field so selwakeup can call KNOTE.
+        *
+        * While 'wq' is not really a queue element, this macro only uses the
+        * pointer to calculate the offset into a structure given an element
+        * name.
+        */
+       si = qe_element(wq, struct selinfo, si_waitq);
+
+       /*
+        * For TTY drivers, the selinfo structure is somewhere in the struct
+        * tty. There are two different selinfo structures, and the one used
+        * corresponds to the type of filter requested.
+        *
+        * While 'si' is not really a queue element, this macro only uses the
+        * pointer to calculate the offset into a structure given an element
+        * name.
+        */
+       switch (seltype) {
+       case FREAD:
+               tp = qe_element(si, struct tty, t_rsel);
+               break;
+       case FWRITE:
+               tp = qe_element(si, struct tty, t_wsel);
+               break;
+       }
+
+       return tp;
+}
+
+static struct tty *
+tty_from_knote(struct knote *kn)
+{
+       return (struct tty *)kn->kn_hook;
+}
+
+/*
+ * Try to lock the TTY structure associated with a knote.
+ *
+ * On success, this function returns a locked TTY structure.  Otherwise, NULL is
+ * returned.
+ */
+__attribute__((warn_unused_result))
+static struct tty *
+tty_lock_from_knote(struct knote *kn)
+{
+       struct tty *tp = tty_from_knote(kn);
+       if (tp) {
+               tty_lock(tp);
+       }
+
+       return tp;
+}
+
+/*
+ * Set the knote's struct tty to the kn_hook field.
+ *
+ * The idea is to fake a call to select with our own waitq set.  If the driver
+ * calls selrecord, we'll get a link to their waitq and access to the tty
+ * structure.
+ *
+ * Returns -1 on failure, with the error set in the knote, or selres on success.
+ */
+static int
+tty_set_knote_hook(struct knote *kn)
+{
+       uthread_t uth;
+       vfs_context_t ctx;
+       vnode_t vp;
+       kern_return_t kr;
+       struct waitq *wq = NULL;
+       struct waitq_set *old_wqs;
+       struct waitq_set tmp_wqs;
+       uint64_t rsvd, rsvd_arg;
+       uint64_t *rlptr = NULL;
+       int selres = -1;
+       struct tty *tp;
+
+       uth = get_bsdthread_info(current_thread());
+
+       ctx = vfs_context_current();
+       vp = (vnode_t)kn->kn_fp->f_fglob->fg_data;
+
+       /*
+        * Reserve a link element to avoid potential allocation under
+        * a spinlock.
+        */
+       rsvd = rsvd_arg = waitq_link_reserve(NULL);
+       rlptr = (void *)&rsvd_arg;
+
+       /*
+        * Trick selrecord into hooking a known waitq set into the device's selinfo
+        * waitq.  Once the link is in place, we can get back into the selinfo from
+        * the waitq and subsequently the tty (see tty_from_waitq).
+        *
+        * We can't use a real waitq set (such as the kqueue's) because wakeups
+        * might happen before we can unlink it.
+        */
+       kr = waitq_set_init(&tmp_wqs, SYNC_POLICY_FIFO | SYNC_POLICY_PREPOST, NULL,
+                       NULL);
+       assert(kr == KERN_SUCCESS);
+
+       /*
+        * Lazy allocate the waitqset to avoid potential allocation under
+        * a spinlock;
+        */
+       waitq_set_lazy_init_link(&tmp_wqs);
+
+       old_wqs = uth->uu_wqset;
+       uth->uu_wqset = &tmp_wqs;
+       /*
+        * FMARK forces selects to always call selrecord, even if data is
+        * available.  See ttselect, ptsselect, ptcselect.
+        *
+        * selres also contains the data currently available in the tty.
+        */
+       selres = VNOP_SELECT(vp, knote_get_seltype(kn) | FMARK, 0, rlptr, ctx);
+       uth->uu_wqset = old_wqs;
+
+       /*
+        * Make sure to cleanup the reserved link - this guards against
+        * drivers that may not actually call selrecord().
+        */
+       waitq_link_release(rsvd);
+       if (rsvd == rsvd_arg) {
+               /*
+                * The driver didn't call selrecord -- there's no tty hooked up so we
+                * can't attach.
+                */
+               knote_set_error(kn, ENOTTY);
+               selres = -1;
+               goto out;
+       }
+
+       /* rlptr may not point to a properly aligned pointer */
+       memcpy(&wq, rlptr, sizeof(void *));
+
+       tp = tty_from_waitq(wq, knote_get_seltype(kn));
+       assert(tp != NULL);
+
+       /*
+        * Take a reference and stash the tty in the knote.
+        */
+       tty_lock(tp);
+       ttyhold(tp);
+       kn->kn_hook = tp;
+       tty_unlock(tp);
+
+out:
+       /*
+        * Cleaning up the wqset will unlink its waitq and clean up any preposts
+        * that occurred as a result of data coming in while the tty was attached.
+        */
+       waitq_set_deinit(&tmp_wqs);
+
+       return selres;
+}
+
+static int
+filt_ttyattach(struct knote *kn, __unused struct kevent_internal_s *kev)
+{
+       int selres = 0;
+       struct tty *tp;
+
+       /*
+        * This function should be called from filt_specattach (spec_vnops.c),
+        * so most of the knote data structure should already be initialized.
+        */
+
+       /* don't support offsets in ttys or drivers that don't use struct tty */
+       if (kn->kn_vnode_use_ofst || !kn->kn_vnode_kqok) {
+               knote_set_error(kn, ENOTSUP);
+               return 0;
+       }
+
+       /*
+        * Connect the struct tty to the knote through the selinfo structure
+        * referenced by the waitq within the selinfo.
+        */
+       selres = tty_set_knote_hook(kn);
+       if (selres < 0) {
+               return 0;
+       }
+
+       /*
+        * Attach the knote to selinfo's klist.
+        */
+       tp = tty_lock_from_knote(kn);
+       if (!tp) {
+               knote_set_error(kn, ENOENT);
+               return 0;
+       }
+
+       switch (knote_get_seltype(kn)) {
+       case FREAD:
+               KNOTE_ATTACH(&tp->t_rsel.si_note, kn);
+               break;
+       case FWRITE:
+               KNOTE_ATTACH(&tp->t_wsel.si_note, kn);
+               break;
+       }
+
+       tty_unlock(tp);
+
+       return selres;
+}
+
+static void
+filt_ttydetach(struct knote *kn)
+{
+       struct tty *tp;
+
+       tp = tty_lock_from_knote(kn);
+       if (!tp) {
+               knote_set_error(kn, ENOENT);
+               return;
+       }
+
+       struct selinfo *si = NULL;
+       switch (knote_get_seltype(kn)) {
+       case FREAD:
+               si = &tp->t_rsel;
+               break;
+       case FWRITE:
+               si = &tp->t_wsel;
+               break;
+       /* knote_get_seltype will panic on default */
+       }
+
+       KNOTE_DETACH(&si->si_note, kn);
+       kn->kn_hook = NULL;
+
+       tty_unlock(tp);
+       ttyfree(tp);
+}
+
+static int
+filt_ttyevent(struct knote *kn, long hint)
+{
+       int ret;
+       struct tty *tp;
+       bool revoked = hint & NOTE_REVOKE;
+       hint &= ~NOTE_REVOKE;
+
+       tp = tty_from_knote(kn);
+       if (!tp) {
+               knote_set_error(kn, ENOENT);
+               return 0;
+       }
+
+       if (!hint) {
+               tty_lock(tp);
+       }
+
+       if (revoked) {
+               kn->kn_flags |= EV_EOF | EV_ONESHOT;
+               ret = 1;
+       } else {
+               ret = filt_tty_common(kn, tp);
+       }
+
+       if (!hint) {
+               tty_unlock(tp);
+       }
+
+       return ret;
+}
+
+static int
+filt_ttytouch(struct knote *kn, struct kevent_internal_s *kev)
+{
+       struct tty *tp;
+       int res = 0;
+
+       tp = tty_lock_from_knote(kn);
+       if (!tp) {
+               knote_set_error(kn, ENOENT);
+               return 0;
+       }
+
+       kn->kn_sdata = kev->data;
+       kn->kn_sfflags = kev->fflags;
+
+       if (kn->kn_vnode_kqok) {
+               res = filt_tty_common(kn, tp);
+       }
+
+       tty_unlock(tp);
+
+       return res;
+}
+
+static int
+filt_ttyprocess(struct knote *kn, __unused struct filt_process_s *data, struct kevent_internal_s *kev)
+{
+       struct tty *tp;
+       int res;
+
+       tp = tty_lock_from_knote(kn);
+       if (!tp) {
+               knote_set_error(kn, ENOENT);
+               return 0;
+       }
+
+       res = filt_tty_common(kn, tp);
+
+       if (res) {
+               *kev = kn->kn_kevent;
+               if (kn->kn_flags & EV_CLEAR) {
+                       kn->kn_fflags = 0;
+                       kn->kn_data = 0;
+               }
+       }
+
+       tty_unlock(tp);
+
+       return res;
+}