* Be careful when reading this code, the terms "reading" and "writing" are
* used both for the tty (slave) and the pty (master) end of the pseudo tty.
* Writes to one end are to be read at the other end and vice-versa.
+ *
+ * In addition to the above, PTY service now also supports Unix98 pseudo-
+ * terminal pairs, thereby allowing non-root users to allocate pseudoterminals.
+ * It requires the presence for PTYFS for this, and supports only old-style
+ * ptys when PTYFS is not running. For Unix98 ptys, the general idea is that a
+ * userland program opens a pty master by opening /dev/ptmx through the use of
+ * posxix_openpt(3). A slave node is allocated on PTYFS when the program calls
+ * grantpt(3) on the master. The program can then obtain the path name for the
+ * slave end through ptsname(3), and open the slave end using this path.
+ *
+ * Implementation-wise, the Unix98 and non-Unix98 pseudoterminals share the
+ * same pool of data structures, but use different ranges of minor numbers.
+ * Access to the two types may not be mixed, and thus, some parts of the code
+ * have checks to make sure a traditional slave is not opened for a master
+ * allocated through /dev/ptmx, etcetera.
*/
#include <minix/drivers.h>
+#include <paths.h>
#include <termios.h>
#include <assert.h>
#include <sys/termios.h>
#include <signal.h>
#include "tty.h"
+#include "ptyfs.h"
+
+/* Device node attributes used for Unix98 slave nodes. */
+#define UNIX98_MODE (S_IFCHR | 0620) /* crw--w---- */
+
+#define UNIX98_MASTER(index) (UNIX98_MINOR + (index) * 2)
+#define UNIX98_SLAVE(index) (UNIX98_MINOR + (index) * 2 + 1)
/* PTY bookkeeping structure, one per pty/tty pair. */
typedef struct pty {
/* select() data. */
unsigned int select_ops; /* Which operations do we want to know about? */
endpoint_t select_proc; /* Who wants to know about it? */
+ devminor_t select_minor; /* Which minor was being selected on? */
} pty_t;
#define TTY_ACTIVE 0x01 /* tty is open/active */
#define PTY_ACTIVE 0x02 /* pty is open/active */
#define TTY_CLOSED 0x04 /* tty side has closed down */
#define PTY_CLOSED 0x08 /* pty side has closed down */
+#define PTY_UNIX98 0x10 /* pty pair is Unix98 */
static pty_t pty_table[NR_PTYS]; /* PTY bookkeeping */
static ssize_t pty_master_write(devminor_t minor, u64_t position,
endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags,
cdev_id_t id);
+static int pty_master_ioctl(devminor_t minor, unsigned long request,
+ endpoint_t endpt, cp_grant_id_t grant, int flags,
+ endpoint_t user_endpt, cdev_id_t id);
static int pty_master_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id);
static int pty_master_select(devminor_t minor, unsigned int ops,
endpoint_t endpt);
.cdr_close = pty_master_close,
.cdr_read = pty_master_read,
.cdr_write = pty_master_write,
+ .cdr_ioctl = pty_master_ioctl,
.cdr_cancel = pty_master_cancel,
.cdr_select = pty_master_select
};
+/*===========================================================================*
+ * get_free_pty *
+ *===========================================================================*/
+static tty_t *get_free_pty(void)
+{
+/* Return a pointer to a free tty structure, or NULL if no tty is free. */
+ tty_t *tp;
+ pty_t *pp;
+
+ for (tp = &tty_table[0]; tp < &tty_table[NR_PTYS]; tp++) {
+ pp = tp->tty_priv;
+
+ if (!(pp->state & (PTY_ACTIVE | TTY_ACTIVE)))
+ return tp;
+ }
+
+ return NULL;
+}
+
/*===========================================================================*
* pty_master_open *
*===========================================================================*/
{
tty_t *tp;
pty_t *pp;
+ int r;
- assert(minor >= PTYPX_MINOR && minor < PTYPX_MINOR + NR_PTYS);
-
- if ((tp = line2tty(minor)) == NULL)
- return ENXIO;
- pp = tp->tty_priv;
-
- if (pp->state & PTY_ACTIVE)
- return EIO;
+ if (minor == PTMX_MINOR) {
+ /* /dev/ptmx acts as a cloning device. We return a free PTY master and
+ * mark it as a UNIX98 type.
+ */
+ if ((tp = get_free_pty()) == NULL)
+ return EAGAIN; /* POSIX says this is the right error code */
+
+ /* The following call has two purposes. First, we check right here
+ * whether PTYFS is running at all; if not, the PTMX device cannot be
+ * opened at all and userland can fall back to other allocation
+ * methods right away. Second, in the exceptional case that the PTY
+ * service is restarted while PTYFS keeps running, PTYFS may expose
+ * stale slave nodes, which are a security hole if not removed as soon
+ * as a new PTY pair is allocated.
+ */
+ if (ptyfs_clear(tp->tty_index) != OK)
+ return EAGAIN;
+
+ pp = tp->tty_priv;
+ pp->state |= PTY_UNIX98;
+
+ minor = UNIX98_MASTER(tp->tty_index);
+
+ r = CDEV_CLONED | minor;
+ } else {
+ /* There is no way to open Unix98 masters directly, except by messing
+ * with mknod. We disallow such tricks altogether, and thus, the rest
+ * of the code deals with opening a non-Unix98 master only.
+ */
+ if (minor < PTYPX_MINOR || minor >= PTYPX_MINOR + NR_PTYS)
+ return EIO;
+
+ if ((tp = line2tty(minor)) == NULL)
+ return ENXIO;
+ pp = tp->tty_priv;
+
+ /* For non-Unix98 PTYs, we allow the slave to be opened before the
+ * master, but the master may be opened only once. This is how userland
+ * is able to find a free non-Unix98 PTY pair.
+ */
+ if (pp->state & PTY_ACTIVE)
+ return EIO;
+ assert(!(pp->state & PTY_UNIX98));
+
+ r = OK;
+ }
pp->state |= PTY_ACTIVE;
+
pp->rdcum = 0;
pp->wrcum = 0;
- return OK;
+ return r;
+}
+
+/*===========================================================================*
+ * pty_reset *
+ *===========================================================================*/
+static void pty_reset(tty_t *tp)
+{
+/* Both sides of a PTY pair have been closed. Clean up its state. */
+ pty_t *pp;
+
+ pp = tp->tty_priv;
+
+ /* For Unix98 pairs, clean up the Unix98 slave node. It may never have been
+ * allocated, but we don't care. Ignore failures altogether.
+ */
+ if (pp->state & PTY_UNIX98)
+ (void)ptyfs_clear(tp->tty_index);
+
+ pp->state = 0;
}
/*===========================================================================*
pp = tp->tty_priv;
if ((pp->state & (TTY_ACTIVE | TTY_CLOSED)) != TTY_ACTIVE) {
- pp->state = 0;
+ pty_reset(tp);
} else {
pp->state |= PTY_CLOSED;
tp->tty_termios.c_ospeed = B0; /* cause EOF on slave side */
return EDONTREPLY; /* do suspend */
}
+/*===========================================================================*
+ * pty_master_ioctl *
+ *===========================================================================*/
+static int pty_master_ioctl(devminor_t minor, unsigned long request,
+ endpoint_t endpt, cp_grant_id_t grant, int flags,
+ endpoint_t user_endpt, cdev_id_t id)
+{
+ tty_t *tp;
+ pty_t *pp;
+ uid_t uid;
+ struct ptmget pm;
+ size_t len;
+
+ if ((tp = line2tty(minor)) == NULL)
+ return ENXIO;
+ pp = tp->tty_priv;
+
+ /* Some IOCTLs are for the master side only. */
+ switch (request) {
+ case TIOCGRANTPT: /* grantpt(3) */
+ if (!(pp->state & PTY_UNIX98))
+ break;
+
+ if ((int)(uid = getnuid(user_endpt)) == -1)
+ return EACCES;
+ if (tty_gid == -1) {
+ printf("PTY: no tty group ID given at startup\n");
+ return EACCES;
+ }
+
+ /* Create or update the slave node. */
+ if (ptyfs_set(tp->tty_index, UNIX98_MODE, uid, tty_gid,
+ makedev(PTY_MAJOR, UNIX98_SLAVE(tp->tty_index))) != OK)
+ return EACCES;
+
+ return OK;
+
+ case TIOCPTSNAME: /* ptsname(3) */
+ if (!(pp->state & PTY_UNIX98))
+ break;
+
+ /* Since pm.sn is 16 bytes, we can have up to a million slaves. */
+ memset(&pm, 0, sizeof(pm));
+
+ strlcpy(pm.sn, _PATH_DEV_PTS, sizeof(pm.sn));
+ len = strlen(pm.sn);
+
+ if (ptyfs_name(tp->tty_index, &pm.sn[len], sizeof(pm.sn) - len) != OK)
+ return EINVAL;
+
+ return sys_safecopyto(endpt, grant, 0, (vir_bytes)&pm, sizeof(pm));
+ }
+
+ /* TODO: historically, all IOCTLs on the master are processed as if issued on
+ * the slave end. Make sure that this can not cause problems, in particular
+ * with blocking IOCTLs.
+ */
+ return tty_ioctl(minor, request, endpt, grant, flags, user_endpt, id);
+}
+
/*===========================================================================*
* pty_master_cancel *
*===========================================================================*/
void select_retry_pty(tty_t *tp)
{
pty_t *pp = tp->tty_priv;
- devminor_t minor;
int r;
/* See if the pty side of a pty is ready to return a select. */
if (pp->select_ops && (r = select_try_pty(tp, pp->select_ops))) {
- minor = PTYPX_MINOR + (int) (pp - pty_table);
- chardriver_reply_select(pp->select_proc, minor, r);
+ chardriver_reply_select(pp->select_proc, pp->select_minor, r);
pp->select_ops &= ~r;
}
}
if (ops && watch) {
pp->select_ops |= ops;
pp->select_proc = endpt;
+ pp->select_minor = minor;
}
return ready_ops;
return 0;
}
+/*===========================================================================*
+ * pty_slave_mayopen *
+ *===========================================================================*/
+static int pty_slave_mayopen(tty_t *tp, devminor_t line)
+{
+/* Check if the user is not mixing Unix98 and non-Unix98 terminal ends. */
+ pty_t *pp;
+ int unix98_line, unix98_pty;
+
+ pp = tp->tty_priv;
+
+ /* A non-Unix98 slave may be opened even if the corresponding master is not
+ * opened yet, but PTY_UNIX98 is always clear for free ptys. A Unix98 slave
+ * may not be opened before its master, but this should not occur anyway.
+ */
+ unix98_line = (line >= UNIX98_MINOR && line < UNIX98_MINOR + NR_PTYS * 2);
+ unix98_pty = !!(pp->state & PTY_UNIX98);
+
+ return (unix98_line == unix98_pty);
+}
+
/*===========================================================================*
* pty_slave_open *
*===========================================================================*/
/* The tty side has been opened. */
pty_t *pp = tp->tty_priv;
- assert(tp->tty_minor >= TTYPX_MINOR && tp->tty_minor < TTYPX_MINOR + NR_PTYS);
-
/* TTY_ACTIVE may already be set, which would indicate that the slave is
* reopened after being fully closed while the master is still open. In that
* case TTY_CLOSED will also be set, so clear that one.
pp->wrcaller = NONE;
}
- if (pp->state & PTY_CLOSED) pp->state = 0;
+ if (pp->state & PTY_CLOSED) pty_reset(tp);
else pp->state |= TTY_CLOSED;
return 0;
tp->tty_echo = pty_slave_echo;
tp->tty_icancel = pty_slave_icancel;
tp->tty_ocancel = pty_slave_ocancel;
+ tp->tty_mayopen = pty_slave_mayopen;
tp->tty_open = pty_slave_open;
tp->tty_close = pty_slave_close;
tp->tty_select_ops = 0;
--- /dev/null
+/* PTYFS - file system for Unix98 pseudoterminal slave nodes (/dev/pts) */
+
+#include <minix/drivers.h>
+#include <minix/fsdriver.h>
+#include <minix/vfsif.h>
+#include <minix/ds.h>
+#include <sys/dirent.h>
+#include <assert.h>
+
+#include "node.h"
+
+#define ROOT_INO_NR 1 /* inode number of the root directory */
+#define BASE_INO_NR 2 /* first inode number for slave nodes */
+
+#define GETDENTS_BUF 1024 /* size of the temporary buffer for getdents */
+
+static struct node_data root_data = {
+ .mode = S_IFDIR | 0755,
+ .uid = 0,
+ .gid = 0,
+ .dev = NO_DEV
+};
+
+/*
+ * Mount the file system.
+ */
+static int
+ptyfs_mount(dev_t __unused dev, unsigned int flags,
+ struct fsdriver_node * root_node, unsigned int * res_flags)
+{
+
+ /* This file system can not be used as a root file system. */
+ if (flags & REQ_ISROOT)
+ return EINVAL;
+
+ /* Return the details of the root node. */
+ root_node->fn_ino_nr = ROOT_INO_NR;
+ root_node->fn_mode = root_data.mode;
+ root_node->fn_uid = root_data.uid;
+ root_node->fn_gid = root_data.gid;
+ root_node->fn_size = 0;
+ root_node->fn_dev = root_data.dev;
+
+ *res_flags = RES_NOFLAGS;
+
+ return OK;
+}
+
+/*
+ * Generate the name string of a slave node based on its node number. Return
+ * OK on success, with the null-terminated name stored in the buffer 'name'
+ * which is 'size' bytes in size. Return an error code on failure.
+ */
+static int
+make_name(char * name, size_t size, node_t index)
+{
+ ssize_t r;
+
+ if ((r = snprintf(name, sizeof(name), "%u", index)) < 0)
+ return EINVAL;
+
+ if (r >= size)
+ return ENAMETOOLONG;
+
+ return OK;
+}
+
+/*
+ * Parse the name of a slave node as given by a user, and check whether it is a
+ * valid slave node number. A valid slave number is any name that can be
+ * produced by make_name(). Return TRUE if the string was successfully parsed
+ * as a slave node number (which may or may not actually be allocated), with
+ * the number stored in 'indexp'. Return FALSE if the name is not a number.
+ */
+static int
+parse_name(const char * name, node_t * indexp)
+{
+ node_t index;
+ const char *p;
+
+ index = 0;
+ for (p = name; *p; p++) {
+ /* Digits only. */
+ if (*p < '0' || *p > '9')
+ return FALSE;
+
+ /* No leading zeroes. */
+ if (p != name && index == 0)
+ return FALSE;
+
+ /* No overflow. */
+ if (index * 10 < index)
+ return FALSE;
+
+ index = index * 10 + *p - '0';
+ }
+
+ *indexp = index;
+ return TRUE;
+}
+
+/*
+ * Look up a name in a directory, yielding a node on success. For a successful
+ * lookup, the given name must either be a single dot, which resolves to the
+ * file system root directory, or the number of an allocated slave node.
+ */
+static int
+ptyfs_lookup(ino_t dir_nr, char * name, struct fsdriver_node * node,
+ int * is_mountpt)
+{
+ struct node_data *data;
+ node_t index;
+ ino_t ino_nr;
+
+ assert(name[0] != '\0');
+
+ if (dir_nr != ROOT_INO_NR)
+ return ENOENT;
+
+ if (name[0] == '.' && name[1] == '\0') {
+ /* The root directory itself is requested. */
+ ino_nr = ROOT_INO_NR;
+
+ data = &root_data;
+ } else {
+ /* Parse the user-provided name, which must be a number. */
+ if (!parse_name(name, &index))
+ return ENOENT;
+
+ ino_nr = BASE_INO_NR + index;
+
+ /* See if the number is in use, and get its details. */
+ if ((data = get_node(index)) == NULL)
+ return ENOENT;
+ }
+
+ node->fn_ino_nr = ino_nr;
+ node->fn_mode = data->mode;
+ node->fn_uid = data->uid;
+ node->fn_gid = data->gid;
+ node->fn_size = 0;
+ node->fn_dev = data->dev;
+
+ *is_mountpt = FALSE;
+
+ return OK;
+}
+
+/*
+ * Enumerate directory contents.
+ */
+static ssize_t
+ptyfs_getdents(ino_t ino_nr, struct fsdriver_data * data,
+ size_t bytes, off_t * posp)
+{
+ struct fsdriver_dentry fsdentry;
+ static char buf[GETDENTS_BUF];
+ char name[NAME_MAX + 1];
+ struct node_data *node_data;
+ unsigned int type;
+ off_t pos;
+ node_t index;
+ ssize_t r;
+
+ if (ino_nr != ROOT_INO_NR)
+ return EINVAL;
+
+ fsdriver_dentry_init(&fsdentry, data, bytes, buf, sizeof(buf));
+
+ for (;;) {
+ pos = (*posp)++;
+
+ if (pos < 2) {
+ strlcpy(name, (pos == 0) ? "." : "..", sizeof(name));
+ ino_nr = ROOT_INO_NR;
+ type = DT_DIR;
+ } else {
+ if (pos - 2 >= get_max_node())
+ break; /* EOF */
+ index = (node_t)(pos - 2);
+
+ if ((node_data = get_node(index)) == NULL)
+ continue; /* index not in use */
+
+ if (make_name(name, sizeof(name), index) != OK)
+ continue; /* could not generate name string */
+ ino_nr = BASE_INO_NR + index;
+ type = IFTODT(node_data->mode);
+ }
+
+ if ((r = fsdriver_dentry_add(&fsdentry, ino_nr, name,
+ strlen(name), type)) < 0)
+ return r;
+ if (r == 0)
+ break; /* result buffer full */
+ }
+
+ return fsdriver_dentry_finish(&fsdentry);
+}
+
+/*
+ * Return a pointer to the node data structure for the given inode number, or
+ * NULL if no node exists for the given inode number.
+ */
+static struct node_data *
+get_data(ino_t ino_nr)
+{
+ node_t index;
+
+ if (ino_nr == ROOT_INO_NR)
+ return &root_data;
+
+ if (ino_nr < BASE_INO_NR || ino_nr >= BASE_INO_NR + get_max_node())
+ return NULL;
+
+ index = (node_t)(ino_nr - BASE_INO_NR);
+
+ return get_node(index);
+}
+
+/*
+ * Change file ownership.
+ */
+static int
+ptyfs_chown(ino_t ino_nr, uid_t uid, gid_t gid, mode_t * mode)
+{
+ struct node_data *data;
+
+ if ((data = get_data(ino_nr)) == NULL)
+ return EINVAL;
+
+ data->uid = uid;
+ data->gid = gid;
+ data->mode &= ~(S_ISUID | S_ISGID);
+
+ *mode = data->mode;
+
+ return OK;
+}
+
+/*
+ * Change file mode.
+ */
+static int
+ptyfs_chmod(ino_t ino_nr, mode_t * mode)
+{
+ struct node_data *data;
+
+ if ((data = get_data(ino_nr)) == NULL)
+ return EINVAL;
+
+ data->mode = (data->mode & ~ALLPERMS) | (*mode & ALLPERMS);
+
+ *mode = data->mode;
+
+ return OK;
+}
+
+/*
+ * Return node details.
+ */
+static int
+ptyfs_stat(ino_t ino_nr, struct stat * buf)
+{
+ struct node_data *data;
+
+ if ((data = get_data(ino_nr)) == NULL)
+ return EINVAL;
+
+ buf->st_mode = data->mode;
+ buf->st_uid = data->uid;
+ buf->st_gid = data->gid;
+ buf->st_nlink = S_ISDIR(data->mode) ? 2 : 1;
+ buf->st_rdev = data->dev;
+ buf->st_atime = data->ctime;
+ buf->st_mtime = data->ctime;
+ buf->st_ctime = data->ctime;
+
+ return OK;
+}
+
+/*
+ * Return file system statistics.
+ */
+static int
+ptyfs_statvfs(struct statvfs * buf)
+{
+
+ buf->f_flag = ST_NOTRUNC;
+ buf->f_namemax = NAME_MAX;
+
+ return OK;
+}
+
+/*
+ * Process non-filesystem messages, in particular slave node creation and
+ * deletion requests from the PTY service.
+ */
+static void
+ptyfs_other(const message * m_ptr, int ipc_status)
+{
+ char label[DS_MAX_KEYLEN];
+ struct node_data data;
+ message m_reply;
+ int r;
+
+ /*
+ * We only accept requests from the service with the label "pty".
+ * More sophisticated access checks are part of future work.
+ */
+ if ((r = ds_retrieve_label_name(label, m_ptr->m_source)) != OK) {
+ printf("PTYFS: unable to obtain label for %u (%d)\n",
+ m_ptr->m_source, r);
+ return;
+ }
+
+ if (strcmp(label, "pty")) {
+ printf("PTYFS: unexpected request %x from %s/%u\n",
+ m_ptr->m_type, label, m_ptr->m_source);
+ return;
+ }
+
+ /* Process the request from PTY. */
+ memset(&m_reply, 0, sizeof(m_reply));
+
+ switch (m_ptr->m_type) {
+ case PTYFS_SET:
+ memset(&data, 0, sizeof(data));
+ data.dev = m_ptr->m_pty_ptyfs_req.dev;
+ data.mode = m_ptr->m_pty_ptyfs_req.mode;
+ data.uid = m_ptr->m_pty_ptyfs_req.uid;
+ data.gid = m_ptr->m_pty_ptyfs_req.gid;
+ data.ctime = clock_time(NULL);
+
+ r = set_node(m_ptr->m_pty_ptyfs_req.index, &data);
+
+ break;
+
+ case PTYFS_CLEAR:
+ clear_node(m_ptr->m_pty_ptyfs_req.index);
+ r = OK;
+
+ break;
+
+ case PTYFS_NAME:
+ r = make_name(m_reply.m_ptyfs_pty_name.name,
+ sizeof(m_reply.m_ptyfs_pty_name.name),
+ m_ptr->m_pty_ptyfs_req.index);
+
+ break;
+
+ default:
+ printf("PTYFS: invalid request %x from PTY\n", m_ptr->m_type);
+ r = ENOSYS;
+ }
+
+ /*
+ * Send a reply to the request. In particular slave node addition
+ * requests must be blocking for the PTY service, so as to avoid race
+ * conditions between PTYFS creating the slave node and userland trying
+ * to open it.
+ */
+ m_reply.m_type = r;
+
+ if (IPC_STATUS_CALL(ipc_status) == SENDREC)
+ r = ipc_sendnb(m_ptr->m_source, &m_reply);
+ else
+ r = asynsend3(m_ptr->m_source, &m_reply, AMF_NOREPLY);
+
+ if (r != OK)
+ printf("PTYFS: unable to reply to PTY (%d)\n", r);
+}
+
+/*
+ * Initialize the service.
+ */
+static int
+ptyfs_init(int __unused type, sef_init_info_t * __unused info)
+{
+
+ init_nodes();
+
+ root_data.ctime = clock_time(NULL);
+
+ return OK;
+}
+
+/*
+ * Process an incoming signal.
+ */
+static void
+ptyfs_signal(int sig)
+{
+
+ if (sig == SIGTERM)
+ fsdriver_terminate();
+}
+
+/*
+ * Perform SEF initialization.
+ */
+static void
+ptyfs_startup(void)
+{
+
+ sef_setcb_init_fresh(ptyfs_init);
+ sef_setcb_signal_handler(ptyfs_signal);
+ sef_startup();
+}
+
+static struct fsdriver ptyfs_table = {
+ .fdr_mount = ptyfs_mount,
+ .fdr_lookup = ptyfs_lookup,
+ .fdr_getdents = ptyfs_getdents,
+ .fdr_stat = ptyfs_stat,
+ .fdr_chown = ptyfs_chown,
+ .fdr_chmod = ptyfs_chmod,
+ .fdr_statvfs = ptyfs_statvfs,
+ .fdr_other = ptyfs_other
+};
+
+/*
+ * The PTYFS service.
+ */
+int
+main(void)
+{
+
+ ptyfs_startup();
+
+ fsdriver_task(&ptyfs_table);
+
+ return 0;
+}
/* Tests for opening/closing pseudo terminals - by D.C. van Moolenbroek */
-/* This test needs to be run as root; otherwise, openpty() won't work. */
+/*
+ * As of the introduction of Unix98 PTY support, this test set actually relies
+ * on the ability to create Unix98 PTYs. The system still supports old-style
+ * PTYs but there is no way to force openpty(3) to use them. However, part of
+ * this test set can still be used to test old-style PTYs: first disable Unix98
+ * PTYs, for example by unmounting PTYFS or temporarily removing /dev/ptmx, and
+ * then run the a-f subtests from this test set as root.
+ */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/syslimits.h>
#include <paths.h>
+#include <dirent.h>
+#include <grp.h>
#include <fcntl.h>
#include <util.h>
#define ITERATIONS 10
+#define MIN_PTYS 4
+
#include "common.h"
static int sighups; /* number of SIGHUP signals received */
{
struct termios tios;
- if (tcgetattr(slavefd, &tios) < 0) e(100);
+ if (tcgetattr(slavefd, &tios) < 0) e(0);
cfmakeraw(&tios);
- if (tcsetattr(slavefd, TCSANOW, &tios) < 0) e(101);
+ if (tcsetattr(slavefd, TCSANOW, &tios) < 0) e(0);
}
/*
make_raw(slavefd);
c = 'A';
- if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(200);
- if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(201);
- if (c != 'A') e(202);
+ if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'A') e(0);
c = 'B';
- if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(203);
- if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(204);
- if (c != 'B') e(205);
+ if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'B') e(0);
c = 'C';
- if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(206);
- if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(207);
- if (c != 'C') e(208);
+ if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'C') e(0);
c = 'D';
- if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(209);
- if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(210);
- if (c != 'D') e(211);
+ if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'D') e(0);
}
/*
- * Get device node names for the master and slave end of a free pseudo
- * terminal. We don't want to replicate the entire openpty(3) logic here, so
- * start by letting openpty(3) do the work for us. We make the assumption that
- * nobody snatches the pair while we are running.
+ * Obtain a pseudo terminal. The master end is opened and its file descriptor
+ * stored in 'pfd'. The slave path name is stored in 'tname'. For old-style
+ * PTYs, the function returns 1 and stores the master name in 'pname' if not
+ * NULL. For Unix98 PTYs, the function returns 0, in which case no master name
+ * is available. For old-style PTYs, the caller may close and reopen the
+ * master. In that case, we make the assumption that nobody snatches the pair
+ * while we are running. For Unix98 PTYs, the master must be kept open.
*/
-static void
-get_names(char pname[PATH_MAX], char tname[PATH_MAX])
+static int
+get_pty(int *pfd, char pname[PATH_MAX], char tname[PATH_MAX])
{
+ char *name;
int len, masterfd, slavefd;
- if (openpty(&masterfd, &slavefd, tname, NULL, NULL) < 0) e(300);
+ /*
+ * First try Unix98 PTY allocation, mainly to avoid opening the slave
+ * end immediately. If this fails, try openpty(3) as well.
+ */
+ if ((masterfd = posix_openpt(O_RDWR | O_NOCTTY)) != -1) {
+ if (grantpt(masterfd) != -1 && unlockpt(masterfd) != -1 &&
+ (name = ptsname(masterfd)) != NULL) {
+ *pfd = masterfd;
+ strlcpy(tname, name, PATH_MAX);
+
+ return 0;
+ }
+ if (close(masterfd) < 0) e(0);
+ }
+
+ if (openpty(&masterfd, &slavefd, tname, NULL, NULL) < 0) e(0);
+
+ test_comm(masterfd, slavefd);
+
+ *pfd = masterfd;
+
+ if (close(slavefd) < 0) e(0);
/*
- * openpty(3) gives us only the slave name, but we also need the master
+ * openpty(3) gives us only the slave name, but we also want the master
* name.
*/
- strlcpy(pname, tname, PATH_MAX);
len = strlen(_PATH_DEV);
+ if (strncmp(tname, _PATH_DEV, len)) e(0);
- if (strncmp(pname, _PATH_DEV, len)) e(301);
-
- /* If this fails, this test needs to be updated. */
- if (pname[len] != 't') e(302);
-
- pname[len] = 'p';
+ if (strncmp(&tname[len], "tty", 3))
+ return 0; /* Unix98 after all? Well okay, whatever.. */
- test_comm(masterfd, slavefd);
+ if (pname != NULL) {
+ strlcpy(pname, tname, PATH_MAX);
+ pname[len] = 'p';
+ }
- if (close(masterfd) < 0) e(303);
- if (close(slavefd) < 0) e(304);
+ return 1;
}
/*
* Test various orders of opening and closing the master and slave sides of a
* pseudo terminal, as well as opening/closing one side without ever opening
- * the other.
+ * the other. This test is meaningful mainly for old-style pseudoterminals.
*/
static void
test77a(void)
{
struct sigaction act, oact;
char pname[PATH_MAX], tname[PATH_MAX];
- int masterfd, slavefd;
+ int oldstyle, masterfd, slavefd;
subtest = 1;
/* We do not want to get SIGHUP signals in this test. */
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
- if (sigaction(SIGHUP, &act, &oact) < 0) e(1);
+ if (sigaction(SIGHUP, &act, &oact) < 0) e(0);
- /* Get master and slave device names for a free pseudo terminal. */
- get_names(pname, tname);
+ /* Obtain a pseudo terminal. */
+ oldstyle = get_pty(&masterfd, pname, tname);
- /* Try opening and then closing the master. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(2);
+ if (oldstyle) {
+ /* Try closing the master. */
+ if (close(masterfd) < 0) e(0);
- if (close(masterfd) < 0) e(3);
+ /* See if we can reopen the master. */
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
+ }
- /* Now see if we can reopen the master as well as the slave. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(4);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(5);
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, slavefd);
/* In the meantime, test different closing orders. This is order A. */
- if (close(slavefd) < 0) e(6);
- if (close(masterfd) < 0) e(7);
+ if (close(slavefd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
- /* Now try opening the pair again. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(8);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(9);
+ /* Now try opening the pair (or a new pair) again. */
+ if (!oldstyle)
+ oldstyle = get_pty(&masterfd, pname, tname);
+ else
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
+
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(10);
+ if (close(slavefd) < 0) e(0);
/*
* Try reopening the slave after closing it. It is not very important
* that this works, but the TTY driver should currently support it.
*/
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(11);
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, slavefd);
/* This is closing order B. This may or may not cause a SIGHUP. */
- if (close(masterfd) < 0) e(12);
- if (close(slavefd) < 0) e(13);
+ if (close(masterfd) < 0) e(0);
+ if (close(slavefd) < 0) e(0);
/* Try the normal open procedure. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(14);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(15);
+ if (!oldstyle)
+ oldstyle = get_pty(&masterfd, pname, tname);
+ else
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
+
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(16);
- if (close(masterfd) < 0) e(17);
+ if (close(slavefd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
- /* Try reopening and closing the slave, without opening the master. */
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(18);
+ /*
+ * Try reopening and closing the slave, without opening the master.
+ * This should work on old-style PTYS, but not on Unix98 PTYs.
+ */
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) >= 0) {
+ if (!oldstyle) e(0);
- if (close(slavefd) < 0) e(19);
+ if (close(slavefd) < 0) e(0);
+ } else
+ if (oldstyle) e(0);
/* Again, try the normal open procedure. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(20);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(21);
+ if (!oldstyle)
+ oldstyle = get_pty(&masterfd, pname, tname);
+ else
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
+
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(22);
- if (close(masterfd) < 0) e(23);
+ if (close(slavefd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
- /* Finally, try opening the slave first. */
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(24);
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(25);
+ /*
+ * Finally, try opening the slave first. This does not work with
+ * Unix98 PTYs.
+ */
+ if (oldstyle) {
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
- test_comm(masterfd, slavefd);
+ test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(26);
- if (close(masterfd) < 0) e(27);
+ if (close(slavefd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
+ }
- if (sigaction(SIGHUP, &oact, NULL) < 0) e(28);
+ if (sigaction(SIGHUP, &oact, NULL) < 0) e(0);
}
/*
test77b(void)
{
char pname[PATH_MAX], tname[PATH_MAX];
- int masterfd, slavefd, extrafd;
+ int oldstyle, masterfd, slavefd, extrafd;
subtest = 2;
- /* Get master and slave device names for a free pseudo terminal. */
- get_names(pname, tname);
+ /* Obtain a pseudo terminal. */
+ oldstyle = get_pty(&masterfd, pname, tname);
- /* It must not be possible to open the master multiple times. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(1);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(2);
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
+ /*
+ * It must not be possible to open the master multiple times. Doing so
+ * is possible only if we have a named master, i.e., an old-style PTY.
+ */
test_comm(masterfd, slavefd);
- if ((extrafd = open(pname, O_RDWR | O_NOCTTY)) >= 0) e(3);
- if (errno != EIO) e(4);
+ if (oldstyle) {
+ if ((extrafd = open(pname, O_RDWR | O_NOCTTY)) >= 0) e(0);
+ if (errno != EIO) e(0);
+ }
test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(5);
- if (close(masterfd) < 0) e(6);
+ if (close(slavefd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
/* The slave can be opened multiple times, though. */
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(7);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(8);
+ oldstyle = get_pty(&masterfd, pname, tname);
+
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, slavefd);
- if ((extrafd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(9);
+ if ((extrafd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
test_comm(masterfd, extrafd);
test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(10);
- if (close(extrafd) < 0) e(11);
- if (close(masterfd) < 0) e(12);
+ if (close(slavefd) < 0) e(0);
+ if (close(extrafd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
}
/*
{
struct sigaction act, oact;
char pname[PATH_MAX], tname[PATH_MAX];
- int masterfd, slavefd;
+ int oldstyle, masterfd, slavefd;
char c;
subtest = 3;
/* We do not want to get SIGHUP signals in this test. */
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
- if (sigaction(SIGHUP, &act, &oact) < 0) e(1);
+ if (sigaction(SIGHUP, &act, &oact) < 0) e(0);
- /* Get master and slave device names for a free pseudo terminal. */
- get_names(pname, tname);
+ /* Obtain a pseudo terminal. */
+ oldstyle = get_pty(&masterfd, pname, tname);
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(2);
+ /*
+ * For old-style pseudo terminals, we have just opened and closed the
+ * slave end, which alters the behavior we are testing below. Close
+ * and reopen the master to start fresh.
+ */
+ if (oldstyle) {
+ if (close(masterfd) < 0) e(0);
+
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
+ }
/* Writes to the master should be buffered until there is a slave. */
c = 'E';
- if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(3);
+ if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(4);
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
make_raw(slavefd);
- if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(5);
- if (c != 'E') e(6);
+ if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'E') e(0);
/* Discard the echo on the master. */
- if (tcflush(slavefd, TCOFLUSH) != 0) e(7);
+ if (tcflush(slavefd, TCOFLUSH) != 0) e(0);
test_comm(masterfd, slavefd);
- if (close(slavefd) < 0) e(8);
+ if (close(slavefd) < 0) e(0);
/* Writes to the master after the slave has been closed should fail. */
- if (write(masterfd, &c, sizeof(c)) >= 0) e(9);
- if (errno != EIO) e(10);
+ if (write(masterfd, &c, sizeof(c)) >= 0) e(0);
+ if (errno != EIO) e(0);
- if (close(masterfd) < 0) e(11);
+ if (oldstyle)
+ if (close(masterfd) < 0) e(0);
- /* Writes to the slave should be buffered until there is a master. */
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(12);
+ /*
+ * Writes to the slave should be buffered until there is a master.
+ * This applies to old-style PTYs only.
+ */
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
- make_raw(slavefd);
+ if (oldstyle) {
+ make_raw(slavefd);
- c = 'F';
- if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(13);
+ c = 'F';
+ if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(14);
+ if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(0);
- if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(15);
- if (c != 'F') e(16);
+ if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'F') e(0);
+ }
test_comm(masterfd, slavefd);
- if (close(masterfd) < 0) e(17);
+ if (close(masterfd) < 0) e(0);
- if (write(slavefd, &c, sizeof(c)) >= 0) e(18);
- if (errno != EIO) e(19);
+ if (write(slavefd, &c, sizeof(c)) >= 0) e(0);
+ if (errno != EIO) e(0);
/* Reads from the slave should return EOF if the master is gone. */
- if (read(slavefd, &c, sizeof(c)) != 0) e(20);
+ if (read(slavefd, &c, sizeof(c)) != 0) e(0);
- if (close(slavefd) < 0) e(21);
+ if (close(slavefd) < 0) e(0);
- if (sigaction(SIGHUP, &oact, NULL) < 0) e(22);
+ if (sigaction(SIGHUP, &oact, NULL) < 0) e(0);
}
/*
subtest = 4;
- /* Get master and slave device names for a free pseudo terminal. */
- get_names(pname, tname);
-
/* Make ourselves process group leader if we aren't already. */
- (void) setsid();
+ (void)setsid();
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(1);
+ /* Obtain a pseudo terminal. */
+ (void)get_pty(&masterfd, NULL, tname);
/*
* Opening the slave with O_NOCTTY should not change its controlling
*/
switch (fork()) {
case 0:
- if (setsid() < 0) e(2);
+ if (close(masterfd) < 0) e(0);
+
+ if (setsid() < 0) e(0);
- if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(3);
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
- if (open("/dev/tty", O_RDWR) >= 0) e(4);
- if (errno != ENXIO) e(5);
+ if (open("/dev/tty", O_RDWR) >= 0) e(0);
+ if (errno != ENXIO) e(0);
exit(errct);
case -1:
- e(6);
+ e(0);
default:
break;
}
- if (waitchild() < 0) e(7);
+ if (waitchild() < 0) e(0);
- if (close(masterfd) < 0) e(8);
+ if (close(masterfd) < 0) e(0);
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(9);
+ (void)get_pty(&masterfd, pname, tname);
/*
* Opening the slave without O_NOCTTY should change its controlling
*/
switch (fork()) {
case 0:
- if (setsid() < 0) e(10);
+ if (close(masterfd) < 0) e(0);
+
+ if (setsid() < 0) e(0);
- if ((slavefd = open(tname, O_RDWR)) < 0) e(11);
+ if ((slavefd = open(tname, O_RDWR)) < 0) e(0);
- if (open("/dev/tty", O_RDWR) < 0) e(12);
+ if (open("/dev/tty", O_RDWR) < 0) e(0);
exit(errct);
case -1:
- e(13);
+ e(0);
default:
break;
}
- if (waitchild() < 0) e(14);
+ if (waitchild() < 0) e(0);
- if (close(masterfd) < 0) e(15);
+ if (close(masterfd) < 0) e(0);
}
/*
{
struct sigaction act, hup_oact, usr_oact;
sigset_t set, oset;
- char pname[PATH_MAX], tname[PATH_MAX];
+ char tname[PATH_MAX];
int masterfd, slavefd;
subtest = 5;
- /* Get master and slave device names for a free pseudo terminal. */
- get_names(pname, tname);
-
memset(&act, 0, sizeof(act));
act.sa_handler = signal_handler;
- if (sigaction(SIGHUP, &act, &hup_oact) < 0) e(1);
+ if (sigaction(SIGHUP, &act, &hup_oact) < 0) e(0);
memset(&act, 0, sizeof(act));
act.sa_handler = signal_handler;
- if (sigaction(SIGUSR1, &act, &usr_oact) < 0) e(2);
+ if (sigaction(SIGUSR1, &act, &usr_oact) < 0) e(0);
sigemptyset(&set);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGUSR1);
- if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) e(3);
+ if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) e(0);
sighups = 0;
/* Make ourselves process group leader if we aren't already. */
- (void) setsid();
+ (void)setsid();
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(4);
+ /* Obtain a pseudo terminal. */
+ (void)get_pty(&masterfd, NULL, tname);
switch (fork()) {
case 0:
- if (close(masterfd) < 0) e(5);
+ if (close(masterfd) < 0) e(0);
/* Become session leader. */
- if (setsid() < 0) e(6);
+ if (setsid() < 0) e(0);
- if ((slavefd = open(tname, O_RDWR)) < 0) e(7);
+ if ((slavefd = open(tname, O_RDWR)) < 0) e(0);
/* Tell the parent we are ready. */
kill(getppid(), SIGUSR1);
/* We should now get a SIGHUP. */
set = oset;
- if (sigsuspend(&set) >= 0) e(8);
+ if (sigsuspend(&set) >= 0) e(0);
- if (sighups != 1) e(9);
+ if (sighups != 1) e(0);
exit(errct);
case -1:
- e(10);
+ e(0);
default:
break;
}
/* Wait for SIGUSR1 from the child. */
set = oset;
- if (sigsuspend(&set) >= 0) e(11);
+ if (sigsuspend(&set) >= 0) e(0);
/* Closing the master should now raise a SIGHUP signal in the child. */
- if (close(masterfd) < 0) e(12);
+ if (close(masterfd) < 0) e(0);
- if (waitchild() < 0) e(13);
+ if (waitchild() < 0) e(0);
- if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) e(14);
+ if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) e(0);
- if (sigaction(SIGHUP, &hup_oact, NULL) < 0) e(15);
- if (sigaction(SIGUSR1, &usr_oact, NULL) < 0) e(16);
+ if (sigaction(SIGHUP, &hup_oact, NULL) < 0) e(0);
+ if (sigaction(SIGUSR1, &usr_oact, NULL) < 0) e(0);
}
/*
test77f(void)
{
struct sigaction act, oact;
- char c, pname[PATH_MAX], tname[PATH_MAX];
+ char c, tname[PATH_MAX];
struct timeval tv;
fd_set fd_set;
int fd, maxfd, masterfd, slavefd;
/* We do not want to get SIGHUP signals in this test. */
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
- if (sigaction(SIGHUP, &act, &oact) < 0) e(1);
+ if (sigaction(SIGHUP, &act, &oact) < 0) e(0);
- /* Get master and slave device names for a free pseudo terminal. */
- get_names(pname, tname);
-
- if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(2);
+ /* Obtain a pseudo terminal. */
+ (void)get_pty(&masterfd, NULL, tname);
switch (fork()) {
case 0:
- if (setsid() < 0) e(3);
+ if (close(masterfd) < 0) e(0);
- close(masterfd);
+ if (setsid() < 0) e(0);
- if ((slavefd = open(tname, O_RDWR)) < 0) e(4);
+ if ((slavefd = open(tname, O_RDWR)) < 0) e(0);
- if ((fd = open("/dev/tty", O_RDWR)) < 0) e(5);
+ if ((fd = open("/dev/tty", O_RDWR)) < 0) e(0);
make_raw(fd);
tv.tv_sec = 0;
tv.tv_usec = 0;
- if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(6);
- if (FD_ISSET(fd, &fd_set)) e(7);
+ if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(0);
+ if (FD_ISSET(fd, &fd_set)) e(0);
FD_SET(fd, &fd_set);
tv.tv_sec = 0;
tv.tv_usec = 10000;
- if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(8);
- if (FD_ISSET(fd, &fd_set)) e(9);
+ if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(0);
+ if (FD_ISSET(fd, &fd_set)) e(0);
/* It will be ready for writing, though. */
FD_SET(fd, &fd_set);
- if (select(fd + 1, NULL, &fd_set, NULL, NULL) != 1) e(10);
- if (!FD_ISSET(fd, &fd_set)) e(11);
+ if (select(fd + 1, NULL, &fd_set, NULL, NULL) != 1) e(0);
+ if (!FD_ISSET(fd, &fd_set)) e(0);
/* Test mixing file descriptors to the same terminal. */
FD_ZERO(&fd_set);
tv.tv_usec = 10000;
maxfd = fd > slavefd ? fd : slavefd;
- if (select(maxfd + 1, &fd_set, NULL, NULL, &tv) != 0) e(12);
- if (FD_ISSET(fd, &fd_set)) e(13);
- if (FD_ISSET(slavefd, &fd_set)) e(14);
+ if (select(maxfd + 1, &fd_set, NULL, NULL, &tv) != 0) e(0);
+ if (FD_ISSET(fd, &fd_set)) e(0);
+ if (FD_ISSET(slavefd, &fd_set)) e(0);
/* The delayed echo on the master must wake up our select. */
c = 'A';
- if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(15);
+ if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
FD_ZERO(&fd_set);
FD_SET(fd, &fd_set);
- if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(16);
- if (!FD_ISSET(fd, &fd_set)) e(17);
+ if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(0);
+ if (!FD_ISSET(fd, &fd_set)) e(0);
/* Select must now still flag readiness for reading. */
tv.tv_sec = 0;
tv.tv_usec = 0;
- if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 1) e(18);
- if (!FD_ISSET(fd, &fd_set)) e(19);
+ if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 1) e(0);
+ if (!FD_ISSET(fd, &fd_set)) e(0);
/* That is, until we read the byte. */
- if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(20);
- if (c != 'B') e(21);
+ if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'B') e(0);
- if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(22);
- if (FD_ISSET(fd, &fd_set)) e(23);
+ if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(0);
+ if (FD_ISSET(fd, &fd_set)) e(0);
/* Ask the parent to close the master. */
c = 'C';
- if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(24);
+ if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(0);
FD_SET(fd, &fd_set);
/* The closure must cause an EOF condition on the slave. */
- if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(25);
- if (!FD_ISSET(fd, &fd_set)) e(26);
+ if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(0);
+ if (!FD_ISSET(fd, &fd_set)) e(0);
- if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(27);
- if (!FD_ISSET(fd, &fd_set)) e(28);
+ if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(0);
+ if (!FD_ISSET(fd, &fd_set)) e(0);
- if (read(slavefd, &c, sizeof(c)) != 0) e(29);
+ if (read(slavefd, &c, sizeof(c)) != 0) e(0);
exit(errct);
case -1:
- e(30);
+ e(0);
default:
/* Wait for the child to write something to the slave. */
FD_ZERO(&fd_set);
FD_SET(masterfd, &fd_set);
if (select(masterfd + 1, &fd_set, NULL, NULL, NULL) != 1)
- e(31);
- if (!FD_ISSET(masterfd, &fd_set)) e(32);
+ e(0);
+ if (!FD_ISSET(masterfd, &fd_set)) e(0);
- if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(33);
- if (c != 'A') e(34);
+ if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'A') e(0);
/* Write a reply once the child is blocked in its select. */
tv.tv_sec = 1;
tv.tv_usec = 0;
if (select(masterfd + 1, &fd_set, NULL, NULL, &tv) != 0)
- e(35);
+ e(0);
c = 'B';
- if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(36);
+ if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
/* Wait for the child to request closing the master. */
- if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(37);
- if (c != 'C') e(38);
+ if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(0);
+ if (c != 'C') e(0);
/* Close the master once the child is blocked in its select. */
sleep(1);
break;
}
- if (waitchild() < 0) e(39);
+ if (waitchild() < 0) e(0);
+
+ if (sigaction(SIGHUP, &oact, NULL) < 0) e(0);
+}
+
+/*
+ * See if the directory contents of /dev/pts are as we expect. We have to keep
+ * in mind that other programs may have pseudo terminals open while we are
+ * running, although we assume that those programs do not open or close PTYs
+ * while we are running.
+ */
+static void
+test_getdents(int nindex, int array[3], int present[3])
+{
+ struct group *group;
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat buf;
+ char path[PATH_MAX], *endp;
+ gid_t tty_gid;
+ int i, n, seen_dot, seen_dotdot, seen_index[3], *seen;
+
+ seen_dot = seen_dotdot = 0;
+ for (i = 0; i < nindex; i++)
+ seen_index[i] = 0;
+
+ if ((group = getgrnam("tty")) == NULL) e(0);
+ tty_gid = group->gr_gid;
+
+ if ((dirp = opendir(_PATH_DEV_PTS)) == NULL) e(0);
+
+ while ((dp = readdir(dirp)) != NULL) {
+ snprintf(path, sizeof(path), _PATH_DEV_PTS "%s", dp->d_name);
+ if (stat(path, &buf) < 0) e(0);
+
+ if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
+ seen =
+ (dp->d_name[1] == '.') ? &seen_dot : &seen_dotdot;
+ if (*seen) e(0);
+ *seen = 1;
+
+ /* Check basic dirent and stat fields. */
+ if (dp->d_type != DT_DIR) e(0);
+ if (dp->d_name[1] == '\0' &&
+ buf.st_ino != dp->d_fileno) e(0);
+ if (!S_ISDIR(buf.st_mode)) e(0);
+ if (buf.st_nlink < 2) e(0);
+ } else {
+ /* The file name must be a number. */
+ errno = 0;
+ n = strtol(dp->d_name, &endp, 10);
+ if (errno != 0) e(0);
+ if (dp->d_name[0] == '\0' || *endp != '\0') e(0);
+ if (n < 0) e(0);
+
+ /* Check basic dirent and stat fields. */
+ if (dp->d_type != DT_CHR) e(0);
+ if (buf.st_ino != dp->d_fileno) e(0);
+ if (!S_ISCHR(buf.st_mode)) e(0);
+ if (buf.st_nlink != 1) e(0);
+ if (buf.st_size != 0) e(0);
+ if (buf.st_rdev == 0) e(0);
+
+ /* Is this one of the PTYs we created? */
+ for (i = 0; i < nindex; i++) {
+ if (array[i] == n) {
+ if (seen_index[i]) e(0);
+ seen_index[i] = 1;
+
+ break;
+ }
+ }
+
+ /* If so, perform some extra tests. */
+ if (i < nindex) {
+ if ((buf.st_mode & ALLPERMS) != 0620) e(0);
+ if (buf.st_uid != getuid()) e(0);
+ if (buf.st_gid != tty_gid) e(0);
+ }
+ }
+ }
+
+ if (closedir(dirp) < 0) e(0);
+
+ if (!seen_dot) e(0);
+ if (!seen_dotdot) e(0);
+ for (i = 0; i < nindex; i++)
+ if (seen_index[i] != present[i]) e(0);
+}
+
+/*
+ * Obtain a Unix98 PTY. Return an open file descriptor for the master side,
+ * and store the name of the slave side in 'tptr'.
+ */
+static int
+get_unix98_pty(char ** tptr)
+{
+ int masterfd;
+
+ if ((masterfd = posix_openpt(O_RDWR | O_NOCTTY)) < 0) e(0);
+
+ if (grantpt(masterfd) < 0) e(0);
+
+ /* This call is a no-op on MINIX3. */
+ if (unlockpt(masterfd) < 0) e(0);
+
+ if ((*tptr = ptsname(masterfd)) == NULL) e(0);
+
+ return masterfd;
+}
+
+/*
+ * Test for Unix98 PTY support and PTYFS.
+ */
+static void
+test77g(void)
+{
+ char *tname;
+ struct stat buf;
+ size_t len;
+ int i, masterfd, slavefd, fd[3], array[3], present[3];
+
+ subtest = 7;
+
+ /*
+ * Test basic operation, and verify that the slave node disappears
+ * after both sides of the pseudo terminal have been closed. We check
+ * different combinations of open master and slave ends (with 'i'):
+ * 0) opening and closing the master only, 1) closing a slave before
+ * the master, and 2) closing the slave after the master.
+ */
+ for (i = 0; i <= 2; i++) {
+ masterfd = get_unix98_pty(&tname);
+
+ if (access(tname, R_OK | W_OK) < 0) e(0);
+
+ if (i > 0) {
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0)
+ e(0);
+
+ if (access(tname, R_OK | W_OK) < 0) e(0);
+
+ if (i > 1) {
+ if (close(masterfd) < 0) e(0);
+
+ masterfd = slavefd; /* ugly but saving code */
+ } else
+ if (close(slavefd) < 0) e(0);
+ }
+
+ if (access(tname, R_OK | W_OK) < 0) e(0);
+
+ if (close(masterfd) < 0) e(0);
+
+ if (access(tname, R_OK | W_OK) == 0) e(0);
+ }
+
+ /*
+ * Test whether we can open multiple pseudo terminals. We need to be
+ * able to open three PTYs. Verify that they are properly listed in
+ * the /dev/pts directory contents, and have proper attributes set.
+ */
+ test_getdents(0, NULL, NULL);
+
+ for (i = 0; i < 3; i++) {
+ fd[i] = get_unix98_pty(&tname);
+
+ /* Figure out the slave index number. */
+ len = strlen(_PATH_DEV_PTS);
+ if (strncmp(tname, _PATH_DEV_PTS, strlen(_PATH_DEV_PTS))) e(0);
+ array[i] = atoi(&tname[len]);
+ present[i] = 1;
+ }
+
+ test_getdents(3, array, present);
+
+ if (close(fd[0]) < 0) e(0);
+ present[0] = 0;
+
+ test_getdents(3, array, present);
+
+ if (close(fd[2]) < 0) e(0);
+ present[2] = 0;
+
+ test_getdents(3, array, present);
+
+ if (close(fd[1]) < 0) e(0);
+ present[1] = 0;
+
+ test_getdents(3, array, present);
+
+ /*
+ * Test chmod(2) on a slave node, and multiple calls to grantpt(3).
+ * The first grantpt(3) call should create the slave node (we currently
+ * can not test this: the slave node may be created earlier as well,
+ * but we do not know its name), whereas subsequent grantpt(3) calls
+ * should reset its mode, uid, and gid. Testing the latter two and
+ * chown(2) on the slave node requires root, so we skip that part.
+ *
+ * Finally, NetBSD revokes access to existing slave file descriptors
+ * upon a call to grantpt(3). This is not a POSIX requirement, but
+ * NetBSD needs this for security reasons because it already creates
+ * the slave node when the master is opened (and it does not lock the
+ * slave until a call to unlockpt(3)). MINIX3 does not implement
+ * revocation this way, because the slave node is created only upon the
+ * call to grantpt(3), thus leaving no insecure window for the slave
+ * side between posix_openpt(3) and grantpt(3). While this behavior
+ * may be changed later, we test for the lack of revocation here now.
+ */
+ masterfd = get_unix98_pty(&tname);
+
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
+
+ if (stat(tname, &buf) != 0) e(0);
+ if (buf.st_mode != (S_IFCHR | 0620)) e(0);
+
+ if (chmod(tname, S_IFCHR | 0630) != 0) e(0);
+
+ if (stat(tname, &buf) != 0) e(0);
+ if (buf.st_mode != (S_IFCHR | 0630)) e(0);
+
+ if (grantpt(masterfd) != 0) e(0);
+
+ if (stat(tname, &buf) != 0) e(0);
+ if (buf.st_mode != (S_IFCHR | 0620)) e(0);
+
+ test_comm(masterfd, slavefd);
+
+ if (close(slavefd) < 0) e(0);
+ if (close(masterfd) < 0) e(0);
+
+ test_getdents(0, NULL, NULL);
+}
+
+/*
+ * Check that the given PTY index, which is in use for an old-style PTY, is not
+ * allocated through Unix98 PTY allocation. This test is not foolproof, but it
+ * does the job well enough.
+ */
+static void
+test_overlap(int m)
+{
+ char *tname;
+ size_t len;
+ int i, n, fd[MIN_PTYS];
+
+ for (i = 0; i < MIN_PTYS; i++) {
+ if ((fd[i] = posix_openpt(O_RDWR | O_NOCTTY)) < 0)
+ break; /* out of PTYs */
+ if (grantpt(fd[i]) < 0) e(0);
+ if (unlockpt(fd[i]) < 0) e(0);
+ if ((tname = ptsname(fd[i])) == NULL) e(0);
+
+ len = strlen(_PATH_DEV_PTS);
+ if (strncmp(tname, _PATH_DEV_PTS, strlen(_PATH_DEV_PTS))) e(0);
+ n = atoi(&tname[len]);
+ if (n < 0 || n > 9) e(0);
+
+ if (m == n) e(0);
+ }
+
+ for (i--; i >= 0; i--)
+ if (close(fd[i]) < 0) e(0);
+}
+
+/*
+ * Test for mixing access to old-style and Unix98 PTYs. Since the PTY service
+ * internally shares the set of pseudo terminals between the two types, it has
+ * to implement checks to prevent that a PTY opened as one type is also
+ * accessed through the other type. We test some of those checks here.
+ */
+static void
+test77h(void)
+{
+ char *tname, ptest[PATH_MAX], ttest[PATH_MAX];
+ struct sigaction act, oact;
+ size_t len;
+ int i, n, masterfd, slavefd;
+
+ subtest = 8;
+
+ /* We do not want to get SIGHUP signals in this test. */
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+ if (sigaction(SIGHUP, &act, &oact) < 0) e(0);
+
+ /*
+ * Check that Unix98 PTYs cannot be accessed through old-style device
+ * nodes. We check different combinations of open master and
+ * slave ends for the Unix98 side (with 'i'): 0) opening and closing
+ * the master only, 1) closing a slave before the master, and 2)
+ * closing the slave after the master.
+ *
+ * This test relies on the implementation aspect that /dev/ttypN and
+ * /dev/pts/N (with N in the range 0..9) map to the same PTY. It also
+ * relies on lack of concurrent PTY allocation outside the test.
+ */
+ for (i = 0; i <= 2; i++) {
+ /* Open a Unix98 PTY and get the slave name. */
+ masterfd = get_unix98_pty(&tname);
+
+ /* Figure out the slave index number. */
+ len = strlen(_PATH_DEV_PTS);
+ if (strncmp(tname, _PATH_DEV_PTS, strlen(_PATH_DEV_PTS))) e(0);
+ n = atoi(&tname[len]);
+ if (n < 0 || n > 9) e(0);
+
+ /* Use this index number to create old-style device names. */
+ snprintf(ptest, sizeof(ptest), _PATH_DEV "ptyp%u", n);
+ snprintf(ttest, sizeof(ttest), _PATH_DEV "ttyp%u", n);
+
+ /*
+ * Now make sure that opening the old-style master and slave
+ * fails as long as either side of the Unix98 PTY is open.
+ */
+ if (open(ptest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+ if (open(ttest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+
+ if (i > 0) {
+ if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0)
+ e(0);
+
+ if (open(ptest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+ if (open(ttest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+
+ if (close(slavefd) < 0) e(0);
+
+ if (i > 1) {
+ if (open(ptest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+ if (open(ttest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+
+ if ((slavefd =
+ open(tname, O_RDWR | O_NOCTTY)) < 0) e(0);
+
+ if (open(ptest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+ if (open(ttest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+
+ if (close(masterfd) < 0) e(0);
+
+ masterfd = slavefd; /* ugly but saving code */
+ }
+
+ if (open(ptest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+ if (open(ttest, O_RDWR | O_NOCTTY) >= 0) e(0);
+ if (errno != EACCES && errno != EIO) e(0);
+ }
+
+ if (close(masterfd) < 0) e(0);
+
+ /*
+ * Once both Unix98 sides are closed, the pseudo terminal can
+ * be reused. Thus, opening the old-style master should now
+ * succeed. However, it is possible that we do not have
+ * permission to open the master at all.
+ */
+ if ((masterfd = open(ptest, O_RDWR | O_NOCTTY)) < 0 &&
+ errno != EACCES) e(0);
+
+ if (masterfd >= 0 && close(masterfd) < 0) e(0);
+ }
+
+ /*
+ * The reverse test, which would check that old-style PTYs cannot be
+ * accessed through Unix98 device nodes, is impossible to perform
+ * properly without root privileges, as we would have to create device
+ * nodes manually with mknod(2). All we can do here is ensure that if
+ * an old-style PTY is opened, it will not also be allocated as a
+ * Unix98 PTY. We do a rather basic check, but only if we can open an
+ * old-style master at all. We check two closing orders (with 'i'):
+ * 0) the slave first, 1) the master first. Here, we make the hard
+ * assumption that the system supports at least four pseudo terminals,
+ * of which at least one is currently free.
+ */
+ for (i = 0; i <= 1; i++) {
+ for (n = 0; n < MIN_PTYS; n++) {
+ snprintf(ptest, sizeof(ptest), _PATH_DEV "ptyp%u", n);
+
+ if ((masterfd = open(ptest, O_RDWR | O_NOCTTY)) >= 0)
+ break;
+ }
+
+ if (n >= MIN_PTYS)
+ break;
+
+ test_overlap(n);
+
+ snprintf(ttest, sizeof(ttest), _PATH_DEV "ttyp%u", n);
+
+ /* We can do part of the test only if we can open the slave. */
+ if ((slavefd = open(ttest, O_RDWR | O_NOCTTY)) >= 0) {
+ test_overlap(n);
+
+ if (i > 0) {
+ if (close(masterfd) < 0) e(0);
+
+ masterfd = slavefd; /* again, ugly */
+ } else
+ if (close(slavefd) < 0) e(0);
+
+ test_overlap(n);
+ }
+
+ if (close(masterfd) < 0) e(0);
+ }
- if (sigaction(SIGHUP, &oact, NULL) < 0) e(28);
+ if (sigaction(SIGHUP, &oact, NULL) < 0) e(0);
}
int
if (m & 0x08) test77d();
if (m & 0x10) test77e();
if (m & 0x20) test77f();
+ if (m & 0x40) test77g();
+ if (m & 0x80) test77h();
}
quit();