[PATCH v4] Cygwin: console: Clear readahead buffer on tcflush()

Takashi Yano takashi.yano@nifty.ne.jp
Tue Sep 15 06:34:23 GMT 2026


Previously, tcflush(TCIFLUSH) only discarded the input events and
did not clear the readahead buffer. Because of this bug, when user
program called select() -> tcflush() -> read(), the last read()
returned the contents of the readahead buffer instead of blocking
as it should.

Correctly, tcflush() must discard all pending input, so read()
should block until new input arrives. With this patch, the read-
ahead buffer and `rapoi` are also cleared as well as input events.

Fixes: 8382778cdb57 ("Cygwin: console: fix select() behaviour")
Suggested-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
Reviewed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
v2: Add input_mutex guard. Clear rapoi as well.
v3: Acquire input_mutex before output_mutex in ioctl() to avoid
    deadlock in tcflush().
    Make input_ready static as con_ra is.
v4: Revert input_mutex in ioctl() and move tcflush() outside
    output_mutex insted.

 winsup/cygwin/fhandler/console.cc       | 29 +++++++++++++++++++------
 winsup/cygwin/local_includes/fhandler.h |  2 +-
 winsup/cygwin/select.cc                 |  8 +++----
 3 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc
index be41bf3a2..e1849aada 100644
--- a/winsup/cygwin/fhandler/console.cc
+++ b/winsup/cygwin/fhandler/console.cc
@@ -70,6 +70,7 @@ bool NO_COPY fhandler_console::invisible_console;
 /* con_ra is shared in the same process.
    Only one console can exist in a process, therefore, static is suitable. */
 static struct fhandler_base::rabuf_t con_ra;
+static bool input_ready;
 
 /* Write pending buffer for ESC sequence handling
    in xterm compatible mode */
@@ -1224,7 +1225,7 @@ fhandler_console::read (void *pv, size_t& buflen)
       (get_ttyp ()->ti.c_cc[VTIME]*100 ? : INFINITE)));
 
 read_more:
-  while (!input_ready && !get_cons_readahead_valid ())
+  while (!::input_ready && !get_cons_readahead_valid ())
     {
       int bgres;
       if ((bgres = bg_check (SIGTTIN)) <= bg_eof)
@@ -1305,7 +1306,7 @@ wait_retry:
     get_readahead_into_buffer (buf + copied_chars, buflen - copied_chars);
 
   if (!con_ra.ralen)
-    input_ready = false;
+    ::input_ready = false;
   release_input_mutex ();
 
   if (buflen > copied_chars && !(get_ttyp ()->ti.c_lflag & ICANON)
@@ -1357,7 +1358,7 @@ fhandler_console::process_input_message (size_t len)
   /* This code is reached only when being passed the input_ready check,
      however, the check was done outside input_mutex. Therefore, another
      thread may set input_ready after the check. Check it again here. */
-  if (input_ready && (len == 0 || (get_ttyp ()->ti.c_lflag & ICANON)))
+  if (::input_ready && (len == 0 || (get_ttyp ()->ti.c_lflag & ICANON)))
     return input_ok;
 
   for (i = 0; i < total_read; i ++)
@@ -1736,14 +1737,14 @@ fhandler_console::process_input_message (size_t len)
 	    }
 	  else if (res == line_edit_input_done)
 	    {
-	      input_ready = true;
+	      ::input_ready = true;
 	      stat = input_ok;
 	      if (ti->c_lflag & ICANON)
 		goto out;
 	    }
 	}
       /* len == 0 if called from select.cc:peek_console() */
-      if (input_ready && (len == 0 || con_ra.ralen >= len))
+      if (::input_ready && (len == 0 || con_ra.ralen >= len))
 	goto out;
     }
 out:
@@ -2335,8 +2336,9 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
 	release_output_mutex ();
 	return res;
       case TCFLSH:
-	res = this->tcflush ((int)(intptr_t) arg);
 	release_output_mutex ();
+	/* tcflush() does not need output_mutex */
+	res = this->tcflush ((int)(intptr_t) arg);
 	return res;
     }
 
@@ -2351,6 +2353,9 @@ fhandler_console::tcflush (int queue)
   if (queue == TCIFLUSH
       || queue == TCIOFLUSH)
     {
+      /* tcflush() may be called inside the input_mutex,
+	 however, mutex of Win32 can be acquired recursively. */
+      acquire_input_mutex (mutex_timeout);
       acquire_attach_mutex (mutex_timeout);
       DWORD resume_pid = attach_console (con.owner);
       BOOL r = FlushConsoleInputBuffer (get_handle ());
@@ -2362,6 +2367,10 @@ fhandler_console::tcflush (int queue)
 	  res = -1;
 	}
       con.num_processed = 0;
+      eat_readahead (-1);
+      ::input_ready = false;
+      con.cons_rapoi = NULL;
+      release_input_mutex ();
     }
   return res;
 }
@@ -2386,7 +2395,7 @@ fhandler_console::tcgetattr (struct termios *t)
 }
 
 fhandler_console::fhandler_console (fh_devices devunit) :
-  fhandler_termios (), input_ready (false), thread_sync_event (NULL),
+  fhandler_termios (), thread_sync_event (NULL),
   input_mutex (NULL), output_mutex (NULL), unit (MAX_CONS_DEV),
   num_input_events_processed (0)
 {
@@ -4912,3 +4921,9 @@ fhandler_console::tcdrain ()
 {
   return 0;
 }
+
+bool
+fhandler_console::input_ready ()
+{
+  return ::input_ready;
+}
diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_includes/fhandler.h
index d11b3ec4f..73d121508 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -2183,7 +2183,6 @@ public:
     tty_min tty_min_state;
     dev_console con;
   };
-  bool input_ready;
   enum input_states
   {
     input_error = -1,
@@ -2366,6 +2365,7 @@ private:
   void wpbuf_send ();
   int fstat (struct stat *buf);
   void discard_key_events (size_t n);
+  bool input_ready ();
 
   class console_unit
   {
diff --git a/winsup/cygwin/select.cc b/winsup/cygwin/select.cc
index b72083447..120273b70 100644
--- a/winsup/cygwin/select.cc
+++ b/winsup/cygwin/select.cc
@@ -1140,7 +1140,7 @@ peek_console (select_record *me, bool)
   if (fh->get_cons_readahead_valid ())
     return me->read_ready = true;
 
-  if (fh->input_ready)
+  if (fh->input_ready ())
     return me->read_ready = true;
 
   if (me->read_ready)
@@ -1155,7 +1155,7 @@ peek_console (select_record *me, bool)
   set_handle_or_return_if_not_open (h, me);
 
   fh->acquire_input_mutex (mutex_timeout);
-  while (!fh->input_ready && !fh->get_cons_readahead_valid ())
+  while (!fh->input_ready () && !fh->get_cons_readahead_valid ())
     {
       if (fh->bg_check (SIGTTIN, true) <= bg_eof)
 	{
@@ -1182,7 +1182,7 @@ peek_console (select_record *me, bool)
 	}
     }
   fh->release_input_mutex ();
-  if (fh->input_ready || fh->get_cons_readahead_valid ())
+  if (fh->input_ready () || fh->get_cons_readahead_valid ())
     return me->read_ready = true;
 
   return me->write_ready;
@@ -1282,7 +1282,7 @@ fhandler_console::select_read (select_stuff *ss)
 
   s->peek = peek_console;
   s->read_selected = true;
-  s->read_ready = input_ready || get_cons_readahead_valid ();
+  s->read_ready = input_ready () || get_cons_readahead_valid ();
   s->cleanup = console_cleanup;
   return s;
 }
-- 
2.51.0



More information about the Cygwin-patches mailing list