[newlib-cygwin/cygwin-3_6-branch] Cygwin: console: Correct previous NOFLSH fix

Takashi Yano via Cygwin-cvs <[email protected]> Wed, 8 Jul 2026 18:09:04 +0000 (GMT)
Newsgroups gmane.os.cygwin.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3Dc31308936b6=
7cb07bb0fdc45c8ba16e8bfdf0787

commit c31308936b67cb07bb0fdc45c8ba16e8bfdf0787
Author: Takashi Yano <[email protected]>
Date:   Tue Jun 30 11:11:31 2026 +0900

    Cygwin: console: Correct previous NOFLSH fix
   =20
    The previous fix for NOFLSH mode does not work as intended.
   =20
    discard_key_events(), added in "Cygwin: console: Fix NOFLSH behaviour a
    bit", loops on ReadConsoleInputW() until it has consumed the requested
    number of records, but ReadConsoleInputW() blocks while the console
    input buffer is empty. sigflush() calls it with a hard-coded count of
    one and no guarantee that a record is actually queued: in the
    master-thread path the signalling record has already been read out of
    the buffer before sigflush() runs, so the call blocks until, and then
    swallows, the user's next keystroke.
   =20
    To avoid this, this patch does not discard input when process_sigs()
    is called from cons_master_thread, where the value of `fh` is NULL,
    because discarding will be done in cons_master_thread.
   =20
    And because the ReadConsoleInputW() return value is unchecked, a failed
    read leaves the count indeterminate, so "n -=3D n1" can underflow and s=
pin.
    Check return value of ReadConsoleInputW() and abort if it fails.
   =20
    Moreover, discard_key_event(1) does not work as intended if the first
    key event is not a bKeyDown event correspoding to the signalling key.
    Use discard_key_events(0) instead. This means discarding input events
    to the current position processed. Since the key-strokes prior to the
    signalling key are already in the readahead buffer, so this call discar=
ds
    only the signalling key. The important point here is to discard input
    before releasing input_mutex by release_input_mutex_if_necessary(),
    because, if not, cons_master_thread starts to process key events before
    discarding signalling key event because the thread can acquire
    input_mutex. This causes the signalling key is processed twice.
   =20
    One separate point: the `process_input_message()` caller wraps
    `discard_key_events()` in `acquire_attach_mutex()` + `attach_console
    (con.owner)`, but the `sigflush()` call site does not, so the
    `ReadConsoleInputW()` there runs against whatever console the calling
    process happens to be attached to. With the guard above the worst case
    is a no-op when the calling process happens not to be attached, so
    it would be more correct to move the attach into the helper itself.
   =20
    This patch also fixes two more special cases. One is done_with_debugger
    case. When `gdb cat` is executed and the `cat` is running, Ctrl-C
    discards all the key events including the events after Ctrl-C. This
    is because tcflush() is used for the purpose. Use discard_key_events(0)
    instead. The other case is not_signalled_but_done case. Previously,
    when `cat | non-cygwin-app` is executed and Ctrl-C is pressed, but
    the `Ctrl-C` is not VINTR, line_edit() wrongly returned
    line_edit_signalled even though `cat` is not signalled by Ctrl-C.
    In this case, `cat` should receive Ctrl-C as a input char, while
    `non-cygwin-app` has been killed by Ctrl-C. Fix this in line_edit().
    In the case of not_signalled_but_done case, setting `sawsig` flag
    and releasing `output_stopped` has been skipped with this patch,
    because this (Ctrl-C) is not a signal key in the case above.
   =20
    Fixes: 66324edf64a9 ("Cygwin: console: Fix NOFLSH behaviour a bit")
    Co-authored-by: Johannes Schindelin <[email protected]>
    Signed-off-by: Takashi Yano <[email protected]>
    Reviewed-by: Johannes Schindelin <[email protected]>
    (cherry picked from commit 0d516c2b1f4d7e4abcf4be55056b5cd87f566e5e)

Diff:
---
 winsup/cygwin/fhandler/console.cc       | 28 +++++++++++++++++-----------
 winsup/cygwin/fhandler/termios.cc       | 30 ++++++++++++++++++------------
 winsup/cygwin/local_includes/fhandler.h |  1 +
 3 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/con=
sole.cc
index dc1eb0644..3de1bcada 100644
--- a/winsup/cygwin/fhandler/console.cc
+++ b/winsup/cygwin/fhandler/console.cc
@@ -1665,6 +1665,7 @@ fhandler_console::process_input_message (size_t len)
 	  continue;
 	}
=20
+      num_input_events_processed =3D i + 1;
       num_chars +=3D nread;
       if (toadd)
 	{
@@ -1695,17 +1696,11 @@ out:
   /* Discard processed recored. */
   DWORD discard_len =3D min (total_read, i + 1);
   /* If input is signalled, do not discard input here because
-     tcflush() is already called from line_edit(). */
-  if (stat =3D=3D input_signalled && !(ti->c_lflag & NOFLSH))
+     discard_key_events() is already called from line_edit(). */
+  if (stat =3D=3D input_signalled)
     discard_len =3D 0;
   if (discard_len && (len || stat !=3D input_ok))
-    {
-      acquire_attach_mutex (mutex_timeout);
-      DWORD resume_pid =3D attach_console (con.owner);
-      discard_key_events (discard_len);
-      detach_console (resume_pid, con.owner);
-      release_attach_mutex ();
-    }
+    discard_key_events (discard_len);
   return stat;
 }
=20
@@ -1713,15 +1708,25 @@ void
 fhandler_console::discard_key_events (size_t n)
 {
   DWORD discarded =3D 0;
+  if (n =3D=3D 0)
+    {
+      n =3D num_input_events_processed;
+      num_input_events_processed =3D 0;
+    }
   INPUT_RECORD input_rec[INREC_SIZE];
   DWORD n1 =3D min (INREC_SIZE, n);
+  acquire_attach_mutex (mutex_timeout);
+  DWORD resume_pid =3D attach_console (con.owner);
   while (n)
     {
-      ReadConsoleInputW (get_handle (), input_rec, n1, &n1);
+      if (!ReadConsoleInputW (get_handle (), input_rec, n1, &n1) || !n1)
+	break;
       n -=3D n1;
       discarded +=3D n1;
       n1 =3D min (INREC_SIZE, n);
     }
+  detach_console (resume_pid, con.owner);
+  release_attach_mutex ();
   con.num_processed -=3D min (con.num_processed, discarded);
 }
=20
@@ -2305,7 +2310,8 @@ fhandler_console::tcgetattr (struct termios *t)
=20
 fhandler_console::fhandler_console (fh_devices devunit) :
   fhandler_termios (), input_ready (false), thread_sync_event (NULL),
-  input_mutex (NULL), output_mutex (NULL), unit (MAX_CONS_DEV)
+  input_mutex (NULL), output_mutex (NULL), unit (MAX_CONS_DEV),
+  num_input_events_processed (0)
 {
   dev_referred_via =3D (dev_t) devunit;
   if (devunit > 0)
diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/ter=
mios.cc
index 650807850..e16b29619 100644
--- a/winsup/cygwin/fhandler/termios.cc
+++ b/winsup/cygwin/fhandler/termios.cc
@@ -353,7 +353,10 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fha=
ndler_termios *fh)
 	      fhandler_pty_common::attach_console_temporarily (p->dwProcessId);
 	  if (fh && p =3D=3D myself && being_debugged ())
 	    { /* Avoid deadlock in gdb on console. */
-	      fh->tcflush(TCIFLUSH);
+	      if (fh->is_console ())
+		fh->discard_key_events (0 /* to current position */);
+	      else
+		fh->tcflush(TCIFLUSH);
 	      fh->release_input_mutex_if_necessary ();
 	    }
 	  /* CTRL_C_EVENT does not work for the process started with
@@ -444,10 +447,14 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fh=
andler_termios *fh)
 	goto not_a_sig;
=20
       termios_printf ("got interrupt %d, sending signal %d", c, sig);
-      if (!(ti.c_lflag & NOFLSH) && fh)
+      if (fh)
 	{
-	  fh->eat_readahead (-1);
-	  fh->discard_input ();
+	  if (!(ti.c_lflag & NOFLSH))
+	    {
+	      fh->eat_readahead (-1);
+	      fh->discard_input ();
+	    }
+	  fh->discard_key_events (0 /* to current position */);
 	}
       if (fh)
 	fh->release_input_mutex_if_necessary ();
@@ -460,6 +467,8 @@ fhandler_termios::process_sigs (char c, tty* ttyp, fhan=
dler_termios *fh)
 not_a_sig:
   if ((ti.c_lflag & ISIG) && need_discard_input)
     {
+      if (need_send_sig)
+	return not_signalled;
       if (!(ti.c_lflag & NOFLSH) && fh)
 	{
 	  fh->eat_readahead (-1);
@@ -525,10 +534,11 @@ fhandler_termios::line_edit (const char *rptr, size_t=
 nread, termios& ti,
       switch (process_sigs (c, get_ttyp (), this))
 	{
 	case signalled:
-	case not_signalled_but_done:
 	case done_with_debugger:
 	  sawsig =3D true;
 	  get_ttyp ()->output_stopped =3D false;
+	  fallthrough;
+	case not_signalled_but_done:
 	  continue;
 	case not_signalled_with_nat_reader:
 	  disable_eof_key =3D true;
@@ -666,13 +676,9 @@ fhandler_termios::sigflush ()
      be NULL while this is alive.  However, we can conceivably close a
      ctty while exiting and that will zero this. */
   if ((!have_execed || have_execed_cygwin) && tc ()
-      && (tc ()->getpgid () =3D=3D myself->pgid))
-    {
-      if (!(tc ()->ti.c_lflag & NOFLSH))
-	tcflush (TCIFLUSH);
-      else
-	discard_key_events (1);
-    }
+      && (tc ()->getpgid () =3D=3D myself->pgid)
+      && !(tc ()->ti.c_lflag & NOFLSH))
+    tcflush (TCIFLUSH);
 }
=20
 pid_t
diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_=
includes/fhandler.h
index 2a7f8b308..0a3e35db8 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -2200,6 +2200,7 @@ private:
   HANDLE input_mutex, output_mutex;
   handle_set_t handle_set;
   _minor_t unit;
+  size_t num_input_events_processed;
=20
   /* Used when we encounter a truncated multi-byte sequence.  The
      lead bytes are stored here and revisited in the next write call. */