]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/tty_ptmx.c
xnu-4570.1.46.tar.gz
[apple/xnu.git] / bsd / kern / tty_ptmx.c
index 43db005d0666f6421381a13750b7e29ed8cf8361..cf89b93ebc1f5d8eeaf5bcfcd0e49727535e5c16 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 1997-2013 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
- * 
+ *
  * This file contains Original Code and/or Modifications of Original Code
  * as defined in and that are subject to the Apple Public Source License
  * Version 2.0 (the 'License'). You may not use this file except in
  * unlawful or unlicensed copies of an Apple operating system, or to
  * circumvent, violate, or enable the circumvention or violation of, any
  * terms of an Apple operating system software license agreement.
- * 
+ *
  * Please obtain a copy of the License at
  * http://www.opensource.apple.com/apsl/ and read it before using this file.
- * 
+ *
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
@@ -22,7 +22,7 @@
  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  * Please see the License for the specific language governing rights and
  * limitations under the License.
- * 
+ *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  */
 /*
@@ -82,6 +82,7 @@
 #include <sys/sysctl.h>
 #include <miscfs/devfs/devfs.h>
 #include <miscfs/devfs/devfsdefs.h>    /* DEVFS_LOCK()/DEVFS_UNLOCK() */
+#include <libkern/section_keywords.h>
 
 #if CONFIG_MACF
 #include <security/mac_framework.h>
@@ -169,39 +170,6 @@ SYSCTL_PROC(_kern_tty, OID_AUTO, ptmx_max,
 
 static int     ptmx_clone(dev_t dev, int minor);
 
-/*
- * Set of locks to keep the interaction between kevents and revoke
- * from causing havoc.
- */
-
-#define        LOG2_PTSD_KE_NLCK       2
-#define        PTSD_KE_NLCK            (1l << LOG2_PTSD_KE_NLCK)
-#define        PTSD_KE_LOCK_INDEX(x)   ((x) & (PTSD_KE_NLCK - 1))
-
-static lck_mtx_t ptsd_kevent_lock[PTSD_KE_NLCK];
-
-static void
-ptsd_kevent_lock_init(void)
-{
-       int i;
-       lck_grp_t *lgrp = lck_grp_alloc_init("ptsd kevent", LCK_GRP_ATTR_NULL);
-
-       for (i = 0; i < PTSD_KE_NLCK; i++)
-               lck_mtx_init(&ptsd_kevent_lock[i], lgrp, LCK_ATTR_NULL);
-}
-
-static void
-ptsd_kevent_mtx_lock(int minor)
-{
-       lck_mtx_lock(&ptsd_kevent_lock[PTSD_KE_LOCK_INDEX(minor)]);
-}
-
-static void
-ptsd_kevent_mtx_unlock(int minor)
-{
-       lck_mtx_unlock(&ptsd_kevent_lock[PTSD_KE_LOCK_INDEX(minor)]);
-}
-
 static struct tty_dev_t _ptmx_driver;
 
 int
@@ -213,12 +181,12 @@ ptmx_init( __unused int config_count)
         */
 
        /* Get a major number for /dev/ptmx */
-       if((ptmx_major = cdevsw_add(-15, &ptmx_cdev)) == -1) {
+       if ((ptmx_major = cdevsw_add(-15, &ptmx_cdev)) == -1) {
                printf("ptmx_init: failed to obtain /dev/ptmx major number\n");
                return (ENOENT);
        }
 
-       if (cdevsw_setkqueueok(ptmx_major, &ptmx_cdev, 0) == -1) {
+       if (cdevsw_setkqueueok(ptmx_major, &ptmx_cdev, CDEVSW_IS_PTC) == -1) {
                panic("Failed to set flags on ptmx cdevsw entry.");
        }
 
@@ -228,16 +196,11 @@ ptmx_init( __unused int config_count)
                printf("ptmx_init: failed to obtain /dev/ptmx major number\n");
                return (ENOENT);
        }
-       
-       if (cdevsw_setkqueueok(ptsd_major, &ptsd_cdev, 0) == -1) {
+
+       if (cdevsw_setkqueueok(ptsd_major, &ptsd_cdev, CDEVSW_IS_PTS) == -1) {
                panic("Failed to set flags on ptmx cdevsw entry.");
        }
 
-       /*
-        * Locks to guard against races between revoke and kevents
-        */
-       ptsd_kevent_lock_init();
-
        /* Create the /dev/ptmx device {<major>,0} */
        (void)devfs_make_node_clone(makedev(ptmx_major, 0),
                                DEVFS_CHAR, UID_ROOT, GID_TTY, 0666,
@@ -341,8 +304,8 @@ ptmx_get_ioctl(int minor, int open_flag)
                        _state.pis_total += PTMX_GROW_VECTOR;
                        if (old_pis_ioctl_list)
                                FREE(old_pis_ioctl_list, M_TTYS);
-               } 
-               
+               }
+
                /* is minor in range now? */
                if (minor < 0 || minor >= _state.pis_total) {
                        ttyfree(new_ptmx_ioctl->pt_tty);
@@ -350,14 +313,14 @@ ptmx_get_ioctl(int minor, int open_flag)
                        FREE(new_ptmx_ioctl, M_TTYS);
                        return (NULL);
                }
-               
+
                if (_state.pis_ioctl_list[minor] != NULL) {
                        ttyfree(new_ptmx_ioctl->pt_tty);
                        DEVFS_UNLOCK();
                        FREE(new_ptmx_ioctl, M_TTYS);
 
                        /* Special error value so we know to redrive the open, we've been raced */
-                       return (struct ptmx_ioctl*)-1; 
+                       return (struct ptmx_ioctl*)-1;
 
                }
 
@@ -381,11 +344,11 @@ ptmx_get_ioctl(int minor, int open_flag)
                        printf("devfs_make_node() call failed for ptmx_get_ioctl()!!!!\n");
                }
        }
-       
+
        if (minor < 0 || minor >= _state.pis_total) {
                return (NULL);
        }
-       
+
        return (_state.pis_ioctl_list[minor]);
 }
 
@@ -398,7 +361,7 @@ ptmx_free_ioctl(int minor, int open_flag)
        struct ptmx_ioctl *old_ptmx_ioctl = NULL;
 
        DEVFS_LOCK();
-       
+
        if (minor < 0 || minor >= _state.pis_total) {
                DEVFS_UNLOCK();
                return (-1);
@@ -498,22 +461,20 @@ ptmx_clone(__unused dev_t dev, int action)
 /*
  * kqueue support.
  */
-int ptsd_kqfilter(dev_t, struct knote *); 
+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);
 
-struct filterops ptsd_kqops = {
+SECURITY_READ_ONLY_EARLY(struct filterops) ptsd_kqops = {
        .f_isfd = 1,
+       /* attach is handled by ptsd_kqfilter -- the dev node must be passed in */
        .f_detach = ptsd_kqops_detach,
        .f_event = ptsd_kqops_event,
        .f_touch = ptsd_kqops_touch,
        .f_process = ptsd_kqops_process,
-};                                    
-
-#define        PTSD_KNOTE_VALID        NULL
-#define        PTSD_KNOTE_REVOKED      ((void *)-911l)
+};
 
 /*
  * In the normal case, by the time the driver_close() routine is called
@@ -527,243 +488,210 @@ struct filterops ptsd_kqops = {
 static void
 ptsd_kqops_detach(struct knote *kn)
 {
-       struct ptmx_ioctl *pti;
        struct tty *tp;
-       dev_t dev, lockdev = (dev_t)kn->kn_hookid;
-
-       ptsd_kevent_mtx_lock(minor(lockdev));
-
-       if ((dev = (dev_t)kn->kn_hookid) != 0) {
-               pti = ptmx_get_ioctl(minor(dev), 0);
-               if (pti != NULL && (tp = pti->pt_tty) != NULL) {
-                       tty_lock(tp);
-                       if (kn->kn_filter == EVFILT_READ)
-                               KNOTE_DETACH(&tp->t_rsel.si_note, kn);
-                       else
-                               KNOTE_DETACH(&tp->t_wsel.si_note, kn);
-                       tty_unlock(tp);
-                       kn->kn_hookid = 0;
+
+       tp = kn->kn_hook;
+       assert(tp != NULL);
+
+       tty_lock(tp);
+
+       /*
+        * Only detach knotes from open ttys -- ttyclose detaches all knotes
+        * under the lock and unsets TS_ISOPEN.
+        */
+       if (tp->t_state & TS_ISOPEN) {
+               switch (kn->kn_filter) {
+               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;
                }
        }
 
-       ptsd_kevent_mtx_unlock(minor(lockdev));
+       kn->kn_hook = NULL;
+       tty_unlock(tp);
+
+       ttyfree(tp);
 }
 
 static int
-ptsd_kqops_common(struct knote *kn, dev_t dev, long hint)
+ptsd_kqops_common(struct knote *kn, struct tty *tp)
 {
-       struct ptmx_ioctl *pti;
-       struct tty *tp;
        int retval = 0;
 
-       do {
-               if (kn->kn_hook != PTSD_KNOTE_VALID ) {
-                       /* We were revoked */
-                       kn->kn_data = 0;
-                       kn->kn_flags |= EV_EOF;
-                       retval = 1;
-                       break;
-               }
+       TTY_LOCK_OWNED(tp);
 
-               pti = ptmx_get_ioctl(minor(dev), 0);
-               if (pti == NULL || (tp = pti->pt_tty) == NULL) {
-                       kn->kn_data = ENXIO;
-                       kn->kn_flags |= EV_ERROR;
+       switch (kn->kn_filter) {
+       case EVFILT_READ:
+               kn->kn_data = ttnread(tp);
+               if (kn->kn_data > 0) {
                        retval = 1;
-                       break;
                }
+               break;
 
-               if (hint == 0)
-                       tty_lock(tp);
-
-               if (kn->kn_filter == EVFILT_READ) {
-                       kn->kn_data = ttnread(tp);
-                       if (kn->kn_data > 0)
-                               retval = 1;
-                       if (ISSET(tp->t_state, TS_ZOMBIE)) {
-                               kn->kn_flags |= EV_EOF;
-                               retval = 1;
-                       }
-               } else {        /* EVFILT_WRITE */
-                       if ((tp->t_outq.c_cc <= tp->t_lowat) &&
-                           ISSET(tp->t_state, TS_CONNECTED)) {
-                               kn->kn_data = tp->t_outq.c_cn - tp->t_outq.c_cc;
-                               retval = 1;
-                       }
-                       if (ISSET(tp->t_state, TS_ZOMBIE)) {
-                               kn->kn_flags |= EV_EOF;
-                               retval = 1;
-                       }
+       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;
+                       retval = 1;
                }
+               break;
 
-               if (hint == 0)
-                       tty_unlock(tp);
+       default:
+               panic("ptsd kevent: unexpected filter: %d, kn = %p, tty = %p",
+                               kn->kn_filter, kn, tp);
+               break;
+       }
 
-       } while (0);
+       if (tp->t_state & TS_ZOMBIE) {
+               kn->kn_flags |= EV_EOF;
+               retval = 1;
+       }
 
-       return (retval);
-}                                                                                                
+       return retval;
+}
 
 static int
 ptsd_kqops_event(struct knote *kn, long hint)
 {
-       dev_t dev = (dev_t)kn->kn_hookid;
-       int res;
+       struct tty *tp = kn->kn_hook;
+       int ret;
+       bool revoked = hint & NOTE_REVOKE;
+       hint &= ~NOTE_REVOKE;
 
-       ptsd_kevent_mtx_lock(minor(dev));
-       res = ptsd_kqops_common(kn, dev, hint);
-       ptsd_kevent_mtx_unlock(minor(dev));
-       return res;
+       if (!hint) {
+               tty_lock(tp);
+       }
+
+       if (revoked) {
+               kn->kn_flags |= EV_EOF | EV_ONESHOT;
+               ret = 1;
+       } else {
+               ret = ptsd_kqops_common(kn, tp);
+       }
+
+       if (!hint) {
+               tty_unlock(tp);
+       }
+
+       return ret;
 }
-       
 
 static int
 ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev)
 {
-       dev_t dev = (dev_t)kn->kn_hookid;
-       int res;
+       struct tty *tp;
+       int ret;
+
+       tp = kn->kn_hook;
 
-       ptsd_kevent_mtx_lock(minor(dev));
+       tty_lock(tp);
 
        /* accept new kevent state */
        kn->kn_sfflags = kev->fflags;
        kn->kn_sdata = kev->data;
-       if ((kn->kn_status & KN_UDATA_SPECIFIC) == 0)
+       if ((kn->kn_status & KN_UDATA_SPECIFIC) == 0) {
                kn->kn_udata = kev->udata;
+       }
 
        /* recapture fired state of knote */
-       res = ptsd_kqops_common(kn, dev, 0);
+       ret = ptsd_kqops_common(kn, tp);
 
-       ptsd_kevent_mtx_unlock(minor(dev));
+       tty_unlock(tp);
 
-       return res;
+       return ret;
 }
 
 static int
-ptsd_kqops_process(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev)
+ptsd_kqops_process(struct knote *kn, __unused struct filt_process_s *data,
+               struct kevent_internal_s *kev)
 {
-#pragma unused(data)
-       dev_t dev = (dev_t)kn->kn_hookid;
-       int res;
+       struct tty *tp = kn->kn_hook;
+       int ret;
 
-       ptsd_kevent_mtx_lock(minor(dev));
-       res = ptsd_kqops_common(kn, dev, 0);
-       if (res) {
+       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;
                }
        }
-       ptsd_kevent_mtx_unlock(minor(dev));
-       return res;
+       tty_unlock(tp);
+
+       return ret;
 }
 
 int
 ptsd_kqfilter(dev_t dev, struct knote *kn)
 {
-       struct tty *tp = NULL; 
+       struct tty *tp = NULL;
        struct ptmx_ioctl *pti = NULL;
-       int retval = 0;
+       int ret;
 
        /* make sure we're talking about the right device type */
        if (cdevsw[major(dev)].d_open != ptsopen) {
-               kn->kn_flags = EV_ERROR;
-               kn->kn_data = EINVAL;
+               knote_set_error(kn, ENODEV);
                return 0;
        }
 
        if ((pti = ptmx_get_ioctl(minor(dev), 0)) == NULL) {
-               kn->kn_flags = EV_ERROR;
-               kn->kn_data = ENXIO;
-               return 0;
+               knote_set_error(kn, ENXIO);
+               return 0;
        }
 
        tp = pti->pt_tty;
        tty_lock(tp);
 
-       kn->kn_hookid = dev;
-       kn->kn_hook = PTSD_KNOTE_VALID;
-       kn->kn_filtid = EVFILTID_PTSD;
-
-        switch (kn->kn_filter) {
-        case EVFILT_READ:
-                KNOTE_ATTACH(&tp->t_rsel.si_note, kn);
-                break;
-        case EVFILT_WRITE:
-                KNOTE_ATTACH(&tp->t_wsel.si_note, kn);
-                break;
-        default:
-               kn->kn_flags = EV_ERROR;
-               kn->kn_data = EINVAL;
-                break;
-        }
-
-        tty_unlock(tp);
+       assert(tp->t_state & TS_ISOPEN);
 
-       ptsd_kevent_mtx_lock(minor(dev));
+       kn->kn_filtid = EVFILTID_PTSD;
+       /* the tty will be freed when detaching the knote */
+       ttyhold(tp);
+       kn->kn_hook = tp;
+
+       switch (kn->kn_filter) {
+       case EVFILT_READ:
+               KNOTE_ATTACH(&tp->t_rsel.si_note, kn);
+               break;
+       case EVFILT_WRITE:
+               KNOTE_ATTACH(&tp->t_wsel.si_note, kn);
+               break;
+       default:
+               panic("ptsd kevent: unexpected filter: %d, kn = %p, tty = %p",
+                               kn->kn_filter, kn, tp);
+               break;
+       }
 
        /* capture current event state */
-       retval = ptsd_kqops_common(kn, dev, 0);
+       ret = ptsd_kqops_common(kn, tp);
 
-       ptsd_kevent_mtx_unlock(minor(dev));
+       tty_unlock(tp);
 
-        return (retval);
+       return ret;
 }
 
 /*
  * Support for revoke(2).
- *
- * Mark all the kn_hook fields so that future invocations of the
- * f_event op will just say "EOF" *without* looking at the
- * ptmx_ioctl structure (which may disappear or be recycled at
- * the end of ptsd_close).  Issue wakeups to post that EOF to
- * anyone listening.  And finally remove the knotes from the
- * tty's klists to keep ttyclose() happy, and set the hookid to
- * zero to make the final detach passively successful.
  */
 static void
-ptsd_revoke_knotes(int minor, struct tty *tp)
+ptsd_revoke_knotes(__unused int minor, struct tty *tp)
 {
-       struct klist *list;
-       struct knote *kn, *tkn;
-
-       /* (Hold and drop the right locks in the right order.) */
-
-       ptsd_kevent_mtx_lock(minor);
        tty_lock(tp);
 
-       list = &tp->t_rsel.si_note;
-       SLIST_FOREACH(kn, list, kn_selnext)
-               kn->kn_hook = PTSD_KNOTE_REVOKED;
-
-       list = &tp->t_wsel.si_note;
-       SLIST_FOREACH(kn, list, kn_selnext)
-               kn->kn_hook = PTSD_KNOTE_REVOKED;
-
-       tty_unlock(tp);
-       ptsd_kevent_mtx_unlock(minor);
-
-       tty_lock(tp);
        ttwakeup(tp);
-       ttwwakeup(tp);
-       tty_unlock(tp);
+       KNOTE(&tp->t_rsel.si_note, NOTE_REVOKE | 1 /* the lock is already held */);
 
-       ptsd_kevent_mtx_lock(minor);
-       tty_lock(tp);
-
-       list = &tp->t_rsel.si_note;
-       SLIST_FOREACH_SAFE(kn, list, kn_selnext, tkn) {
-               (void) KNOTE_DETACH(list, kn);
-               kn->kn_hookid = 0;
-       }
-
-       list = &tp->t_wsel.si_note;
-       SLIST_FOREACH_SAFE(kn, list, kn_selnext, tkn) {
-               (void) KNOTE_DETACH(list, kn);
-               kn->kn_hookid = 0;
-       }
+       ttwwakeup(tp);
+       KNOTE(&tp->t_wsel.si_note, NOTE_REVOKE | 1);
 
        tty_unlock(tp);
-       ptsd_kevent_mtx_unlock(minor);
 }