From da21d850255e4f11bb2023f69a0b3aad4eab62e2 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Mon, 22 Jun 2015 17:14:34 +0000 Subject: [PATCH] Add PTYFS, Unix98 pseudo terminal support This patch adds support for Unix98 pseudo terminals, that is, posix_openpt(3), grantpt(3), unlockpt(3), /dev/ptmx, and /dev/pts/. The latter is implemented with a new pseudo file system, PTYFS. In effect, this patch adds secure support for unprivileged pseudo terminal allocation, allowing programs such as tmux(1) to be used by non-root users as well. Test77 has been extended with new tests, and no longer needs to run as root. The new functionality is optional. To revert to the old behavior, remove the "ptyfs" entry from /etc/fstab. Technical nodes: o The reason for not implementing the NetBSD /dev/ptm approach is that implementing the corresponding ioctl (TIOCPTMGET) would require adding a number of extremely hairy exceptions to VFS, including the PTY driver having to create new file descriptors for its own device nodes. o PTYFS is required for Unix98 PTYs in order to avoid that the PTY driver has to be aware of old-style PTY naming schemes and even has to call chmod(2) on a disk-backed file system. PTY cannot be its own PTYFS since a character driver may currently not also be a file system. However, PTYFS may be subsumed into a DEVFS in the future. o The Unix98 PTY behavior differs somewhat from NetBSD's, in that slave nodes are created on ptyfs only upon the first call to grantpt(3). This approach obviates the need to revoke access as part of the grantpt(3) call. o Shutting down PTY may leave slave nodes on PTYFS, but once PTY is restarted, these leftover slave nodes will be removed before they create a security risk. Unmounting PTYFS will make existing PTY slaves permanently unavailable, and absence of PTYFS will block allocation of new Unix98 PTYs until PTYFS is (re)mounted. Change-Id: I822b43ba32707c8815fd0f7d5bb7a438f51421c1 --- distrib/sets/lists/minix/mi | 2 + docs/UPDATING | 6 + etc/mtree/NetBSD.dist.base | 1 + etc/system.conf | 7 + etc/usr/rc | 3 +- lib/libc/stdlib/Makefile.inc | 3 +- lib/libutil/pty.c | 19 +- minix/commands/DESCRIBE/DESCRIBE.sh | 3 + minix/commands/MAKEDEV/MAKEDEV.sh | 15 +- minix/commands/setup/setup.sh | 7 +- minix/drivers/tty/pty/Makefile | 2 +- minix/drivers/tty/pty/pty.c | 214 ++++++- minix/drivers/tty/pty/ptyfs.c | 112 ++++ minix/drivers/tty/pty/ptyfs.h | 9 + minix/drivers/tty/pty/tty.c | 65 +- minix/drivers/tty/pty/tty.h | 13 +- minix/fs/Makefile | 1 + minix/fs/procfs/service.c | 1 + minix/fs/ptyfs/Makefile | 10 + minix/fs/ptyfs/node.c | 84 +++ minix/fs/ptyfs/node.h | 20 + minix/fs/ptyfs/ptyfs.c | 434 +++++++++++++ minix/include/minix/com.h | 11 + minix/include/minix/ipc.h | 20 + minix/tests/run | 2 +- minix/tests/test76.c | 1 - minix/tests/test77.c | 902 +++++++++++++++++++++------- minix/usr.bin/trace/ioctl/char.c | 10 +- releasetools/arm_sdimage.sh | 7 +- releasetools/x86_hdimage.sh | 7 +- 30 files changed, 1725 insertions(+), 266 deletions(-) create mode 100644 minix/drivers/tty/pty/ptyfs.c create mode 100644 minix/drivers/tty/pty/ptyfs.h create mode 100644 minix/fs/ptyfs/Makefile create mode 100644 minix/fs/ptyfs/node.c create mode 100644 minix/fs/ptyfs/node.h create mode 100644 minix/fs/ptyfs/ptyfs.c diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index 99e5f7c30..ba41a70f7 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -73,6 +73,7 @@ ./boot/minix/.temp/mod11_init minix-sys ./boot/minix_default minix-sys ./dev minix-sys +./dev/pts minix-sys ./etc minix-sys ./etc/atf minix-sys atf ./etc/boot.cfg.default minix-sys @@ -201,6 +202,7 @@ ./service/pm minix-sys ./service/procfs minix-sys ./service/pty minix-sys +./service/ptyfs minix-sys ./service/readclock.drv minix-sys ./service/rs minix-sys ./service/sched minix-sys diff --git a/docs/UPDATING b/docs/UPDATING index c3f237f70..c9368766d 100644 --- a/docs/UPDATING +++ b/docs/UPDATING @@ -1,3 +1,9 @@ +20150623: + In order to use the new Unix98 PTYs, and to ensure that the test set + continues to pass, please add the following line to your /etc/fstab: + + "none /dev/pts ptyfs rw,rslabel=ptyfs 0 0" + 20140801: As the ABI went under heavy changes it is not possible to do a source upgrade (make build) between: diff --git a/etc/mtree/NetBSD.dist.base b/etc/mtree/NetBSD.dist.base index a7274e74e..3e2e59511 100644 --- a/etc/mtree/NetBSD.dist.base +++ b/etc/mtree/NetBSD.dist.base @@ -17,6 +17,7 @@ ./boot/minix ./boot/minix_default ./dev +./dev/pts ./etc ./etc/X11 ./etc/X11/fs diff --git a/etc/system.conf b/etc/system.conf index ae4730b22..d1a026cfb 100644 --- a/etc/system.conf +++ b/etc/system.conf @@ -739,6 +739,13 @@ service pty ; }; +service ptyfs +{ + ipc + SYSTEM pm vfs rs pty ds vm + ; +}; + service edfictl { ipc ALL; diff --git a/etc/usr/rc b/etc/usr/rc index 16d8b1a8e..9a758fcb0 100644 --- a/etc/usr/rc +++ b/etc/usr/rc @@ -196,7 +196,8 @@ start|autoboot) up inet -script /etc/rs.inet -dev /dev/ip fi - up pty -dev /dev/ptyp0 + # pty needs to know the "tty" group ID + up pty -dev /dev/ptmx -args "gid=`stat -f '%g' /dev/ptmx`" up uds -dev /dev/uds diff --git a/lib/libc/stdlib/Makefile.inc b/lib/libc/stdlib/Makefile.inc index a015dbf10..ddc6cfc67 100644 --- a/lib/libc/stdlib/Makefile.inc +++ b/lib/libc/stdlib/Makefile.inc @@ -4,14 +4,13 @@ # stdlib sources .PATH: ${ARCHDIR}/stdlib ${.CURDIR}/stdlib -#LSC: MINIX: pty.c not compiled SRCS+= _env.c _rand48.c \ a64l.c abort.c atexit.c atof.c atoi.c atol.c atoll.c \ bsearch.c drand48.c exit.c \ getenv.c getopt.c getopt_long.c getsubopt.c \ hcreate.c heapsort.c imaxdiv.c insque.c jrand48.c l64a.c lldiv.c \ lcong48.c lrand48.c lsearch.c merge.c mi_vector_hash.c mrand48.c \ - nrand48.c putenv.c qabs.c qdiv.c qsort.c posix_openpt.c \ + nrand48.c putenv.c qabs.c qdiv.c qsort.c posix_openpt.c pty.c \ quick_exit.c radixsort.c rand.c rand_r.c random.c remque.c \ seed48.c setenv.c srand48.c strsuftoll.c \ strtoimax.c strtol.c strtoll.c strtoq.c strtoul.c strtoull.c \ diff --git a/lib/libutil/pty.c b/lib/libutil/pty.c index 7ad1fb41d..06cd29089 100644 --- a/lib/libutil/pty.c +++ b/lib/libutil/pty.c @@ -42,6 +42,9 @@ __RCSID("$NetBSD: pty.c,v 1.31 2009/02/20 16:44:06 christos Exp $"); #include #include +#ifdef __minix +#include +#endif /* __minix */ #include #include #include @@ -89,7 +92,19 @@ openpty(int *amaster, int *aslave, char *name, struct termios *term, } (void)close(master); } -#endif /* !defined(__minix) */ +#else /* defined(__minix) */ + /* + * On MINIX3, we implement non-root openpty(3) using Unix98 PTYs. + * If this fails, the fallback code below works for root only. + */ + if ((master = posix_openpt(O_RDWR | O_NOCTTY)) != -1) { + if (grantpt(master) != -1 && unlockpt(master) != -1 && + (linep = ptsname(master)) != NULL && + (slave = open(linep, O_RDWR | O_NOCTTY)) != -1) + goto gotit; + (void)close(master); + } +#endif /* defined(__minix) */ (void)getgrnam_r("tty", &grs, grbuf, sizeof(grbuf), &grp); if (grp != NULL) { @@ -129,9 +144,7 @@ openpty(int *amaster, int *aslave, char *name, struct termios *term, #else (slave = open(line, O_RDWR, 0)) != -1) { #endif -#if !defined(__minix) gotit: -#endif /* !defined(__minix) */ *amaster = master; *aslave = slave; if (name) diff --git a/minix/commands/DESCRIBE/DESCRIBE.sh b/minix/commands/DESCRIBE/DESCRIBE.sh index 7f44975fe..e826052ab 100644 --- a/minix/commands/DESCRIBE/DESCRIBE.sh +++ b/minix/commands/DESCRIBE/DESCRIBE.sh @@ -159,6 +159,9 @@ do fi esac ;; + 9,0) + des="unix98 pseudoterminal master" dev=ptmx + ;; 9,12[89]|9,1[3-8]?|9,19[01]) p=`expr \\( $minor - 128 \\) / 16 | tr '0123' 'pqrs'` n=`expr $minor % 16` diff --git a/minix/commands/MAKEDEV/MAKEDEV.sh b/minix/commands/MAKEDEV/MAKEDEV.sh index bbd4c90b3..81330b6fa 100755 --- a/minix/commands/MAKEDEV/MAKEDEV.sh +++ b/minix/commands/MAKEDEV/MAKEDEV.sh @@ -28,10 +28,6 @@ RAMDISK_DEVICES=" fd0 fd1 fd0p0 fd1p0 pci ttyc1 ttyc2 ttyc3 tty00 tty01 tty02 tty03 - ttyp0 ttyp1 ttyp2 ttyp3 ttyp4 ttyp5 ttyp6 ttyp7 ttyp8 ttyp9 - ttypa ttypb ttypc ttypd ttype ttypf - ttyq0 ttyq1 ttyq2 ttyq3 ttyq4 ttyq5 ttyq6 ttyq7 ttyq8 ttyq9 - ttyqa ttyqb ttyqc ttyqd ttyqe ttyqf " #eth => ip tcp udp @@ -46,9 +42,13 @@ STD_DEVICES=" eepromb3s54 eepromb3s55 eepromb3s56 eepromb3s57 eth fb0 fbd filter hello i2c-1 i2c-2 i2c-3 - klog random + klog ptmx random sht21b1s40 sht21b2s40 sht21b3s40 tsl2550b1s39 tsl2550b2s39 tsl2550b3s39 + ttyp0 ttyp1 ttyp2 ttyp3 ttyp4 ttyp5 ttyp6 ttyp7 ttyp8 ttyp9 + ttypa ttypb ttypc ttypd ttype ttypf + ttyq0 ttyq1 ttyq2 ttyq3 ttyq4 ttyq5 ttyq6 ttyq7 ttyq8 ttyq9 + ttyqa ttyqb ttyqc ttyqd ttyqe ttyqf uds vnd0 vnd0p0 vnd0p0s0 vnd1 vnd1p0 vnd1p0s0 vnd2 vnd3 vnd4 vnd5 vnd6 vnd7 @@ -132,6 +132,7 @@ Where key is one of the following: eth ip tcp udp # One of these makes some TCP/IP devices audio mixer # Make audio devices klog # Make /dev/klog + ptmx # Make /dev/ptmx random # Make /dev/random, /dev/urandom uds # Make /dev/uds filter # Make /dev/filter @@ -369,6 +370,10 @@ do # PCI server, manages PCI buses makedev pci c 134 0 ${uname} ${gname} ${permissions} ;; + ptmx) + # Unix98 pseudoterminal master + makedev ptmx c 9 0 ${uname} tty 666 + ;; ram|mem|kmem|null|boot|zero|imgrd) # Memory devices. makedev ram b 1 0 ${uname} kmem ${permissions} diff --git a/minix/commands/setup/setup.sh b/minix/commands/setup/setup.sh index 0fa2501f1..c3c4bab40 100644 --- a/minix/commands/setup/setup.sh +++ b/minix/commands/setup/setup.sh @@ -713,10 +713,11 @@ ln -s /usr/log /mnt/var/log # CD remnants that aren't for the installed system rm /mnt/etc/issue /mnt/CD /mnt/.* 2>/dev/null -echo >/mnt/etc/fstab "/dev/$root / mfs rw 0 1 -/dev/$usr /usr $FSTYPE rw 0 2 +echo >/mnt/etc/fstab "/dev/$root / mfs rw 0 1 +/dev/$usr /usr $FSTYPE rw 0 2 $fshome -none /sys devman rw,rslabel=devman 0 0" +none /sys devman rw,rslabel=devman 0 0 +none /dev/pts ptyfs rw,rslabel=ptyfs 0 0" # National keyboard map. test -n "$keymap" && cp -p "/usr/lib/keymaps/$keymap.map" /mnt/etc/keymap diff --git a/minix/drivers/tty/pty/Makefile b/minix/drivers/tty/pty/Makefile index c2d1d0d29..6ab626d71 100644 --- a/minix/drivers/tty/pty/Makefile +++ b/minix/drivers/tty/pty/Makefile @@ -1,6 +1,6 @@ # Makefile for pseudo terminal driver (PTY) PROG= pty -SRCS= tty.c pty.c +SRCS= tty.c pty.c ptyfs.c DPADD+= ${LIBCHARDRIVER} ${LIBSYS} ${LIBTIMERS} LDADD+= -lchardriver -lsys -ltimers diff --git a/minix/drivers/tty/pty/pty.c b/minix/drivers/tty/pty/pty.c index 031d6082d..1a6b55696 100644 --- a/minix/drivers/tty/pty/pty.c +++ b/minix/drivers/tty/pty/pty.c @@ -14,14 +14,37 @@ * 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 +#include #include #include #include #include #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 { @@ -51,12 +74,14 @@ 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 */ @@ -72,6 +97,9 @@ static ssize_t pty_master_read(devminor_t minor, u64_t position, 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); @@ -81,10 +109,30 @@ static struct chardriver pty_master_tab = { .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 * *===========================================================================*/ @@ -93,21 +141,80 @@ static int pty_master_open(devminor_t minor, int UNUSED(access), { tty_t *tp; pty_t *pp; + int r; - assert(minor >= PTYPX_MINOR && minor < PTYPX_MINOR + NR_PTYS); + 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 */ - if ((tp = line2tty(minor)) == NULL) - return ENXIO; - pp = tp->tty_priv; + /* 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; - if (pp->state & PTY_ACTIVE) - return EIO; + 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; } /*===========================================================================* @@ -123,7 +230,7 @@ static int pty_master_close(devminor_t minor) 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 */ @@ -228,6 +335,66 @@ static ssize_t pty_master_write(devminor_t minor, u64_t UNUSED(position), 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 * *===========================================================================*/ @@ -292,13 +459,11 @@ static int select_try_pty(tty_t *tp, int ops) 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; } } @@ -326,6 +491,7 @@ static int pty_master_select(devminor_t minor, unsigned int ops, if (ops && watch) { pp->select_ops |= ops; pp->select_proc = endpt; + pp->select_minor = minor; } return ready_ops; @@ -534,6 +700,27 @@ static int pty_slave_read(tty_t *tp, int try) 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 * *===========================================================================*/ @@ -542,8 +729,6 @@ static int pty_slave_open(tty_t *tp, int UNUSED(try)) /* 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. @@ -576,7 +761,7 @@ static int pty_slave_close(tty_t *tp, int UNUSED(try)) 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; @@ -638,6 +823,7 @@ void pty_init(tty_t *tp) 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; diff --git a/minix/drivers/tty/pty/ptyfs.c b/minix/drivers/tty/pty/ptyfs.c new file mode 100644 index 000000000..03a6becea --- /dev/null +++ b/minix/drivers/tty/pty/ptyfs.c @@ -0,0 +1,112 @@ +/* ptyfs.c - communication to PTYFS */ + +#include +#include + +#include "ptyfs.h" + +/* + * Perform synchronous communication with PTYFS, if PTYFS is actually running. + * This function is expected to return only once PTYFS has acknowledged + * processing the request, in order to avoid race conditions between PTYFS and + * userland. The function must always fail when PTYFS is not available for any + * reason. Return OK on success, or an IPC-level error on failure. + */ +static int +ptyfs_sendrec(message * m_ptr) +{ + endpoint_t endpt; + + /* + * New pseudoterminals are created sufficiently rarely that we need not + * optimize this by for example caching the PTYFS endpoint, especially + * since caching brings along new issues, such as having to reissue the + * request if the cached endpoint turns out to be outdated (e.g., when + * ptyfs is unmounted and remounted for whatever reason). + */ + if (ds_retrieve_label_endpt("ptyfs", &endpt) != OK) + return EDEADSRCDST; /* ptyfs is not available */ + + return ipc_sendrec(endpt, m_ptr); +} + +/* + * Add or update a node on PTYFS, with the given node index and attributes. + * Return OK on success, or an error code on failure. Errors may include + * communication failures and out-of-memory conditions. + */ +int +ptyfs_set(unsigned int index, mode_t mode, uid_t uid, gid_t gid, dev_t dev) +{ + message m; + int r; + + memset(&m, 0, sizeof(m)); + + m.m_type = PTYFS_SET; + m.m_pty_ptyfs_req.index = index; + m.m_pty_ptyfs_req.mode = mode; + m.m_pty_ptyfs_req.uid = uid; + m.m_pty_ptyfs_req.gid = gid; + m.m_pty_ptyfs_req.dev = dev; + + if ((r = ptyfs_sendrec(&m)) != OK) + return r; + + return m.m_type; +} + +/* + * Remove a node from PTYFS. Return OK on success, or an error code on + * failure. The function succeeds even if no node existed for the given index. + */ +int +ptyfs_clear(unsigned int index) +{ + message m; + int r; + + memset(&m, 0, sizeof(m)); + + m.m_type = PTYFS_CLEAR; + m.m_pty_ptyfs_req.index = index; + + if ((r = ptyfs_sendrec(&m)) != OK) + return r; + + return m.m_type; +} + +/* + * Obtain the file name for the PTYFS node with the given index, and store it + * in the given 'name' buffer which consists of 'size' bytes. On success, + * return OK, with the file name stored as a null-terminated string. The + * returned name does not include the PTYFS mount path. On failure, return an + * error code. Among other reasons, the function fails if no node is allocated + * for the given index, and if the name does not fit in the given buffer. + */ +int +ptyfs_name(unsigned int index, char * name, size_t size) +{ + message m; + int r; + + memset(&m, 0, sizeof(m)); + + m.m_type = PTYFS_NAME; + m.m_pty_ptyfs_req.index = index; + + if ((r = ptyfs_sendrec(&m)) != OK) + return r; + + if (m.m_type != OK) + return m.m_type; + + /* Ensure null termination, and make sure the string fits. */ + m.m_ptyfs_pty_name.name[sizeof(m.m_ptyfs_pty_name.name) - 1] = 0; + if (strlen(m.m_ptyfs_pty_name.name) >= size) + return ENAMETOOLONG; + + strlcpy(name, m.m_ptyfs_pty_name.name, size); + return OK; +} diff --git a/minix/drivers/tty/pty/ptyfs.h b/minix/drivers/tty/pty/ptyfs.h new file mode 100644 index 000000000..ebce59d7e --- /dev/null +++ b/minix/drivers/tty/pty/ptyfs.h @@ -0,0 +1,9 @@ +#ifndef _MINIX_PTY_PTYFS_H +#define _MINIX_PTY_PTYFS_H + +int ptyfs_set(unsigned int index, mode_t mode, uid_t uid, gid_t gid, + dev_t dev); +int ptyfs_clear(unsigned int index); +int ptyfs_name(unsigned int index, char * name, size_t size); + +#endif /* !_MINIX_PTY_PTYFS_H */ diff --git a/minix/drivers/tty/pty/tty.c b/minix/drivers/tty/pty/tty.c index 1f71a17d5..a513f8482 100644 --- a/minix/drivers/tty/pty/tty.c +++ b/minix/drivers/tty/pty/tty.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -22,8 +23,6 @@ /* A device exists if at least its 'devread' function is defined. */ #define tty_active(tp) ((tp)->tty_devread != NULL) -struct kmessages kmess; - static void tty_timed_out(minix_timer_t *tp); static void settimer(tty_t *tty_ptr, int enable); static void in_transfer(tty_t *tp); @@ -41,8 +40,6 @@ static ssize_t do_read(devminor_t minor, u64_t position, endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags, cdev_id_t id); static ssize_t do_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 do_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 do_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id); static int do_select(devminor_t minor, unsigned int ops, endpoint_t endpt); @@ -51,7 +48,7 @@ static struct chardriver tty_tab = { .cdr_close = do_close, .cdr_read = do_read, .cdr_write = do_write, - .cdr_ioctl = do_ioctl, + .cdr_ioctl = tty_ioctl, .cdr_cancel = do_cancel, .cdr_select = do_select }; @@ -89,14 +86,39 @@ static const char lined[TTLINEDNAMELEN] = "termios"; /* line discipline */ /* Global variables for the TTY task (declared extern in tty.h). */ tty_t tty_table[NR_PTYS]; u32_t system_hz; +int tty_gid; + +static struct optset optset_table[] = { + { "gid", OPT_INT, &tty_gid, 10 }, + { NULL, 0, NULL, 0 } +}; static void tty_startup(void); static int tty_init(int, sef_init_info_t *); +/*===========================================================================* + * is_pty * + *===========================================================================*/ +static int is_pty(devminor_t line) +{ +/* Return TRUE if the given minor device number refers to a pty (the master + * side of a pty/tty pair), and FALSE otherwise. + */ + + if (line == PTMX_MINOR) + return TRUE; + if (line >= PTYPX_MINOR && line < PTYPX_MINOR + NR_PTYS) + return TRUE; + if (line >= UNIX98_MINOR && line < UNIX98_MINOR + NR_PTYS * 2 && !(line & 1)) + return TRUE; + + return FALSE; +} + /*===========================================================================* * tty_task * *===========================================================================*/ -int main(void) +int main(int argc, char **argv) { /* Main routine of the terminal task. */ @@ -106,6 +128,8 @@ int main(void) int r; register tty_t *tp; + env_setargs(argc, argv); + tty_startup(); while (TRUE) { @@ -145,11 +169,10 @@ int main(void) if (OK != chardriver_get_minor(&tty_mess, &line)) continue; - if ((line >= PTYPX_MINOR) && (line < (PTYPX_MINOR + NR_PTYS)) && - tty_mess.m_type != CDEV_IOCTL) { + if (is_pty(line)) { /* Terminals and pseudo terminals belong together. We can only - * make a distinction between the two based on position in the - * tty_table and not on minor number. Hence this special case. + * make a distinction between the two based on minor number and + * not on position in the tty_table. Hence this special case. */ do_pty(&tty_mess, ipc_status); continue; @@ -173,6 +196,8 @@ line2tty(devminor_t line) tp = tty_addr(line - TTYPX_MINOR); } else if ((line - PTYPX_MINOR) < NR_PTYS) { tp = tty_addr(line - PTYPX_MINOR); + } else if ((line - UNIX98_MINOR) < NR_PTYS * 2) { + tp = tty_addr((line - UNIX98_MINOR) >> 1); } else { tp = NULL; } @@ -315,9 +340,9 @@ static ssize_t do_write(devminor_t minor, u64_t UNUSED(position), } /*===========================================================================* - * do_ioctl * + * tty_ioctl * *===========================================================================*/ -static int do_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt, +int tty_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) { /* Perform an IOCTL on this terminal. POSIX termios calls are handled @@ -444,6 +469,10 @@ static int do_open(devminor_t minor, int access, endpoint_t user_endpt) if ((tp = line2tty(minor)) == NULL) return ENXIO; + /* Make sure the user is not mixing Unix98 and old-style ends. */ + if ((*tp->tty_mayopen)(tp, minor) == FALSE) + return EIO; + if (!(access & CDEV_NOCTTY)) { tp->tty_pgrp = user_endpt; r = CDEV_CTTY; @@ -716,7 +745,7 @@ int count; /* number of input characters */ * the number of characters processed. */ - int ch, sig, ct; + int ch, ct; int timeset = FALSE; for (ct = 0; ct < count; ct++) { @@ -1170,9 +1199,6 @@ tty_t *tp; /* Setting the output speed to zero hangs up the phone. */ if (tp->tty_termios.c_ospeed == B0) sigchar(tp, SIGHUP, 1); - - /* Set new line speed, character size, etc at the device level. */ - (*tp->tty_ioctl)(tp, 0); } /*===========================================================================* @@ -1239,6 +1265,10 @@ static int tty_init(int UNUSED(type), sef_init_info_t *UNUSED(info)) system_hz = sys_hz(); + tty_gid = -1; + if (env_argc > 1) + optset_parse(optset_table, env_argv[1]); + /* Initialize the terminal lines. */ memset(tty_table, '\0' , sizeof(tty_table)); @@ -1251,10 +1281,9 @@ static int tty_init(int UNUSED(type), sef_init_info_t *UNUSED(info)) tp->tty_min = 1; tp->tty_incaller = tp->tty_outcaller = tp->tty_iocaller = NONE; tp->tty_termios = termios_defaults; - tp->tty_icancel = tp->tty_ocancel = tp->tty_ioctl = tp->tty_close = + tp->tty_icancel = tp->tty_ocancel = tp->tty_close = tp->tty_open = tty_devnop; pty_init(tp); - tp->tty_minor = s + TTYPX_MINOR; } return OK; diff --git a/minix/drivers/tty/pty/tty.h b/minix/drivers/tty/pty/tty.h index 8d353cfec..7dbb5f5c6 100644 --- a/minix/drivers/tty/pty/tty.h +++ b/minix/drivers/tty/pty/tty.h @@ -4,8 +4,10 @@ #include /* First minor numbers for the various classes of TTY devices. */ +#define PTMX_MINOR 0 /* minor of the Unix98 clone device */ #define TTYPX_MINOR 128 #define PTYPX_MINOR 192 +#define UNIX98_MINOR 256 /* start of Unix98 pairs */ #define TTY_IN_BYTES 256 /* tty input queue size */ #define TTY_OUT_BYTES 2048 /* tty output queue size */ @@ -15,13 +17,13 @@ #define ESC '\33' /* escape */ struct tty; -typedef int(*devfun_t) (struct tty *tp, int try_only); -typedef void(*devfunarg_t) (struct tty *tp, int c); +typedef int (*devfun_t)(struct tty *tp, int try_only); +typedef void (*devfunarg_t)(struct tty *tp, int c); +typedef int (*devfunline_t)(struct tty *tp, devminor_t line); typedef struct tty { int tty_events; /* set when TTY should inspect this line */ int tty_index; /* index into TTY table */ - devminor_t tty_minor; /* device minor number */ /* Input queue. Typed characters are stored here until read by a program. */ u16_t *tty_inhead; /* pointer to place where next char goes */ @@ -70,7 +72,7 @@ typedef struct tty { devminor_t tty_select_minor; /* minor used to start select query */ /* Miscellaneous. */ - devfun_t tty_ioctl; /* set line speed, etc. at the device level */ + devfunline_t tty_mayopen; /* check whether this tty may be opened */ devfun_t tty_open; /* tell the device that the tty is opened */ devfun_t tty_close; /* tell the device that the tty is closed */ void *tty_priv; /* pointer to per device private data */ @@ -84,6 +86,7 @@ typedef struct tty { /* Memory allocated in tty.c, so extern here. */ extern tty_t tty_table[NR_PTYS]; extern u32_t system_hz; /* system clock frequency */ +extern int tty_gid; /* group ID of the "tty" group */ /* Values for the fields. */ #define NOT_ESCAPED 0 /* previous character is not LNEXT (^V) */ @@ -115,6 +118,8 @@ void out_process(struct tty *tp, char *bstart, char *bpos, char *bend, void tty_wakeup(clock_t now); int select_try(struct tty *tp, int ops); int select_retry(struct tty *tp); +int tty_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); /* pty.c */ void do_pty(message *m_ptr, int ipc_status); diff --git a/minix/fs/Makefile b/minix/fs/Makefile index 5dec62ce8..6f18ad55c 100644 --- a/minix/fs/Makefile +++ b/minix/fs/Makefile @@ -7,6 +7,7 @@ SUBDIR+= pfs SUBDIR+= ext2 SUBDIR+= isofs SUBDIR+= procfs +SUBDIR+= ptyfs . if ${MACHINE_ARCH} == "i386" SUBDIR+= hgfs diff --git a/minix/fs/procfs/service.c b/minix/fs/procfs/service.c index 4385741aa..852332a48 100644 --- a/minix/fs/procfs/service.c +++ b/minix/fs/procfs/service.c @@ -112,6 +112,7 @@ service_get_policies(struct policies * pol, index_t slot) { .label = "mfs", .policy_str = "" }, { .label = "pfs", .policy_str = "" }, { .label = "procfs", .policy_str = "" }, + { .label = "ptyfs", .policy_str = "" }, { .label = "vbfs", .policy_str = "" }, /* net */ { .label = "inet", .policy_str = "reset" }, diff --git a/minix/fs/ptyfs/Makefile b/minix/fs/ptyfs/Makefile new file mode 100644 index 000000000..3858c861c --- /dev/null +++ b/minix/fs/ptyfs/Makefile @@ -0,0 +1,10 @@ +# Makefile for PTYFS server +.include + +PROG= ptyfs +SRCS= ptyfs.c node.c + +DPADD+= ${LIBFSDRIVER} +LDADD+= -lfsdriver + +.include diff --git a/minix/fs/ptyfs/node.c b/minix/fs/ptyfs/node.c new file mode 100644 index 000000000..0840eb669 --- /dev/null +++ b/minix/fs/ptyfs/node.c @@ -0,0 +1,84 @@ +/* PTYFS slave node management */ +/* + * While the interface of this module should be flexible enough to implement + * various memory management approaches, the current code simply relies on + * NR_PTYS being small enough to preallocate all data structures. In the + * future, NR_PTYS will no longer be a system-global definition, and future + * implementations of this module should not rely on NR_PTYS at all. + */ + +#include + +#include "node.h" + +static bitchunk_t node_map[BITMAP_CHUNKS(NR_PTYS)]; +static struct node_data node_data[NR_PTYS]; + +/* + * Initialize the node module. + */ +void +init_nodes(void) +{ + + memset(&node_map, 0, sizeof(node_map)); +} + +/* + * Allocate a node with a given node index number, and save node data for it. + * It is possible that the node is in use already; in that case, only update + * its associated data. Return OK on success, or an error code on failure. + */ +int +set_node(node_t index, struct node_data * data) +{ + + if (index >= NR_PTYS) + return ENOMEM; + + SET_BIT(node_map, index); + + node_data[index] = *data; + + return OK; +} + +/* + * Deallocate a node using its node index number. This function always + * succeeds, intentionally ignoring the case that the node was not allocated. + */ +void +clear_node(node_t index) +{ + + UNSET_BIT(node_map, index); +} + +/* + * Return a pointer to the node data associated with the given node index + * number. If the node is not allocated, return NULL. + */ +struct node_data * +get_node(node_t index) +{ + + if (index >= NR_PTYS || !GET_BIT(node_map, index)) + return NULL; + + return &node_data[index]; +} + +/* + * Return the highest allocated node index number, plus one. This value is + * used to check given node indices and limit linear iterations. + */ +node_t +get_max_node(void) +{ + + /* + * NR_PTYS is low enough that we can always return it instead of + * tracking the actual value. + */ + return NR_PTYS; +} diff --git a/minix/fs/ptyfs/node.h b/minix/fs/ptyfs/node.h new file mode 100644 index 000000000..5ea7e83b9 --- /dev/null +++ b/minix/fs/ptyfs/node.h @@ -0,0 +1,20 @@ +#ifndef _MINIX_PTYFS_NODE_H +#define _MINIX_PTYFS_NODE_H + +typedef unsigned int node_t; + +struct node_data { + dev_t dev; + mode_t mode; + uid_t uid; + gid_t gid; + time_t ctime; +}; + +void init_nodes(void); +int set_node(node_t index, struct node_data *data); +void clear_node(node_t index); +struct node_data *get_node(node_t index); +node_t get_max_node(void); + +#endif /* !_MINIX_PTYFS_NODE_H */ diff --git a/minix/fs/ptyfs/ptyfs.c b/minix/fs/ptyfs/ptyfs.c new file mode 100644 index 000000000..39a6c65bc --- /dev/null +++ b/minix/fs/ptyfs/ptyfs.c @@ -0,0 +1,434 @@ +/* PTYFS - file system for Unix98 pseudoterminal slave nodes (/dev/pts) */ + +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/minix/include/minix/com.h b/minix/include/minix/com.h index fb4a11f8d..c3d42cc77 100644 --- a/minix/include/minix/com.h +++ b/minix/include/minix/com.h @@ -28,6 +28,7 @@ * 0x1400 - 0x14FF Real Time Clock requests and responses * 0x1500 - 0x15FF Input server messages * 0x1600 - 0x16FF VirtualBox (VBOX) requests (see vboxif.h) + * 0x1700 - 0x17FF PTYFS requests * * Zero and negative values are widely used for OK and error responses. */ @@ -848,6 +849,16 @@ #define INPUT_EVENT (INPUT_RS_BASE + 0) /* send input event */ +/*===========================================================================* + * Messages for PTYFS * + *===========================================================================*/ + +#define PTYFS_BASE 0x1700 + +#define PTYFS_SET (PTYFS_BASE + 0) /* add/update node */ +#define PTYFS_CLEAR (PTYFS_BASE + 1) /* delete node */ +#define PTYFS_NAME (PTYFS_BASE + 2) /* get node name */ + /*===========================================================================* * VFS-FS TRANSACTION IDs * *===========================================================================*/ diff --git a/minix/include/minix/ipc.h b/minix/include/minix/ipc.h index bc4e2cf3a..c05a5c113 100644 --- a/minix/include/minix/ipc.h +++ b/minix/include/minix/ipc.h @@ -1558,6 +1558,24 @@ typedef struct { } mess_pm_sched_scheduling_set_nice; _ASSERT_MSG_SIZE(mess_pm_sched_scheduling_set_nice); +typedef struct { + dev_t dev; + mode_t mode; + uid_t uid; + gid_t gid; + uint32_t index; + + uint8_t padding[32]; +} mess_pty_ptyfs_req; +_ASSERT_MSG_SIZE(mess_pty_ptyfs_req); + +typedef struct { + char name[20]; + + uint8_t padding[36]; +} mess_ptyfs_pty_name; +_ASSERT_MSG_SIZE(mess_ptyfs_pty_name); + typedef struct { int status; @@ -2176,6 +2194,8 @@ typedef struct { mess_pm_lsys_getprocnr m_pm_lsys_getprocnr; mess_pm_lsys_sigs_signal m_pm_lsys_sigs_signal; mess_pm_sched_scheduling_set_nice m_pm_sched_scheduling_set_nice; + mess_pty_ptyfs_req m_pty_ptyfs_req; + mess_ptyfs_pty_name m_ptyfs_pty_name; mess_readclock_lc_rtcdev m_readclock_lc_rtcdev; mess_rs_init m_rs_init; mess_rs_pm_exec_restart m_rs_pm_exec_restart; diff --git a/minix/tests/run b/minix/tests/run index fd24433c4..d46a3c3c5 100755 --- a/minix/tests/run +++ b/minix/tests/run @@ -21,7 +21,7 @@ badones= # list of tests that failed # Programs that require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69 test76 test73 test74 test77 test78" + test69 test73 test74 test78" # Scripts that require to be run as root rootscripts="testisofs testvnd testrelpol" diff --git a/minix/tests/test76.c b/minix/tests/test76.c index 7b41d18a3..0ec2be4de 100644 --- a/minix/tests/test76.c +++ b/minix/tests/test76.c @@ -1,5 +1,4 @@ /* Tests for interrupting VFS calls - by D.C. van Moolenbroek */ -/* This test needs to be run as root; otherwise, openpty() won't work. */ #include #include #include diff --git a/minix/tests/test77.c b/minix/tests/test77.c index e1e6c7290..2ad246159 100644 --- a/minix/tests/test77.c +++ b/minix/tests/test77.c @@ -1,5 +1,12 @@ /* 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 #include #include @@ -8,11 +15,15 @@ #include #include #include +#include +#include #include #include #define ITERATIONS 10 +#define MIN_PTYS 4 + #include "common.h" static int sighups; /* number of SIGHUP signals received */ @@ -36,11 +47,11 @@ make_raw(int slavefd) { 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); } /* @@ -55,149 +66,196 @@ test_comm(int masterfd, int slavefd) 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); - /* - * openpty(3) gives us only the slave name, but we also need the master - * name. + * First try Unix98 PTY allocation, mainly to avoid opening the slave + * end immediately. If this fails, try openpty(3) as well. */ - strlcpy(pname, tname, PATH_MAX); - len = strlen(_PATH_DEV); + 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); - if (strncmp(pname, _PATH_DEV, len)) e(301); + return 0; + } + if (close(masterfd) < 0) e(0); + } - /* If this fails, this test needs to be updated. */ - if (pname[len] != 't') e(302); - - pname[len] = 'p'; + if (openpty(&masterfd, &slavefd, tname, NULL, NULL) < 0) e(0); test_comm(masterfd, slavefd); - if (close(masterfd) < 0) e(303); - if (close(slavefd) < 0) e(304); + *pfd = masterfd; + + if (close(slavefd) < 0) e(0); + + /* + * openpty(3) gives us only the slave name, but we also want the master + * name. + */ + len = strlen(_PATH_DEV); + if (strncmp(tname, _PATH_DEV, len)) e(0); + + if (strncmp(&tname[len], "tty", 3)) + return 0; /* Unix98 after all? Well okay, whatever.. */ + + if (pname != NULL) { + strlcpy(pname, tname, PATH_MAX); + pname[len] = 'p'; + } + + 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); } /* @@ -207,41 +265,46 @@ static void 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 (oldstyle) { + if ((extrafd = open(pname, O_RDWR | O_NOCTTY)) >= 0) e(0); + if (errno != EIO) e(0); + } test_comm(masterfd, slavefd); - if ((extrafd = open(pname, O_RDWR | O_NOCTTY)) >= 0) e(3); - if (errno != EIO) e(4); - - 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); } /* @@ -252,7 +315,7 @@ test77c(void) { struct sigaction act, oact; char pname[PATH_MAX], tname[PATH_MAX]; - int masterfd, slavefd; + int oldstyle, masterfd, slavefd; char c; subtest = 3; @@ -260,63 +323,78 @@ test77c(void) /* 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); } /* @@ -346,13 +424,11 @@ test77d(void) 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 @@ -360,25 +436,27 @@ test77d(void) */ switch (fork()) { case 0: - if (setsid() < 0) e(2); + if (close(masterfd) < 0) e(0); - if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(3); + if (setsid() < 0) e(0); - if (open("/dev/tty", O_RDWR) >= 0) e(4); - if (errno != ENXIO) e(5); + if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(0); + + 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 @@ -386,22 +464,24 @@ test77d(void) */ switch (fork()) { case 0: - if (setsid() < 0) e(10); + if (close(masterfd) < 0) e(0); - if ((slavefd = open(tname, O_RDWR)) < 0) e(11); + if (setsid() < 0) e(0); - if (open("/dev/tty", O_RDWR) < 0) e(12); + if ((slavefd = open(tname, O_RDWR)) < 0) e(0); + + 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); } /* @@ -414,72 +494,70 @@ test77e(void) { 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(0); memset(&act, 0, sizeof(act)); act.sa_handler = signal_handler; - if (sigaction(SIGHUP, &act, &hup_oact) < 0) e(1); - - 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); } /* @@ -490,7 +568,7 @@ static void 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; @@ -500,22 +578,20 @@ test77f(void) /* 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); @@ -525,21 +601,21 @@ test77f(void) 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); @@ -549,76 +625,76 @@ test77f(void) 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); @@ -628,9 +704,421 @@ test77f(void) break; } - if (waitchild() < 0) e(39); + if (waitchild() < 0) e(0); - if (sigaction(SIGHUP, &oact, NULL) < 0) e(28); + 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(0); } int @@ -652,6 +1140,8 @@ main(int argc, char **argv) if (m & 0x08) test77d(); if (m & 0x10) test77e(); if (m & 0x20) test77f(); + if (m & 0x40) test77g(); + if (m & 0x80) test77h(); } quit(); diff --git a/minix/usr.bin/trace/ioctl/char.c b/minix/usr.bin/trace/ioctl/char.c index a90a56151..a8ac00d0f 100644 --- a/minix/usr.bin/trace/ioctl/char.c +++ b/minix/usr.bin/trace/ioctl/char.c @@ -273,6 +273,7 @@ char_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, struct volume_level *level; struct inout_ctrl *inout; struct termios *tc; + struct ptmget *pm; struct winsize *ws; struct kio_bell *bell; struct kio_leds *leds; @@ -282,7 +283,6 @@ char_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, struct pciio_map *pci_iomap; struct pciio_acl *pci_acl; - switch (req) { case MINIX_I2C_IOCTL_EXEC: if ((iie = (minix_i2c_ioctl_exec_t *)ptr) == NULL) @@ -452,6 +452,14 @@ char_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, put_value(proc, NULL, "%d", *(int *)ptr); return IF_ALL; + case TIOCPTSNAME: + if ((pm = (struct ptmget *)ptr) == NULL) + return IF_IN; + + put_buf(proc, "sn", PF_LOCADDR | PF_STRING, (vir_bytes)pm->sn, + sizeof(pm->sn)); + return IF_ALL; + case TIOCSTI: if (ptr == NULL) return dir; diff --git a/releasetools/arm_sdimage.sh b/releasetools/arm_sdimage.sh index ae771d9f3..db1ae53fb 100755 --- a/releasetools/arm_sdimage.sh +++ b/releasetools/arm_sdimage.sh @@ -174,9 +174,10 @@ fi # setup phase on x86 # cat >${FSTAB} <${FSTAB} <