Re: [PATCH] Cygwin: console: Fix regression in console input

Takashi Yano <[email protected]>
Newsgroups gmane.os.cygwin.patches
Message-ID <[email protected]>
On Fri, 7 Aug 2026 01:25:51 +0900
Takashi Yano wrote:
> Hi Johannes,
> 
> Thanks for reviewing!
> 
> On Thu, 6 Aug 2026 16:21:49 +0200 (CEST)
> Johannes Schindelin wrote:
> > Hi Takashi,
> > 
> > On Mon, 3 Aug 2026, Takashi Yano wrote:
> > 
> > > The commit fac73911f5a0 ("Cygwin: console: Fix typeahead input for
> > > bash") introduced a bug where select() consumes some input chars in
> > > canonical mode, preventing read() from reading them. This is due to
> > > discarding input events when process_input_message() does not return
> > > `input_ok` even if it is called from select().
> > > The basic idea of that commit was making process_input_message()
> > > not to store processed chars into readahead buffer. This was not
> > > correct because the key input events ware processed twice, once by
> > > select() and again by read(). Thus even if that commit worked as
> > > intended, the side effect such as input echo would be applied twice.
> > > 
> > > With this patch, process_input_message() handles only the minimum
> > > necessary of input events in both cases, those processed by select()
> > > and those processed by read(). To achive this behaviour, the function
> > > returns without processing when `input_ready` is already satisfied,
> > > or after it has processed the specified number of chars.
> > > 
> > > Addresses: https://cygwin.com/pipermail/cygwin/2026-August/259915.html
> > > Reported-by: Steven Doerfler <[email protected]>
> > > Fixes: fac73911f5a0 ("Cygwin: console: Fix typeahead input for bash")
> > > Signed-off-by: Takashi Yano <[email protected]>
> > > Revewed-by:
> > 
> > This explanation, as well as the diff, look sound to me.
> > 
> > You may want to fix the typos "Revewed" -> "Reviewed", "ware" -> "were"
> > and "achive" -> "achieve", but those are tiny nits.
> > 
> > You could also fix the pre-existing typo in the comment "recored" ->
> > "recorded" while at it, but again, that's just a nit.
> > 
> > > ---
> > >  winsup/cygwin/fhandler/console.cc | 14 ++++++--------
> > >  1 file changed, 6 insertions(+), 8 deletions(-)
> > > 
> > > diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc
> > > index 0219a37ef..3904fbd57 100644
> > > --- a/winsup/cygwin/fhandler/console.cc
> > > +++ b/winsup/cygwin/fhandler/console.cc
> > > @@ -1406,7 +1406,6 @@ fhandler_console::input_states
> > >  fhandler_console::process_input_message (size_t len)
> > >  {
> > >    char tmp[60];
> > > -  size_t num_chars = 0;
> > >  
> > >    if (!shared_console_info[unit])
> > >      return input_error;
> > > @@ -1429,6 +1428,10 @@ fhandler_console::process_input_message (size_t len)
> > >        return input_error;
> > >      }
> > >  
> > > +  /* len == 0 if called from select.cc:peek_console() */
> > > +  if (input_ready && (len == 0 || (get_ttyp ()->ti.c_lflag & ICANON)))
> > > +    return input_ok;
> > 
> > It is worth pointing out that while this `input_ready &&` part looks
> > superfluous (both callers guard the call behind `!input_ready`), it is a
> > concurrency guard: a second thread reading the same console fd can set
> > `inpuy_ready` in the window between `read()`'s lock-free `while
> > (!input_ready)` check and `acquire_input_mutex`.
> > 
> > Maybe worth an extra code comment?
> 
> I added short comment here. Thanks.
> 
> > > +
> > >    for (i = 0; i < total_read; i ++)
> > >      {
> > >        DWORD nread = 1;
> > > @@ -1794,7 +1797,6 @@ fhandler_console::process_input_message (size_t len)
> > >  	}
> > >  
> > >        num_input_events_processed = i + 1;
> > > -      num_chars += nread;
> > >        if (toadd)
> > >  	{
> > >  	  ssize_t ret;
> > > @@ -1813,21 +1815,17 @@ fhandler_console::process_input_message (size_t len)
> > >  	    }
> > >  	}
> > >        /* len == 0 if called from select.cc:peek_console() */
> > > -      if (len && num_chars >= len)
> > > +      if (input_ready && (len == 0 || con_ra.ralen >= len))
> > 
> > Something GPT 5.6 Sol stumbled over, before noticing that this is a
> > pre-existing issue: the raw multi-pass caller passes full `buflen` (not
> > `buflen - copied_chars`). That was already there before this patch (and
> > both GPT and Opus think that this is incorrect, I haven't had time to wrap
> > my head around it), and the new `ralen`-based check is actually
> > neutral-to-better; and `tcflush(TCIFLUSH)` doesn't flush `con_ra`.
> > 
> > I'm fairly certain that this is out of scope for this here bug fix, if it
> > is an issue at all (what do you think? Is this `buflen` issue real? Should
> > `tcflush()` also flush `con_ra`?).
> 
> Thanks for finding this. I rechecked here and concluded passing `buflen`
> for process_input_message() is correct. This is because, the length check
> in the process_input_message is:
> 
>       if (input_ready && (len == 0 || con_ra.ralen >= len))
>         goto out;
> 
> After the `while` loop, the chars in readahead data of length `con_ra.ralen`
> will be copied into buffer of `buflen`. So the `len` should be total buffer
> size here.
> 
> As for tcflush(), let me check. Please wait for a while.

`tcflush()` seems to need clearing readahead buffer and `input_ready` flag.
Without that, select()->tcflush()->read() does not block at read() and
returns the contents of readahead buffer.

I'll submit another patch shortly.

-- 
Takashi Yano <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.