Print this page
14249 pseudo-terminal nomenclature should reflect POSIX
Change-Id: Ib4a3cef899ff4c71b09cb0dc6878863c5e8357bc
*** 24,124 ****
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
*/
/*
! * Pseudo Terminal Master Driver.
*
! * The pseudo-tty subsystem simulates a terminal connection, where the master
! * side represents the terminal and the slave represents the user process's
! * special device end point. The master device is set up as a cloned device
! * where its major device number is the major for the clone device and its minor
! * device number is the major for the ptm driver. There are no nodes in the file
! * system for master devices. The master pseudo driver is opened using the
! * open(2) system call with /dev/ptmx as the device parameter. The clone open
! * finds the next available minor device for the ptm major device.
*
! * A master device is available only if it and its corresponding slave device
! * are not already open. When the master device is opened, the corresponding
! * slave device is automatically locked out. Only one open is allowed on a
! * master device. Multiple opens are allowed on the slave device. After both
! * the master and slave have been opened, the user has two file descriptors
! * which are the end points of a full duplex connection composed of two streams
! * which are automatically connected at the master and slave drivers. The user
! * may then push modules onto either side of the stream pair.
*
! * The master and slave drivers pass all messages to their adjacent queues.
! * Only the M_FLUSH needs some processing. Because the read queue of one side
! * is connected to the write queue of the other, the FLUSHR flag is changed to
! * the FLUSHW flag and vice versa. When the master device is closed an M_HANGUP
! * message is sent to the slave device which will render the device
! * unusable. The process on the slave side gets the EIO when attempting to write
! * on that stream but it will be able to read any data remaining on the stream
! * head read queue. When all the data has been read, read() returns 0
! * indicating that the stream can no longer be used. On the last close of the
! * slave device, a 0-length message is sent to the master device. When the
! * application on the master side issues a read() or getmsg() and 0 is returned,
! * the user of the master device decides whether to issue a close() that
! * dismantles the pseudo-terminal subsystem. If the master device is not closed,
! * the pseudo-tty subsystem will be available to another user to open the slave
! * device.
*
! * If O_NONBLOCK or O_NDELAY is set, read on the master side returns -1 with
* errno set to EAGAIN if no data is available, and write returns -1 with errno
* set to EAGAIN if there is internal flow control.
*
- * IOCTLS:
*
! * ISPTM: determines whether the file descriptor is that of an open master
! * device. Return code of zero indicates that the file descriptor
! * represents master device.
*
! * UNLKPT: unlocks the master and slave devices. It returns 0 on success. On
! * failure, the errno is set to EINVAL indicating that the master
! * device is not open.
*
! * ZONEPT: sets the zone membership of the associated pts device.
*
! * GRPPT: sets the group owner of the associated pts device.
*
! * Synchronization:
*
- * All global data synchronization between ptm/pts is done via global
- * ptms_lock mutex which is initialized at system boot time from
- * ptms_initspace (called from space.c).
*
* Individual fields of pt_ttys structure (except ptm_rdq, pts_rdq and
* pt_nullmsg) are protected by pt_ttys.pt_lock mutex.
*
* PT_ENTER_READ/PT_ENTER_WRITE are reference counter based read-write locks
* which allow reader locks to be reacquired by the same thread (usual
! * reader/writer locks can't be used for that purpose since it is illegal for
! * a thread to acquire a lock it already holds, even as a reader). The sole
* purpose of these macros is to guarantee that the peer queue will not
* disappear (due to closing peer) while it is used. It is safe to use
* PT_ENTER_READ/PT_EXIT_READ brackets across calls like putq/putnext (since
* they are not real locks but reference counts).
*
! * PT_ENTER_WRITE/PT_EXIT_WRITE brackets are used ONLY in master/slave
* open/close paths to modify ptm_rdq and pts_rdq fields. These fields should
* be set to appropriate queues *after* qprocson() is called during open (to
* prevent peer from accessing the queue with incomplete plumbing) and set to
* NULL before qprocsoff() is called during close.
*
* The pt_nullmsg field is only used in open/close routines and it is also
* protected by PT_ENTER_WRITE/PT_EXIT_WRITE brackets to avoid extra mutex
* holds.
*
- * Lock Ordering:
*
* If both ptms_lock and per-pty lock should be held, ptms_lock should always
* be entered first, followed by per-pty lock.
*
! * See ptms.h, pts.c and ptms_conf.c for more information.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
--- 24,134 ----
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
+ * Copyright 2021 Oxide Computer Company
*/
/*
! * PSEUDO-TERMINAL MANAGER DRIVER (PTM)
*
! * The pseudo-terminal subsystem simulates a terminal connection, where the
! * manager side represents the terminal and the subsidiary represents the user
! * process's special device end point. The manager device is set up as a
! * cloned device where its major device number is the major for the clone
! * device and its minor device number is the major for the ptm driver. There
! * are no nodes in the file system for manager devices. The manager pseudo
! * driver is opened using the open(2) system call with /dev/ptmx as the device
! * parameter. The clone open finds the next available minor device for the ptm
! * major device.
*
! * A manager device is available only if it and its corresponding subsidiary
! * device are not already open. When the manager device is opened, the
! * corresponding subsidiary device is automatically locked out. Only one open
! * is allowed on a manager device. Multiple opens are allowed on the
! * subsidiary device. After both the manager and subsidiary have been opened,
! * the user has two file descriptors which are the end points of a full duplex
! * connection composed of two streams which are automatically connected at the
! * manager and subsidiary drivers. The user may then push modules onto either
! * side of the stream pair.
*
! * The manager and subsidiary drivers pass all messages to their adjacent
! * queues. Only the M_FLUSH needs some processing. Because the read queue of
! * one side is connected to the write queue of the other, the FLUSHR flag is
! * changed to the FLUSHW flag and vice versa. When the manager device is
! * closed an M_HANGUP message is sent to the subsidiary device which will
! * render the device unusable. The process on the subsidiary side gets an EIO
! * error when attempting to write on that stream but it will be able to read
! * any data remaining on the stream head read queue. When all the data has
! * been read, read() returns 0 indicating that the stream can no longer be
! * used. On the last close of the subsidiary device, a 0-length message is
! * sent to the manager device. When the application on the manager side issues
! * a read() or getmsg() and 0 is returned, the user of the manager device
! * decides whether to issue a close() that dismantles the pseudo-terminal
! * subsystem. If the manager device is not closed, the pseudo-terminal
! * subsystem will be available to another user to open the subsidiary device.
*
! * If O_NONBLOCK or O_NDELAY is set, read on the manager side returns -1 with
* errno set to EAGAIN if no data is available, and write returns -1 with errno
* set to EAGAIN if there is internal flow control.
*
*
! * IOCTLS
*
! * ISPTM
! * Determines whether the file descriptor is that of an open
! * manager device. Return code of zero indicates that the file
! * descriptor represents a manager device.
*
! * UNLKPT
! * Unlocks the manager and subsidiary devices. It returns 0 on
! * success. On failure, the errno is set to EINVAL indicating that
! * the manager device is not open.
*
! * ZONEPT
! * Sets the zone membership of the associated subsidiary device.
*
! * GRPPT
! * Sets the group owner of the associated subsidiary device.
*
*
+ * SYNCHRONIZATION
+ *
+ * All global data synchronization between ptm/pts is done via global ptms_lock
+ * mutex which is initialized at system boot time from ptms_initspace (called
+ * from space.c).
+ *
* Individual fields of pt_ttys structure (except ptm_rdq, pts_rdq and
* pt_nullmsg) are protected by pt_ttys.pt_lock mutex.
*
* PT_ENTER_READ/PT_ENTER_WRITE are reference counter based read-write locks
* which allow reader locks to be reacquired by the same thread (usual
! * reader/writer locks can't be used for that purpose since it is illegal for a
! * thread to acquire a lock it already holds, even as a reader). The sole
* purpose of these macros is to guarantee that the peer queue will not
* disappear (due to closing peer) while it is used. It is safe to use
* PT_ENTER_READ/PT_EXIT_READ brackets across calls like putq/putnext (since
* they are not real locks but reference counts).
*
! * PT_ENTER_WRITE/PT_EXIT_WRITE brackets are used ONLY in manager/subsidiary
* open/close paths to modify ptm_rdq and pts_rdq fields. These fields should
* be set to appropriate queues *after* qprocson() is called during open (to
* prevent peer from accessing the queue with incomplete plumbing) and set to
* NULL before qprocsoff() is called during close.
*
* The pt_nullmsg field is only used in open/close routines and it is also
* protected by PT_ENTER_WRITE/PT_EXIT_WRITE brackets to avoid extra mutex
* holds.
*
*
+ * LOCK ORDERING
+ *
* If both ptms_lock and per-pty lock should be held, ptms_lock should always
* be entered first, followed by per-pty lock.
*
! * See ptms.h, pts.c, and ptms_conf.c for more information.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
*** 150,163 ****
static int ptmclose(queue_t *, int, cred_t *);
static int ptmwput(queue_t *, mblk_t *);
static int ptmrsrv(queue_t *);
static int ptmwsrv(queue_t *);
- /*
- * Master Stream Pseudo Terminal Module: stream data structure definitions
- */
-
static struct module_info ptm_info = {
0xdead,
"ptm",
0,
512,
--- 160,169 ----
*** 207,219 ****
/*
* Module linkage information for the kernel.
*/
static struct modldrv modldrv = {
! &mod_driverops, /* Type of module. This one is a pseudo driver */
! "Master streams driver 'ptm'",
! &ptm_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
--- 213,225 ----
/*
* Module linkage information for the kernel.
*/
static struct modldrv modldrv = {
! &mod_driverops,
! "Pseudo-Terminal Manager Driver",
! &ptm_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
*** 271,281 ****
ddi_remove_minor_node(devi, NULL);
return (DDI_SUCCESS);
}
- /*ARGSUSED*/
static int
ptm_devinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
int error;
--- 277,286 ----
*** 298,313 ****
}
return (error);
}
- /* ARGSUSED */
/*
! * Open a minor of the master device. Store the write queue pointer and set the
! * pt_state field to (PTMOPEN | PTLOCK).
* This code will work properly with both clone opens and direct opens of the
! * master device.
*/
static int
ptmopen(
queue_t *rqp, /* pointer to the read side queue */
dev_t *devp, /* pointer to stream tail's dev */
--- 303,317 ----
}
return (error);
}
/*
! * Open a minor of the manager device. Store the write queue pointer and set
! * the pt_state field to (PTMOPEN | PTLOCK).
* This code will work properly with both clone opens and direct opens of the
! * manager device.
*/
static int
ptmopen(
queue_t *rqp, /* pointer to the read side queue */
dev_t *devp, /* pointer to stream tail's dev */
*** 327,349 ****
if (sflag & MODOPEN)
return (ENXIO);
if (!(sflag & CLONEOPEN) && dminor != 0) {
/*
! * This is a direct open to specific master device through an
* artificially created entry with specific minor in
* /dev/directory. Such behavior is not supported.
*/
return (ENXIO);
}
/*
! * The master open requires that the slave be attached
! * before it returns so that attempts to open the slave will
! * succeeed
*/
! if (ptms_attach_slave() != 0) {
return (ENXIO);
}
mop = allocb(sizeof (struct stroptions), BPRI_MED);
if (mop == NULL) {
--- 331,352 ----
if (sflag & MODOPEN)
return (ENXIO);
if (!(sflag & CLONEOPEN) && dminor != 0) {
/*
! * This is a direct open to specific manager device through an
* artificially created entry with specific minor in
* /dev/directory. Such behavior is not supported.
*/
return (ENXIO);
}
/*
! * The manager open requires that the subsidiary be attached before it
! * returns so that attempts to open the subsidiary will succeeed
*/
! if (ptms_attach_subsidiary() != 0) {
return (ENXIO);
}
mop = allocb(sizeof (struct stroptions), BPRI_MED);
if (mop == NULL) {
*** 364,374 ****
WR(rqp)->q_ptr = rqp->q_ptr = ptmp;
qprocson(rqp);
! /* Allow slave to send messages to master */
PT_ENTER_WRITE(ptmp);
ptmp->ptm_rdq = rqp;
PT_EXIT_WRITE(ptmp);
/*
--- 367,377 ----
WR(rqp)->q_ptr = rqp->q_ptr = ptmp;
qprocson(rqp);
! /* Allow subsidiary to send messages to manager */
PT_ENTER_WRITE(ptmp);
ptmp->ptm_rdq = rqp;
PT_EXIT_WRITE(ptmp);
/*
*** 395,411 ****
return (0);
}
/*
! * Find the address to private data identifying the slave's write queue.
! * Send a hang-up message up the slave's read queue to designate the
! * master/slave pair is tearing down. Uattach the master and slave by
! * nulling out the write queue fields in the private data structure.
! * Finally, unlock the master/slave pair and mark the master as closed.
*/
- /*ARGSUSED1*/
static int
ptmclose(queue_t *rqp, int flag, cred_t *credp)
{
struct pt_ttys *ptmp;
queue_t *pts_rdq;
--- 398,413 ----
return (0);
}
/*
! * Find the address to private data identifying the subsidiary's write queue.
! * Send a hang-up message up the subsidiary's read queue to designate the
! * manager/subsidiary pair is tearing down. Uattach the manager and subsidiary
! * by nulling out the write queue fields in the private data structure.
! * Finally, unlock the manager/subsidiary pair and mark the manager as closed.
*/
static int
ptmclose(queue_t *rqp, int flag, cred_t *credp)
{
struct pt_ttys *ptmp;
queue_t *pts_rdq;
*** 415,425 ****
ptmp = (struct pt_ttys *)rqp->q_ptr;
PT_ENTER_READ(ptmp);
if (ptmp->pts_rdq) {
pts_rdq = ptmp->pts_rdq;
if (pts_rdq->q_next) {
! DBG(("send hangup message to slave\n"));
(void) putnextctl(pts_rdq, M_HANGUP);
}
}
PT_EXIT_READ(ptmp);
/*
--- 417,427 ----
ptmp = (struct pt_ttys *)rqp->q_ptr;
PT_ENTER_READ(ptmp);
if (ptmp->pts_rdq) {
pts_rdq = ptmp->pts_rdq;
if (pts_rdq->q_next) {
! DBG(("send hangup message to subsidiary\n"));
(void) putnextctl(pts_rdq, M_HANGUP);
}
}
PT_EXIT_READ(ptmp);
/*
*** 429,440 ****
PT_ENTER_WRITE(ptmp);
ptmp->ptm_rdq = NULL;
freemsg(ptmp->pt_nullmsg);
ptmp->pt_nullmsg = NULL;
/*
! * qenable slave side write queue so that it can flush
! * its messages as master's read queue is going away
*/
if (ptmp->pts_rdq)
qenable(WR(ptmp->pts_rdq));
PT_EXIT_WRITE(ptmp);
--- 431,442 ----
PT_ENTER_WRITE(ptmp);
ptmp->ptm_rdq = NULL;
freemsg(ptmp->pt_nullmsg);
ptmp->pt_nullmsg = NULL;
/*
! * qenable subsidiary side write queue so that it can flush
! * its messages as manager's read queue is going away
*/
if (ptmp->pts_rdq)
qenable(WR(ptmp->pts_rdq));
PT_EXIT_WRITE(ptmp);
*** 464,492 ****
ptmp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptmp);
switch (mp->b_datap->db_type) {
/*
! * if write queue request, flush master's write
! * queue and send FLUSHR up slave side. If read
! * queue request, convert to FLUSHW and putnext().
*/
case M_FLUSH:
{
unsigned char flush_flg = 0;
DBG(("ptm got flush request\n"));
if (*mp->b_rptr & FLUSHW) {
DBG(("got FLUSHW, flush ptm write Q\n"));
! if (*mp->b_rptr & FLUSHBAND)
/*
* if it is a FLUSHBAND, do flushband.
*/
flushband(qp, *(mp->b_rptr + 1),
FLUSHDATA);
! else
flushq(qp, FLUSHDATA);
flush_flg = (*mp->b_rptr & ~FLUSHW) | FLUSHR;
}
if (*mp->b_rptr & FLUSHR) {
DBG(("got FLUSHR, set FLUSHW\n"));
flush_flg |= (*mp->b_rptr & ~FLUSHR) | FLUSHW;
--- 466,495 ----
ptmp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptmp);
switch (mp->b_datap->db_type) {
/*
! * If this is a write queue request, flush manager's write queue and
! * send FLUSHR up subsidiary side. If it is a read queue request,
! * convert to FLUSHW and putnext().
*/
case M_FLUSH:
{
unsigned char flush_flg = 0;
DBG(("ptm got flush request\n"));
if (*mp->b_rptr & FLUSHW) {
DBG(("got FLUSHW, flush ptm write Q\n"));
! if (*mp->b_rptr & FLUSHBAND) {
/*
* if it is a FLUSHBAND, do flushband.
*/
flushband(qp, *(mp->b_rptr + 1),
FLUSHDATA);
! } else {
flushq(qp, FLUSHDATA);
+ }
flush_flg = (*mp->b_rptr & ~FLUSHW) | FLUSHR;
}
if (*mp->b_rptr & FLUSHR) {
DBG(("got FLUSHR, set FLUSHW\n"));
flush_flg |= (*mp->b_rptr & ~FLUSHR) | FLUSHW;
*** 494,515 ****
if (flush_flg != 0 && ptmp->pts_rdq &&
!(ptmp->pt_state & PTLOCK)) {
DBG(("putnext to pts\n"));
*mp->b_rptr = flush_flg;
putnext(ptmp->pts_rdq, mp);
! } else
freemsg(mp);
break;
}
case M_IOCTL:
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
default:
if ((ptmp->pt_state & PTLOCK) ||
(ptmp->pts_rdq == NULL)) {
! DBG(("got M_IOCTL but no slave\n"));
miocnak(qp, mp, 0, EINVAL);
PT_EXIT_READ(ptmp);
return (0);
}
(void) putq(qp, mp);
--- 497,519 ----
if (flush_flg != 0 && ptmp->pts_rdq &&
!(ptmp->pt_state & PTLOCK)) {
DBG(("putnext to pts\n"));
*mp->b_rptr = flush_flg;
putnext(ptmp->pts_rdq, mp);
! } else {
freemsg(mp);
+ }
break;
}
case M_IOCTL:
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
default:
if ((ptmp->pt_state & PTLOCK) ||
(ptmp->pts_rdq == NULL)) {
! DBG(("got M_IOCTL but no subsidiary\n"));
miocnak(qp, mp, 0, EINVAL);
PT_EXIT_READ(ptmp);
return (0);
}
(void) putq(qp, mp);
*** 585,614 ****
}
}
break;
case M_READ:
! /* Caused by ldterm - can not pass to slave */
freemsg(mp);
break;
/*
! * send other messages to slave
*/
default:
if ((ptmp->pt_state & PTLOCK) || (ptmp->pts_rdq == NULL)) {
! DBG(("got msg. but no slave\n"));
mp = mexchange(NULL, mp, 2, M_ERROR, -1);
if (mp != NULL) {
mp->b_rptr[0] = NOERROR;
mp->b_rptr[1] = EINVAL;
qreply(qp, mp);
}
PT_EXIT_READ(ptmp);
return (0);
}
! DBG(("put msg on master's write queue\n"));
(void) putq(qp, mp);
break;
}
DBG(("return from ptmwput()\n"));
PT_EXIT_READ(ptmp);
--- 589,618 ----
}
}
break;
case M_READ:
! /* Caused by ldterm - can not pass to subsidiary */
freemsg(mp);
break;
/*
! * Send other messages to the subsidiary:
*/
default:
if ((ptmp->pt_state & PTLOCK) || (ptmp->pts_rdq == NULL)) {
! DBG(("got msg. but no subsidiary\n"));
mp = mexchange(NULL, mp, 2, M_ERROR, -1);
if (mp != NULL) {
mp->b_rptr[0] = NOERROR;
mp->b_rptr[1] = EINVAL;
qreply(qp, mp);
}
PT_EXIT_READ(ptmp);
return (0);
}
! DBG(("put msg on manager's write queue\n"));
(void) putq(qp, mp);
break;
}
DBG(("return from ptmwput()\n"));
PT_EXIT_READ(ptmp);
*** 615,627 ****
return (0);
}
/*
! * enable the write side of the slave. This triggers the
! * slave to send any messages queued on its write side to
! * the read side of this master.
*/
static int
ptmrsrv(queue_t *qp)
{
struct pt_ttys *ptmp;
--- 619,630 ----
return (0);
}
/*
! * Enable the write side of the subsidiary. This triggers the subsidiary to
! * send any messages queued on its write side to the read side of this manager.
*/
static int
ptmrsrv(queue_t *qp)
{
struct pt_ttys *ptmp;
*** 639,652 ****
return (0);
}
/*
! * If there are messages on this queue that can be sent to
! * slave, send them via putnext(). Else, if queued messages
! * cannot be sent, leave them on this queue. If priority
! * messages on this queue, send them to slave no matter what.
*/
static int
ptmwsrv(queue_t *qp)
{
struct pt_ttys *ptmp;
--- 642,655 ----
return (0);
}
/*
! * If there are messages on this queue that can be sent to subsidiary, send
! * them via putnext(). Otherwise, if queued messages cannot be sent, leave
! * them on this queue. If priority messages on this queue, send them to the
! * subsidiary no matter what.
*/
static int
ptmwsrv(queue_t *qp)
{
struct pt_ttys *ptmp;
*** 663,673 ****
return (0);
}
PT_ENTER_READ(ptmp);
if ((ptmp->pt_state & PTLOCK) || (ptmp->pts_rdq == NULL)) {
! DBG(("in master write srv proc but no slave\n"));
/*
* Free messages on the write queue and send
* NAK for any M_IOCTL type messages to wakeup
* the user process waiting for ACK/NAK from
* the ioctl invocation
--- 666,676 ----
return (0);
}
PT_ENTER_READ(ptmp);
if ((ptmp->pt_state & PTLOCK) || (ptmp->pts_rdq == NULL)) {
! DBG(("in manager write srv proc but no subsidiary\n"));
/*
* Free messages on the write queue and send
* NAK for any M_IOCTL type messages to wakeup
* the user process waiting for ACK/NAK from
* the ioctl invocation
*** 688,715 ****
}
PT_EXIT_READ(ptmp);
return (0);
}
/*
! * while there are messages on this write queue...
*/
do {
/*
! * if don't have control message and cannot put
! * msg. on slave's read queue, put it back on
! * this queue.
*/
if (mp->b_datap->db_type <= QPCTL &&
!bcanputnext(ptmp->pts_rdq, mp->b_band)) {
DBG(("put msg. back on queue\n"));
(void) putbq(qp, mp);
break;
}
/*
! * else send the message up slave's stream
*/
! DBG(("send message to slave\n"));
putnext(ptmp->pts_rdq, mp);
} while ((mp = getq(qp)) != NULL);
DBG(("leaving ptmwsrv\n"));
PT_EXIT_READ(ptmp);
return (0);
--- 691,717 ----
}
PT_EXIT_READ(ptmp);
return (0);
}
/*
! * While there are messages on this write queue...
*/
do {
/*
! * If this is not a control message, and we cannot put messages
! * on the subsidiary's read queue, put it back on this queue.
*/
if (mp->b_datap->db_type <= QPCTL &&
!bcanputnext(ptmp->pts_rdq, mp->b_band)) {
DBG(("put msg. back on queue\n"));
(void) putbq(qp, mp);
break;
}
/*
! * Otherwise send the message up subsidiary's stream
*/
! DBG(("send message to subsidiary\n"));
putnext(ptmp->pts_rdq, mp);
} while ((mp = getq(qp)) != NULL);
DBG(("leaving ptmwsrv\n"));
PT_EXIT_READ(ptmp);
return (0);