* Re: n_tty: Check the other end of pty pair before returning EAGAIN on a read()
@ 2016-02-29 3:56 Brian Bloniarz
2016-03-01 4:30 ` Peter Hurley
0 siblings, 1 reply; 4+ messages in thread
From: Brian Bloniarz @ 2016-02-29 3:56 UTC (permalink / raw)
To: linux-kernel; +Cc: peter, tsi
(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 <tsi@tuyoix.net>
Reported-by: Volth <openssh@volth.com>
Reported-by: Marc Aurele La France <tsi@tuyoix.net>
BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=52
BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=2492
Signed-off-by: Brian Bloniarz <brian.bloniarz@gmail.com>
---
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(-)
diff --git a/Documentation/serial/tty.txt b/Documentation/serial/tty.txt
index bc3842d..e2dea3d 100644
--- a/Documentation/serial/tty.txt
+++ b/Documentation/serial/tty.txt
@@ -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.
diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
index bbc4ce6..644ddb8 100644
--- a/drivers/tty/n_hdlc.c
+++ b/drivers/tty/n_hdlc.c
@@ -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;
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index b280abaa..fc04011 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -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))) {
diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index 2348fa6..6427a39 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -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;
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index 3cd31e0..3c0e914 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -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;
diff --git a/include/linux/tty.h b/include/linux/tty.h
index d9fb4b0..af18365 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -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 */
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: n_tty: Check the other end of pty pair before returning EAGAIN on a read()
2016-02-29 3:56 n_tty: Check the other end of pty pair before returning EAGAIN on a read() Brian Bloniarz
@ 2016-03-01 4:30 ` Peter Hurley
2016-03-01 6:40 ` [PATCH] Fix OpenSSH pty regression on close Brian Bloniarz
0 siblings, 1 reply; 4+ messages in thread
From: Peter Hurley @ 2016-03-01 4:30 UTC (permalink / raw)
To: Brian Bloniarz; +Cc: linux-kernel, tsi
Hi Brian,
On 02/28/2016 07:56 PM, Brian Bloniarz wrote:
> (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.
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.
...
> Inspired by analysis and patch from Marc Aurele La France <tsi@tuyoix.net>
>
> Reported-by: Volth <openssh@volth.com>
> Reported-by: Marc Aurele La France <tsi@tuyoix.net>
> BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=52
> BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=2492
> Signed-off-by: Brian Bloniarz <brian.bloniarz@gmail.com>
> ---
> 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(-)
>
> diff --git a/Documentation/serial/tty.txt b/Documentation/serial/tty.txt
> index bc3842d..e2dea3d 100644
> --- a/Documentation/serial/tty.txt
> +++ b/Documentation/serial/tty.txt
> @@ -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.
>
> diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
> index bbc4ce6..644ddb8 100644
> --- a/drivers/tty/n_hdlc.c
> +++ b/drivers/tty/n_hdlc.c
> @@ -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;
> diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
> index b280abaa..fc04011 100644
> --- a/drivers/tty/n_tty.c
> +++ b/drivers/tty/n_tty.c
> @@ -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.
> + *
> + * 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().
> +
> + 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))) {
> diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
> index 2348fa6..6427a39 100644
> --- a/drivers/tty/pty.c
> +++ b/drivers/tty/pty.c
> @@ -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;
>
> diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
> index 3cd31e0..3c0e914 100644
> --- a/drivers/tty/tty_buffer.c
> +++ b/drivers/tty/tty_buffer.c
> @@ -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;
> diff --git a/include/linux/tty.h b/include/linux/tty.h
> index d9fb4b0..af18365 100644
> --- a/include/linux/tty.h
> +++ b/include/linux/tty.h
> @@ -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 */
>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH] Fix OpenSSH pty regression on close
2016-03-01 4:30 ` Peter Hurley
@ 2016-03-01 6:40 ` Brian Bloniarz
2016-03-01 16:09 ` Peter Hurley
0 siblings, 1 reply; 4+ messages in thread
From: Brian Bloniarz @ 2016-03-01 6:40 UTC (permalink / raw)
To: Peter Hurley; +Cc: linux-kernel, tsi
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 <tsi@tuyoix.net>
Reported-by: Volth <openssh@volth.com>
Reported-by: Marc Aurele La France <tsi@tuyoix.net>
BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=52
BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=2492
Signed-off-by: Brian Bloniarz <brian.bloniarz@gmail.com>
---
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(-)
diff --git a/Documentation/serial/tty.txt b/Documentation/serial/tty.txt
index bc3842d..e2dea3d 100644
--- a/Documentation/serial/tty.txt
+++ b/Documentation/serial/tty.txt
@@ -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.
diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
index bbc4ce6..644ddb8 100644
--- a/drivers/tty/n_hdlc.c
+++ b/drivers/tty/n_hdlc.c
@@ -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;
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index b280abaa..c12def7 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -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))) {
diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index 2348fa6..6427a39 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -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;
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index 3cd31e0..fb31eec 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -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);
+}
diff --git a/include/linux/tty.h b/include/linux/tty.h
index d9fb4b0..663155b 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -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,
--
2.5.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] Fix OpenSSH pty regression on close
2016-03-01 6:40 ` [PATCH] Fix OpenSSH pty regression on close Brian Bloniarz
@ 2016-03-01 16:09 ` Peter Hurley
0 siblings, 0 replies; 4+ messages in thread
From: Peter Hurley @ 2016-03-01 16:09 UTC (permalink / raw)
To: Brian Bloniarz; +Cc: linux-kernel, tsi
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 tree
git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git
On 02/29/2016 10:40 PM, Brian Bloniarz wrote:
> 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
I would drop the above reference as you won't be waiting for
buffer work unless the initial test for input fails.
>
> 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 <tsi@tuyoix.net>
>
> Reported-by: Volth <openssh@volth.com>
> Reported-by: Marc Aurele La France <tsi@tuyoix.net>
> BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=52
> BugLink: https://bugzilla.mindrot.org/show_bug.cgi?id=2492
> Signed-off-by: Brian Bloniarz <brian.bloniarz@gmail.com>
> ---
> 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(-)
>
> diff --git a/Documentation/serial/tty.txt b/Documentation/serial/tty.txt
> index bc3842d..e2dea3d 100644
> --- a/Documentation/serial/tty.txt
> +++ b/Documentation/serial/tty.txt
> @@ -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.
>
> diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
> index bbc4ce6..644ddb8 100644
> --- a/drivers/tty/n_hdlc.c
> +++ b/drivers/tty/n_hdlc.c
> @@ -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;
> diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
> index b280abaa..c12def7 100644
> --- a/drivers/tty/n_tty.c
> +++ b/drivers/tty/n_tty.c
> @@ -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))) {
> diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
> index 2348fa6..6427a39 100644
> --- a/drivers/tty/pty.c
> +++ b/drivers/tty/pty.c
> @@ -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;
>
> diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
> index 3cd31e0..fb31eec 100644
> --- a/drivers/tty/tty_buffer.c
> +++ b/drivers/tty/tty_buffer.c
> @@ -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);
> +}
> diff --git a/include/linux/tty.h b/include/linux/tty.h
> index d9fb4b0..663155b 100644
> --- a/include/linux/tty.h
> +++ b/include/linux/tty.h
> @@ -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,
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2016-03-01 16:09 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-02-29 3:56 n_tty: Check the other end of pty pair before returning EAGAIN on a read() Brian Bloniarz
2016-03-01 4:30 ` Peter Hurley
2016-03-01 6:40 ` [PATCH] Fix OpenSSH pty regression on close Brian Bloniarz
2016-03-01 16:09 ` Peter Hurley
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox