* 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,
* 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@
*/
/*
#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>
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
*/
/* 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.");
}
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,
_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);
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;
}
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]);
}
struct ptmx_ioctl *old_ptmx_ioctl = NULL;
DEVFS_LOCK();
-
+
if (minor < 0 || minor >= _state.pis_total) {
DEVFS_UNLOCK();
return (-1);
/*
* 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
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);
}