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