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);