Skip to content
  • Jiri Slaby's avatar
    TTY: ldisc, do not close until there are readers · 92f6fa09
    Jiri Slaby authored
    We restored tty_ldisc_wait_idle in 100eeae2 (TTY: restore
    tty_ldisc_wait_idle). We used it in the ldisc changing path to fix the
    case where there are tasks in n_tty_read waiting for data and somebody
    tries to change ldisc.
    
    Similar to the case above, there may be also tasks waiting in
    n_tty_read while hangup is performed. As 65b77046 (tty-ldisc: turn
    ldisc user count into a proper refcount) removed the wait-until-idle
    from all paths, hangup path won't wait for them to disappear either
    now. So add it back even to the hangup path.
    
    There is a difference, we need uninterruptible sleep as there is
    obviously HUP signal pending. So tty_ldisc_wait_idle now sleeps
    without possibility to be interrupted. This is what original
    tty_ldisc_wait_idle did. After the wait idle reintroduction
    (100eeae2), we have had interruptible sleeps for the ldisc changing
    path. But as there is a 5s timeout anyway, we don't allow it to be
    interrupted from now on. It's not worth the added complexity of
    deciding what kind of sleep we want.
    
    Before 65b77046 tty_ldisc_release was called also from
    tty_ldisc_release. It is called from tty_release, so I don't think we
    need to restore that one.
    
    This is nicely reproducible after constifying the timing when
    drivers/tty/n_tty.c is patched as follows ("TTY: ntty, add one more
    sanity check" patch is needed to actually see it explode):
    %% -1548,6 +1549,7 @@ static int n_tty_open(struct tty_struct *tty)
    
            /* These are ugly. Currently a malloc failure here can panic */
            if (!tty->read_buf) {
    +               msleep(100);
                    tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
                    if (!tty->read_buf)
                            return -ENOMEM;
    %% -1785,6 +1788,7 @@ do_it_again:
                                    break;
                            }
                            timeout = schedule_timeout(timeout);
    +                       msleep(20);
                            continue;
                    }
                    __set_current_state(TASK_RUNNING);
    ===== With a process: =====
        while (1) {
            int fd = open(argv[1], O_RDWR);
            read(fd, buf, sizeof(buf));
            close(fd);
        }
    ===== and its child: =====
            setsid();
            while (1) {
                    int fd = open(tty, O_RDWR|O_NOCTTY);
                    ioctl(fd, TIOCSCTTY, 1);
                    vhangup();
                    close(fd);
                    usleep(100 * (10 + random() % 1000));
            }
    ===== EOF =====
    
    References: https://bugzilla.novell.com/show_bug.cgi?id=693374
    References: https://bugzilla.novell.com/show_bug.cgi?id=694509
    
    
    Signed-off-by: default avatarJiri Slaby <jslaby@suse.cz>
    Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
    Cc: Linus Torvalds <torvalds@linux-foundation.org>
    Cc: stable <stable@kernel.org> [32, 33, 34, 39]
    Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
    92f6fa09