[PATCH v5 2/3] Cygwin: pty: Update workaround for rlwrap for pseudo console

Takashi Yano takashi.yano@nifty.ne.jp
Wed Mar 25 12:58:50 GMT 2026


Hi Johannes,

On Mon, 16 Mar 2026 16:40:01 +0100 (CET)
Johannes Schindelin wrote:
> Hi Takashi,
> 
> On Thu, 12 Mar 2026, Takashi Yano wrote:
> 
> > In tcgetattr(), the conventional workaround for rlwrap v0.40 or later
> > is not work as expected with OpenConsole.exe for some reason. This
> > patch update the workaround so that it works even with OpenConsole.exe
> > by rebuilding tcgetattr responce reffering the corrent console mode
> > instead of just overriding it depends on pseudo console setting up
> > state. The patch also handle tcsetattr() so that the change is applied
> > to the console mode.
> 
> Calling `attach_console_temporarily()` in every `tcgetattr`/`tcsetattr`
> call might incur a prohibitively large performance penalty: This involves
> `FreeConsole()` + `AttachConsole()` + `FreeConsole()` + `AttachConsole()`.
> That's four kernel calls plus the mutex. `tcgetattr` can be called
> frequently (rlwrap polls it). This is expensive and introduces latency in
> a path that was previously just a memory read.

Hmm, maybe. I mesured the latency of FreeConsole()/ AttachConsole().
The result is 170 micro sec. in my environment. If an application calls
tcsetattr()/tcgetattr() 100 times in a second, it consume 34 msec every
one second. This may not be acceptable.

> 
> > 
> > Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
> > Reviewed-by:
> > ---
> >  winsup/cygwin/fhandler/pty.cc | 148 ++++++++++++++++++++++++++++++----
> >  1 file changed, 131 insertions(+), 17 deletions(-)
> > 
> > diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
> > index 85d29f1cc..bd5c24625 100644
> > --- a/winsup/cygwin/fhandler/pty.cc
> > +++ b/winsup/cygwin/fhandler/pty.cc
> > @@ -1764,18 +1764,37 @@ fhandler_pty_slave::tcgetattr (struct termios *t)
> >  {
> >    *t = get_ttyp ()->ti;
> >  
> > -  /* Workaround for rlwrap */
> > -  cygheap_fdenum cfd (false);
> > -  while (cfd.next () >= 0)
> > -    if (cfd->get_major () == DEV_PTYM_MAJOR
> > -	&& cfd->get_minor () == get_minor ())
> > -      {
> > -	if (get_ttyp ()->pcon_start)
> > -	  t->c_lflag &= ~(ICANON | ECHO);
> > -	if (get_ttyp ()->pcon_activated)
> > -	  t->c_iflag &= ~ICRNL;
> > -	break;
> > -      }
> 
> Hmm.  The code now no longer checks whether the master fd is open: The old
> code iterated cygheap_fdenum to verify the caller actually has the master
> side open. The new code skips this check entirely. The commit message
> doesn't mention this behavioral change.

Just a result of lazy.

> > +  /* Conventional workaround for rlwrap v0.40 or later is not work
> > +     as expected with OpenConsole.exe for some reason. The following
> 
> I'll never be a fan of reading "for some reason" in a patch that changes
> behavior in a fundamental way. If such changes (which typically come with
> a high risk of unintended side effects) are made, I'd rather want to have
> a really good reason for that. In this instance, I have concerns that the
> underlying problem might not be understood well enough, and hence the
> chosen approach might need to be improved to fully address the bug.
> 
> Could you please describe the full picture here?

I looked into more closely, and found that fixing up tcgetattr()
is also necessary for pcon_start_csi_c case. And it is enough for
rlwrap and full translation between terminal attributes and console
mode is not necessary. New patch series drops this patch.

> > +     workaround is perhaps better solution even for apps other than
> > +     rlwrap under pcon_activated mode. */
> > +  if (get_ttyp ()->pcon_activated
> > +      && (to_be_read_from_nat_pipe ()
> > +	  || get_ttyp ()->pcon_start || get_ttyp ()->pcon_start_csi_c))
> > +    {
> > +      DWORD mode = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
> > +      t->c_lflag &= ~(ICANON | ECHO);
> > +      t->c_iflag &= ~ICRNL;
> 
> Hmm. What if the application specifically set ICRNL? This unconditional
> clearing 

Without this, rlwrap cmd.exe does not accept CR (Enter key).

> > +      HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
> > +				       get_ttyp ()->nat_pipe_owner_pid);
> 
> This call could fail.
> 
> > +      HANDLE h_pcon_in;
> > +      DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
> > +		       GetCurrentProcess (), &h_pcon_in,
> > +		       0, FALSE, DUPLICATE_SAME_ACCESS);
> 
> if this `DuplicateHandle()` call fails, `h_pcon_in` is uninitialized, yet
> it is happily used in the `GetConsoleMode()` call below.
> 
> And since this is a near-duplicate of the other `tcgetattr()` method and
> both `tcsetattr()` methods touched by this patch, this problem is
> multiplied by 4.



> > +      DWORD resume_pid =
> > +	attach_console_temporarily (get_ttyp()->nat_pipe_owner_pid);
> > +      if (!GetConsoleMode (h_pcon_in, &mode)
> > +	  && (get_ttyp ()->pcon_start || get_ttyp ()->pcon_start_csi_c))
> > +	mode = 0;
> > +      resume_from_temporarily_attach (resume_pid);
> > +      CloseHandle (h_pcon_in);
> > +      CloseHandle (pcon_owner);
> > +
> > +      if (mode & ENABLE_LINE_INPUT)
> > +	t->c_lflag |= ICANON;
> > +      if (mode & ENABLE_ECHO_INPUT)
> > +	t->c_lflag |= ECHO;
> > +    }
> >    return 0;
> >  }
> >  
> > @@ -1784,6 +1803,40 @@ fhandler_pty_slave::tcsetattr (int, const struct termios *t)
> >  {
> >    acquire_output_mutex (mutex_timeout);
> >    get_ttyp ()->ti = *t;
> > +
> > +  if (get_ttyp ()->pcon_activated
> > +      && (to_be_read_from_nat_pipe ()
> > +	  || get_ttyp ()->pcon_start || get_ttyp ()->pcon_start_csi_c))
> > +    {
> > +      DWORD mode;
> > +      HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
> > +				       get_ttyp ()->nat_pipe_owner_pid);
> > +      HANDLE h_pcon_in;
> > +      DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
> > +		       GetCurrentProcess (), &h_pcon_in,
> > +		       0, FALSE, DUPLICATE_SAME_ACCESS);
> > +      DWORD resume_pid =
> > +	attach_console_temporarily (get_ttyp()->nat_pipe_owner_pid);
> > +      if (!GetConsoleMode (h_pcon_in, &mode)
> > +	  && (get_ttyp ()->pcon_start || get_ttyp ()->pcon_start_csi_c))
> > +	mode = 0;
> > +
> > +      mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT
> > +		| ENABLE_PROCESSED_INPUT);
> > +      if (t->c_lflag & ICANON)
> > +	mode |= ENABLE_LINE_INPUT;
> > +      if (t->c_lflag & ECHO)
> > +	mode |= ENABLE_ECHO_INPUT;
> > +      if (t->c_lflag & ISIG)
> > +	mode |= ENABLE_PROCESSED_INPUT;
> > +      SetConsoleMode (h_pcon_in, mode);
> 
> Wouldn't this potentially wreak havoc with native Win32 apps that are
> attached to this Console? They typically do not handle it well when their
> Console changes under their feet.

This is for rlwrap with non-cygwin app. But, yes, you are right.
Changing console mode by rlwrap does not have meaning for non-cygwin
app such as cmd.exe...

> > +
> > +      resume_from_temporarily_attach (resume_pid);
> > +      CloseHandle (h_pcon_in);
> > +      CloseHandle (pcon_owner);
> > +
> > +      get_ttyp ()->ti.c_iflag |= ICRNL;
> > +    }
> 
> This code seems to be a near duplicate of the master side, same goes for
> the `tcgetattr()` methods. This is not only hard to review, in my
> experience it _will_ lead to maintenance nightmares.
> 
> Couldn't this be refactored to not only be much easier to maintain and
> reduce redundant code dramatically, but at the same time also improve
> readability and the ease of review in a quite meaningful way? I mean, the
> `c_lflag` handling is _already_ duplicated across
> `fhandler_pty_slave::setup_pseudoconsole()` and
> `fhandler_console::set_input_mode()`... I'd rather see this copy/edit
> pattern reduced than increased.

Thanks. Though I'll withdraw this patch, I agree with that your point is
important indeed.

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>


More information about the Cygwin-patches mailing list