Print this page
14249 pseudo-terminal nomenclature should reflect POSIX
Change-Id: Ib4a3cef899ff4c71b09cb0dc6878863c5e8357bc
*** 25,108 ****
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
*/
/*
! * Pseudo Terminal Slave 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.
*
- * 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, ptm.c and ptms_conf.c fore more information.
- *
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
--- 25,112 ----
/* 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 SUBSIDIARY DRIVER (PTS)
*
! * 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 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 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-tty subsystem will be available to
! * another user to open the 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, ptm.c and ptms_conf.c fore more information.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
*** 133,145 ****
static int ptsclose(queue_t *, int, cred_t *);
static int ptswput(queue_t *, mblk_t *);
static int ptsrsrv(queue_t *);
static int ptswsrv(queue_t *);
- /*
- * Slave Stream Pseudo Terminal Module: stream data structure definitions
- */
static struct module_info pts_info = {
0xface,
"pts",
0,
_TTY_BUFSIZ,
--- 137,146 ----
*** 190,202 ****
/*
* Module linkage information for the kernel.
*/
static struct modldrv modldrv = {
! &mod_driverops, /* Type of module. This one is a pseudo driver */
! "Slave Stream Pseudo Terminal driver 'pts'",
! &pts_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
--- 191,203 ----
/*
* Module linkage information for the kernel.
*/
static struct modldrv modldrv = {
! &mod_driverops,
! "Pseudo-Terminal Subsidiary Driver",
! &pts_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
*** 237,247 ****
mutex_exit(&ptms_lock);
return (DDI_SUCCESS);
}
- /*ARGSUSED*/
static int
pts_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
--- 238,247 ----
*** 250,260 ****
* For now, pts cannot be detached.
*/
return (DDI_FAILURE);
}
- /*ARGSUSED*/
static int
pts_devinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
int error;
--- 250,259 ----
*** 278,290 ****
return (error);
}
/* ARGSUSED */
/*
! * Open the slave device. Reject a clone open and do not allow the
! * driver to be pushed. If the slave/master pair is locked or if
! * the master is not open, return EACCESS.
* Upon success, store the write queue pointer in private data and
* set the PTSOPEN bit in the pt_state field.
*/
static int
ptsopen(
--- 277,289 ----
return (error);
}
/* ARGSUSED */
/*
! * Open the subsidiary device. Reject a clone open and do not allow the
! * driver to be pushed. If the subsidiary/manager pair is locked or if
! * the manager is not open, return EACCESS.
* Upon success, store the write queue pointer in private data and
* set the PTSOPEN bit in the pt_state field.
*/
static int
ptsopen(
*** 367,380 ****
mutex_exit(&ptms_lock);
return (ENOMEM);
}
/*
! * Slave should send zero-length message to a master when it is
! * closing. If memory is low at that time, master will not detect slave
! * closes, this pty will not be deallocated. So, preallocate this
! * zero-length message block early.
*/
if ((mp = allocb(0, BPRI_MED)) == NULL) {
mutex_exit(&ptsp->pt_lock);
mutex_exit(&ptms_lock);
freemsg(mop);
--- 366,379 ----
mutex_exit(&ptms_lock);
return (ENOMEM);
}
/*
! * Subsidiary should send zero-length message to a manager when it is
! * closing. If memory is low at that time, manager will not detect
! * subsidiary closes, this pty will not be deallocated. So,
! * preallocate this zero-length message block early.
*/
if ((mp = allocb(0, BPRI_MED)) == NULL) {
mutex_exit(&ptsp->pt_lock);
mutex_exit(&ptms_lock);
freemsg(mop);
*** 393,405 ****
qprocson(rqp);
/*
* After qprocson pts driver is fully plumbed into the stream and can
! * send/receive messages. Setting pts_rdq will allow master side to send
! * messages to the slave. This setting can't occur before qprocson() is
! * finished because slave is not ready to process them.
*/
PT_ENTER_WRITE(ptsp);
ptsp->pts_rdq = rqp;
ASSERT(ptsp->pt_nullmsg == NULL);
ptsp->pt_nullmsg = mp;
--- 392,405 ----
qprocson(rqp);
/*
* After qprocson pts driver is fully plumbed into the stream and can
! * send/receive messages. Setting pts_rdq will allow manager side to
! * send messages to the subsidiary. This setting can't occur before
! * qprocson() is finished because subsidiary is not ready to process
! * them.
*/
PT_ENTER_WRITE(ptsp);
ptsp->pts_rdq = rqp;
ASSERT(ptsp->pt_nullmsg == NULL);
ptsp->pt_nullmsg = mp;
*** 420,435 ****
return (0);
}
/*
! * Find the address to private data identifying the slave's write
! * queue. Send a 0-length msg up the slave's read queue to designate
! * the master is closing. Uattach the master from the slave by nulling
! * out master's write queue field in private data.
*/
- /*ARGSUSED1*/
static int
ptsclose(queue_t *rqp, int flag, cred_t *credp)
{
struct pt_ttys *ptsp;
queue_t *wqp;
--- 420,434 ----
return (0);
}
/*
! * Find the address to private data identifying the subsidiary's write queue.
! * Send a 0-length msg up the subsidiary's read queue to designate the manager
! * is closing. Uattach the manager from the subsidiary by nulling out
! * manager's write queue field in private data.
*/
static int
ptsclose(queue_t *rqp, int flag, cred_t *credp)
{
struct pt_ttys *ptsp;
queue_t *wqp;
*** 448,461 ****
}
ptsp = (struct pt_ttys *)rqp->q_ptr;
/*
! * Slave is going to close and doesn't want any new messages coming
! * from the master side, so set pts_rdq to NULL. This should be done
! * before call to qprocsoff() since slave can't process additional
! * messages from the master after qprocsoff is called.
*/
PT_ENTER_WRITE(ptsp);
mp = ptsp->pt_nullmsg;
ptsp->pt_nullmsg = NULL;
ptsp->pts_rdq = NULL;
--- 447,460 ----
}
ptsp = (struct pt_ttys *)rqp->q_ptr;
/*
! * Subsidiary is going to close and doesn't want any new messages
! * coming from the manager side, so set pts_rdq to NULL. This should
! * be done before call to qprocsoff() since subsidiary can't process
! * additional messages from the manager after qprocsoff is called.
*/
PT_ENTER_WRITE(ptsp);
mp = ptsp->pt_nullmsg;
ptsp->pt_nullmsg = NULL;
ptsp->pts_rdq = NULL;
*** 477,488 ****
} else {
freemsg(bp);
}
}
/*
! * qenable master side write queue so that it can flush
! * its messages as slaves's read queue is going away
*/
if (ptsp->ptm_rdq) {
if (mp)
putnext(ptsp->ptm_rdq, mp);
else
--- 476,487 ----
} else {
freemsg(bp);
}
}
/*
! * qenable manager side write queue so that it can flush its messages
! * as subsidiarys's read queue is going away:
*/
if (ptsp->ptm_rdq) {
if (mp)
putnext(ptsp->ptm_rdq, mp);
else
*** 501,513 ****
return (0);
}
/*
! * The wput procedure will only handle flush messages.
! * All other messages are queued and the write side
! * service procedure sends them off to the master side.
*/
static int
ptswput(queue_t *qp, mblk_t *mp)
{
struct pt_ttys *ptsp;
--- 500,512 ----
return (0);
}
/*
! * The wput procedure will only handle flush messages. All other messages are
! * queued and the write side service procedure sends them off to the manager
! * side.
*/
static int
ptswput(queue_t *qp, mblk_t *mp)
{
struct pt_ttys *ptsp;
*** 518,530 ****
ASSERT(qp->q_ptr);
ptsp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptsp);
if (ptsp->ptm_rdq == NULL) {
! DBG(("in write put proc but no master\n"));
/*
! * NAK ioctl as slave side read queue is gone.
* Or else free the message.
*/
if (mp->b_datap->db_type == M_IOCTL) {
mp->b_datap->db_type = M_IOCNAK;
freemsg(mp->b_cont);
--- 517,529 ----
ASSERT(qp->q_ptr);
ptsp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptsp);
if (ptsp->ptm_rdq == NULL) {
! DBG(("in write put proc but no manager\n"));
/*
! * NAK ioctl as subsidiary side read queue is gone.
* Or else free the message.
*/
if (mp->b_datap->db_type == M_IOCTL) {
mp->b_datap->db_type = M_IOCNAK;
freemsg(mp->b_cont);
*** 538,548 ****
if (type >= QPCTL) {
switch (type) {
/*
! * if write queue request, flush slave's write
* queue and send FLUSHR to ptm. If read queue
* request, send FLUSHR to ptm.
*/
case M_FLUSH:
DBG(("pts got flush request\n"));
--- 537,547 ----
if (type >= QPCTL) {
switch (type) {
/*
! * if write queue request, flush subsidiary's write
* queue and send FLUSHR to ptm. If read queue
* request, send FLUSHR to ptm.
*/
case M_FLUSH:
DBG(("pts got flush request\n"));
*** 583,595 ****
putnext(ptsp->ptm_rdq, nmp);
}
}
}
/*
! * Since the packet module will toss any
! * M_FLUSHES sent to the master's stream head
! * read queue, we simply turn it around here.
*/
if (*mp->b_rptr & FLUSHR) {
ASSERT(RD(qp)->q_first == NULL);
DBG(("qreply(qp) turning FLUSHR around\n"));
qreply(qp, mp);
--- 582,594 ----
putnext(ptsp->ptm_rdq, nmp);
}
}
}
/*
! * Since the packet module will toss any M_FLUSHES sent to the
! * manager's stream head read queue, we simply turn it around
! * here.
*/
if (*mp->b_rptr & FLUSHR) {
ASSERT(RD(qp)->q_first == NULL);
DBG(("qreply(qp) turning FLUSHR around\n"));
qreply(qp, mp);
*** 597,607 ****
freemsg(mp);
}
break;
case M_READ:
! /* Caused by ldterm - can not pass to master */
freemsg(mp);
break;
default:
if (ptsp->ptm_rdq)
--- 596,606 ----
freemsg(mp);
}
break;
case M_READ:
! /* Caused by ldterm - can not pass to manager */
freemsg(mp);
break;
default:
if (ptsp->ptm_rdq)
*** 643,655 ****
return (0);
}
/* FALLTHROUGH */
default:
/*
! * send other messages to the master
*/
! DBG(("put msg on slave's write queue\n"));
(void) putq(qp, mp);
break;
}
PT_EXIT_READ(ptsp);
--- 642,654 ----
return (0);
}
/* FALLTHROUGH */
default:
/*
! * send other messages to the manager
*/
! DBG(("put msg on subsidiary's write queue\n"));
(void) putq(qp, mp);
break;
}
PT_EXIT_READ(ptsp);
*** 657,669 ****
return (0);
}
/*
! * enable the write side of the master. This triggers the
! * master to send any messages queued on its write side to
! * the read side of this slave.
*/
static int
ptsrsrv(queue_t *qp)
{
struct pt_ttys *ptsp;
--- 656,667 ----
return (0);
}
/*
! * Enable the write side of the manager. This triggers the manager to send any
! * messages queued on its write side to the read side of this subsidiary.
*/
static int
ptsrsrv(queue_t *qp)
{
struct pt_ttys *ptsp;
*** 672,682 ****
ASSERT(qp->q_ptr);
ptsp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptsp);
if (ptsp->ptm_rdq == NULL) {
! DBG(("in read srv proc but no master\n"));
PT_EXIT_READ(ptsp);
return (0);
}
qenable(WR(ptsp->ptm_rdq));
PT_EXIT_READ(ptsp);
--- 670,680 ----
ASSERT(qp->q_ptr);
ptsp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptsp);
if (ptsp->ptm_rdq == NULL) {
! DBG(("in read srv proc but no manager\n"));
PT_EXIT_READ(ptsp);
return (0);
}
qenable(WR(ptsp->ptm_rdq));
PT_EXIT_READ(ptsp);
*** 683,696 ****
DBG(("leaving ptsrsrv\n"));
return (0);
}
/*
! * If there are messages on this queue that can be sent to
! * master, 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 master no matter what.
*/
static int
ptswsrv(queue_t *qp)
{
struct pt_ttys *ptsp;
--- 681,694 ----
DBG(("leaving ptsrsrv\n"));
return (0);
}
/*
! * If there are messages on this queue that can be sent to manager, 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 manager no
! * matter what.
*/
static int
ptswsrv(queue_t *qp)
{
struct pt_ttys *ptsp;
*** 701,716 ****
ASSERT(qp->q_ptr);
ptsp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptsp);
if (ptsp->ptm_rdq == NULL) {
! DBG(("in write srv proc but no master\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
*/
while ((mp = getq(qp)) != NULL) {
if (mp->b_datap->db_type == M_IOCTL) {
mp->b_datap->db_type = M_IOCNAK;
freemsg(mp->b_cont);
--- 699,713 ----
ASSERT(qp->q_ptr);
ptsp = (struct pt_ttys *)qp->q_ptr;
PT_ENTER_READ(ptsp);
if (ptsp->ptm_rdq == NULL) {
! DBG(("in write srv proc but no manager\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
*/
while ((mp = getq(qp)) != NULL) {
if (mp->b_datap->db_type == M_IOCTL) {
mp->b_datap->db_type = M_IOCNAK;
freemsg(mp->b_cont);
*** 724,751 ****
} else {
ptm_rdq = ptsp->ptm_rdq;
}
/*
! * while there are messages on this write queue...
*/
while ((mp = getq(qp)) != NULL) {
/*
! * if don't have control message and cannot put
! * msg. on master's read queue, put it back on
! * this queue.
*/
if (mp->b_datap->db_type <= QPCTL &&
!bcanputnext(ptm_rdq, mp->b_band)) {
DBG(("put msg. back on Q\n"));
(void) putbq(qp, mp);
break;
}
/*
! * else send the message up master's stream
*/
! DBG(("send message to master\n"));
putnext(ptm_rdq, mp);
}
DBG(("leaving ptswsrv\n"));
PT_EXIT_READ(ptsp);
return (0);
--- 721,747 ----
} else {
ptm_rdq = ptsp->ptm_rdq;
}
/*
! * While there are messages on this write queue...
*/
while ((mp = getq(qp)) != NULL) {
/*
! * If this is not a control message and we cannot put messages
! * on the manager's read queue, put it back on this queue.
*/
if (mp->b_datap->db_type <= QPCTL &&
!bcanputnext(ptm_rdq, mp->b_band)) {
DBG(("put msg. back on Q\n"));
(void) putbq(qp, mp);
break;
}
/*
! * Otherwise, send the message up manager's stream:
*/
! DBG(("send message to manager\n"));
putnext(ptm_rdq, mp);
}
DBG(("leaving ptswsrv\n"));
PT_EXIT_READ(ptsp);
return (0);