On Mon, Aug 24, 2015 at 02:10:49PM +0300, Cyrill Gorcunov wrote:
...
> Index: linux-pcs7.git/drivers/tty/pty.c
> ===================================================================
> --- linux-pcs7.git.orig/drivers/tty/pty.c
> +++ linux-pcs7.git/drivers/tty/pty.c
> @@ -820,10 +820,566 @@ static void __init unix98_pty_init(void)
>  static inline void unix98_pty_init(void) { }
>  #endif
>  
> +#if defined(CONFIG_VE) && defined(CONFIG_UNIX98_PTYS)
> +
> +/*
> + * VTTY architecture overview.
> + *
> + * With VTTY we make /dev/console and /dev/tty[X] virtualized
> + * per container (note the real names may vary because the
> + * kernel itself uses major:minor numbers to distinguish
> + * devices and doesn't care how they are named inside /dev.
> + * /dev/console stands for TTYAUX_MAJOR:1 while /dev/tty[X]
> + * stands for TTY_MAJOR:[0:12]. That said from inside of
> + * VTTY /dev/console is the same as /dev/tty0.
> + *
> + * For every container here is a tty map represented by
> + * vtty_map_t. It carries @veid of VE and associated slave
> + * tty peers. Also there is a per-VE spinlock to order
> + * close() operation.
> + *
> + * map
> + *  veid -> CTID
> + *    vttys -> [ 0 ]
> + *               `- @slave -> link -> @master
> + *             [ 1 ]
> + *               `- @slave -> link -> @master
> + *
> + * When container is opening tty by self (init process
> + * or whatever) we create @slave peer and a linked master,
> + * and carry them inside map until the kernel explicitly
> + * close them. Because the @master may be hanging around
> + * unconnected during the whole lifetime of a pair, we're
> + * assiging an extra refernce on it so kernel won't complain
> + * that link has nonzero @count but no real file descriptor
> + * assigned.
> + */
> +
> +#include <linux/ve.h>
> +#include <linux/file.h>
> +#include <linux/anon_inodes.h>
> +
> +static struct tty_driver *vttym_driver;
> +static struct tty_driver *vttys_driver;
> +static DEFINE_IDR(vtty_idr);
> +
> +static struct file_operations vtty_fops;
> +
> +#define vtty_match_index(idx)                ((idx) >= 0 && (idx) < 
> MAX_NR_VTTY_CONSOLES)

nit: this line is longer than 80 symbols

> +
> +typedef struct {
> +     envid_t                 veid;
> +     struct tty_struct       *vttys[MAX_NR_VTTY_CONSOLES];
> +     spinlock_t              close_lock;
> +} vtty_map_t;
> +
> +static vtty_map_t *vtty_map_lookup(envid_t veid)
> +{
> +     lockdep_assert_held(&tty_mutex);
> +     return idr_find(&vtty_idr, veid);
> +}
> +
> +static void vtty_map_set(vtty_map_t *map, struct tty_struct *tty)
> +{
> +     lockdep_assert_held(&tty_mutex);

nit: WARN_ON(map->vttys[tty->index] != NULL)

> +     map->vttys[tty->index] = tty;
> +}
> +
> +static void vtty_map_del(vtty_map_t *map, struct tty_struct *tty)
> +{
> +     lockdep_assert_held(&tty_mutex);
> +     if (map) {

nit: WARN_ON(map->vttys[tty->index] !=
             (tty->driver == ttys_driver ? tty : tty->link))

> +             tty->driver_data = tty->link->driver_data = NULL;
> +             map->vttys[tty->index] = NULL;
> +     }
> +}
> +
> +static void vtty_map_free(vtty_map_t *map)
> +{
> +     lockdep_assert_held(&tty_mutex);
> +     idr_remove(&vtty_idr, map->veid);
> +     kfree(map);
> +}
> +
> +static vtty_map_t *vtty_map_alloc(envid_t veid)
> +{
> +     vtty_map_t *map = kzalloc(sizeof(*map), GFP_KERNEL);
> +
> +     lockdep_assert_held(&tty_mutex);
> +     if (map) {
> +             map->veid = veid;
> +             veid = idr_alloc(&vtty_idr, map, veid, veid + 1, GFP_KERNEL);
> +             if (veid < 0) {
> +                     kfree(map);
> +                     return ERR_PTR(veid);
> +             }
> +             spin_lock_init(&map->close_lock);
> +     } else
> +             map = ERR_PTR(-ENOMEM);
> +     return map;
> +}
> +
> +/*
> + * vttys are never supposed to be opened from inside
> + * of VE0 except special ioctl call, so treat zero as
> + * "unused" sign.
> + */
> +static unsigned long current_veid;
> +
> +static void vtty_set_context(envid_t veid)
> +{

nit: WARN_ON(veid)

> +     lockdep_assert_held(&tty_mutex);
> +     current_veid = veid;
> +}
> +
> +static void vtty_drop_context(void)
> +{
> +     lockdep_assert_held(&tty_mutex);
> +     current_veid = 0;
> +}
> +
> +static envid_t vtty_get_context(void)
> +{
> +     BUILD_BUG_ON(sizeof(current_veid) < sizeof(envid_t));
> +     lockdep_assert_held(&tty_mutex);
> +
> +     return current_veid ?: get_exec_env()->veid;
> +}
> +
> +static struct tty_struct *vtty_lookup(struct tty_driver *driver,
> +                                   struct inode *inode, int idx)
> +{
> +     vtty_map_t *map = vtty_map_lookup(vtty_get_context());
> +     struct tty_struct *tty;
> +
> +     WARN_ON_ONCE((driver != vttym_driver) &&
> +                  (driver != vttys_driver));

nit: this check is rather useless

> +
> +     if (!vtty_match_index(idx))
> +             return ERR_PTR(-EIO);
> +
> +     /*
> +      * Nothing ever been opened yet, allocate a new
> +      * tty map together with both peers from the scratch
> +      * in install procedure.
> +      */
> +     if (!map)
> +             return NULL;
> +
> +     tty = map->vttys[idx];
> +     if (tty) {
> +             if (driver == vttym_driver)
> +                     tty = tty->link;
> +             WARN_ON(!tty);
> +     }
> +     return tty;
> +}
> +
> +static void vtty_standard_install(vtty_map_t *map, struct tty_driver 
> *driver, struct tty_struct *tty)

nit: this line is longer than 80 symbols

> +{
> +     WARN_ON(tty_init_termios(tty));
> +
> +     tty_driver_kref_get(driver);
> +     tty->driver_data = map;
> +
> +     tty_port_init(tty->port);
> +     tty->port->itty = tty;
> +}
> +
> +static struct tty_struct *vtty_install_peer(vtty_map_t *map, struct 
> tty_driver *driver,
> +                                         struct tty_port *port, int index)

ditto

> +{
> +     struct tty_struct *tty;
> +
> +     tty = alloc_tty_struct();
> +     if (!tty)
> +             return ERR_PTR(-ENOMEM);
> +     initialize_tty_struct(tty, driver, index);
> +
> +     tty->port = port;
> +     vtty_standard_install(map, driver, tty);
> +     return tty;
> +}
> +
> +static int vtty_install(struct tty_driver *driver, struct tty_struct *tty)
> +{
> +     envid_t veid = vtty_get_context();
> +     struct tty_port *peer_port;
> +     struct tty_struct *peer;
> +     vtty_map_t *map;
> +     int ret;
> +
> +     WARN_ON_ONCE(driver != vttys_driver);
> +
> +     map = vtty_map_lookup(veid);
> +     if (!map) {
> +             map = vtty_map_alloc(veid);
> +             if (IS_ERR(map))
> +                     return PTR_ERR(map);
> +     }
> +
> +     tty->port = kzalloc(sizeof(*tty->port), GFP_KERNEL);
> +     peer_port = kzalloc(sizeof(*peer_port), GFP_KERNEL);
> +     if (!tty->port || !peer_port) {
> +             ret = -ENOMEM;
> +             goto err_free;
> +     }
> +
> +     /*
> +      * The peer will have no real @fd assigned yet,
> +      * so add an extra @fd sign over so kernel won't
> +      * treat it as closing until a real file appear
> +      * on the peer. Sill this spare peer may be in this
> +      * state during the whole pair lifetime (imagine
> +      * noone ever opened it and @tty get closed).
> +      */
> +     peer = vtty_install_peer(map, vttym_driver, peer_port, tty->index);
> +     if (IS_ERR(peer)) {
> +             ret = PTR_ERR(peer);
> +             goto err_free;
> +     }
> +
> +     vtty_standard_install(map, vttys_driver, tty);
> +     vtty_map_set(map, tty);
> +
> +     tty->count++;
> +     tty->count++;
> +     peer->count++;
> +     set_bit(TTY_EXTRA_REF, &peer->flags);
> +
> +     tty->link = peer;
> +     peer->link = tty;
> +     return 0;
> +
> +err_free:
> +     kfree(tty->port);
> +     kfree(peer_port);
> +     return ret;
> +}
> +
> +static int vtty_open(struct tty_struct *tty, struct file *filp)
> +{
> +     if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
> +             return -EIO;

What's the point in this check? You only set TTY_OTHER_CLOSED when both
ends are closed and hence the pair is dead.

> +
> +     clear_bit(TTY_IO_ERROR, &tty->flags);
> +     clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);

Again, you only set TTY_IO_ERROR and TTY_OTHER_CLOSED if the pair is
dead and therefore cannot be reopened, so I think you don't need to
touch these bits at all.

> +     set_bit(TTY_THROTTLED, &tty->flags);
> +     return 0;
> +}
> +
> +static void vtty_close(struct tty_struct *tty, struct file *filp)
> +{
> +     struct tty_struct *peer = tty->link;
> +     vtty_map_t *map = tty->driver_data;
> +
> +     spin_lock(&map->close_lock);

This spinlock doesn't protect us from concurrent modifications of
peer->count from e.g. tty_reopen. What about taking tty_mutex instead?

On the second thought, this wouldn't help us if we patched mainstream
kernel, because tty_mutex is not held for tty->count modifications there
anymore. That said, if we simply substitute the spinlock with tty_mutex,
we will surely have problems sooner or later...

May be, we'd better move our ->count tweaking to the generic path, i.e.
tty_release, similarly to pty? We could distinguish vtty from pty by
checking TTY_EXTRA_REF. What do you think?

> +
> +     /*
> +      * More active file descriptors are hooked on,
> +      * wait for the last for closing.
> +      */
> +     if (tty->count > 2) {
> +             spin_unlock(&map->close_lock);
> +             return;
> +     }
> +
> +     /*
> +      * Here is a small trick we do with @count:
> +      * the pair may be opened from inside of
> +      * container (slave first) as well as from
> +      * the node (master first). But if master
> +      * is about to close and there is still an
> +      * active file hooked on a slave peer we defer
> +      * the real pair destruction until slave is
> +      * closed (incrementing @count).
> +      */
> +     if (tty->driver == vttys_driver) {
> +             if (peer->count == 1) {
> +                     clear_bit(TTY_EXTRA_REF, &peer->flags);
> +                     peer->count--;
> +                     tty->count--;
> +             }
> +     } else {
> +             if (peer->count == 1) {
> +                     clear_bit(TTY_EXTRA_REF, &tty->flags);
> +                     tty->count--;
> +             } else
> +                     peer->count++;
> +     }
> +
> +     if (tty->count == 1) {
> +             set_bit(TTY_IO_ERROR, &tty->flags);
> +             set_bit(TTY_OTHER_CLOSED, &peer->flags);

See my comment to vtty_open regarding setting these bits.

> +     }
> +
> +     spin_unlock(&map->close_lock);
> +
> +     wake_up_interruptible(&tty->read_wait);
> +     wake_up_interruptible(&tty->write_wait);
> +
> +     wake_up_interruptible(&peer->read_wait);
> +     wake_up_interruptible(&peer->write_wait);
> +}
> +
> +static void vtty_shutdown(struct tty_struct *tty)
> +{
> +     vtty_map_del(tty->driver_data, tty);
> +}
> +
> +static int vtty_write(struct tty_struct *tty, const unsigned char *buf, int 
> count)

nit: this line is longer than 80 symbols

> +{
> +     struct tty_struct *peer = tty->link;
> +
> +     if (tty->stopped)
> +             return 0;
> +
> +     if (count > 0) {
> +             count = tty_insert_flip_string(peer->port, buf, count);
> +             if (count) {
> +                     tty_flip_buffer_push(peer->port);
> +                     tty_wakeup(tty);
> +             } else {
> +                     /*
> +                      * Flush the slave reader if noone
> +                      * is actually hooked on. Otherwise
> +                      * wait until reader fetch all data.
> +                      */
> +                     if (peer->count < 2)
> +                             tty_perform_flush(peer, TCIFLUSH);
> +             }
> +     }
> +
> +     return count;
> +}
> +
> +static int vtty_write_room(struct tty_struct *tty)
> +{
> +     struct tty_struct *peer = tty->link;
> +
> +     if (tty->stopped)
> +             return 0;
> +
> +     if (peer->count < 2)
> +             return 2048;
> +
> +     return pty_space(peer);
> +}
> +
> +static const struct tty_operations vtty_ops = {
> +     .lookup         = vtty_lookup,
> +     .install        = vtty_install,
> +     .open           = vtty_open,
> +     .close          = vtty_close,
> +     .shutdown       = vtty_shutdown,
> +     .cleanup        = pty_cleanup,
> +     .write          = vtty_write,
> +     .write_room     = vtty_write_room,
> +     .set_termios    = pty_set_termios,
> +     .unthrottle     = pty_unthrottle,

> +     .remove         = pty_unix98_remove,

nit: I wouldn't reuse this one; it's empty, so we it shouldn't be very
difficult to implement our own vtty_remove; the benefit is that we can
drop dependency from UNIX98_PTYS

> +};
> +
> +struct tty_driver *vtty_console_driver(int *index)
> +{
> +     *index = 0;
> +     return vttys_driver;
> +}
> +
> +struct tty_driver *vtty_driver(dev_t dev, int *index)
> +{
> +     if (MAJOR(dev) == TTY_MAJOR &&
> +         MINOR(dev) < MAX_NR_VTTY_CONSOLES) {
> +             *index = MINOR(dev);
> +             return vttys_driver;
> +     }
> +     return NULL;
> +}
> +
> +static void ve_vtty_fini(void *data)
> +{
> +     struct ve_struct *ve = data;
> +     vtty_map_t *map;
> +     int i;
> +
> +     mutex_lock(&tty_mutex);
> +     map = vtty_map_lookup(ve->veid);
> +     if (map) {
> +             for (i = 0; i < MAX_NR_VTTY_CONSOLES; i++) {
> +                     struct tty_struct *tty = map->vttys[i];
> +                     if (!tty)
> +                             continue;
> +                     tty->driver_data = tty->link->driver_data = NULL;
> +             }
> +             vtty_map_free(map);
> +     }
> +     mutex_unlock(&tty_mutex);
> +}
> +
> +static struct ve_hook vtty_hook = {
> +     .fini           = ve_vtty_fini,
> +     .priority       = HOOK_PRIO_DEFAULT,
> +     .owner          = THIS_MODULE,
> +};
> +
> +static int __init vtty_init(void)
> +{
> +#define VTTY_DRIVER_ALLOC_FLAGS                      \
> +     (TTY_DRIVER_REAL_RAW            |       \
> +      TTY_DRIVER_RESET_TERMIOS       |       \
> +      TTY_DRIVER_DYNAMIC_DEV         |       \
> +      TTY_DRIVER_INSTALLED           |       \
> +      TTY_DRIVER_DEVPTS_MEM)
> +
> +     vttym_driver = tty_alloc_driver(MAX_NR_VTTY_CONSOLES, 
> VTTY_DRIVER_ALLOC_FLAGS);

nit: this line is longer than 80 symbols

> +     if (IS_ERR(vttym_driver))
> +             panic(pr_fmt("Can't allocate master vtty driver\n"));
> +
> +     vttys_driver = tty_alloc_driver(MAX_NR_VTTY_CONSOLES, 
> VTTY_DRIVER_ALLOC_FLAGS);

ditto

> +     if (IS_ERR(vttys_driver))
> +             panic(pr_fmt("Can't allocate slave vtty driver\n"));
> +
> +     vttym_driver->driver_name               = "vtty_master";
> +     vttym_driver->name                      = "vttym";
> +     vttym_driver->name_base                 = 0;
> +     vttym_driver->major                     = 0;
> +     vttym_driver->minor_start               = 0;
> +     vttym_driver->type                      = TTY_DRIVER_TYPE_PTY;
> +     vttym_driver->subtype                   = PTY_TYPE_MASTER;
> +     vttym_driver->init_termios              = tty_std_termios;
> +     vttym_driver->init_termios.c_iflag      = 0;
> +     vttym_driver->init_termios.c_oflag      = 0;
> +
> +     /* 38400 boud rate, 8 bit char size, enable receiver */
> +     vttym_driver->init_termios.c_cflag      = B38400 | CS8 | CREAD;
> +     vttym_driver->init_termios.c_lflag      = 0;
> +     vttym_driver->init_termios.c_ispeed     = 38400;
> +     vttym_driver->init_termios.c_ospeed     = 38400;
> +     tty_set_operations(vttym_driver, &vtty_ops);
> +
> +     vttys_driver->driver_name               = "vtty_slave";
> +     vttys_driver->name                      = "vttys";
> +     vttys_driver->name_base                 = 0;
> +     vttys_driver->major                     = 0;
> +     vttys_driver->minor_start               = 0;
> +     vttys_driver->type                      = TTY_DRIVER_TYPE_PTY;
> +     vttys_driver->subtype                   = PTY_TYPE_SLAVE;
> +     vttys_driver->init_termios              = tty_std_termios;
> +     vttys_driver->init_termios.c_iflag      = 0;
> +     vttys_driver->init_termios.c_oflag      = 0;
> +     vttys_driver->init_termios.c_cflag      = B38400 | CS8 | CREAD;
> +     vttys_driver->init_termios.c_lflag      = 0;
> +     vttys_driver->init_termios.c_ispeed     = 38400;
> +     vttys_driver->init_termios.c_ospeed     = 38400;
> +     tty_set_operations(vttys_driver, &vtty_ops);
> +
> +     if (tty_register_driver(vttym_driver))
> +             panic(pr_fmt("Can't register master vtty driver\n"));
> +
> +     if (tty_register_driver(vttys_driver))
> +             panic(pr_fmt("Can't register slave vtty driver\n"));
> +
> +     ve_hook_register(VE_SS_CHAIN, &vtty_hook);
> +     tty_default_fops(&vtty_fops);
> +     return 0;
> +}
> +
> +int vtty_open_master(envid_t veid, int idx)
> +{
> +     struct tty_struct *tty;
> +     struct file *file;
> +     char devname[64];
> +     int fd, ret;
> +
> +     if (!vtty_match_index(idx))
> +             return -EIO;
> +
> +     fd = get_unused_fd_flags(0);
> +     if (fd < 0)
> +             return fd;
> +
> +     snprintf(devname, sizeof(devname), "v%utty%d", veid, idx);
> +     file = anon_inode_getfile(devname, &vtty_fops, NULL, O_RDWR);
> +     if (IS_ERR(file)) {
> +             ret = PTR_ERR(file);
> +             goto err_put_unused_fd;
> +     }
> +     nonseekable_open(NULL, file);
> +
> +     ret = tty_alloc_file(file);
> +     if (ret)
> +             goto err_fput;
> +
> +     /*
> +      * Opening comes from ve0 context so
> +      * setup VE's context until master fetched.
> +      * This is done under @tty_mutex so noone
> +      * else would access it while we're holding
> +      * the lock.
> +      */
> +     mutex_lock(&tty_mutex);
> +     vtty_set_context(veid);
> +
> +     tty = vtty_lookup(vttym_driver, NULL, idx);
> +     if (!tty ||
> +         (test_bit(TTY_CLOSING, &tty->flags) ||
> +          test_bit(TTY_CLOSING, &tty->link->flags))) {
> +             /*
> +              * The previous connection is about to
> +              * be closed so drop it from the map and
> +              * allocate a new one.
> +              */
> +             if (tty)
> +                     vtty_map_del(tty->driver_data, tty);
> +             tty = tty_init_dev(vttys_driver, idx);

So here you install a new tty slave in place of the closing one, right?
If ->shutdown is called for the old tty after this point, you'll get
empty map. This has to be fixed. Checking that we're actually clearing
our own tty in vtty_map_del should do the trick.

> +             if (IS_ERR(tty))
> +                     goto err_install;
> +             tty->count--;
> +             tty_unlock(tty);
> +             tty = tty->link;
> +     }
> +
> +     /* One master at a time */
> +     if (tty->count > 1) {
> +             ret = -EBUSY;
> +             goto err_install;
> +     }
> +
> +     vtty_drop_context();
> +
> +     WARN_ON(!test_bit(TTY_LDISC, &tty->flags));
> +
> +     tty_add_file(tty, file);
> +     tty->count++;
> +     fd_install(fd, file);
> +     WARN_ON(vtty_open(tty, file));
> +
> +     mutex_unlock(&tty_mutex);
> +     ret = fd;
> +
> +out:
> +     return ret;
> +
> +err_install:
> +     vtty_drop_context();
> +     mutex_unlock(&tty_mutex);
> +     tty_free_file(file);
> +err_fput:
> +     file->f_op = NULL;
> +     fput(file);
> +err_put_unused_fd:
> +     put_unused_fd(fd);
> +     goto out;
> +}
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to