X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/5ba3f43ea354af8ad55bea84372a2bc834d8757c..a991bd8d3e7fe02dbca0644054bab73c5b75324a:/bsd/kern/tty_ptmx.c diff --git a/bsd/kern/tty_ptmx.c b/bsd/kern/tty_ptmx.c index cf89b93eb..d4efb5c12 100644 --- a/bsd/kern/tty_ptmx.c +++ b/bsd/kern/tty_ptmx.c @@ -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@ * @@ -64,7 +64,7 @@ * Pseudo-teletype Driver * (Actually two drivers, requiring two entries in 'cdevsw') */ -#include "pty.h" /* XXX */ +#include "pty.h" /* XXX */ #include #include @@ -81,7 +81,7 @@ #include #include #include -#include /* DEVFS_LOCK()/DEVFS_UNLOCK() */ +#include /* DEVFS_LOCK()/DEVFS_UNLOCK() */ #include #if CONFIG_MACF @@ -99,76 +99,97 @@ static int ptmx_free_ioctl(int minor, int open_flag); static int ptmx_get_name(int minor, char *buffer, size_t size); static void ptsd_revoke_knotes(int minor, struct tty *tp); -extern d_open_t ptsopen; -extern d_close_t ptsclose; -extern d_read_t ptsread; -extern d_write_t ptswrite; -extern d_ioctl_t ptyioctl; -extern d_stop_t ptsstop; -extern d_reset_t ptsreset; -extern d_select_t ptsselect; - -extern d_open_t ptcopen; -extern d_close_t ptcclose; -extern d_read_t ptcread; -extern d_write_t ptcwrite; -extern d_stop_t ptcstop; -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 +extern d_open_t ptsopen; +extern d_close_t ptsclose; +extern d_read_t ptsread; +extern d_write_t ptswrite; +extern d_ioctl_t ptyioctl; +extern d_stop_t ptsstop; +extern d_reset_t ptsreset; +extern d_select_t ptsselect; + +extern d_open_t ptcopen; +extern d_close_t ptcclose; +extern d_read_t ptcread; +extern d_write_t ptcwrite; +extern d_stop_t ptcstop; +extern d_reset_t ptcreset; +extern d_select_t ptcselect; + +static int ptmx_major; /* dynamically assigned major number */ +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 int ptsd_major; /* dynamically assigned major number */ +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 }; /* * ptmx == /dev/ptmx * ptsd == /dev/pts[0123456789]{3} */ -#define PTMX_TEMPLATE "ptmx" -#define PTSD_TEMPLATE "ttys%03d" +#define PTMX_TEMPLATE "ptmx" +#define PTSD_TEMPLATE "ttys%03d" /* * System-wide limit on the max number of cloned ptys */ -#define PTMX_MAX_DEFAULT 127 /* 128 entries */ -#define PTMX_MAX_HARD 999 /* 1000 entries, due to PTSD_TEMPLATE */ +#define PTMX_MAX_DEFAULT 511 /* 512 entries */ +#define PTMX_MAX_HARD 999 /* 1000 entries, due to PTSD_TEMPLATE */ -static int ptmx_max = PTMX_MAX_DEFAULT; /* default # of clones we allow */ +static int ptmx_max = PTMX_MAX_DEFAULT; /* default # of clones we allow */ /* Range enforcement for the sysctl */ static int sysctl_ptmx_max(__unused struct sysctl_oid *oidp, __unused void *arg1, - __unused int arg2, struct sysctl_req *req) + __unused int arg2, struct sysctl_req *req) { int new_value, changed; int error = sysctl_io_number(req, ptmx_max, sizeof(int), &new_value, &changed); if (changed) { - if (new_value > 0 && new_value <= PTMX_MAX_HARD) + if (new_value > 0 && new_value <= PTMX_MAX_HARD) { ptmx_max = new_value; - else + } else { error = EINVAL; + } } - return(error); + return error; } -SYSCTL_NODE(_kern, KERN_TTY, tty, CTLFLAG_RW|CTLFLAG_LOCKED, 0, "TTY"); +SYSCTL_NODE(_kern, KERN_TTY, tty, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "TTY"); SYSCTL_PROC(_kern_tty, OID_AUTO, ptmx_max, - CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, - &ptmx_max, 0, &sysctl_ptmx_max, "I", "ptmx_max"); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, + &ptmx_max, 0, &sysctl_ptmx_max, "I", "ptmx_max"); -static int ptmx_clone(dev_t dev, int minor); +static int ptmx_clone(dev_t dev, int minor); static struct tty_dev_t _ptmx_driver; @@ -183,7 +204,7 @@ ptmx_init( __unused int config_count) /* Get a major number for /dev/ptmx */ if ((ptmx_major = cdevsw_add(-15, &ptmx_cdev)) == -1) { printf("ptmx_init: failed to obtain /dev/ptmx major number\n"); - return (ENOENT); + return ENOENT; } if (cdevsw_setkqueueok(ptmx_major, &ptmx_cdev, CDEVSW_IS_PTC) == -1) { @@ -194,7 +215,7 @@ ptmx_init( __unused int config_count) if ((ptsd_major = cdevsw_add(-15, &ptsd_cdev)) == -1) { (void)cdevsw_remove(ptmx_major, &ptmx_cdev); printf("ptmx_init: failed to obtain /dev/ptmx major number\n"); - return (ENOENT); + return ENOENT; } if (cdevsw_setkqueueok(ptsd_major, &ptsd_cdev, CDEVSW_IS_PTS) == -1) { @@ -203,8 +224,8 @@ ptmx_init( __unused int config_count) /* Create the /dev/ptmx device {,0} */ (void)devfs_make_node_clone(makedev(ptmx_major, 0), - DEVFS_CHAR, UID_ROOT, GID_TTY, 0666, - ptmx_clone, PTMX_TEMPLATE); + DEVFS_CHAR, UID_ROOT, GID_TTY, 0666, + ptmx_clone, PTMX_TEMPLATE); _ptmx_driver.master = ptmx_major; _ptmx_driver.slave = ptsd_major; @@ -219,16 +240,16 @@ ptmx_init( __unused int config_count) _ptmx_driver.revoke = &ptsd_revoke_knotes; tty_dev_register(&_ptmx_driver); - return (0); + return 0; } static struct _ptmx_ioctl_state { - struct ptmx_ioctl **pis_ioctl_list; /* pointer vector */ - int pis_total; /* total slots */ - int pis_free; /* free slots */ + struct ptmx_ioctl **pis_ioctl_list; /* pointer vector */ + int pis_total; /* total slots */ + int pis_free; /* free slots */ } _state; -#define PTMX_GROW_VECTOR 16 /* Grow by this many slots at a time */ +#define PTMX_GROW_VECTOR 16 /* Grow by this many slots at a time */ /* * Given a minor number, return the corresponding structure for that minor @@ -248,10 +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,19 +285,21 @@ ptmx_get_ioctl(int minor, int open_flag) * snapping to the nearest PTMX_GROW_VECTOR... */ if ((_state.pis_total - _state.pis_free) >= ptmx_max) { - return (NULL); + DEVFS_UNLOCK(); + return NULL; } + DEVFS_UNLOCK(); - MALLOC(new_ptmx_ioctl, struct ptmx_ioctl *, sizeof(struct ptmx_ioctl), M_TTYS, M_WAITOK|M_ZERO); + MALLOC(new_ptmx_ioctl, struct ptmx_ioctl *, sizeof(struct ptmx_ioctl), M_TTYS, M_WAITOK | M_ZERO); if (new_ptmx_ioctl == NULL) { - return (NULL); + return NULL; } if ((new_ptmx_ioctl->pt_tty = ttymalloc()) == NULL) { FREE(new_ptmx_ioctl, M_TTYS); - return (NULL); + return NULL; } - + /* * Hold the DEVFS_LOCK() over this whole operation; devfs * itself does this over malloc/free as well, so this should @@ -282,18 +307,30 @@ 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; struct ptmx_ioctl **old_pis_ioctl_list = NULL; /* Yes. */ - MALLOC(new_pis_ioctl_list, struct ptmx_ioctl **, sizeof(struct ptmx_ioctl *) * (_state.pis_total + PTMX_GROW_VECTOR), M_TTYS, M_WAITOK|M_ZERO); + MALLOC(new_pis_ioctl_list, struct ptmx_ioctl **, sizeof(struct ptmx_ioctl *) * (_state.pis_total + PTMX_GROW_VECTOR), M_TTYS, M_WAITOK | M_ZERO); if (new_pis_ioctl_list == NULL) { ttyfree(new_ptmx_ioctl->pt_tty); DEVFS_UNLOCK(); FREE(new_ptmx_ioctl, M_TTYS); - return (NULL); + return NULL; } /* If this is not the first time, copy the old over */ @@ -302,8 +339,9 @@ ptmx_get_ioctl(int minor, int open_flag) _state.pis_ioctl_list = new_pis_ioctl_list; _state.pis_free += PTMX_GROW_VECTOR; _state.pis_total += PTMX_GROW_VECTOR; - if (old_pis_ioctl_list) + if (old_pis_ioctl_list) { FREE(old_pis_ioctl_list, M_TTYS); + } } /* is minor in range now? */ @@ -311,7 +349,7 @@ ptmx_get_ioctl(int minor, int open_flag) ttyfree(new_ptmx_ioctl->pt_tty); DEVFS_UNLOCK(); FREE(new_ptmx_ioctl, M_TTYS); - return (NULL); + return NULL; } if (_state.pis_ioctl_list[minor] != NULL) { @@ -321,7 +359,6 @@ ptmx_get_ioctl(int minor, int open_flag) /* Special error value so we know to redrive the open, we've been raced */ return (struct ptmx_ioctl*)-1; - } /* Vector is large enough; grab a new ptmx_ioctl */ @@ -337,19 +374,25 @@ ptmx_get_ioctl(int minor, int open_flag) /* Create the /dev/ttysXXX device {,XXX} */ _state.pis_ioctl_list[minor]->pt_devhandle = devfs_make_node( - makedev(ptsd_major, minor), - DEVFS_CHAR, UID_ROOT, GID_TTY, 0620, - PTSD_TEMPLATE, minor); + makedev(ptsd_major, minor), + DEVFS_CHAR, UID_ROOT, GID_TTY, 0620, + PTSD_TEMPLATE, minor); if (_state.pis_ioctl_list[minor]->pt_devhandle == NULL) { printf("devfs_make_node() call failed for ptmx_get_ioctl()!!!!\n"); } } - 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; } /* @@ -364,7 +407,7 @@ ptmx_free_ioctl(int minor, int open_flag) if (minor < 0 || minor >= _state.pis_total) { DEVFS_UNLOCK(); - return (-1); + return -1; } _state.pis_ioctl_list[minor]->pt_flags &= ~(open_flag); @@ -374,9 +417,9 @@ ptmx_free_ioctl(int minor, int open_flag) * a notification on the last close of a device, and we will have * cleared both the master and the slave open bits in the flags. */ - if (!(_state.pis_ioctl_list[minor]->pt_flags & (PF_OPEN_M|PF_OPEN_S))) { + if (!(_state.pis_ioctl_list[minor]->pt_flags & (PF_OPEN_M | PF_OPEN_S))) { /* Mark as free so it can be reallocated later */ - old_ptmx_ioctl = _state.pis_ioctl_list[ minor]; + old_ptmx_ioctl = _state.pis_ioctl_list[minor]; _state.pis_ioctl_list[minor] = NULL; _state.pis_free++; } @@ -390,13 +433,14 @@ ptmx_free_ioctl(int minor, int open_flag) * XXX Conditional to be removed when/if tty/pty reference * XXX counting and mutex implemented. */ - if (old_ptmx_ioctl->pt_devhandle != NULL) + if (old_ptmx_ioctl->pt_devhandle != NULL) { devfs_remove(old_ptmx_ioctl->pt_devhandle); + } ttyfree(old_ptmx_ioctl->pt_tty); FREE(old_ptmx_ioctl, M_TTYS); } - return (0); /* Success */ + return 0; /* Success */ } static int @@ -428,16 +472,18 @@ ptmx_clone(__unused dev_t dev, int action) if (action == DEVFS_CLONE_ALLOC) { /* First one */ - if (_state.pis_total == 0) - return (0); + if (_state.pis_total == 0) { + return 0; + } /* * Note: We can add hinting on free slots, if this linear search * ends up being a performance bottleneck... */ - for(i = 0; i < _state.pis_total; i++) { - if (_state.pis_ioctl_list[ i] == NULL) + for (i = 0; i < _state.pis_total; i++) { + if (_state.pis_ioctl_list[i] == NULL) { break; + } } /* @@ -452,9 +498,9 @@ ptmx_clone(__unused dev_t dev, int action) * XXX explicit return. */ - return (i); /* empty slot or next slot */ + return i; /* empty slot or next slot */ } - return(-1); + return -1; } @@ -464,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, @@ -488,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); @@ -504,57 +547,60 @@ 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; + (tp->t_state & TS_CONNECTED)) { + data = tp->t_outq.c_cn - tp->t_outq.c_cc; retval = 1; } break; default: panic("ptsd kevent: unexpected filter: %d, kn = %p, tty = %p", - kn->kn_filter, kn, tp); + kn->kn_filter, kn, tp); break; } 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; } @@ -563,46 +609,33 @@ 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 */ kn->kn_sfflags = kev->fflags; kn->kn_sdata = kev->data; - if ((kn->kn_status & KN_UDATA_SPECIFIC) == 0) { - kn->kn_udata = kev->udata; - } /* 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; @@ -667,12 +692,12 @@ ptsd_kqfilter(dev_t dev, struct knote *kn) break; default: panic("ptsd kevent: unexpected filter: %d, kn = %p, tty = %p", - kn->kn_filter, kn, tp); + kn->kn_filter, kn, tp); break; } /* capture current event state */ - ret = ptsd_kqops_common(kn, tp); + ret = ptsd_kqops_common(kn, NULL, tp); tty_unlock(tp); @@ -688,10 +713,234 @@ 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); +} + +/* + * kevent filter routines for the master side of a pty, a ptmx. + * + * Stuff the ptmx_ioctl structure into the hook for ptmx knotes. Use the + * embedded tty's lock for synchronization. + */ + +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_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, + /* attach is handled by ptmx_kqfilter -- the dev node must be passed in */ + .f_detach = ptmx_kqops_detach, + .f_event = ptmx_kqops_event, + .f_touch = ptmx_kqops_touch, + .f_process = ptmx_kqops_process, +}; + +static struct ptmx_ioctl * +ptmx_knote_ioctl(struct knote *kn) +{ + return (struct ptmx_ioctl *)kn->kn_hook; +} + +static struct tty * +ptmx_knote_tty(struct knote *kn) +{ + return ptmx_knote_ioctl(kn)->pt_tty; +} + +int +ptmx_kqfilter(dev_t dev, struct knote *kn) +{ + struct tty *tp = NULL; + struct ptmx_ioctl *pti = NULL; + int ret; + + /* make sure we're talking about the right device type */ + if (cdevsw[major(dev)].d_open != ptcopen) { + knote_set_error(kn, ENODEV); + return 0; + } + + if ((pti = ptmx_get_ioctl(minor(dev), 0)) == NULL) { + knote_set_error(kn, ENXIO); + return 0; + } + + tp = pti->pt_tty; + tty_lock(tp); + + kn->kn_filtid = EVFILTID_PTMX; + /* the tty will be freed when detaching the knote */ + ttyhold(tp); + kn->kn_hook = pti; + + /* + * Attach to the ptmx's selinfo structures. This is the major difference + * to the ptsd filtops, which use the selinfo structures in the tty + * structure. + */ + switch (kn->kn_filter) { + case EVFILT_READ: + KNOTE_ATTACH(&pti->pt_selr.si_note, kn); + break; + case EVFILT_WRITE: + KNOTE_ATTACH(&pti->pt_selw.si_note, kn); + break; + default: + panic("ptmx kevent: unexpected filter: %d, kn = %p, tty = %p", + kn->kn_filter, kn, tp); + break; + } + + /* capture current event state */ + ret = ptmx_kqops_common(kn, NULL, pti, tp); + + tty_unlock(tp); + + return ret; +} + +static void +ptmx_kqops_detach(struct knote *kn) +{ + struct ptmx_ioctl *pti = kn->kn_hook; + struct tty *tp = pti->pt_tty; + + 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; + } tty_unlock(tp); + ttyfree(tp); +} + +static int +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); + + 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)) { + 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; + } + break; + + case EVFILT_WRITE: + if (pti->pt_flags & PF_REMOTE) { + if (tp->t_canq.c_cc == 0) { + retval = TTYHOG - 1; + } + } else { + retval = (TTYHOG - 2) - (tp->t_rawq.c_cc + tp->t_canq.c_cc); + if (tp->t_canq.c_cc == 0 && (tp->t_lflag & ICANON)) { + retval = 1; + } + if (retval < 0) { + retval = 0; + } + } + break; + + default: + panic("ptmx kevent: unexpected filter: %d, kn = %p, tty = %p", + kn->kn_filter, kn, tp); + break; + } + + /* 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; +} + +static int +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; + + TTY_LOCK_OWNED(tp); + + if (hint & NOTE_REVOKE) { + kn->kn_flags |= EV_EOF | EV_ONESHOT; + ret = 1; + } else { + ret = ptmx_kqops_common(kn, NULL, pti, tp); + } + + return ret; +} + +static int +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); + int ret; + + tty_lock(tp); + + /* accept new kevent state */ + kn->kn_sfflags = kev->fflags; + kn->kn_sdata = kev->data; + + /* recapture fired state of knote */ + ret = ptmx_kqops_common(kn, NULL, pti, tp); + + tty_unlock(tp); + + return ret; +} + +static int +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, kev, pti, tp); + tty_unlock(tp); + + return ret; }