summaryrefslogtreecommitdiffstats
path: root/sys/kern/tty_pts.c
diff options
context:
space:
mode:
authored <ed@FreeBSD.org>2008-08-20 08:31:58 +0000
committered <ed@FreeBSD.org>2008-08-20 08:31:58 +0000
commitcc3116a9380fe32a751b584f3d8083698ccfba15 (patch)
treebd0c08a66997254385160ce71ea32029b99f99f9 /sys/kern/tty_pts.c
parentb49301b5cd9ff43a7af0bd9054d9d1a328c0d212 (diff)
downloadFreeBSD-src-cc3116a9380fe32a751b584f3d8083698ccfba15.zip
FreeBSD-src-cc3116a9380fe32a751b584f3d8083698ccfba15.tar.gz
Integrate the new MPSAFE TTY layer to the FreeBSD operating system.
The last half year I've been working on a replacement TTY layer for the FreeBSD kernel. The new TTY layer was designed to improve the following: - Improved driver model: The old TTY layer has a driver model that is not abstract enough to make it friendly to use. A good example is the output path, where the device drivers directly access the output buffers. This means that an in-kernel PPP implementation must always convert network buffers into TTY buffers. If a PPP implementation would be built on top of the new TTY layer (still needs a hooks layer, though), it would allow the PPP implementation to directly hand the data to the TTY driver. - Improved hotplugging: With the old TTY layer, it isn't entirely safe to destroy TTY's from the system. This implementation has a two-step destructing design, where the driver first abandons the TTY. After all threads have left the TTY, the TTY layer calls a routine in the driver, which can be used to free resources (unit numbers, etc). The pts(4) driver also implements this feature, which means posix_openpt() will now return PTY's that are created on the fly. - Improved performance: One of the major improvements is the per-TTY mutex, which is expected to improve scalability when compared to the old Giant locking. Another change is the unbuffered copying to userspace, which is both used on TTY device nodes and PTY masters. Upgrading should be quite straightforward. Unlike previous versions, existing kernel configuration files do not need to be changed, except when they reference device drivers that are listed in UPDATING. Obtained from: //depot/projects/mpsafetty/... Approved by: philip (ex-mentor) Discussed: on the lists, at BSDCan, at the DevSummit Sponsored by: Snow B.V., the Netherlands dcons(4) fixed by: kan
Diffstat (limited to 'sys/kern/tty_pts.c')
-rw-r--r--sys/kern/tty_pts.c1303
1 files changed, 518 insertions, 785 deletions
diff --git a/sys/kern/tty_pts.c b/sys/kern/tty_pts.c
index ff8838e..8a51448 100644
--- a/sys/kern/tty_pts.c
+++ b/sys/kern/tty_pts.c
@@ -1,16 +1,9 @@
-/*
- * Copyright (c) 2003 Networks Associates Technology, Inc.
- * Copyright (c) 2006 Robert N. M. Watson
- * Copyright (c) 2006 Olivier Houchard
+/*-
+ * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
* All rights reserved.
*
- * This software was developed for the FreeBSD Project in part by Network
- * Associates Laboratories, the Security Research Division of Network
- * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"),
- * as part of the DARPA CHATS research program.
- *
- * Copyright (c) 1982, 1986, 1989, 1993
- * The Regents of the University of California. All rights reserved.
+ * Portions of this software were developed under sponsorship from Snow
+ * B.V., the Netherlands.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -20,14 +13,11 @@
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
- * 4. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
*
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
@@ -35,889 +25,632 @@
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
- *
- * @(#)tty_pty.c 8.4 (Berkeley) 2/20/95
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
-/*
- * Pseudo-teletype Driver
- * (Actually two drivers, requiring two entries in 'cdevsw')
- */
-#include "opt_compat.h"
#include "opt_tty.h"
+
+/* Add compatibility bits for FreeBSD. */
+#define PTS_COMPAT
+#ifdef DEV_PTY
+/* Add /dev/ptyXX compat bits. */
+#define PTS_EXTERNAL
+#endif /* DEV_PTY */
+/* Add bits to make Linux binaries work. */
+#define PTS_LINUX
+
#include <sys/param.h>
-#include <sys/systm.h>
#include <sys/lock.h>
-#include <sys/mutex.h>
-#include <sys/sx.h>
-#if defined(COMPAT_43TTY)
-#include <sys/ioctl_compat.h>
-#endif
-#include <sys/priv.h>
-#include <sys/proc.h>
-#include <sys/queue.h>
-#include <sys/tty.h>
+#include <sys/condvar.h>
+#include <sys/conf.h>
#include <sys/fcntl.h>
-#include <sys/poll.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#include <sys/filio.h>
#include <sys/kernel.h>
-#include <sys/vnode.h>
-#include <sys/signalvar.h>
#include <sys/malloc.h>
-#include <sys/conf.h>
-#include <sys/sysctl.h>
-#include <sys/filio.h>
-
-static MALLOC_DEFINE(M_PTY, "ptys", "pty data structures");
-
-static void ptsstart(struct tty *tp);
-static void ptsstop(struct tty *tp, int rw);
-static void ptcwakeup(struct tty *tp, int flag);
-
-static d_open_t ptsopen;
-static d_close_t ptsclose;
-static d_read_t ptsread;
-static d_write_t ptswrite;
-static d_ioctl_t ptsioctl;
-static d_ioctl_t ptcioctl;
-static d_open_t ptcopen;
-static d_close_t ptcclose;
-static d_read_t ptcread;
-static d_write_t ptcwrite;
-static d_poll_t ptcpoll;
-
-static struct cdevsw pts_cdevsw = {
- .d_version = D_VERSION,
- .d_open = ptsopen,
- .d_close = ptsclose,
- .d_read = ptsread,
- .d_write = ptswrite,
- .d_ioctl = ptsioctl,
- .d_poll = ttypoll,
- .d_name = "pts",
- .d_flags = D_TTY | D_NEEDGIANT,
- .d_kqfilter = ttykqfilter,
-};
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/resourcevar.h>
+#include <sys/serial.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/syscallsubr.h>
+#include <sys/sysent.h>
+#include <sys/sysproto.h>
+#include <sys/systm.h>
+#include <sys/tty.h>
+#include <sys/ttycom.h>
-static struct cdevsw ptc_cdevsw = {
- .d_version = D_VERSION,
- .d_open = ptcopen,
- .d_close = ptcclose,
- .d_read = ptcread,
- .d_write = ptcwrite,
- .d_ioctl = ptcioctl,
- .d_poll = ptcpoll,
- .d_name = "ptc",
- .d_flags = D_TTY | D_NEEDGIANT,
- .d_kqfilter = ttykqfilter,
-};
+#include <machine/stdarg.h>
-#define BUFSIZ 100 /* Chunk size iomoved to/from user */
+static struct unrhdr *pts_pool;
+#define MAXPTSDEVS 999
-#define TSA_PTC_READ(tp) ((void *)&(tp)->t_outq.c_cf)
-#define TSA_PTC_WRITE(tp) ((void *)&(tp)->t_rawq.c_cl)
-#define TSA_PTS_READ(tp) ((void *)&(tp)->t_canq)
+static MALLOC_DEFINE(M_PTS, "pts", "pseudo tty device");
-#define NUM_TO_MINOR(c) ((c & 0xff) | ((c & ~0xff) << 16))
-/*-
- * Once a tty is allocated, it cannot (currently) be freed. As such,
- * we keep a global list of ptys that have been used so we can recycle
- * them. Another list is provided for released pts, which are
- * not currently allocated, permitting reuse. pt_flags holds state
- * associated with a particular session, so isn't overloaded for this.
- * When a pty descriptor is unused, its number is set to -1 giving
- * more consistent and traditional allocation orders to pty numbers.
+/*
+ * Per-PTS structure.
*
- * Locking: (p) indicates that the field is locked by the global pt_mtx.
- * (c) indicates the value is constant after allocation. Other fields
- * await tty locking generally, and are protected by Giant.
+ * List of locks
+ * (t) locked by tty_lock()
+ * (c) const until freeing
*/
-struct pt_desc {
- int pt_num; /* (c) pty number */
- LIST_ENTRY(pt_desc) pt_list; /* (p) global pty list */
-
- int pt_flags;
- struct selinfo pt_selr, pt_selw;
- u_char pt_send;
- u_char pt_ucntl;
- struct tty *pt_tty;
- struct cdev *pt_devs, *pt_devc;
- int pt_pts_open, pt_ptc_open;
- struct prison *pt_prison;
-};
+struct pts_softc {
+ int pts_unit; /* (c) Device unit number. */
+ unsigned int pts_flags; /* (t) Device flags. */
+#define PTS_PKT 0x1 /* Packet mode. */
-static struct mtx pt_mtx;
-static LIST_HEAD(,pt_desc) pt_list;
-static LIST_HEAD(,pt_desc) pt_free_list;
+ struct cv pts_inwait; /* (t) Blocking write() on master. */
+ struct selinfo pts_inpoll; /* (t) Select queue for write(). */
+ struct cv pts_outwait; /* (t) Blocking read() on master. */
+ struct selinfo pts_outpoll; /* (t) Select queue for read(). */
-#define PF_PKT 0x008 /* packet mode */
-#define PF_STOPPED 0x010 /* user told stopped */
-#define PF_NOSTOP 0x040
-#define PF_UCNTL 0x080 /* user control mode */
+#ifdef PTS_EXTERNAL
+ struct cdev *pts_cdev; /* (c) Master device node. */
+#endif /* PTS_EXTERNAL */
-static unsigned int next_avail_nb;
-
-static int use_pts = 0;
+ struct uidinfo *pts_uidinfo; /* (c) Resource limit. */
+};
-static unsigned int max_pts = 1000;
+/*
+ * Controller-side file operations.
+ */
-static unsigned int nb_allocated;
+static int
+ptsdev_read(struct file *fp, struct uio *uio, struct ucred *active_cred,
+ int flags, struct thread *td)
+{
+ struct tty *tp = fp->f_data;
+ struct pts_softc *psc = tty_softc(tp);
+ int error, oresid;
-TUNABLE_INT("kern.pts.enable", &use_pts);
+ if (uio->uio_resid == 0)
+ return (0);
+
+ /*
+ * Implement packet mode. When packet mode is turned on, the
+ * first byte contains a bitmask of events that occured (start,
+ * stop, flush, window size, etc).
+ */
-SYSCTL_NODE(_kern, OID_AUTO, pts, CTLFLAG_RD, 0, "pts");
+ if (psc->pts_flags & PTS_PKT) {
+ /* XXX: return proper bits. */
+ error = ureadc(0, uio);
+ if (error != 0)
+ return (error);
+ if (uio->uio_resid == 0)
+ return (0);
+ }
-SYSCTL_INT(_kern_pts, OID_AUTO, enable, CTLFLAG_RW, &use_pts, 0,
- "enable pts");
+ oresid = uio->uio_resid;
-SYSCTL_INT(_kern_pts, OID_AUTO, max, CTLFLAG_RW, &max_pts, 0, "max pts");
+ tty_lock(tp);
+ for (;;) {
+ error = ttydisc_getc_uio(tp, uio);
+ /* We've got data (or an error). */
+ if (error != 0 || uio->uio_resid != oresid)
+ break;
-/*
- * If there's a free pty descriptor in the pty descriptor list, retrieve it.
- * Otherwise, allocate a new one, initialize it, and hook it up. If there's
- * not a tty number, reject.
- */
-static struct pt_desc *
-pty_new(void)
-{
- struct pt_desc *pt;
- int nb;
+ /* Maybe the device isn't used anyway. */
+ if (tty_opened(tp) == 0) {
+ error = ENXIO;
+ break;
+ }
- mtx_lock(&pt_mtx);
- if (nb_allocated >= max_pts || nb_allocated == 0xffffff) {
- mtx_unlock(&pt_mtx);
- return (NULL);
- }
- nb_allocated++;
- pt = LIST_FIRST(&pt_free_list);
- if (pt) {
- LIST_REMOVE(pt, pt_list);
- } else {
- nb = next_avail_nb++;
- mtx_unlock(&pt_mtx);
- pt = malloc(sizeof(*pt), M_PTY, M_WAITOK | M_ZERO);
- pt->pt_tty = ttyalloc();
- mtx_lock(&pt_mtx);
- pt->pt_num = nb;
+ /* Wait for more data. */
+ if (fp->f_flag & O_NONBLOCK) {
+ error = EWOULDBLOCK;
+ break;
+ }
+ error = cv_wait_sig(&psc->pts_outwait, tp->t_mtx);
+ if (error != 0)
+ break;
}
- LIST_INSERT_HEAD(&pt_list, pt, pt_list);
- mtx_unlock(&pt_mtx);
- return (pt);
-}
-
-/*
- * Release a pty descriptor back to the pool for reuse. The pty number
- * remains allocated.
- */
-static void
-pty_release(void *v)
-{
- struct pt_desc *pt = (struct pt_desc *)v;
-
- mtx_lock(&pt_mtx);
- KASSERT(pt->pt_ptc_open == 0 && pt->pt_pts_open == 0,
- ("pty_release: pts/%d freed while open\n", pt->pt_num));
- KASSERT(pt->pt_devs == NULL && pt->pt_devc == NULL,
- ("pty_release: pts/%d freed whith non-null struct cdev\n", pt->pt_num));
- nb_allocated--;
- LIST_REMOVE(pt, pt_list);
- LIST_INSERT_HEAD(&pt_free_list, pt, pt_list);
- mtx_unlock(&pt_mtx);
-}
+ tty_unlock(tp);
-/*
- * Given a pty descriptor, if both endpoints are closed, release all
- * resources and destroy the device nodes to flush file system level
- * state for the tty (owner, avoid races, etc).
- */
-static void
-pty_maybecleanup(struct pt_desc *pt)
-{
- struct cdev *pt_devs, *pt_devc;
-
- if (pt->pt_ptc_open || pt->pt_pts_open)
- return;
-
- if (pt->pt_tty->t_refcnt > 1)
- return;
-
- if (bootverbose)
- printf("destroying pty %d\n", pt->pt_num);
-
- pt_devs = pt->pt_devs;
- pt_devc = pt->pt_devc;
- pt->pt_devs = pt->pt_devc = NULL;
- pt->pt_tty->t_dev = NULL;
- pt_devc->si_drv1 = NULL;
- ttyrel(pt->pt_tty);
- pt->pt_tty = NULL;
- destroy_dev_sched(pt_devs);
- destroy_dev_sched_cb(pt_devc, pty_release, pt);
+ return (error);
}
-/*ARGSUSED*/
static int
-ptsopen(struct cdev *dev, int flag, int devtype, struct thread *td)
+ptsdev_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
+ int flags, struct thread *td)
{
- struct tty *tp;
- int error;
- struct pt_desc *pt;
-
- pt = dev->si_drv1;
- tp = dev->si_tty;
- if ((tp->t_state & TS_ISOPEN) == 0)
- ttyinitmode(tp, 1, 0);
- else if (tp->t_state & TS_XCLUDE && priv_check(td,
- PRIV_TTY_EXCLUSIVE)) {
- return (EBUSY);
- } else if (pt->pt_prison != td->td_ucred->cr_prison &&
- priv_check(td, PRIV_TTY_PRISON)) {
- return (EBUSY);
- }
- if (tp->t_oproc) /* Ctrlr still around. */
- ttyld_modem(tp, 1);
- while ((tp->t_state & TS_CARR_ON) == 0) {
- if (flag & FNONBLOCK)
- break;
- error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH,
- "ptsopn", 0);
- if (error)
- return (error);
- }
- error = ttyld_open(tp, dev);
- if (error == 0) {
- ptcwakeup(tp, FREAD|FWRITE);
- pt->pt_pts_open = 1;
+ struct tty *tp = fp->f_data;
+ struct pts_softc *psc = tty_softc(tp);
+ char ib[256], *ibstart;
+ size_t iblen, rintlen;
+ int error = 0;
+
+ tty_lock(tp);
+
+ while (uio->uio_resid > 0) {
+ /* Temporarily unlock to buffer new characters. */
+ tty_unlock(tp);
+ ibstart = ib;
+ iblen = MIN(uio->uio_resid, sizeof ib);
+ error = uiomove(ib, iblen, uio);
+ tty_lock(tp);
+ if (error != 0)
+ goto done;
+
+ /*
+ * When possible, avoid the slow path. rint_bypass()
+ * copies all input to the input queue at once.
+ */
+ while (iblen > 0) {
+ if (ttydisc_can_bypass(tp)) {
+ /* Store data at once. */
+ rintlen = ttydisc_rint_bypass(tp,
+ ibstart, iblen);
+ ibstart += rintlen;
+ iblen -= rintlen;
+
+ if (iblen == 0) {
+ /* All data written. */
+ continue;
+ }
+ } else {
+ error = ttydisc_rint(tp, *ibstart, 0);
+ if (error == 0) {
+ /* Character stored successfully. */
+ ibstart++;
+ iblen--;
+ continue;
+ }
+ }
+
+ /* Maybe the device isn't used anyway. */
+ if (tty_opened(tp) == 0) {
+ error = ENXIO;
+ goto done;
+ }
+
+ /* Wait for more data. */
+ if (fp->f_flag & O_NONBLOCK) {
+ error = EWOULDBLOCK;
+ goto done;
+ }
+
+ /* Wake up users on the slave side. */
+ ttydisc_rint_done(tp);
+ error = cv_wait_sig(&psc->pts_inwait, tp->t_mtx);
+ if (error != 0)
+ goto done;
+ }
}
+
+done: ttydisc_rint_done(tp);
+ tty_unlock(tp);
return (error);
}
static int
-ptsclose(struct cdev *dev, int flag, int mode, struct thread *td)
+ptsdev_ioctl(struct file *fp, u_long cmd, void *data,
+ struct ucred *active_cred, struct thread *td)
{
- struct pt_desc *pt = dev->si_drv1;
- struct tty *tp;
- int err;
-
- tp = dev->si_tty;
- err = ttyld_close(tp, flag);
- ptsstop(tp, FREAD|FWRITE);
- (void) tty_close(tp);
- pt->pt_pts_open = 0;
- pty_maybecleanup(pt);
- return (err);
-}
+ struct tty *tp = fp->f_data;
+ struct pts_softc *psc = tty_softc(tp);
+ int error = 0, sig;
-static int
-ptsread(struct cdev *dev, struct uio *uio, int flag)
-{
- struct tty *tp = dev->si_tty;
- int error = 0;
+ switch (cmd) {
+ case FIONBIO:
+ /* This device supports non-blocking operation. */
+ return (0);
+ case FIODGNAME: {
+ struct fiodgname_arg *fgn;
+ const char *p;
+ int i;
+
+ /* Reverse device name lookups, for ptsname() and ttyname(). */
+ fgn = data;
+#ifdef PTS_EXTERNAL
+ if (psc->pts_cdev != NULL)
+ p = devtoname(psc->pts_cdev);
+ else
+#endif /* PTS_EXTERNAL */
+ p = tty_devname(tp);
+ i = strlen(p) + 1;
+ if (i > fgn->len)
+ return (EINVAL);
+ return copyout(p, fgn->buf, i);
+ }
+
+ /*
+ * We need to implement TIOCGPGRP and TIOCGSID here again. When
+ * called on the pseudo-terminal master, it should not check if
+ * the terminal is the foreground terminal of the calling
+ * process.
+ *
+ * TIOCGETA is also implemented here. Various Linux PTY routines
+ * often call isatty(), which is implemented by tcgetattr().
+ */
+#ifdef PTS_LINUX
+ case TIOCGETA:
+ /* Obtain terminal flags through tcgetattr(). */
+ tty_lock(tp);
+ bcopy(&tp->t_termios, data, sizeof(struct termios));
+ tty_unlock(tp);
+ return (0);
+#endif /* PTS_LINUX */
+ case TIOCSETAF:
+ case TIOCSETAW:
+ /*
+ * We must make sure we turn tcsetattr() calls of TCSAFLUSH and
+ * TCSADRAIN into something different. If an application would
+ * call TCSAFLUSH or TCSADRAIN on the master descriptor, it may
+ * deadlock waiting for all data to be read.
+ */
+ cmd = TIOCSETA;
+ break;
+#if defined(PTS_COMPAT) || defined(PTS_LINUX)
+ case TIOCGPTN:
+ /*
+ * Get the device unit number.
+ */
+ if (psc->pts_unit < 0)
+ return (ENOTTY);
+ *(unsigned int *)data = psc->pts_unit;
+ return (0);
+#endif /* PTS_COMPAT || PTS_LINUX */
+ case TIOCGPGRP:
+ /* Get the foreground process group ID. */
+ tty_lock(tp);
+ if (tp->t_pgrp != NULL)
+ *(int *)data = tp->t_pgrp->pg_id;
+ else
+ *(int *)data = NO_PID;
+ tty_unlock(tp);
+ return (0);
+ case TIOCGSID:
+ /* Get the session leader process ID. */
+ tty_lock(tp);
+ if (tp->t_session == NULL)
+ error = ENOTTY;
+ else
+ *(int *)data = tp->t_session->s_sid;
+ tty_unlock(tp);
+ return (error);
+ case TIOCPTMASTER:
+ /* Yes, we are a pseudo-terminal master. */
+ return (0);
+ case TIOCSIG:
+ /* Signal the foreground process group. */
+ sig = *(int *)data;
+ if (sig < 1 || sig >= NSIG)
+ return (EINVAL);
+
+ tty_lock(tp);
+ tty_signal_pgrp(tp, sig);
+ tty_unlock(tp);
+ return (0);
+ case TIOCPKT:
+ /* Enable/disable packet mode. */
+ tty_lock(tp);
+ if (*(int *)data)
+ psc->pts_flags |= PTS_PKT;
+ else
+ psc->pts_flags &= ~PTS_PKT;
+ tty_unlock(tp);
+ return (0);
+ }
+
+ /* Just redirect this ioctl to the slave device. */
+ tty_lock(tp);
+ error = tty_ioctl(tp, cmd, data, td);
+ tty_unlock(tp);
- if (tp->t_oproc)
- error = ttyld_read(tp, uio, flag);
- ptcwakeup(tp, FWRITE);
return (error);
}
-/*
- * Write to pseudo-tty.
- * Wakeups of controlling tty will happen
- * indirectly, when tty driver calls ptsstart.
- */
static int
-ptswrite(struct cdev *dev, struct uio *uio, int flag)
+ptsdev_poll(struct file *fp, int events, struct ucred *active_cred,
+ struct thread *td)
{
- struct tty *tp;
+ struct tty *tp = fp->f_data;
+ struct pts_softc *psc = tty_softc(tp);
+ int revents = 0;
- tp = dev->si_tty;
- if (tp->t_oproc == 0)
- return (EIO);
- return (ttyld_write(tp, uio, flag));
-}
+ tty_lock(tp);
-/*
- * Start output on pseudo-tty.
- * Wake up process selecting or sleeping for input from controlling tty.
- */
-static void
-ptsstart(struct tty *tp)
-{
- struct pt_desc *pt = tp->t_dev->si_drv1;
+ if (tty_opened(tp) == 0) {
+ /* Slave device is not opened. */
+ tty_unlock(tp);
+ return (events &
+ (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
+ }
- if (tp->t_state & TS_TTSTOP)
- return;
- if (pt->pt_flags & PF_STOPPED) {
- pt->pt_flags &= ~PF_STOPPED;
- pt->pt_send = TIOCPKT_START;
+ if (events & (POLLIN|POLLRDNORM)) {
+ /* See if we can getc something. */
+ if (ttydisc_getc_poll(tp))
+ revents |= events & (POLLIN|POLLRDNORM);
+ }
+ if (events & (POLLOUT|POLLWRNORM)) {
+ /* See if we can rint something. */
+ if (ttydisc_rint_poll(tp))
+ revents |= events & (POLLOUT|POLLWRNORM);
}
- ptcwakeup(tp, FREAD);
-}
-static void
-ptcwakeup(struct tty *tp, int flag)
-{
- struct pt_desc *pt = tp->t_dev->si_drv1;
+ /*
+ * No need to check for POLLHUP here. This device cannot be used
+ * as a callout device, which means we always have a carrier,
+ * because the master is.
+ */
- if (flag & FREAD) {
- selwakeup(&pt->pt_selr);
- wakeup(TSA_PTC_READ(tp));
- }
- if (flag & FWRITE) {
- selwakeup(&pt->pt_selw);
- wakeup(TSA_PTC_WRITE(tp));
+ if (revents == 0) {
+ /*
+ * This code might look misleading, but the naming of
+ * poll events on this side is the opposite of the slave
+ * device.
+ */
+ if (events & (POLLIN|POLLRDNORM))
+ selrecord(td, &psc->pts_outpoll);
+ if (events & (POLLOUT|POLLWRNORM))
+ selrecord(td, &psc->pts_inpoll);
}
+
+ tty_unlock(tp);
+
+ return (revents);
}
-/*
- * ptcopen implementes exclusive access to the master/control device
- * as well as creating the slave device based on the credential of the
- * process opening the master. By creating the slave here, we avoid
- * a race to access the master in terms of having a process with access
- * to an incorrectly owned slave, but it does create the possibility
- * that a racing process can cause a ptmx user to get EIO if it gets
- * there first. Consumers of ptmx must look for EIO and retry if it
- * happens. VFS locking may actually prevent this from occurring due
- * to the lookup into devfs holding the vnode lock through open, but
- * it's better to be careful.
- */
static int
-ptcopen(struct cdev *dev, int flag, int devtype, struct thread *td)
+ptsdev_stat(struct file *fp, struct stat *sb, struct ucred *active_cred,
+ struct thread *td)
{
- struct pt_desc *pt;
- struct tty *tp;
- struct cdev *devs;
+ struct tty *tp = fp->f_data;
+#ifdef PTS_EXTERNAL
+ struct pts_softc *psc = tty_softc(tp);
+#endif /* PTS_EXTERNAL */
- pt = dev->si_drv1;
- if (pt == NULL)
- return (EIO);
/*
- * In case we have destroyed the struct tty at the last connect time,
- * we need to recreate it.
+ * According to POSIX, we must implement an fstat(). This also
+ * makes this implementation compatible with Linux binaries,
+ * because Linux calls fstat() on the pseudo-terminal master to
+ * obtain st_rdev.
+ *
+ * XXX: POSIX also mentions we must fill in st_dev, st_atime,
+ * st_ctime and st_mtime, but how?
*/
- if (pt->pt_tty == NULL) {
- tp = ttyalloc();
- mtx_lock(&pt_mtx);
- if (pt->pt_tty == NULL) {
- pt->pt_tty = tp;
- dev->si_tty = pt->pt_tty;
- mtx_unlock(&pt_mtx);
- } else {
- mtx_unlock(&pt_mtx);
- ttyrel(tp);
- }
- }
- tp = dev->si_tty;
- if (tp->t_oproc)
- return (EIO);
- /*
- * XXX: Might want to make the ownership/permissions here more
- * configurable.
- */
- if (pt->pt_devs)
- devs = pt->pt_devs;
+ bzero(sb, sizeof *sb);
+#ifdef PTS_EXTERNAL
+ if (psc->pts_cdev != NULL)
+ sb->st_ino = sb->st_rdev = dev2udev(psc->pts_cdev);
else
- pt->pt_devs = devs = make_dev_cred(&pts_cdevsw,
- NUM_TO_MINOR(pt->pt_num),
- td->td_ucred, UID_ROOT, GID_WHEEL, 0666, "pts/%d",
- pt->pt_num);
- devs->si_drv1 = pt;
- devs->si_tty = pt->pt_tty;
- pt->pt_tty->t_dev = devs;
-
- tp->t_timeout = -1;
- tp->t_oproc = ptsstart;
- tp->t_stop = ptsstop;
- ttyld_modem(tp, 1);
- tp->t_lflag &= ~EXTPROC;
- pt = dev->si_drv1;
- pt->pt_prison = td->td_ucred->cr_prison;
- pt->pt_flags = 0;
- pt->pt_send = 0;
- pt->pt_ucntl = 0;
- pt->pt_ptc_open = 1;
+#endif /* PTS_EXTERNAL */
+ sb->st_ino = sb->st_rdev = tty_udev(tp);
+ sb->st_mode = S_IFCHR;
+ sb->st_uid = tp->t_dev->si_cred->cr_ruid;
+ sb->st_gid = GID_TTY;
+
return (0);
}
static int
-ptcclose(struct cdev *dev, int flags, int fmt, struct thread *td)
+ptsdev_close(struct file *fp, struct thread *td)
{
- struct pt_desc *pt = dev->si_drv1;
- struct tty *tp;
+ struct tty *tp = fp->f_data;
- tp = dev->si_tty;
- ttyld_modem(tp, 0);
+ /* Deallocate TTY device. */
+ tty_lock(tp);
+ tty_rel_gone(tp);
- /*
- * XXX MDMBUF makes no sense for ptys but would inhibit the above
- * l_modem(). CLOCAL makes sense but isn't supported. Special
- * l_modem()s that ignore carrier drop make no sense for ptys but
- * may be in use because other parts of the line discipline make
- * sense for ptys. Recover by doing everything that a normal
- * ttymodem() would have done except for sending a SIGHUP.
- */
- if (tp->t_state & TS_ISOPEN) {
- tp->t_state &= ~(TS_CARR_ON | TS_CONNECTED);
- tp->t_state |= TS_ZOMBIE;
- ttyflush(tp, FREAD | FWRITE);
- }
-
- tp->t_oproc = 0; /* mark closed */
- pt->pt_ptc_open = 0;
- pty_maybecleanup(pt);
return (0);
}
-static int
-ptcread(struct cdev *dev, struct uio *uio, int flag)
-{
- struct tty *tp = dev->si_tty;
- struct pt_desc *pt = dev->si_drv1;
- char buf[BUFSIZ];
- int error = 0, cc;
+static struct fileops ptsdev_ops = {
+ .fo_read = ptsdev_read,
+ .fo_write = ptsdev_write,
+ .fo_ioctl = ptsdev_ioctl,
+ .fo_poll = ptsdev_poll,
+ .fo_stat = ptsdev_stat,
+ .fo_close = ptsdev_close,
+ .fo_flags = DFLAG_PASSABLE,
+};
- /*
- * We want to block until the slave
- * is open, and there's something to read;
- * but if we lost the slave or we're NBIO,
- * then return the appropriate error instead.
- */
- for (;;) {
- if (tp->t_state&TS_ISOPEN) {
- if (pt->pt_flags&PF_PKT && pt->pt_send) {
- error = ureadc((int)pt->pt_send, uio);
- if (error)
- return (error);
- if (pt->pt_send & TIOCPKT_IOCTL) {
- cc = min(uio->uio_resid,
- sizeof(tp->t_termios));
- uiomove(&tp->t_termios, cc, uio);
- }
- pt->pt_send = 0;
- return (0);
- }
- if (pt->pt_flags&PF_UCNTL && pt->pt_ucntl) {
- error = ureadc((int)pt->pt_ucntl, uio);
- if (error)
- return (error);
- pt->pt_ucntl = 0;
- return (0);
- }
- if (tp->t_outq.c_cc && (tp->t_state&TS_TTSTOP) == 0)
- break;
- }
- if ((tp->t_state & TS_CONNECTED) == 0)
- return (0); /* EOF */
- if (flag & O_NONBLOCK)
- return (EWOULDBLOCK);
- error = tsleep(TSA_PTC_READ(tp), TTIPRI | PCATCH, "ptcin", 0);
- if (error)
- return (error);
- }
- if (pt->pt_flags & (PF_PKT|PF_UCNTL))
- error = ureadc(0, uio);
- while (uio->uio_resid > 0 && error == 0) {
- cc = q_to_b(&tp->t_outq, buf, min(uio->uio_resid, BUFSIZ));
- if (cc <= 0)
- break;
- error = uiomove(buf, cc, uio);
- }
- ttwwakeup(tp);
- return (error);
-}
+/*
+ * Driver-side hooks.
+ */
static void
-ptsstop(struct tty *tp, int flush)
+ptsdrv_outwakeup(struct tty *tp)
{
- struct pt_desc *pt = tp->t_dev->si_drv1;
- int flag;
-
- /* note: FLUSHREAD and FLUSHWRITE already ok */
- if (flush == 0) {
- flush = TIOCPKT_STOP;
- pt->pt_flags |= PF_STOPPED;
- } else
- pt->pt_flags &= ~PF_STOPPED;
- pt->pt_send |= flush;
- /* change of perspective */
- flag = 0;
- if (flush & FREAD)
- flag |= FWRITE;
- if (flush & FWRITE)
- flag |= FREAD;
- ptcwakeup(tp, flag);
+ struct pts_softc *psc = tty_softc(tp);
+
+ cv_broadcast(&psc->pts_outwait);
+ selwakeup(&psc->pts_outpoll);
}
-static int
-ptcpoll(struct cdev *dev, int events, struct thread *td)
+static void
+ptsdrv_inwakeup(struct tty *tp)
{
- struct tty *tp = dev->si_tty;
- struct pt_desc *pt = dev->si_drv1;
- int revents = 0;
- int s;
+ struct pts_softc *psc = tty_softc(tp);
- if ((tp->t_state & TS_CONNECTED) == 0)
- return (events &
- (POLLHUP | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM));
+ cv_broadcast(&psc->pts_inwait);
+ selwakeup(&psc->pts_inpoll);
+}
- /*
- * Need to block timeouts (ttrstart).
- */
- s = spltty();
+static void
+ptsdrv_close(struct tty *tp)
+{
- if (events & (POLLIN | POLLRDNORM))
- if ((tp->t_state & TS_ISOPEN) &&
- ((tp->t_outq.c_cc && (tp->t_state & TS_TTSTOP) == 0) ||
- ((pt->pt_flags & PF_PKT) && pt->pt_send) ||
- ((pt->pt_flags & PF_UCNTL) && pt->pt_ucntl)))
- revents |= events & (POLLIN | POLLRDNORM);
+ /* Wake up any blocked readers/writers. */
+ ptsdrv_outwakeup(tp);
+ ptsdrv_inwakeup(tp);
+}
- if (events & (POLLOUT | POLLWRNORM))
- if (tp->t_state & TS_ISOPEN &&
- (((tp->t_rawq.c_cc + tp->t_canq.c_cc < TTYHOG - 2) ||
- (tp->t_canq.c_cc == 0 && (tp->t_lflag & ICANON)))))
- revents |= events & (POLLOUT | POLLWRNORM);
+static void
+ptsdrv_free(void *softc)
+{
+ struct pts_softc *psc = softc;
- if (events & POLLHUP)
- if ((tp->t_state & TS_CARR_ON) == 0)
- revents |= POLLHUP;
+ /* Make device number available again. */
+ if (psc->pts_unit >= 0)
+ free_unr(pts_pool, psc->pts_unit);
- if (revents == 0) {
- if (events & (POLLIN | POLLRDNORM))
- selrecord(td, &pt->pt_selr);
+ chgptscnt(psc->pts_uidinfo, -1, 0);
+ uifree(psc->pts_uidinfo);
- if (events & (POLLOUT | POLLWRNORM))
- selrecord(td, &pt->pt_selw);
- }
- splx(s);
+#ifdef PTS_EXTERNAL
+ /* Destroy master device as well. */
+ if (psc->pts_cdev != NULL)
+ destroy_dev_sched(psc->pts_cdev);
+#endif /* PTS_EXTERNAL */
- return (revents);
+ free(psc, M_PTS);
}
+static struct ttydevsw pts_class = {
+ .tsw_flags = TF_NOPREFIX,
+ .tsw_outwakeup = ptsdrv_outwakeup,
+ .tsw_inwakeup = ptsdrv_inwakeup,
+ .tsw_close = ptsdrv_close,
+ .tsw_free = ptsdrv_free,
+};
+
static int
-ptcwrite(struct cdev *dev, struct uio *uio, int flag)
+pts_alloc(int fflags, struct thread *td, struct file *fp)
{
- struct tty *tp = dev->si_tty;
- u_char *cp = 0;
- int cc = 0;
- u_char locbuf[BUFSIZ];
- int cnt = 0;
- int error = 0;
-
-again:
- if ((tp->t_state&TS_ISOPEN) == 0)
- goto block;
- while (uio->uio_resid > 0 || cc > 0) {
- if (cc == 0) {
- cc = min(uio->uio_resid, BUFSIZ);
- cp = locbuf;
- error = uiomove(cp, cc, uio);
- if (error)
- return (error);
- /* check again for safety */
- if ((tp->t_state & TS_ISOPEN) == 0) {
- /* adjust for data copied in but not written */
- uio->uio_resid += cc;
- return (EIO);
- }
- }
- while (cc > 0) {
- if ((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= TTYHOG - 2 &&
- (tp->t_canq.c_cc > 0 || !(tp->t_lflag&ICANON))) {
- wakeup(TSA_HUP_OR_INPUT(tp));
- goto block;
- }
- ttyld_rint(tp, *cp++);
- cnt++;
- cc--;
- }
- cc = 0;
+ int unit, ok;
+ struct tty *tp;
+ struct pts_softc *psc;
+ struct proc *p = td->td_proc;
+ struct uidinfo *uid = td->td_ucred->cr_ruidinfo;
+
+ /* Resource limiting. */
+ PROC_LOCK(p);
+ ok = chgptscnt(uid, 1, lim_cur(p, RLIMIT_NPTS));
+ PROC_UNLOCK(p);
+ if (!ok)
+ return (EAGAIN);
+
+ /* Try to allocate a new pts unit number. */
+ unit = alloc_unr(pts_pool);
+ if (unit < 0) {
+ chgptscnt(uid, -1, 0);
+ return (EAGAIN);
}
+
+ /* Allocate TTY and softc. */
+ psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO);
+ cv_init(&psc->pts_inwait, "pts inwait");
+ cv_init(&psc->pts_outwait, "pts outwait");
+
+ psc->pts_unit = unit;
+ psc->pts_uidinfo = uid;
+ uihold(uid);
+
+ tp = tty_alloc(&pts_class, psc, NULL);
+
+ /* Expose the slave device as well. */
+ tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit);
+
+ finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops);
+
return (0);
-block:
- /*
- * Come here to wait for slave to open, for space
- * in outq, or space in rawq, or an empty canq.
- */
- if ((tp->t_state & TS_CONNECTED) == 0) {
- /* adjust for data copied in but not written */
- uio->uio_resid += cc;
- return (EIO);
- }
- if (flag & IO_NDELAY) {
- /* adjust for data copied in but not written */
- uio->uio_resid += cc;
- if (cnt == 0)
- return (EWOULDBLOCK);
- return (0);
- }
- error = tsleep(TSA_PTC_WRITE(tp), TTOPRI | PCATCH, "ptcout", 0);
- if (error) {
- /* adjust for data copied in but not written */
- uio->uio_resid += cc;
- return (error);
- }
- goto again;
}
-static int
-ptcioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
+#ifdef PTS_EXTERNAL
+int
+pts_alloc_external(int fflags, struct thread *td, struct file *fp,
+ struct cdev *dev, const char *name)
{
- struct tty *tp = dev->si_tty;
- struct pt_desc *pt = dev->si_drv1;
-#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
- defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
- int ival;
-#endif
+ int ok;
+ struct tty *tp;
+ struct pts_softc *psc;
+ struct proc *p = td->td_proc;
+ struct uidinfo *uid = td->td_ucred->cr_ruidinfo;
- switch (cmd) {
-
- case TIOCGPGRP:
- /*
- * We avoid calling ttioctl on the controller since,
- * in that case, tp must be the controlling terminal.
- */
- *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : 0;
- return (0);
-
- case TIOCPKT:
- if (*(int *)data) {
- if (pt->pt_flags & PF_UCNTL)
- return (EINVAL);
- pt->pt_flags |= PF_PKT;
- } else
- pt->pt_flags &= ~PF_PKT;
- return (0);
-
- case TIOCUCNTL:
- if (*(int *)data) {
- if (pt->pt_flags & PF_PKT)
- return (EINVAL);
- pt->pt_flags |= PF_UCNTL;
- } else
- pt->pt_flags &= ~PF_UCNTL;
- return (0);
- case TIOCGPTN:
- *(unsigned int *)data = pt->pt_num;
- return (0);
- }
-
- /*
- * The rest of the ioctls shouldn't be called until
- * the slave is open.
- */
- if ((tp->t_state & TS_ISOPEN) == 0) {
- if (cmd == TIOCGETA) {
- /*
- * TIOCGETA is used by isatty() to make sure it's
- * a tty. Linux openpty() calls isatty() very early,
- * before the slave is opened, so don't actually
- * fill the struct termios, but just let isatty()
- * know it's a tty.
- */
- return (0);
- }
- if (cmd != FIONBIO && cmd != FIOASYNC)
- return (EAGAIN);
- }
-
- switch (cmd) {
-#ifdef COMPAT_43TTY
- case TIOCSETP:
- case TIOCSETN:
-#endif
- case TIOCSETD:
- case TIOCSETA:
- case TIOCSETAW:
- case TIOCSETAF:
- /*
- * IF CONTROLLER STTY THEN MUST FLUSH TO PREVENT A HANG.
- * ttywflush(tp) will hang if there are characters in
- * the outq.
- */
- ndflush(&tp->t_outq, tp->t_outq.c_cc);
- break;
-
-#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
- defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
- case _IO('t', 95):
- ival = IOCPARM_IVAL(data);
- data = (caddr_t)&ival;
- /* FALLTHROUGH */
-#endif
- case TIOCSIG:
- if (*(unsigned int *)data >= NSIG ||
- *(unsigned int *)data == 0)
- return(EINVAL);
- if ((tp->t_lflag&NOFLSH) == 0)
- ttyflush(tp, FREAD|FWRITE);
- if (tp->t_pgrp != NULL) {
- PGRP_LOCK(tp->t_pgrp);
- pgsignal(tp->t_pgrp, *(unsigned int *)data, 1);
- PGRP_UNLOCK(tp->t_pgrp);
- }
- if ((*(unsigned int *)data == SIGINFO) &&
- ((tp->t_lflag&NOKERNINFO) == 0))
- ttyinfo(tp);
- return(0);
- }
- return (ptsioctl(dev, cmd, data, flag, td));
-}
-/*ARGSUSED*/
-static int
-ptsioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
-{
- struct tty *tp = dev->si_tty;
- struct pt_desc *pt = dev->si_drv1;
- u_char *cc = tp->t_cc;
- int stop, error;
+ /* Resource limiting. */
+ PROC_LOCK(p);
+ ok = chgptscnt(uid, 1, lim_cur(p, RLIMIT_NPTS));
+ PROC_UNLOCK(p);
+ if (!ok)
+ return (EAGAIN);
- if (cmd == TIOCEXT) {
- /*
- * When the EXTPROC bit is being toggled, we need
- * to send an TIOCPKT_IOCTL if the packet driver
- * is turned on.
- */
- if (*(int *)data) {
- if (pt->pt_flags & PF_PKT) {
- pt->pt_send |= TIOCPKT_IOCTL;
- ptcwakeup(tp, FREAD);
- }
- tp->t_lflag |= EXTPROC;
- } else {
- if ((tp->t_lflag & EXTPROC) &&
- (pt->pt_flags & PF_PKT)) {
- pt->pt_send |= TIOCPKT_IOCTL;
- ptcwakeup(tp, FREAD);
- }
- tp->t_lflag &= ~EXTPROC;
- }
- return(0);
- }
- error = ttioctl(tp, cmd, data, flag);
- if (error == ENOTTY) {
- if (pt->pt_flags & PF_UCNTL &&
- (cmd & ~0xff) == UIOCCMD(0)) {
- if (cmd & 0xff) {
- pt->pt_ucntl = (u_char)cmd;
- ptcwakeup(tp, FREAD);
- }
- return (0);
- }
- error = ENOTTY;
- }
- /*
- * If external processing and packet mode send ioctl packet.
- */
- if ((tp->t_lflag&EXTPROC) && (pt->pt_flags & PF_PKT)) {
- switch(cmd) {
- case TIOCSETA:
- case TIOCSETAW:
- case TIOCSETAF:
-#ifdef COMPAT_43TTY
- case TIOCSETP:
- case TIOCSETN:
- case TIOCSETC:
- case TIOCSLTC:
- case TIOCLBIS:
- case TIOCLBIC:
- case TIOCLSET:
-#endif
- pt->pt_send |= TIOCPKT_IOCTL;
- ptcwakeup(tp, FREAD);
- break;
- default:
- break;
- }
- }
- stop = (tp->t_iflag & IXON) && CCEQ(cc[VSTOP], CTRL('s'))
- && CCEQ(cc[VSTART], CTRL('q'));
- if (pt->pt_flags & PF_NOSTOP) {
- if (stop) {
- pt->pt_send &= ~TIOCPKT_NOSTOP;
- pt->pt_send |= TIOCPKT_DOSTOP;
- pt->pt_flags &= ~PF_NOSTOP;
- ptcwakeup(tp, FREAD);
- }
- } else {
- if (!stop) {
- pt->pt_send &= ~TIOCPKT_DOSTOP;
- pt->pt_send |= TIOCPKT_NOSTOP;
- pt->pt_flags |= PF_NOSTOP;
- ptcwakeup(tp, FREAD);
- }
- }
- return (error);
-}
+ /* Allocate TTY and softc. */
+ psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO);
+ cv_init(&psc->pts_inwait, "pts inwait");
+ cv_init(&psc->pts_outwait, "pts outwait");
-/*
- * Match lookups on /dev/ptmx, find the next free pty (if any), set up
- * the pty descriptor, register it, and return a reference to the master.
- *
- * pts == /dev/pts/xxx (oldstyle: ttyp...)
- * ptc == /dev/pty/xxx (oldstyle: ptyp...)
- */
-static void
-pty_clone(void *arg, struct ucred *cred, char *name, int namelen,
- struct cdev **dev)
-{
- struct pt_desc *pt;
- struct cdev *devc;
+ psc->pts_unit = -1;
+ psc->pts_cdev = dev;
+ psc->pts_uidinfo = uid;
+ uihold(uid);
- if (!use_pts)
- return;
+ tp = tty_alloc(&pts_class, psc, NULL);
- if (*dev != NULL)
- return;
+ /* Expose the slave device as well. */
+ tty_makedev(tp, td->td_ucred, "%s", name);
- if (strcmp(name, "ptmx") != 0)
- return;
+ finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops);
- mtx_lock(&Giant);
- pt = pty_new();
- if (pt == NULL) {
- mtx_unlock(&Giant);
- return;
- }
+ return (0);
+}
+#endif /* PTS_EXTERNAL */
+
+int
+posix_openpt(struct thread *td, struct posix_openpt_args *uap)
+{
+ int error, fd;
+ struct file *fp;
/*
- * XXX: Lack of locking here considered worrying. We expose the
- * pts/pty device nodes before they are fully initialized, although
- * Giant likely protects us (unless make_dev blocks...?).
- *
- * XXX: If a process performs a lookup on /dev/ptmx but never an
- * open, we won't GC the device node. We should have a callout
- * sometime later that GC's device instances that were never
- * opened, or some way to tell devfs that "this had better be for
- * an open() or we won't create a device".
+ * POSIX states it's unspecified when other flags are passed. We
+ * don't allow this.
*/
- pt->pt_devc = devc = make_dev_credf(MAKEDEV_REF, &ptc_cdevsw,
- NUM_TO_MINOR(pt->pt_num), cred, UID_ROOT, GID_WHEEL, 0666,
- "pty/%d", pt->pt_num);
+ if (uap->flags & ~(O_RDWR|O_NOCTTY))
+ return (EINVAL);
+
+ error = falloc(td, &fp, &fd);
+ if (error)
+ return (error);
+
+ /* Allocate the actual pseudo-TTY. */
+ error = pts_alloc(FFLAGS(uap->flags & O_ACCMODE), td, fp);
+ if (error != 0) {
+ fdclose(td->td_proc->p_fd, fp, fd, td);
+ return (error);
+ }
+
+ /* Pass it back to userspace. */
+ td->td_retval[0] = fd;
+ fdrop(fp, td);
+
+ return (0);
+}
- devc->si_drv1 = pt;
- devc->si_tty = pt->pt_tty;
- *dev = devc;
- mtx_unlock(&Giant);
+#if defined(PTS_COMPAT) || defined(PTS_LINUX)
+static int
+ptmx_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *fp)
+{
+ int error;
- if (bootverbose)
- printf("pty_clone: allocated pty %d to uid %d\n", pt->pt_num,
- cred->cr_ruid);
+ error = pts_alloc(fflags & (FREAD|FWRITE), td, fp);
+ if (error != 0)
+ return (error);
- return;
+ return (0);
}
+static struct cdevsw ptmx_cdevsw = {
+ .d_version = D_VERSION,
+ .d_fdopen = ptmx_fdopen,
+ .d_name = "ptmx",
+};
+#endif /* PTS_COMPAT || PTS_LINUX */
+
static void
-pty_drvinit(void *unused)
+pts_init(void *unused)
{
- mtx_init(&pt_mtx, "pt_mtx", NULL, MTX_DEF);
- LIST_INIT(&pt_list);
- LIST_INIT(&pt_free_list);
- EVENTHANDLER_REGISTER(dev_clone, pty_clone, 0, 1000);
+ pts_pool = new_unrhdr(0, MAXPTSDEVS, NULL);
+#if defined(PTS_COMPAT) || defined(PTS_LINUX)
+ make_dev(&ptmx_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "ptmx");
+#endif /* PTS_COMPAT || PTS_LINUX */
}
-SYSINIT(ptydev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE,pty_drvinit,NULL);
+SYSINIT(pts, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, pts_init, NULL);
OpenPOWER on IntegriCloud