Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[cascardo/linux.git] / drivers / tty / tty_ldisc.c
index 629e3c8..a054d03 100644 (file)
@@ -185,7 +185,7 @@ static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
  *
  *     Complement of tty_ldisc_get().
  */
-static inline void tty_ldisc_put(struct tty_ldisc *ld)
+static void tty_ldisc_put(struct tty_ldisc *ld)
 {
        if (WARN_ON_ONCE(!ld))
                return;
@@ -417,6 +417,10 @@ EXPORT_SYMBOL_GPL(tty_ldisc_flush);
  *     they are not on hot paths so a little discipline won't do
  *     any harm.
  *
+ *     The line discipline-related tty_struct fields are reset to
+ *     prevent the ldisc driver from re-using stale information for
+ *     the new ldisc instance.
+ *
  *     Locking: takes termios_rwsem
  */
 
@@ -425,6 +429,9 @@ static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
        down_write(&tty->termios_rwsem);
        tty->termios.c_line = num;
        up_write(&tty->termios_rwsem);
+
+       tty->disc_data = NULL;
+       tty->receive_room = 0;
 }
 
 /**
@@ -529,34 +536,21 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
        tty_lock(tty);
        retval = tty_ldisc_lock(tty, 5 * HZ);
-       if (retval) {
-               tty_ldisc_put(new_ldisc);
-               tty_unlock(tty);
-               return retval;
-       }
+       if (retval)
+               goto err;
 
-       /*
-        *      Check the no-op case
-        */
+       /* Check the no-op case */
+       if (tty->ldisc->ops->num == ldisc)
+               goto out;
 
-       if (tty->ldisc->ops->num == ldisc) {
-               tty_ldisc_unlock(tty);
-               tty_ldisc_put(new_ldisc);
-               tty_unlock(tty);
-               return 0;
+       if (test_bit(TTY_HUPPED, &tty->flags)) {
+               /* We were raced by hangup */
+               retval = -EIO;
+               goto out;
        }
 
        old_ldisc = tty->ldisc;
 
-       if (test_bit(TTY_HUPPED, &tty->flags)) {
-               /* We were raced by the hangup method. It will have stomped
-                  the ldisc data and closed the ldisc down */
-               tty_ldisc_unlock(tty);
-               tty_ldisc_put(new_ldisc);
-               tty_unlock(tty);
-               return -EIO;
-       }
-
        /* Shutdown the old discipline. */
        tty_ldisc_close(tty, old_ldisc);
 
@@ -582,18 +576,15 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
           the old ldisc (if it was restored as part of error cleanup
           above). In either case, releasing a single reference from
           the old ldisc is correct. */
-
-       tty_ldisc_put(old_ldisc);
-
-       /*
-        *      Allow ldisc referencing to occur again
-        */
+       new_ldisc = old_ldisc;
+out:
        tty_ldisc_unlock(tty);
 
        /* Restart the work queue in case no characters kick it off. Safe if
           already running */
        tty_buffer_restart_work(tty->port);
-
+err:
+       tty_ldisc_put(new_ldisc);       /* drop the extra reference */
        tty_unlock(tty);
        return retval;
 }