pty - Introduce unix98 ptys
authorAlex Hornung <ahornung@gmail.com>
Tue, 1 Sep 2009 13:00:15 +0000 (14:00 +0100)
committerAlex Hornung <ahornung@gmail.com>
Tue, 1 Sep 2009 15:26:03 +0000 (16:26 +0100)
* Add the clone base device, /dev/ptmx.

* Add TIOCISPTMASTER ioctl, which returns successfully if the
  device is a unix98 pty master.

* Add sysctl kern.pty_debug, default to 0, to control pty debug
  info printing.

* Remove old unix98 pty code, which was inactive and inherited
  from an initial implementation during devfs development.

* Add userland functions:
  - posix_openpt (is equivalent to opening /dev/ptmx)
  - ptsname (as described in standard but thread-safe)
  - unlockpt (no op)
  - grantpt (no op)

* Implement proper permissions for unix98 pty slaves.
  Set them to real uid - group "tty" - 0620 for
  the slave device, as specified in standard (for grantpt).
  Master's permissions don't matter as the device cannot be opened
  again.
  Set the permissions of the unix98 ptys to be overridable by
  setattr() as the old ptys are.

* Use the define UNIX98_PTYS to activate/deactivate the unix98
  pty code in tty_pty.c. By default it is enabled.

NOTE: due to the permission handling on cloning of the slave
      pty, grantpt is not needed and would only pose a security
      risk.

include/stdlib.h
lib/libc/gen/Makefile.inc
lib/libc/gen/ptsname.c [new file with mode: 0644]
sys/kern/tty_pty.c
sys/sys/ttycom.h

index 171498d..7a137f0 100644 (file)
@@ -188,7 +188,7 @@ double       erand48(unsigned short[3]);
 /* char        *fcvt(double, int, int * __restrict, int * __restrict); */
 /* char        *gcvt(double, int, int * __restrict, int * __restrict); */
 int     getsubopt(char **, char *const *, char **);
-/* int  grantpt(int); */
+int     grantpt(int);
 char   *initstate(unsigned long /* XSI requires u_int */, char *, long);
 long    jrand48(unsigned short[3]);
 char   *l64a(long);
@@ -200,8 +200,8 @@ char        *mktemp(char *);
 #endif
 long    mrand48(void);
 long    nrand48(unsigned short[3]);
-/* int  posix_openpt(int); */
-/* char        *ptsname(int); */
+int     posix_openpt(int);
+char   *ptsname(int);
 int     putenv(char *);
 long    random(void);
 char   *realpath(const char *, char resolved_path[]);
@@ -211,7 +211,7 @@ int  setkey(const char *);
 char   *setstate(/* const */ char *);
 void    srand48(long);
 void    srandom(unsigned long);
-/* int  unlockpt(int); */
+int     unlockpt(int);
 #endif /* __XSI_VISIBLE */
 
 #if __BSD_VISIBLE
index 4a7c246..edbdaba 100644 (file)
@@ -24,7 +24,7 @@ SRCS+=  _pthread_stubs.c _rand48.c _spinlock_stub.c _thread_init.c \
        lockf.c lrand48.c mrand48.c msgctl.c \
        msgget.c msgrcv.c msgsnd.c nftw.c nice.c \
        nlist.c nrand48.c ntp_gettime.c opendir.c \
-       pause.c pmadvise.c popen.c posix_spawn.c posixshm.c \
+       pause.c pmadvise.c popen.c posix_spawn.c posixshm.c ptsname.c \
        psignal.c pw_scan.c pwcache.c \
        raise.c readdir.c readpassphrase.c rewinddir.c \
        scandir.c seed48.c seekdir.c semconfig.c semctl.c semget.c semop.c \
diff --git a/lib/libc/gen/ptsname.c b/lib/libc/gen/ptsname.c
new file mode 100644 (file)
index 0000000..446cdc8
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2009 The DragonFly Project.  All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Alex Hornung <ahornung@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "namespace.h"
+#include <sys/types.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <string.h>
+#include <paths.h>
+#include <errno.h>
+#include <machine/stdint.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include "reentrant.h"
+#include "un-namespace.h"
+
+#include "libc_private.h"
+
+#define TTYNAME_DEVFS_COMPAT 1
+
+static char ptsname_buf[sizeof(_PATH_DEV) + NAME_MAX];
+
+static once_t          ptsname_init_once = ONCE_INITIALIZER;
+static thread_key_t    ptsname_key;
+static int             ptsname_keycreated = 0;
+
+static int
+__isptmaster(int fd)
+{
+       int error;
+
+       error = _ioctl(fd, TIOCISPTMASTER);
+       if ((error) && (errno != EBADF))
+               errno = EINVAL;
+
+       return error;
+}
+
+static void
+ptsname_keycreate(void)
+{
+       ptsname_keycreated = (thr_keycreate(&ptsname_key, free) == 0);
+}
+
+char *
+ptsname(int fd)
+{
+       int     error;
+       size_t used;
+       char    *buf;
+
+       error = __isptmaster(fd);
+       if (error)
+               return (error);
+
+       if (thr_main() != 0)
+               buf = ptsname_buf;
+       else {
+               if (thr_once(&ptsname_init_once, ptsname_keycreate) != 0 ||
+                   !ptsname_keycreated)
+                       return (NULL);
+               if ((buf = thr_getspecific(ptsname_key)) == NULL) {
+                       if ((buf = malloc(sizeof ptsname_buf)) == NULL)
+                               return (NULL);
+                       if (thr_setspecific(ptsname_key, buf) != 0) {
+                               free(buf);
+                               return (NULL);
+                       }
+               }
+       }
+
+       strcpy(buf, "/dev/");
+       used = strlen(buf);
+
+       if (((error = fdevname_r(fd, buf+used, sizeof(ptsname_buf)-used))) != 0) {
+               errno = error;
+               return (NULL);
+       }
+
+       buf[used+2] = 's';
+
+       return (buf);
+}
+
+int
+posix_openpt(int oflag)
+{
+       return open("/dev/ptmx", oflag);
+}
+
+int
+unlockpt(int fd)
+{
+       return __isptmaster(fd);
+}
+
+int
+grantpt(int fd)
+{
+       return __isptmaster(fd);
+}
\ No newline at end of file
index 9be0253..40f3030 100644 (file)
 #include <sys/device.h>
 #include <sys/thread2.h>
 #include <sys/devfs.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
 
-DEVFS_DECLARE_CLONE_BITMAP(pty);
-DEVFS_DECLARE_CLONE_BITMAP(pts);
+#define        UNIX98_PTYS     1
 
 MALLOC_DEFINE(M_PTY, "ptys", "pty data structures");
 
@@ -81,8 +82,13 @@ static       d_close_t       ptcclose;
 static d_read_t        ptcread;
 static d_write_t       ptcwrite;
 static d_poll_t        ptcpoll;
-#if 0
+
+#ifdef UNIX98_PTYS
+DEVFS_DECLARE_CLONE_BITMAP(pty);
+
 static d_clone_t       ptyclone;
+
+static int     pty_debug_level = 0;
 #endif
 
 #define        CDEV_MAJOR_S    5
@@ -115,13 +121,13 @@ static struct dev_ops ptc_ops = {
 
 struct pt_ioctl {
        int     pt_flags;
+       int     pt_flags2;
        struct  selinfo pt_selr, pt_selw;
        u_char  pt_send;
        u_char  pt_ucntl;
        struct tty pt_tty;
        cdev_t  devs, devc;
        struct  prison *pt_prison;
-       short   ref_count;
 };
 
 #define        PF_PKT          0x08            /* packet mode */
@@ -130,6 +136,23 @@ struct     pt_ioctl {
 #define        PF_NOSTOP       0x40
 #define PF_UCNTL       0x80            /* user control mode */
 
+#define        PF_UNIX98       0x01
+#define        PF_SOPEN        0x02
+#define        PF_MOPEN        0x04
+
+static int
+ptydebug(int level, char *fmt, ...)
+{
+       __va_list ap;
+
+       __va_start(ap, fmt);
+       if (level <= pty_debug_level)
+               kvprintf(fmt, ap);
+       __va_end(ap);
+
+       return 0;
+}
+
 /*
  * This function creates and initializes a pts/ptc pair
  *
@@ -164,29 +187,42 @@ ptyinit(int n)
        ttyregister(&pt->pt_tty);
 }
 
-#if 0
+#ifdef UNIX98_PTYS
 static int
 ptyclone(struct dev_clone_args *ap)
 {
        int unit;
        struct pt_ioctl *pt;
 
-       unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(pty), 256);
+       /*
+        * Limit the number of unix98 pty (slave) devices to 1000, as
+        * the utmp(5) format only allows for 8 bytes for the tty,
+        * "pts/XXX".
+        * If this limit is reached, we don't clone and return error
+        * to devfs.
+        */
+       unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(pty), 1000);
 
-       if (unit < 0)
+       if (unit < 0) {
+               ap->a_dev = NULL;
                return 1;
+       }
 
        pt = kmalloc(sizeof(*pt), M_PTY, M_WAITOK | M_ZERO);
-       pt->ref_count++;
-       pt->devc = ap->a_dev = make_only_dev(&ptc_ops, unit, ap->a_cred->cr_ruid, 0, 0600, "ptm/%d", unit);
-       pt->devs = make_dev(&pts_ops, unit, ap->a_cred->cr_ruid, 0, 0600, "pts/%d", unit);
 
-       //reference_dev(pt->devc);
-       //reference_dev(pt->devs);
+       pt->devc = ap->a_dev = make_only_dev(&ptc_ops, unit, ap->a_cred->cr_ruid,
+           0, 0600, "ptm/%d", unit);
+       pt->devs = make_dev(&pts_ops, unit, ap->a_cred->cr_ruid, GID_TTY, 0620,
+           "pts/%d", unit);
+
+       pt->devs->si_flags |= SI_OVERRIDE;      /* uid, gid, perms from dev */
+       pt->devc->si_flags |= SI_OVERRIDE;      /* uid, gid, perms from dev */
 
        pt->devs->si_drv1 = pt->devc->si_drv1 = pt;
        pt->devs->si_tty = pt->devc->si_tty = &pt->pt_tty;
        pt->pt_tty.t_dev = pt->devs;
+       pt->pt_flags2 |= PF_UNIX98;
+
        ttyregister(&pt->pt_tty);
 
        return 0;
@@ -233,13 +269,17 @@ ptsopen(struct dev_open_args *ap)
        if (error == 0)
                ptcwakeup(tp, FREAD|FWRITE);
 
-#if 0
-       /* unix98 pty stuff */
-       if ((!error) && (!memcmp(dev->si_name, "pts/", 4))) {
-               ((struct pt_ioctl *)dev->si_drv1)->ref_count++;
-               //reference_dev(dev);
-               //reference_dev(((struct pt_ioctl *)dev->si_drv1)->devc);
-               //devfs_clone_bitmap_set(&DEVFS_CLONE_BITMAP(pts), dev->si_uminor-300);
+#ifdef UNIX98_PTYS
+       /*
+        * Unix98 pty stuff.
+        * On open of the slave, we set the corresponding flag in the common
+        * struct.
+        */
+       ptydebug(1, "ptsopen=%s | unix98? %s\n", dev->si_name,
+           (pti->pt_flags2 & PF_UNIX98)?"yes":"no");
+
+       if ((!error) && (pti->pt_flags2 & PF_UNIX98)) {
+               pti->pt_flags2 |= PF_SOPEN;
        }
 #endif
 
@@ -251,21 +291,36 @@ ptsclose(struct dev_close_args *ap)
 {
        cdev_t dev = ap->a_head.a_dev;
        struct tty *tp;
+       struct pt_ioctl *pti = dev->si_drv1;
        int err;
-#if 0
-       /* unix98 pty stuff */
-       if (!memcmp(dev->si_name, "pts/", 4)) {
-               if (--((struct pt_ioctl *)dev->si_drv1)->ref_count == 0) {
-                       kfree(dev->si_drv1, M_PTY);
+
+       tp = dev->si_tty;
+       err = (*linesw[tp->t_line].l_close)(tp, ap->a_fflag);
+       ptsstop(tp, FREAD|FWRITE);
+       (void) ttyclose(tp);
+
+#ifdef UNIX98_PTYS
+       /*
+        * Unix98 pty stuff.
+        * On close of the slave, we unset the corresponding flag, and if the master
+        * isn't open anymore, we destroy the slave and unset the unit.
+        */
+       ptydebug(1, "ptsclose=%s | unix98? %s\n", dev->si_name,
+           (pti->pt_flags2 & PF_UNIX98)?"yes":"no");
+
+       if (pti->pt_flags2 & PF_UNIX98) {
+               pti->pt_flags2 &= ~PF_SOPEN;
+               KKASSERT((pti->pt_flags2 & PF_SOPEN) == 0);
+               ptydebug(1, "master open? %s\n",
+                   (pti->pt_flags2 & PF_MOPEN)?"yes":"no");
+
+               if (!(pti->pt_flags2 & PF_SOPEN) && !(pti->pt_flags2 & PF_MOPEN)) {
                        devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(pty), dev->si_uminor);
                        destroy_dev(dev);
                }
        }
 #endif
-       tp = dev->si_tty;
-       err = (*linesw[tp->t_line].l_close)(tp, ap->a_fflag);
-       ptsstop(tp, FREAD|FWRITE);
-       (void) ttyclose(tp);
+
        return (err);
 }
 
@@ -402,6 +457,20 @@ ptcopen(struct dev_open_args *ap)
        pti->devc->si_gid = 0;
        pti->devc->si_perms = 0600;
 
+#ifdef UNIX98_PTYS
+       /*
+        * Unix98 pty stuff.
+        * On open of the master, we set the corresponding flag in the common
+        * struct.
+        */
+       ptydebug(1, "ptcopen=%s (master) | unix98? %s\n", dev->si_name,
+           (pti->pt_flags2 & PF_UNIX98)?"yes":"no");
+
+       if (pti->pt_flags2 & PF_UNIX98) {
+               pti->pt_flags2 |= PF_MOPEN;
+       }
+#endif
+
        return (0);
 }
 
@@ -410,11 +479,20 @@ ptcclose(struct dev_close_args *ap)
 {
        cdev_t dev = ap->a_head.a_dev;
        struct tty *tp;
-       struct pt_ioctl *pti;
+       struct pt_ioctl *pti = dev->si_drv1;
 
        tp = dev->si_tty;
        (void)(*linesw[tp->t_line].l_modem)(tp, 0);
 
+#ifdef UNIX98_PTYS
+       /*
+        * Unix98 pty stuff.
+        * On close of the master, we unset the corresponding flag in the common
+        * struct asap.
+        */
+       pti->pt_flags2 &= ~PF_MOPEN;
+#endif
+
        /*
         * XXX MDMBUF makes no sense for ptys but would inhibit the above
         * l_modem().  CLOCAL makes sense but isn't supported.   Special
@@ -439,17 +517,27 @@ ptcclose(struct dev_close_args *ap)
        pti->devc->si_gid = 0;
        pti->devc->si_perms = 0666;
 
-#if 0
-       if (!memcmp(dev->si_name, "ptm/", 4)) {
-               ((struct pt_ioctl *)dev->si_drv1)->devc = NULL;
-               if (--((struct pt_ioctl *)dev->si_drv1)->ref_count == 0) {
-                       kfree(dev->si_drv1, M_PTY);
+#ifdef UNIX98_PTYS
+       /*
+        * Unix98 pty stuff.
+        * On close of the master, we destroy the master and, if no slaves are open,
+        * we destroy the slave device and unset the unit.
+        */
+       ptydebug(1, "ptcclose=%s (master) | unix98? %s\n", dev->si_name,
+           (pti->pt_flags2 & PF_UNIX98)?"yes":"no");
+       if (pti->pt_flags2 & PF_UNIX98) {
+               KKASSERT((pti->pt_flags2 & PF_MOPEN) == 0);
+               destroy_dev(dev);
+               pti->devc = NULL;
+
+               if (!(pti->pt_flags2 & PF_SOPEN)) {
+                       ptydebug(1, "ptcclose: slaves are not open\n");
+                       destroy_dev(pti->devs);
                        devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(pty), dev->si_uminor);
                }
-               //release_dev(dev);
-               //release_dev(((struct pt_ioctl *)dev->si_drv1)->devs);
        }
 #endif
+
        return (0);
 }
 
@@ -740,6 +828,12 @@ ptyioctl(struct dev_ioctl_args *ap)
                                pti->pt_flags &= ~PF_REMOTE;
                        ttyflush(tp, FREAD|FWRITE);
                        return (0);
+
+               case TIOCISPTMASTER:
+                       if ((pti->pt_flags2 & PF_UNIX98) && (pti->devc == dev))
+                               return (0);
+                       else
+                               return (EINVAL);
                }
 
                /*
@@ -864,19 +958,25 @@ ptyioctl(struct dev_ioctl_args *ap)
 
 static void ptc_drvinit (void *unused);
 
+#ifdef UNIX98_PTYS
+SYSCTL_INT(_kern, OID_AUTO, pty_debug, CTLFLAG_RW, &pty_debug_level,
+               0, "Change pty debug level");
+#endif
+
 static void
 ptc_drvinit(void *unused)
 {
        int i;
 
-       devfs_clone_bitmap_init(&DEVFS_CLONE_BITMAP(pty));
-       devfs_clone_bitmap_init(&DEVFS_CLONE_BITMAP(pts));
-
-#if 0
-       /* Unix98 pty stuff, leave out for now */
-       make_dev(&ptc_ops, 0, 0, 0, 0666, "ptmx");
-       devfs_clone_handler_add("ptmx", ptyclone);
+#ifdef UNIX98_PTYS
+       /*
+        * Unix98 pty stuff.
+        * Create the clonable base device.
+        */
+       make_autoclone_dev(&ptc_ops, &DEVFS_CLONE_BITMAP(pty), ptyclone,
+           0, 0, 0666, "ptmx");
 #endif
+
        for (i = 0; i < 256; i++) {
                ptyinit(i);
        }
index 466e209..c3913a3 100644 (file)
@@ -132,6 +132,7 @@ struct winsize {
                                                 * of last DCd rise */
 #define        TIOCSDRAINWAIT  _IOW('t', 87, int)      /* set ttywait timeout */
 #define        TIOCGDRAINWAIT  _IOR('t', 86, int)      /* get ttywait timeout */
+#define        TIOCISPTMASTER  _IO('t', 85)    /* is pty master */
 
 #define        TTYDISC         0               /* termios tty line discipline */
 #define        SLIPDISC        4               /* serial IP discipline */