]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/tty_ptmx.c
xnu-7195.81.3.tar.gz
[apple/xnu.git] / bsd / kern / tty_ptmx.c
index 6da6e641abaf9a2b2d0729656718aec67c57af38..d4efb5c12f5a7d6741396b8afb53e22b162bce5e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997-2013 Apple Inc. All rights reserved.
+ * Copyright (c) 1997-2019 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  *
@@ -117,19 +117,39 @@ extern  d_reset_t       ptcreset;
 extern  d_select_t      ptcselect;
 
 static int ptmx_major;          /* dynamically assigned major number */
-static struct cdevsw ptmx_cdev = {
-       ptcopen, ptcclose, ptcread, ptcwrite,
-       ptyioctl, ptcstop, ptcreset, 0,
-       ptcselect, eno_mmap, eno_strat, eno_getc,
-       eno_putc, D_TTY
+static const struct cdevsw ptmx_cdev = {
+       .d_open       = ptcopen,
+       .d_close      = ptcclose,
+       .d_read       = ptcread,
+       .d_write      = ptcwrite,
+       .d_ioctl      = ptyioctl,
+       .d_stop       = ptcstop,
+       .d_reset      = ptcreset,
+       .d_ttys       = NULL,
+       .d_select     = ptcselect,
+       .d_mmap       = eno_mmap,
+       .d_strategy   = eno_strat,
+       .d_reserved_1 = eno_getc,
+       .d_reserved_2 = eno_putc,
+       .d_type       = D_TTY
 };
 
 static int ptsd_major;          /* dynamically assigned major number */
-static struct cdevsw ptsd_cdev = {
-       ptsopen, ptsclose, ptsread, ptswrite,
-       ptyioctl, ptsstop, ptsreset, 0,
-       ptsselect, eno_mmap, eno_strat, eno_getc,
-       eno_putc, D_TTY
+static const struct cdevsw ptsd_cdev = {
+       .d_open       = ptsopen,
+       .d_close      = ptsclose,
+       .d_read       = ptsread,
+       .d_write      = ptswrite,
+       .d_ioctl      = ptyioctl,
+       .d_stop       = ptsstop,
+       .d_reset      = ptsreset,
+       .d_ttys       = NULL,
+       .d_select     = ptsselect,
+       .d_mmap       = eno_mmap,
+       .d_strategy   = eno_strat,
+       .d_reserved_1 = eno_getc,
+       .d_reserved_2 = eno_putc,
+       .d_type       = D_TTY
 };
 
 /*
@@ -249,9 +269,12 @@ static struct _ptmx_ioctl_state {
 static struct ptmx_ioctl *
 ptmx_get_ioctl(int minor, int open_flag)
 {
-       struct ptmx_ioctl *new_ptmx_ioctl;
+       struct ptmx_ioctl *ptmx_ioctl = NULL;
 
        if (open_flag & PF_OPEN_M) {
+               struct ptmx_ioctl *new_ptmx_ioctl;
+
+               DEVFS_LOCK();
                /*
                 * If we are about to allocate more memory, but we have
                 * already hit the administrative limit, then fail the
@@ -262,8 +285,10 @@ ptmx_get_ioctl(int minor, int open_flag)
                 *              snapping to the nearest PTMX_GROW_VECTOR...
                 */
                if ((_state.pis_total - _state.pis_free) >= ptmx_max) {
+                       DEVFS_UNLOCK();
                        return NULL;
                }
+               DEVFS_UNLOCK();
 
                MALLOC(new_ptmx_ioctl, struct ptmx_ioctl *, sizeof(struct ptmx_ioctl), M_TTYS, M_WAITOK | M_ZERO);
                if (new_ptmx_ioctl == NULL) {
@@ -282,6 +307,18 @@ ptmx_get_ioctl(int minor, int open_flag)
                 * doing so avoids a reallocation race on the minor number.
                 */
                DEVFS_LOCK();
+
+               /*
+                * Check again to ensure the limit is not reached after initial check
+                * when the lock was dropped momentarily for malloc.
+                */
+               if ((_state.pis_total - _state.pis_free) >= ptmx_max) {
+                       ttyfree(new_ptmx_ioctl->pt_tty);
+                       DEVFS_UNLOCK();
+                       FREE(new_ptmx_ioctl, M_TTYS);
+                       return NULL;
+               }
+
                /* Need to allocate a larger vector? */
                if (_state.pis_free == 0) {
                        struct ptmx_ioctl **new_pis_ioctl_list;
@@ -345,11 +382,17 @@ ptmx_get_ioctl(int minor, int open_flag)
                }
        }
 
-       if (minor < 0 || minor >= _state.pis_total) {
-               return NULL;
+       /*
+        * Lock is held here to protect race when the 'pis_ioctl_list' array is
+        * being reallocated to increase its slots.
+        */
+       DEVFS_LOCK();
+       if (minor >= 0 && minor < _state.pis_total) {
+               ptmx_ioctl = _state.pis_ioctl_list[minor];
        }
+       DEVFS_UNLOCK();
 
-       return _state.pis_ioctl_list[minor];
+       return ptmx_ioctl;
 }
 
 /*
@@ -467,8 +510,8 @@ ptmx_clone(__unused dev_t dev, int action)
 int ptsd_kqfilter(dev_t dev, struct knote *kn);
 static void ptsd_kqops_detach(struct knote *);
 static int ptsd_kqops_event(struct knote *, long);
-static int ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev);
-static int ptsd_kqops_process(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev);
+static int ptsd_kqops_touch(struct knote *kn, struct kevent_qos_s *kev);
+static int ptsd_kqops_process(struct knote *kn, struct kevent_qos_s *kev);
 
 SECURITY_READ_ONLY_EARLY(struct filterops) ptsd_kqops = {
        .f_isfd = 1,
@@ -491,10 +534,7 @@ SECURITY_READ_ONLY_EARLY(struct filterops) ptsd_kqops = {
 static void
 ptsd_kqops_detach(struct knote *kn)
 {
-       struct tty *tp;
-
-       tp = kn->kn_hook;
-       assert(tp != NULL);
+       struct tty *tp = kn->kn_hook;
 
        tty_lock(tp);
 
@@ -507,42 +547,41 @@ ptsd_kqops_detach(struct knote *kn)
                case EVFILT_READ:
                        KNOTE_DETACH(&tp->t_rsel.si_note, kn);
                        break;
-
                case EVFILT_WRITE:
                        KNOTE_DETACH(&tp->t_wsel.si_note, kn);
                        break;
-
                default:
                        panic("invalid knote %p detach, filter: %d", kn, kn->kn_filter);
                        break;
                }
        }
 
-       kn->kn_hook = NULL;
        tty_unlock(tp);
-
        ttyfree(tp);
 }
 
 static int
-ptsd_kqops_common(struct knote *kn, struct tty *tp)
+ptsd_kqops_common(struct knote *kn, struct kevent_qos_s *kev, struct tty *tp)
 {
        int retval = 0;
+       int64_t data = 0;
 
        TTY_LOCK_OWNED(tp);
 
        switch (kn->kn_filter) {
        case EVFILT_READ:
-               kn->kn_data = ttnread(tp);
-               if (kn->kn_data > 0) {
-                       retval = 1;
-               }
+               /*
+                * ttnread can change the tty state,
+                * hence must be done upfront, before any other check.
+                */
+               data = ttnread(tp);
+               retval = (data > 0);
                break;
 
        case EVFILT_WRITE:
                if ((tp->t_outq.c_cc <= tp->t_lowat) &&
                    (tp->t_state & TS_CONNECTED)) {
-                       kn->kn_data = tp->t_outq.c_cn - tp->t_outq.c_cc;
+                       data = tp->t_outq.c_cn - tp->t_outq.c_cc;
                        retval = 1;
                }
                break;
@@ -555,9 +594,13 @@ ptsd_kqops_common(struct knote *kn, struct tty *tp)
 
        if (tp->t_state & TS_ZOMBIE) {
                kn->kn_flags |= EV_EOF;
+       }
+       if (kn->kn_flags & EV_EOF) {
                retval = 1;
        }
-
+       if (retval && kev) {
+               knote_fill_kevent(kn, kev, data);
+       }
        return retval;
 }
 
@@ -566,35 +609,25 @@ ptsd_kqops_event(struct knote *kn, long hint)
 {
        struct tty *tp = kn->kn_hook;
        int ret;
-       bool revoked = hint & NOTE_REVOKE;
-       hint &= ~NOTE_REVOKE;
 
-       if (!hint) {
-               tty_lock(tp);
-       }
+       TTY_LOCK_OWNED(tp);
 
-       if (revoked) {
+       if (hint & NOTE_REVOKE) {
                kn->kn_flags |= EV_EOF | EV_ONESHOT;
                ret = 1;
        } else {
-               ret = ptsd_kqops_common(kn, tp);
-       }
-
-       if (!hint) {
-               tty_unlock(tp);
+               ret = ptsd_kqops_common(kn, NULL, tp);
        }
 
        return ret;
 }
 
 static int
-ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
+ptsd_kqops_touch(struct knote *kn, struct kevent_qos_s *kev)
 {
-       struct tty *tp;
+       struct tty *tp = kn->kn_hook;
        int ret;
 
-       tp = kn->kn_hook;
-
        tty_lock(tp);
 
        /* accept new kevent state */
@@ -602,7 +635,7 @@ ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
        kn->kn_sdata = kev->data;
 
        /* recapture fired state of knote */
-       ret = ptsd_kqops_common(kn, tp);
+       ret = ptsd_kqops_common(kn, NULL, tp);
 
        tty_unlock(tp);
 
@@ -610,21 +643,13 @@ ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
 }
 
 static int
-ptsd_kqops_process(struct knote *kn, __unused struct filt_process_s *data,
-    struct kevent_internal_s *kev)
+ptsd_kqops_process(struct knote *kn, struct kevent_qos_s *kev)
 {
        struct tty *tp = kn->kn_hook;
        int ret;
 
        tty_lock(tp);
-       ret = ptsd_kqops_common(kn, tp);
-       if (ret) {
-               *kev = kn->kn_kevent;
-               if (kn->kn_flags & EV_CLEAR) {
-                       kn->kn_fflags = 0;
-                       kn->kn_data = 0;
-               }
-       }
+       ret = ptsd_kqops_common(kn, kev, tp);
        tty_unlock(tp);
 
        return ret;
@@ -672,7 +697,7 @@ ptsd_kqfilter(dev_t dev, struct knote *kn)
        }
 
        /* capture current event state */
-       ret = ptsd_kqops_common(kn, tp);
+       ret = ptsd_kqops_common(kn, NULL, tp);
 
        tty_unlock(tp);
 
@@ -688,10 +713,12 @@ ptsd_revoke_knotes(__unused int minor, struct tty *tp)
        tty_lock(tp);
 
        ttwakeup(tp);
-       KNOTE(&tp->t_rsel.si_note, NOTE_REVOKE | 1 /* the lock is already held */);
+       assert((tp->t_rsel.si_flags & SI_KNPOSTING) == 0);
+       KNOTE(&tp->t_rsel.si_note, NOTE_REVOKE);
 
        ttwwakeup(tp);
-       KNOTE(&tp->t_wsel.si_note, NOTE_REVOKE | 1);
+       assert((tp->t_wsel.si_flags & SI_KNPOSTING) == 0);
+       KNOTE(&tp->t_wsel.si_note, NOTE_REVOKE);
 
        tty_unlock(tp);
 }
@@ -706,9 +733,10 @@ ptsd_revoke_knotes(__unused int minor, struct tty *tp)
 int ptmx_kqfilter(dev_t dev, struct knote *kn);
 static void ptmx_kqops_detach(struct knote *);
 static int ptmx_kqops_event(struct knote *, long);
-static int ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev);
-static int ptmx_kqops_process(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev);
-static int ptmx_kqops_common(struct knote *kn, struct ptmx_ioctl *pti, struct tty *tp);
+static int ptmx_kqops_touch(struct knote *kn, struct kevent_qos_s *kev);
+static int ptmx_kqops_process(struct knote *kn, struct kevent_qos_s *kev);
+static int ptmx_kqops_common(struct knote *kn, struct kevent_qos_s *kev,
+    struct ptmx_ioctl *pti, struct tty *tp);
 
 SECURITY_READ_ONLY_EARLY(struct filterops) ptmx_kqops = {
        .f_isfd = 1,
@@ -728,8 +756,7 @@ ptmx_knote_ioctl(struct knote *kn)
 static struct tty *
 ptmx_knote_tty(struct knote *kn)
 {
-       struct ptmx_ioctl *pti = kn->kn_hook;
-       return pti->pt_tty;
+       return ptmx_knote_ioctl(kn)->pt_tty;
 }
 
 int
@@ -754,6 +781,8 @@ ptmx_kqfilter(dev_t dev, struct knote *kn)
        tty_lock(tp);
 
        kn->kn_filtid = EVFILTID_PTMX;
+       /* the tty will be freed when detaching the knote */
+       ttyhold(tp);
        kn->kn_hook = pti;
 
        /*
@@ -775,10 +804,8 @@ ptmx_kqfilter(dev_t dev, struct knote *kn)
        }
 
        /* capture current event state */
-       ret = ptmx_kqops_common(kn, pti, tp);
+       ret = ptmx_kqops_common(kn, NULL, pti, tp);
 
-       /* take a reference on the TTY */
-       ttyhold(tp);
        tty_unlock(tp);
 
        return ret;
@@ -790,49 +817,39 @@ ptmx_kqops_detach(struct knote *kn)
        struct ptmx_ioctl *pti = kn->kn_hook;
        struct tty *tp = pti->pt_tty;
 
-       assert(tp != NULL);
-
        tty_lock(tp);
 
        switch (kn->kn_filter) {
        case EVFILT_READ:
                KNOTE_DETACH(&pti->pt_selr.si_note, kn);
                break;
-
        case EVFILT_WRITE:
                KNOTE_DETACH(&pti->pt_selw.si_note, kn);
                break;
-
        default:
                panic("invalid knote %p detach, filter: %d", kn, kn->kn_filter);
                break;
        }
 
-       kn->kn_hook = NULL;
        tty_unlock(tp);
-
        ttyfree(tp);
 }
 
 static int
-ptmx_kqops_common(struct knote *kn, struct ptmx_ioctl *pti, struct tty *tp)
+ptmx_kqops_common(struct knote *kn, struct kevent_qos_s *kev,
+    struct ptmx_ioctl *pti, struct tty *tp)
 {
        int retval = 0;
+       int64_t data = 0;
 
        TTY_LOCK_OWNED(tp);
 
-       /* disconnects should force a wakeup (EOF) */
-       if (!(tp->t_state & TS_CONNECTED)) {
-               kn->kn_flags |= EV_EOF;
-               return 1;
-       }
-
        switch (kn->kn_filter) {
        case EVFILT_READ:
                /* there's data on the TTY and it's not stopped */
                if (tp->t_outq.c_cc && !(tp->t_state & TS_TTSTOP)) {
-                       retval = tp->t_outq.c_cc;
-                       kn->kn_data = retval;
+                       data = tp->t_outq.c_cc;
+                       retval = data > 0;
                } else if (((pti->pt_flags & PF_PKT) && pti->pt_send) ||
                    ((pti->pt_flags & PF_UCNTL) && pti->pt_ucntl)) {
                        retval = 1;
@@ -861,11 +878,16 @@ ptmx_kqops_common(struct knote *kn, struct ptmx_ioctl *pti, struct tty *tp)
                break;
        }
 
-       if (tp->t_state & TS_ZOMBIE) {
+       /* disconnects should force a wakeup (EOF) */
+       if (!(tp->t_state & TS_CONNECTED) || (tp->t_state & TS_ZOMBIE)) {
                kn->kn_flags |= EV_EOF;
+       }
+       if (kn->kn_flags & EV_EOF) {
                retval = 1;
        }
-
+       if (retval && kev) {
+               knote_fill_kevent(kn, kev, data);
+       }
        return retval;
 }
 
@@ -875,29 +897,21 @@ ptmx_kqops_event(struct knote *kn, long hint)
        struct ptmx_ioctl *pti = ptmx_knote_ioctl(kn);
        struct tty *tp = ptmx_knote_tty(kn);
        int ret;
-       bool revoked = hint & NOTE_REVOKE;
-       hint &= ~NOTE_REVOKE;
 
-       if (!hint) {
-               tty_lock(tp);
-       }
+       TTY_LOCK_OWNED(tp);
 
-       if (revoked) {
+       if (hint & NOTE_REVOKE) {
                kn->kn_flags |= EV_EOF | EV_ONESHOT;
                ret = 1;
        } else {
-               ret = ptmx_kqops_common(kn, pti, tp);
-       }
-
-       if (!hint) {
-               tty_unlock(tp);
+               ret = ptmx_kqops_common(kn, NULL, pti, tp);
        }
 
        return ret;
 }
 
 static int
-ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
+ptmx_kqops_touch(struct knote *kn, struct kevent_qos_s *kev)
 {
        struct ptmx_ioctl *pti = ptmx_knote_ioctl(kn);
        struct tty *tp = ptmx_knote_tty(kn);
@@ -910,7 +924,7 @@ ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
        kn->kn_sdata = kev->data;
 
        /* recapture fired state of knote */
-       ret = ptmx_kqops_common(kn, pti, tp);
+       ret = ptmx_kqops_common(kn, NULL, pti, tp);
 
        tty_unlock(tp);
 
@@ -918,22 +932,14 @@ ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
 }
 
 static int
-ptmx_kqops_process(struct knote *kn, __unused struct filt_process_s *data,
-    struct kevent_internal_s *kev)
+ptmx_kqops_process(struct knote *kn, struct kevent_qos_s *kev)
 {
        struct ptmx_ioctl *pti = ptmx_knote_ioctl(kn);
        struct tty *tp = ptmx_knote_tty(kn);
        int ret;
 
        tty_lock(tp);
-       ret = ptmx_kqops_common(kn, pti, tp);
-       if (ret) {
-               *kev = kn->kn_kevent;
-               if (kn->kn_flags & EV_CLEAR) {
-                       kn->kn_fflags = 0;
-                       kn->kn_data = 0;
-               }
-       }
+       ret = ptmx_kqops_common(kn, kev, pti, tp);
        tty_unlock(tp);
 
        return ret;