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

Takashi Yano takashi.yano@nifty.ne.jp
Sun Sep 13 21:45:25 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.

 winsup/cygwin/fhandler/console.cc       | 41 +++++++++++++++++++++----
 winsup/cygwin/local_includes/fhandler.h |  2 +-
 winsup/cygwin/select.cc                 |  8 ++---
 3 files changed, 40 insertions(+), 11 deletions(-)

diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc
index be41bf3a2..a995f3acc 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:
@@ -2217,6 +2218,7 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
   int res = fhandler_termios::ioctl (cmd, arg);
   if (res <= 0)
     return res;
+  acquire_input_mutex (mutex_timeout);
   acquire_output_mutex (mutex_timeout);
   switch (cmd)
     {
@@ -2234,6 +2236,7 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
 			   ((struct winsize *) arg)->ws_row,
 			   ((struct winsize *) arg)->ws_col);
 	    release_output_mutex ();
+	    release_input_mutex ();
 	    return 0;
 	  }
 	else
@@ -2241,17 +2244,21 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
 	    syscall_printf ("WINSZ failed");
 	    __seterrno ();
 	    release_output_mutex ();
+	    release_input_mutex ();
 	    return -1;
 	  }
 	release_output_mutex ();
+	release_input_mutex ();
 	return 0;
       case TIOCSWINSZ:
 	bg_check (SIGTTOU);
 	release_output_mutex ();
+	release_input_mutex ();
 	return 0;
       case KDGKBMETA:
 	*(int *) arg = (con.metabit) ? K_METABIT : K_ESCPREFIX;
 	release_output_mutex ();
+	release_input_mutex ();
 	return 0;
       case KDSKBMETA:
 	if ((intptr_t) arg == K_METABIT)
@@ -2262,19 +2269,23 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
 	  {
 	    set_errno (EINVAL);
 	    release_output_mutex ();
+	    release_input_mutex ();
 	    return -1;
 	  }
 	release_output_mutex ();
+	release_input_mutex ();
 	return 0;
       case TIOCLINUX:
 	if (*(unsigned char *) arg == 6)
 	  {
 	    *(unsigned char *) arg = (unsigned char) con.nModifiers;
 	    release_output_mutex ();
+	    release_input_mutex ();
 	    return 0;
 	  }
 	set_errno (EINVAL);
 	release_output_mutex ();
+	release_input_mutex ();
 	return -1;
       case FIONREAD:
       case TIOCINQ:
@@ -2291,6 +2302,7 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
 	    {
 	      set_errno (EINVAL);
 	      release_output_mutex ();
+	      release_input_mutex ();
 	      return -1;
 	    }
 	  bool saw_eol = false;
@@ -2327,20 +2339,24 @@ fhandler_console::ioctl (unsigned int cmd, void *arg)
 	  else
 	    *(int *) arg = ret;
 	  release_output_mutex ();
+	  release_input_mutex ();
 	  return 0;
 	}
 	break;
       case TCXONC:
 	res = this->tcflow ((int)(intptr_t) arg);
 	release_output_mutex ();
+	release_input_mutex ();
 	return res;
       case TCFLSH:
 	res = this->tcflush ((int)(intptr_t) arg);
 	release_output_mutex ();
+	release_input_mutex ();
 	return res;
     }
 
   release_output_mutex ();
+  release_input_mutex ();
   return fhandler_base::ioctl (cmd, arg);
 }
 
@@ -2351,6 +2367,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 +2381,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 +2409,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 +4935,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