]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Merge tag 'tty-4.11-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 16 Apr 2017 18:35:34 +0000 (11:35 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 16 Apr 2017 18:35:34 +0000 (11:35 -0700)
Pull tty fix from Greg KH:
 "Here is a single tty core revert for a patch that was reported to
  cause problems.

  The original issue is one that we have lived with for decades, so
  trying to scramble to fix the fix in time for 4.11-final does not make
  sense due to the fragility of the tty ldisc layer. Just reverting it
  makes sense for now"

* tag 'tty-4.11-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty:
  Revert "tty: don't panic on OOM in tty_set_ldisc()"

drivers/tty/tty_ldisc.c

index b0500a0a87b86161b8cf8befcee9753ff6cda74d..e4603b09863a8fa5ffd4467e81a463538f0533bb 100644 (file)
@@ -491,6 +491,41 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
        tty_ldisc_debug(tty, "%p: closed\n", ld);
 }
 
+/**
+ *     tty_ldisc_restore       -       helper for tty ldisc change
+ *     @tty: tty to recover
+ *     @old: previous ldisc
+ *
+ *     Restore the previous line discipline or N_TTY when a line discipline
+ *     change fails due to an open error
+ */
+
+static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
+{
+       struct tty_ldisc *new_ldisc;
+       int r;
+
+       /* There is an outstanding reference here so this is safe */
+       old = tty_ldisc_get(tty, old->ops->num);
+       WARN_ON(IS_ERR(old));
+       tty->ldisc = old;
+       tty_set_termios_ldisc(tty, old->ops->num);
+       if (tty_ldisc_open(tty, old) < 0) {
+               tty_ldisc_put(old);
+               /* This driver is always present */
+               new_ldisc = tty_ldisc_get(tty, N_TTY);
+               if (IS_ERR(new_ldisc))
+                       panic("n_tty: get");
+               tty->ldisc = new_ldisc;
+               tty_set_termios_ldisc(tty, N_TTY);
+               r = tty_ldisc_open(tty, new_ldisc);
+               if (r < 0)
+                       panic("Couldn't open N_TTY ldisc for "
+                             "%s --- error %d.",
+                             tty_name(tty), r);
+       }
+}
+
 /**
  *     tty_set_ldisc           -       set line discipline
  *     @tty: the terminal to set
@@ -504,7 +539,12 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
 
 int tty_set_ldisc(struct tty_struct *tty, int disc)
 {
-       int retval, old_disc;
+       int retval;
+       struct tty_ldisc *old_ldisc, *new_ldisc;
+
+       new_ldisc = tty_ldisc_get(tty, disc);
+       if (IS_ERR(new_ldisc))
+               return PTR_ERR(new_ldisc);
 
        tty_lock(tty);
        retval = tty_ldisc_lock(tty, 5 * HZ);
@@ -517,8 +557,7 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
        }
 
        /* Check the no-op case */
-       old_disc = tty->ldisc->ops->num;
-       if (old_disc == disc)
+       if (tty->ldisc->ops->num == disc)
                goto out;
 
        if (test_bit(TTY_HUPPED, &tty->flags)) {
@@ -527,25 +566,34 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
                goto out;
        }
 
-       retval = tty_ldisc_reinit(tty, disc);
+       old_ldisc = tty->ldisc;
+
+       /* Shutdown the old discipline. */
+       tty_ldisc_close(tty, old_ldisc);
+
+       /* Now set up the new line discipline. */
+       tty->ldisc = new_ldisc;
+       tty_set_termios_ldisc(tty, disc);
+
+       retval = tty_ldisc_open(tty, new_ldisc);
        if (retval < 0) {
                /* Back to the old one or N_TTY if we can't */
-               if (tty_ldisc_reinit(tty, old_disc) < 0) {
-                       pr_err("tty: TIOCSETD failed, reinitializing N_TTY\n");
-                       if (tty_ldisc_reinit(tty, N_TTY) < 0) {
-                               /* At this point we have tty->ldisc == NULL. */
-                               pr_err("tty: reinitializing N_TTY failed\n");
-                       }
-               }
+               tty_ldisc_put(new_ldisc);
+               tty_ldisc_restore(tty, old_ldisc);
        }
 
-       if (tty->ldisc && tty->ldisc->ops->num != old_disc &&
-           tty->ops->set_ldisc) {
+       if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) {
                down_read(&tty->termios_rwsem);
                tty->ops->set_ldisc(tty);
                up_read(&tty->termios_rwsem);
        }
 
+       /* At this point we hold a reference to the new ldisc and a
+          reference to the old ldisc, or we hold two references to
+          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. */
+       new_ldisc = old_ldisc;
 out:
        tty_ldisc_unlock(tty);
 
@@ -553,6 +601,7 @@ out:
           already running */
        tty_buffer_restart_work(tty->port);
 err:
+       tty_ldisc_put(new_ldisc);       /* drop the extra reference */
        tty_unlock(tty);
        return retval;
 }
@@ -613,8 +662,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
        int retval;
 
        ld = tty_ldisc_get(tty, disc);
-       if (IS_ERR(ld))
+       if (IS_ERR(ld)) {
+               BUG_ON(disc == N_TTY);
                return PTR_ERR(ld);
+       }
 
        if (tty->ldisc) {
                tty_ldisc_close(tty, tty->ldisc);
@@ -626,8 +677,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
        tty_set_termios_ldisc(tty, disc);
        retval = tty_ldisc_open(tty, tty->ldisc);
        if (retval) {
-               tty_ldisc_put(tty->ldisc);
-               tty->ldisc = NULL;
+               if (!WARN_ON(disc == N_TTY)) {
+                       tty_ldisc_put(tty->ldisc);
+                       tty->ldisc = NULL;
+               }
        }
        return retval;
 }