n_tty: Check the other end of pty pair before returning EAGAIN on a read()
Subsystems (2)
(Take 3, fix compile error in n_hdlc.c)
Hi Peter, I saw Marc Aurele La France's proposed patch to n_tty to fix OpenSSH, and your feedback. Patch below is an attempt to address that feedback. Please let me know if this is the change you envisioned; (see Marc's excellent original writeup for details on the issue).
[PATCH] n_tty: wait for buffer work in read() and poll().
Undoes the following four changes:
1) f95499c3030fe1bfad57745f2db1959c5b43dca8
n_tty: Don't wait for buffer work in read() loop
2) f8747d4a466ab2cafe56112c51b3379f9fdb7a12
tty: Fix pty master read() after slave closes
3) 52bce7f8d4fc633c9a9d0646eef58ba6ae9a3b73 pty, n_tty: Simplify input processing on final close
4) 1a48632ffed61352a7810ce089dc5a8bcd505a60
pty: Fix input race when closing
These changes caused a regression in OpenSSH, as it assumes that the first read() to return EAGAIN after a SIGCHLD means that all the child's output has been returned.
Inspired by analysis and patch from Marc Aurele La France <[email protected]>
--- Documentation/serial/tty.txt | 3 --- drivers/tty/n_hdlc.c | 4 ++-- drivers/tty/n_tty.c | 34 +++++++++++++++------------------- drivers/tty/pty.c | 4 +--- drivers/tty/tty_buffer.c | 29 +---------------------------- include/linux/tty.h | 1 - 6 files changed, 19 insertions(+), 56 deletions(-)
Documentation/serial/tty.txt +0 -3
@@ -213,9 +213,6 @@ TTY_IO_ERROR If set, causes all subsequent userspace read/write TTY_OTHER_CLOSED Device is a pty and the other side has closed. -TTY_OTHER_DONE Device is a pty and the other side has closed and - all pending input processing has been completed. - TTY_NO_WRITE_SPLIT Prevent driver from splitting up writes into smaller chunks.
drivers/tty/n_hdlc.c +2 -2
@@ -600,7 +600,7 @@ static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,add_wait_queue (&tty->read_wait, &wait);for (;;) { -if (test_bit (TTY_OTHER_DONE , &tty->flags)) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { ret = -EIO ;break ; }
@@ -828,7 +828,7 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,/* set bits for operations that won't block */ if (n_hdlc->rx_buf_list.head) mask |=POLLIN |POLLRDNORM ;/* readable */ -if (test_bit (TTY_OTHER_DONE , &tty->flags)) +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) mask |=POLLHUP ;if (tty_hung_up_p (filp)) mask |=POLLHUP ;
drivers/tty/n_tty.c +15 -19
@@ -1952,10 +1952,20 @@ err:return -ENOMEM ; } +/** +* Synchronously pushes the terminal flip buffers to the line discipline +* and checks for available data. +* +* Must not be called from IRQ context. +*/ static inline int input_available_p (struct tty_struct *tty,int poll) {struct n_tty_data *ldata = tty->disc_data; -int amt = poll && !TIME_CHAR (tty) &&MIN_CHAR (tty) ?MIN_CHAR (tty) :1 ; +int amt; + +flush_work (&tty->port->buf.work); + + amt = poll && !TIME_CHAR (tty) &&MIN_CHAR (tty) ?MIN_CHAR (tty) :1 ;if (ldata->icanon && !L_EXTPROC (tty))return ldata->canon_head != ldata->read_tail;
@@ -1963,18 +1973,6 @@ static inline int input_available_p(struct tty_struct *tty, int poll)return ldata->commit_head - ldata->read_tail >= amt; } -static inline int check_other_done (struct tty_struct *tty) -{ -int done =test_bit (TTY_OTHER_DONE , &tty->flags); -if (done) { -/* paired with cmpxchg() in check_other_closed(); ensures -* read buffer head index is not stale -*/ -smp_mb__after_atomic (); - } -return done; -} -/** * copy_from_read_buf - copy read data directly * @tty: terminal device
@@ -2170,7 +2168,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,struct n_tty_data *ldata = tty->disc_data;unsigned char __user *b = buf;DEFINE_WAIT_FUNC (wait, woken_wake_function); -int c, done; +int c;int minimum, time;ssize_t retval =0 ;long timeout;
@@ -2238,10 +2236,8 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, ((minimum - (b - buf)) >= 1)) ldata->minimum_to_wake = (minimum - (b - buf)); - done =check_other_done (tty); -if (!input_available_p (tty,0 )) { -if (done) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { retval = -EIO ;break ; }
@@ -2445,12 +2441,12 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,poll_wait (file, &tty->read_wait, wait);poll_wait (file, &tty->write_wait, wait); -if (check_other_done (tty)) - mask |=POLLHUP ;if (input_available_p (tty,1 )) mask |=POLLIN |POLLRDNORM ;if (tty->packet && tty->link->ctrl_status) mask |=POLLPRI |POLLIN |POLLRDNORM ; +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) + mask |=POLLHUP ;if (tty_hung_up_p (file)) mask |=POLLHUP ;if (!(mask & (POLLHUP |POLLIN |POLLRDNORM ))) {
drivers/tty/pty.c +1 -3
@@ -59,7 +59,7 @@ static void pty_close(struct tty_struct *tty, struct file *filp)if (!tty->link)return ;set_bit (TTY_OTHER_CLOSED , &tty->link->flags); -tty_flip_buffer_push (tty->link->port); +wake_up_interruptible (&tty->link->read_wait);wake_up_interruptible (&tty->link->write_wait);if (tty->driver->subtype ==PTY_TYPE_MASTER ) {set_bit (TTY_OTHER_CLOSED , &tty->flags);
@@ -247,9 +247,7 @@ static int pty_open(struct tty_struct *tty, struct file *filp) goto out;clear_bit (TTY_IO_ERROR , &tty->flags); -/* TTY_OTHER_CLOSED must be cleared before TTY_OTHER_DONE */ clear_bit (TTY_OTHER_CLOSED , &tty->link->flags); -clear_bit (TTY_OTHER_DONE , &tty->link->flags);set_bit (TTY_THROTTLED , &tty->flags);return 0 ;
drivers/tty/tty_buffer.c +1 -28
@@ -37,29 +37,6 @@#define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) -/* -* If all tty flip buffers have been processed by flush_to_ldisc() or -* dropped by tty_buffer_flush(), check if the linked pty has been closed. -* If so, wake the reader/poll to process -*/ -static inline void check_other_closed (struct tty_struct *tty) -{ -unsigned long flags, old; - -/* transition from TTY_OTHER_CLOSED => TTY_OTHER_DONE must be atomic */ -for (flags =ACCESS_ONCE (tty->flags); -test_bit (TTY_OTHER_CLOSED , &flags); - ) { - old = flags; -__set_bit (TTY_OTHER_DONE , &flags); - flags =cmpxchg (&tty->flags, old, flags); -if (old == flags) { -wake_up_interruptible (&tty->read_wait); -break ; - } - } -} -/** * tty_buffer_lock_exclusive - gain exclusive access to buffer * tty_buffer_unlock_exclusive - release exclusive access
@@ -254,8 +231,6 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld)if (ld && ld->ops->flush_buffer) ld->ops->flush_buffer (tty); -check_other_closed (tty); -atomic_dec (&buf->priority);mutex_unlock (&buf->lock); }
@@ -505,10 +480,8 @@ static void flush_to_ldisc(struct work_struct *work)*/ count =smp_load_acquire (&head->commit) - head->read;if (!count) { -if (next ==NULL ) { -check_other_closed (tty); +if (next ==NULL )break ; - } buf->head = next;tty_buffer_free (port, head);continue ;
include/linux/tty.h +0 -1
@@ -338,7 +338,6 @@ struct tty_file_private {#define TTY_EXCLUSIVE 3/* Exclusive open mode */ #define TTY_DEBUG 4/* Debugging */ #define TTY_DO_WRITE_WAKEUP 5/* Call write_wakeup after queuing new */ -#define TTY_OTHER_DONE 6/* Closed pty has completed input processing */ #define TTY_LDISC_OPEN 11/* Line discipline is open */ #define TTY_PTY_LOCK 16/* pty private */ #define TTY_NO_WRITE_SPLIT 17/* Preserve write boundaries to driver */
Hi Brian,
Quoted 20 lines · Brian Bloniarz
(Take 3, fix compile error in
n_hdlc.c)Hi Peter, I saw Marc Aurele La France's proposed patch to
n_ttyto fix OpenSSH, and your feedback. Patch below is an attempt to address that feedback. Please let me know if this is the change you envisioned; (see Marc's excellent original writeup for details on the issue).[PATCH]
n_tty: wait for buffer work inread()andpoll().Undoes the following four changes:
1)
f95499c3030fe1bfad57745f2db1959c5b43dca8
n_tty: Don't wait for buffer work inread()loop2)
f8747d4a466ab2cafe56112c51b3379f9fdb7a12
tty: Fix pty masterread()after slave closes3)
52bce7f8d4fc633c9a9d0646eef58ba6ae9a3b73pty,n_tty: Simplify input processing on final close
Quoted 6 lines
4)
1a48632ffed61352a7810ce089dc5a8bcd505a60
pty: Fix input race when closingThese changes caused a regression in OpenSSH, as it assumes that the first
read()to return EAGAIN after a SIGCHLD means that all the child's output has been returned.
This is not descriptive enough of the requirements of OpenSSH. For example, it fails to note that the attempted read() is non-blocking for which EAGAIN is an expected result that doesn't not mean the slave-side has been closed.
Something like:
Fix OpenSSH pty regression on close
OpenSSH expects the (non-blocking) read() of pty master to return EAGAIN only if it has received all of the slave-side output after it has received SIGCHLD. This used to work on pre-3.12 kernels.
This fix effectively forces non-blocking read() and poll() to block for parallel i/o to complete for all ttys.
...
Quoted 57 lines
Inspired by analysis and patch from Marc Aurele La France <[email protected]>
Reported-by Volth <[email protected]>Reported-by Marc Aurele La France <[email protected]>Signed-off-by Brian Bloniarz <[email protected]>--- Documentation/serial/tty.txt | 3 --- drivers/tty/n_hdlc.c | 4 ++-- drivers/tty/n_tty.c | 34 +++++++++++++++------------------- drivers/tty/pty.c | 4 +--- drivers/tty/tty_buffer.c | 29 +---------------------------- include/linux/tty.h | 1 - 6 files changed, 19 insertions(+), 56 deletions(-)Documentation/serial/tty.txt +0
-3@@ -213,9 +213,6 @@ TTY_IO_ERROR If set, causes all subsequent userspace read/write TTY_OTHER_CLOSED Device is a pty and the other side has closed. -TTY_OTHER_DONE Device is a pty and the other side has closed and - all pending input processing has been completed. - TTY_NO_WRITE_SPLIT Prevent driver from splitting up writes into smaller chunks.drivers/tty/n_hdlc.c +2
-2@@ -600,7 +600,7 @@ static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,add_wait_queue (&tty->read_wait, &wait);for (;;) { -if (test_bit (TTY_OTHER_DONE , &tty->flags)) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { ret = -EIO ;break ; }@@ -828,7 +828,7 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,/* set bits for operations that won't block */ if (n_hdlc->rx_buf_list.head) mask |=POLLIN |POLLRDNORM ;/* readable */ -if (test_bit (TTY_OTHER_DONE , &tty->flags)) +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) mask |=POLLHUP ;if (tty_hung_up_p (filp)) mask |=POLLHUP ;drivers/tty/n_tty.c +0
-0
drivers/tty/n_tty.c err: · line 1957
Quoted 6 lines
drivers/tty/n_tty.c +3
-0@@ -1952,10 +1952,20 @@ err:return -ENOMEM ; } +/** +* Synchronously pushes the terminal flip buffers to the line discipline +* and checks for available data.
No, see below.
drivers/tty/n_tty.c err: · line 1966
Quoted 10 lines
drivers/tty/n_tty.c +6
-1+* +* Must not be called from IRQ context. +*/ static inline int input_available_p (struct tty_struct *tty,int poll) {struct n_tty_data *ldata = tty->disc_data; -int amt = poll && !TIME_CHAR (tty) &&MIN_CHAR (tty) ?MIN_CHAR (tty) :1 ; +int amt; + +flush_work (&tty->port->buf.work);
This is not necessary and can deadlock.
The wait for buffer work to finish is only necessary input_available_p() returns false. Then in n_tty_read() the termios lock needs to be dropped before waiting for buffer work and rechecking input_available_p().
Please test your patches with lockdep enabled.
Also, abstract the naked flush_work(), similar to tty_buffer_cancel_work().
Quoted 151 lines
drivers/tty/n_tty.c +6
-18+ + amt = poll && !TIME_CHAR (tty) &&MIN_CHAR (tty) ?MIN_CHAR (tty) :1 ;if (ldata->icanon && !L_EXTPROC (tty))return ldata->canon_head != ldata->read_tail;@@ -1963,18 +1973,6 @@ static inline int input_available_p(struct tty_struct *tty, int poll)return ldata->commit_head - ldata->read_tail >= amt; } -static inline int check_other_done (struct tty_struct *tty) -{ -int done =test_bit (TTY_OTHER_DONE , &tty->flags); -if (done) { -/* paired with cmpxchg() in check_other_closed(); ensures -* read buffer head index is not stale -*/ -smp_mb__after_atomic (); - } -return done; -} -/** * copy_from_read_buf - copy read data directly * @tty: terminal device @@ -2170,7 +2168,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,struct n_tty_data *ldata = tty->disc_data;unsigned char __user *b = buf;DEFINE_WAIT_FUNC (wait, woken_wake_function); -int c, done; +int c;int minimum, time;ssize_t retval =0 ;long timeout;@@ -2238,10 +2236,8 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, ((minimum - (b - buf)) >= 1)) ldata->minimum_to_wake = (minimum - (b - buf)); - done =check_other_done (tty); -if (!input_available_p (tty,0 )) { -if (done) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { retval = -EIO ;break ; }@@ -2445,12 +2441,12 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,poll_wait (file, &tty->read_wait, wait);poll_wait (file, &tty->write_wait, wait); -if (check_other_done (tty)) - mask |=POLLHUP ;if (input_available_p (tty,1 )) mask |=POLLIN |POLLRDNORM ;if (tty->packet && tty->link->ctrl_status) mask |=POLLPRI |POLLIN |POLLRDNORM ; +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) + mask |=POLLHUP ;if (tty_hung_up_p (file)) mask |=POLLHUP ;if (!(mask & (POLLHUP |POLLIN |POLLRDNORM ))) {drivers/tty/pty.c +1
-3@@ -59,7 +59,7 @@ static void pty_close(struct tty_struct *tty, struct file *filp)if (!tty->link)return ;set_bit (TTY_OTHER_CLOSED , &tty->link->flags); -tty_flip_buffer_push (tty->link->port); +wake_up_interruptible (&tty->link->read_wait);wake_up_interruptible (&tty->link->write_wait);if (tty->driver->subtype ==PTY_TYPE_MASTER ) {set_bit (TTY_OTHER_CLOSED , &tty->flags);@@ -247,9 +247,7 @@ static int pty_open(struct tty_struct *tty, struct file *filp) goto out;clear_bit (TTY_IO_ERROR , &tty->flags); -/* TTY_OTHER_CLOSED must be cleared before TTY_OTHER_DONE */ clear_bit (TTY_OTHER_CLOSED , &tty->link->flags); -clear_bit (TTY_OTHER_DONE , &tty->link->flags);set_bit (TTY_THROTTLED , &tty->flags);return 0 ;drivers/tty/tty_buffer.c +1
-28@@ -37,29 +37,6 @@#define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) -/* -* If all tty flip buffers have been processed by flush_to_ldisc() or -* dropped by tty_buffer_flush(), check if the linked pty has been closed. -* If so, wake the reader/poll to process -*/ -static inline void check_other_closed (struct tty_struct *tty) -{ -unsigned long flags, old; - -/* transition from TTY_OTHER_CLOSED => TTY_OTHER_DONE must be atomic */ -for (flags =ACCESS_ONCE (tty->flags); -test_bit (TTY_OTHER_CLOSED , &flags); - ) { - old = flags; -__set_bit (TTY_OTHER_DONE , &flags); - flags =cmpxchg (&tty->flags, old, flags); -if (old == flags) { -wake_up_interruptible (&tty->read_wait); -break ; - } - } -} -/** * tty_buffer_lock_exclusive - gain exclusive access to buffer * tty_buffer_unlock_exclusive - release exclusive access @@ -254,8 +231,6 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld)if (ld && ld->ops->flush_buffer) ld->ops->flush_buffer (tty); -check_other_closed (tty); -atomic_dec (&buf->priority);mutex_unlock (&buf->lock); }@@ -505,10 +480,8 @@ static void flush_to_ldisc(struct work_struct *work)*/ count =smp_load_acquire (&head->commit) - head->read;if (!count) { -if (next ==NULL ) { -check_other_closed (tty); +if (next ==NULL )break ; - } buf->head = next;tty_buffer_free (port, head);continue ;include/linux/tty.h +0
-1@@ -338,7 +338,6 @@ struct tty_file_private {#define TTY_EXCLUSIVE 3/* Exclusive open mode */ #define TTY_DEBUG 4/* Debugging */ #define TTY_DO_WRITE_WAKEUP 5/* Call write_wakeup after queuing new */ -#define TTY_OTHER_DONE 6/* Closed pty has completed input processing */ #define TTY_LDISC_OPEN 11/* Line discipline is open */ #define TTY_PTY_LOCK 16/* pty private */ #define TTY_NO_WRITE_SPLIT 17/* Preserve write boundaries to driver */
PATCH 1/1 Fix OpenSSH pty regression on close
OpenSSH expects the (non-blocking) read() of pty master to return EAGAIN only if it has received all of the slave-side output after it has received SIGCHLD. This used to work on pre-3.12 kernels.
This fix effectively forces non-blocking read() and poll() to block for parallel i/o to complete for all ttys. It unwinds these changes:
1) f95499c3030fe1bfad57745f2db1959c5b43dca8
n_tty: Don't wait for buffer work in read() loop
2) f8747d4a466ab2cafe56112c51b3379f9fdb7a12
tty: Fix pty master read() after slave closes
3) 52bce7f8d4fc633c9a9d0646eef58ba6ae9a3b73 pty, n_tty: Simplify input processing on final close
4) 1a48632ffed61352a7810ce089dc5a8bcd505a60
pty: Fix input race when closing
Inspired by analysis and patch from Marc Aurele La France <[email protected]>
--- Documentation/serial/tty.txt | 3 -- drivers/tty/n_hdlc.c | 4 +-- drivers/tty/n_tty.c | 70 +++++++++++++++++++++----------------------- drivers/tty/pty.c | 4 +-- drivers/tty/tty_buffer.c | 34 ++++----------------- include/linux/tty.h | 2 +- 6 files changed, 43 insertions(+), 74 deletions(-)
Documentation/serial/tty.txt +0 -3
@@ -213,9 +213,6 @@ TTY_IO_ERROR If set, causes all subsequent userspace read/write TTY_OTHER_CLOSED Device is a pty and the other side has closed. -TTY_OTHER_DONE Device is a pty and the other side has closed and - all pending input processing has been completed. - TTY_NO_WRITE_SPLIT Prevent driver from splitting up writes into smaller chunks.
drivers/tty/n_hdlc.c +2 -2
@@ -600,7 +600,7 @@ static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,add_wait_queue (&tty->read_wait, &wait);for (;;) { -if (test_bit (TTY_OTHER_DONE , &tty->flags)) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { ret = -EIO ;break ; }
@@ -828,7 +828,7 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,/* set bits for operations that won't block */ if (n_hdlc->rx_buf_list.head) mask |=POLLIN |POLLRDNORM ;/* readable */ -if (test_bit (TTY_OTHER_DONE , &tty->flags)) +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) mask |=POLLHUP ;if (tty_hung_up_p (filp)) mask |=POLLHUP ;
drivers/tty/n_tty.c +33 -37
102 lines not on this page · raw message
drivers/tty/pty.c +1 -3
@@ -59,7 +59,7 @@ static void pty_close(struct tty_struct *tty, struct file *filp)if (!tty->link)return ;set_bit (TTY_OTHER_CLOSED , &tty->link->flags); -tty_flip_buffer_push (tty->link->port); +wake_up_interruptible (&tty->link->read_wait);wake_up_interruptible (&tty->link->write_wait);if (tty->driver->subtype ==PTY_TYPE_MASTER ) {set_bit (TTY_OTHER_CLOSED , &tty->flags);
@@ -247,9 +247,7 @@ static int pty_open(struct tty_struct *tty, struct file *filp) goto out;clear_bit (TTY_IO_ERROR , &tty->flags); -/* TTY_OTHER_CLOSED must be cleared before TTY_OTHER_DONE */ clear_bit (TTY_OTHER_CLOSED , &tty->link->flags); -clear_bit (TTY_OTHER_DONE , &tty->link->flags);set_bit (TTY_THROTTLED , &tty->flags);return 0 ;
drivers/tty/tty_buffer.c +6 -28
@@ -37,29 +37,6 @@#define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) -/* -* If all tty flip buffers have been processed by flush_to_ldisc() or -* dropped by tty_buffer_flush(), check if the linked pty has been closed. -* If so, wake the reader/poll to process -*/ -static inline void check_other_closed (struct tty_struct *tty) -{ -unsigned long flags, old; - -/* transition from TTY_OTHER_CLOSED => TTY_OTHER_DONE must be atomic */ -for (flags =ACCESS_ONCE (tty->flags); -test_bit (TTY_OTHER_CLOSED , &flags); - ) { - old = flags; -__set_bit (TTY_OTHER_DONE , &flags); - flags =cmpxchg (&tty->flags, old, flags); -if (old == flags) { -wake_up_interruptible (&tty->read_wait); -break ; - } - } -} -/** * tty_buffer_lock_exclusive - gain exclusive access to buffer * tty_buffer_unlock_exclusive - release exclusive access
@@ -254,8 +231,6 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld)if (ld && ld->ops->flush_buffer) ld->ops->flush_buffer (tty); -check_other_closed (tty); -atomic_dec (&buf->priority);mutex_unlock (&buf->lock); }
@@ -505,10 +480,8 @@ static void flush_to_ldisc(struct work_struct *work)*/ count =smp_load_acquire (&head->commit) - head->read;if (!count) { -if (next ==NULL ) { -check_other_closed (tty); +if (next ==NULL )break ; - } buf->head = next;tty_buffer_free (port, head);continue ;
@@ -597,3 +570,8 @@ bool tty_buffer_cancel_work(struct tty_port *port) {return cancel_work_sync (&port->buf.work); } + +void tty_buffer_flush_work (struct tty_port *port) +{ +flush_work (&port->buf.work); +}
include/linux/tty.h +1 -1
@@ -338,7 +338,6 @@ struct tty_file_private {#define TTY_EXCLUSIVE 3/* Exclusive open mode */ #define TTY_DEBUG 4/* Debugging */ #define TTY_DO_WRITE_WAKEUP 5/* Call write_wakeup after queuing new */ -#define TTY_OTHER_DONE 6/* Closed pty has completed input processing */ #define TTY_LDISC_OPEN 11/* Line discipline is open */ #define TTY_PTY_LOCK 16/* pty private */ #define TTY_NO_WRITE_SPLIT 17/* Preserve write boundaries to driver */
@@ -464,6 +463,7 @@ extern void tty_buffer_init(struct tty_port *port);extern void tty_buffer_set_lock_subclass (struct tty_port *port);extern bool tty_buffer_restart_work (struct tty_port *port);extern bool tty_buffer_cancel_work (struct tty_port *port); +extern void tty_buffer_flush_work (struct tty_port *port);extern speed_t tty_termios_baud_rate (struct ktermios *termios);extern speed_t tty_termios_input_baud_rate (struct ktermios *termios); extern void tty_termios_encode_baud_rate(struct ktermios *termios,
Signature · 1 line
2.5.0
Re: [PATCH] Fix OpenSSH pty regression on close
Hi Brian,
Couple of procedural notes:
I'm not a maintainer so can't take this patch directly. This patch should be addressed to the tty maintainers and cc me.
What tree was this diff'd from? You should be using GregKH's tty-next branch on this treegit://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git
Quoted 10 lines · Brian Bloniarz
OpenSSH expects the (non-blocking)
read()of pty master to return EAGAIN only if it has received all of the slave-side output after it has received SIGCHLD. This used to work on pre-3.12 kernels.This fix effectively forces non-blocking
read()andpoll()to block for parallel i/o to complete for all ttys. It unwinds these changes:1)
f95499c3030fe1bfad57745f2db1959c5b43dca8
n_tty: Don't wait for buffer work inread()loop
I would drop the above reference as you won't be waiting for buffer work unless the initial test for input fails.
Quoted 280 lines
2)
f8747d4a466ab2cafe56112c51b3379f9fdb7a12
tty: Fix pty masterread()after slave closes3)
52bce7f8d4fc633c9a9d0646eef58ba6ae9a3b73pty,n_tty: Simplify input processing on final close4)
1a48632ffed61352a7810ce089dc5a8bcd505a60
pty: Fix input race when closingInspired by analysis and patch from Marc Aurele La France <[email protected]>
Reported-by Volth <[email protected]>Reported-by Marc Aurele La France <[email protected]>Signed-off-by Brian Bloniarz <[email protected]>--- Documentation/serial/tty.txt | 3 -- drivers/tty/n_hdlc.c | 4 +-- drivers/tty/n_tty.c | 70 +++++++++++++++++++++----------------------- drivers/tty/pty.c | 4 +-- drivers/tty/tty_buffer.c | 34 ++++----------------- include/linux/tty.h | 2 +- 6 files changed, 43 insertions(+), 74 deletions(-)Documentation/serial/tty.txt +0
-3@@ -213,9 +213,6 @@ TTY_IO_ERROR If set, causes all subsequent userspace read/write TTY_OTHER_CLOSED Device is a pty and the other side has closed. -TTY_OTHER_DONE Device is a pty and the other side has closed and - all pending input processing has been completed. - TTY_NO_WRITE_SPLIT Prevent driver from splitting up writes into smaller chunks.drivers/tty/n_hdlc.c +2
-2@@ -600,7 +600,7 @@ static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,add_wait_queue (&tty->read_wait, &wait);for (;;) { -if (test_bit (TTY_OTHER_DONE , &tty->flags)) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { ret = -EIO ;break ; }@@ -828,7 +828,7 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,/* set bits for operations that won't block */ if (n_hdlc->rx_buf_list.head) mask |=POLLIN |POLLRDNORM ;/* readable */ -if (test_bit (TTY_OTHER_DONE , &tty->flags)) +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) mask |=POLLHUP ;if (tty_hung_up_p (filp)) mask |=POLLHUP ;drivers/tty/n_tty.c +33
-37@@ -1963,18 +1963,6 @@ static inline int input_available_p(struct tty_struct *tty, int poll)return ldata->commit_head - ldata->read_tail >= amt; } -static inline int check_other_done (struct tty_struct *tty) -{ -int done =test_bit (TTY_OTHER_DONE , &tty->flags); -if (done) { -/* paired with cmpxchg() in check_other_closed(); ensures -* read buffer head index is not stale -*/ -smp_mb__after_atomic (); - } -return done; -} -/** * copy_from_read_buf - copy read data directly * @tty: terminal device @@ -2170,7 +2158,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,struct n_tty_data *ldata = tty->disc_data;unsigned char __user *b = buf;DEFINE_WAIT_FUNC (wait, woken_wake_function); -int c, done; +int c;int minimum, time;ssize_t retval =0 ;long timeout;@@ -2238,32 +2226,35 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, ((minimum - (b - buf)) >= 1)) ldata->minimum_to_wake = (minimum - (b - buf)); - done =check_other_done (tty); -if (!input_available_p (tty,0 )) { -if (done) { - retval = -EIO ; -break ; - } -if (tty_hung_up_p (file)) -break ; -if (!timeout) -break ; -if (file->f_flags &O_NONBLOCK ) { - retval = -EAGAIN ; -break ; - } -if (signal_pending (current)) { - retval = -ERESTARTSYS ; -break ; - }up_read (&tty->termios_rwsem); +tty_buffer_flush_work (tty->port); +down_read (&tty->termios_rwsem); +if (!input_available_p (tty,0 )) { +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) { + retval = -EIO ; +break ; + } +if (tty_hung_up_p (file)) +break ; +if (!timeout) +break ; +if (file->f_flags &O_NONBLOCK ) { + retval = -EAGAIN ; +break ; + } +if (signal_pending (current)) { + retval = -ERESTARTSYS ; +break ; + } +up_read (&tty->termios_rwsem); - timeout =wait_woken (&wait,TASK_INTERRUPTIBLE , - timeout); + timeout =wait_woken (&wait,TASK_INTERRUPTIBLE , + timeout); -down_read (&tty->termios_rwsem); -continue ; +down_read (&tty->termios_rwsem); +continue ; + } }if (ldata->icanon && !L_EXTPROC (tty)) {@@ -2445,12 +2436,17 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,poll_wait (file, &tty->read_wait, wait);poll_wait (file, &tty->write_wait, wait); -if (check_other_done (tty)) - mask |=POLLHUP ;if (input_available_p (tty,1 )) mask |=POLLIN |POLLRDNORM ; +else { +tty_buffer_flush_work (tty->port); +if (input_available_p (tty,1 )) + mask |=POLLIN |POLLRDNORM ; + }if (tty->packet && tty->link->ctrl_status) mask |=POLLPRI |POLLIN |POLLRDNORM ; +if (test_bit (TTY_OTHER_CLOSED , &tty->flags)) + mask |=POLLHUP ;if (tty_hung_up_p (file)) mask |=POLLHUP ;if (!(mask & (POLLHUP |POLLIN |POLLRDNORM ))) {drivers/tty/pty.c +1
-3@@ -59,7 +59,7 @@ static void pty_close(struct tty_struct *tty, struct file *filp)if (!tty->link)return ;set_bit (TTY_OTHER_CLOSED , &tty->link->flags); -tty_flip_buffer_push (tty->link->port); +wake_up_interruptible (&tty->link->read_wait);wake_up_interruptible (&tty->link->write_wait);if (tty->driver->subtype ==PTY_TYPE_MASTER ) {set_bit (TTY_OTHER_CLOSED , &tty->flags);@@ -247,9 +247,7 @@ static int pty_open(struct tty_struct *tty, struct file *filp) goto out;clear_bit (TTY_IO_ERROR , &tty->flags); -/* TTY_OTHER_CLOSED must be cleared before TTY_OTHER_DONE */ clear_bit (TTY_OTHER_CLOSED , &tty->link->flags); -clear_bit (TTY_OTHER_DONE , &tty->link->flags);set_bit (TTY_THROTTLED , &tty->flags);return 0 ;drivers/tty/tty_buffer.c +6
-28@@ -37,29 +37,6 @@#define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) -/* -* If all tty flip buffers have been processed by flush_to_ldisc() or -* dropped by tty_buffer_flush(), check if the linked pty has been closed. -* If so, wake the reader/poll to process -*/ -static inline void check_other_closed (struct tty_struct *tty) -{ -unsigned long flags, old; - -/* transition from TTY_OTHER_CLOSED => TTY_OTHER_DONE must be atomic */ -for (flags =ACCESS_ONCE (tty->flags); -test_bit (TTY_OTHER_CLOSED , &flags); - ) { - old = flags; -__set_bit (TTY_OTHER_DONE , &flags); - flags =cmpxchg (&tty->flags, old, flags); -if (old == flags) { -wake_up_interruptible (&tty->read_wait); -break ; - } - } -} -/** * tty_buffer_lock_exclusive - gain exclusive access to buffer * tty_buffer_unlock_exclusive - release exclusive access @@ -254,8 +231,6 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld)if (ld && ld->ops->flush_buffer) ld->ops->flush_buffer (tty); -check_other_closed (tty); -atomic_dec (&buf->priority);mutex_unlock (&buf->lock); }@@ -505,10 +480,8 @@ static void flush_to_ldisc(struct work_struct *work)*/ count =smp_load_acquire (&head->commit) - head->read;if (!count) { -if (next ==NULL ) { -check_other_closed (tty); +if (next ==NULL )break ; - } buf->head = next;tty_buffer_free (port, head);continue ;@@ -597,3 +570,8 @@ bool tty_buffer_cancel_work(struct tty_port *port) {return cancel_work_sync (&port->buf.work); } + +void tty_buffer_flush_work (struct tty_port *port) +{ +flush_work (&port->buf.work); +}include/linux/tty.h +1
-1@@ -338,7 +338,6 @@ struct tty_file_private {#define TTY_EXCLUSIVE 3/* Exclusive open mode */ #define TTY_DEBUG 4/* Debugging */ #define TTY_DO_WRITE_WAKEUP 5/* Call write_wakeup after queuing new */ -#define TTY_OTHER_DONE 6/* Closed pty has completed input processing */ #define TTY_LDISC_OPEN 11/* Line discipline is open */ #define TTY_PTY_LOCK 16/* pty private */ #define TTY_NO_WRITE_SPLIT 17/* Preserve write boundaries to driver */ @@ -464,6 +463,7 @@ extern void tty_buffer_init(struct tty_port *port);extern void tty_buffer_set_lock_subclass (struct tty_port *port);extern bool tty_buffer_restart_work (struct tty_port *port);extern bool tty_buffer_cancel_work (struct tty_port *port); +extern void tty_buffer_flush_work (struct tty_port *port);extern speed_t tty_termios_baud_rate (struct ktermios *termios);extern speed_t tty_termios_input_baud_rate (struct ktermios *termios); extern void tty_termios_encode_baud_rate(struct ktermios *termios,