@@ -491,6 +491,41 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
491491 tty_ldisc_debug (tty , "%p: closed\n" , ld );
492492}
493493
494+ /**
495+ * tty_ldisc_restore - helper for tty ldisc change
496+ * @tty: tty to recover
497+ * @old: previous ldisc
498+ *
499+ * Restore the previous line discipline or N_TTY when a line discipline
500+ * change fails due to an open error
501+ */
502+
503+ static void tty_ldisc_restore (struct tty_struct * tty , struct tty_ldisc * old )
504+ {
505+ struct tty_ldisc * new_ldisc ;
506+ int r ;
507+
508+ /* There is an outstanding reference here so this is safe */
509+ old = tty_ldisc_get (tty , old -> ops -> num );
510+ WARN_ON (IS_ERR (old ));
511+ tty -> ldisc = old ;
512+ tty_set_termios_ldisc (tty , old -> ops -> num );
513+ if (tty_ldisc_open (tty , old ) < 0 ) {
514+ tty_ldisc_put (old );
515+ /* This driver is always present */
516+ new_ldisc = tty_ldisc_get (tty , N_TTY );
517+ if (IS_ERR (new_ldisc ))
518+ panic ("n_tty: get" );
519+ tty -> ldisc = new_ldisc ;
520+ tty_set_termios_ldisc (tty , N_TTY );
521+ r = tty_ldisc_open (tty , new_ldisc );
522+ if (r < 0 )
523+ panic ("Couldn't open N_TTY ldisc for "
524+ "%s --- error %d." ,
525+ tty_name (tty ), r );
526+ }
527+ }
528+
494529/**
495530 * tty_set_ldisc - set line discipline
496531 * @tty: the terminal to set
@@ -504,7 +539,12 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
504539
505540int tty_set_ldisc (struct tty_struct * tty , int disc )
506541{
507- int retval , old_disc ;
542+ int retval ;
543+ struct tty_ldisc * old_ldisc , * new_ldisc ;
544+
545+ new_ldisc = tty_ldisc_get (tty , disc );
546+ if (IS_ERR (new_ldisc ))
547+ return PTR_ERR (new_ldisc );
508548
509549 tty_lock (tty );
510550 retval = tty_ldisc_lock (tty , 5 * HZ );
@@ -517,8 +557,7 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
517557 }
518558
519559 /* Check the no-op case */
520- old_disc = tty -> ldisc -> ops -> num ;
521- if (old_disc == disc )
560+ if (tty -> ldisc -> ops -> num == disc )
522561 goto out ;
523562
524563 if (test_bit (TTY_HUPPED , & tty -> flags )) {
@@ -527,32 +566,42 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
527566 goto out ;
528567 }
529568
530- retval = tty_ldisc_reinit (tty , disc );
569+ old_ldisc = tty -> ldisc ;
570+
571+ /* Shutdown the old discipline. */
572+ tty_ldisc_close (tty , old_ldisc );
573+
574+ /* Now set up the new line discipline. */
575+ tty -> ldisc = new_ldisc ;
576+ tty_set_termios_ldisc (tty , disc );
577+
578+ retval = tty_ldisc_open (tty , new_ldisc );
531579 if (retval < 0 ) {
532580 /* Back to the old one or N_TTY if we can't */
533- if (tty_ldisc_reinit (tty , old_disc ) < 0 ) {
534- pr_err ("tty: TIOCSETD failed, reinitializing N_TTY\n" );
535- if (tty_ldisc_reinit (tty , N_TTY ) < 0 ) {
536- /* At this point we have tty->ldisc == NULL. */
537- pr_err ("tty: reinitializing N_TTY failed\n" );
538- }
539- }
581+ tty_ldisc_put (new_ldisc );
582+ tty_ldisc_restore (tty , old_ldisc );
540583 }
541584
542- if (tty -> ldisc && tty -> ldisc -> ops -> num != old_disc &&
543- tty -> ops -> set_ldisc ) {
585+ if (tty -> ldisc -> ops -> num != old_ldisc -> ops -> num && tty -> ops -> set_ldisc ) {
544586 down_read (& tty -> termios_rwsem );
545587 tty -> ops -> set_ldisc (tty );
546588 up_read (& tty -> termios_rwsem );
547589 }
548590
591+ /* At this point we hold a reference to the new ldisc and a
592+ reference to the old ldisc, or we hold two references to
593+ the old ldisc (if it was restored as part of error cleanup
594+ above). In either case, releasing a single reference from
595+ the old ldisc is correct. */
596+ new_ldisc = old_ldisc ;
549597out :
550598 tty_ldisc_unlock (tty );
551599
552600 /* Restart the work queue in case no characters kick it off. Safe if
553601 already running */
554602 tty_buffer_restart_work (tty -> port );
555603err :
604+ tty_ldisc_put (new_ldisc ); /* drop the extra reference */
556605 tty_unlock (tty );
557606 return retval ;
558607}
@@ -613,8 +662,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
613662 int retval ;
614663
615664 ld = tty_ldisc_get (tty , disc );
616- if (IS_ERR (ld ))
665+ if (IS_ERR (ld )) {
666+ BUG_ON (disc == N_TTY );
617667 return PTR_ERR (ld );
668+ }
618669
619670 if (tty -> ldisc ) {
620671 tty_ldisc_close (tty , tty -> ldisc );
@@ -626,8 +677,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
626677 tty_set_termios_ldisc (tty , disc );
627678 retval = tty_ldisc_open (tty , tty -> ldisc );
628679 if (retval ) {
629- tty_ldisc_put (tty -> ldisc );
630- tty -> ldisc = NULL ;
680+ if (!WARN_ON (disc == N_TTY )) {
681+ tty_ldisc_put (tty -> ldisc );
682+ tty -> ldisc = NULL ;
683+ }
631684 }
632685 return retval ;
633686}
0 commit comments