[PATCH v2] Cygwin: pty: Fix handling of data after CSI6n response
Takashi Yano
takashi.yano@nifty.ne.jp
Tue Mar 3 11:37:47 GMT 2026
Hi Johannes,
On Mon, 2 Mar 2026 14:24:39 +0100 (CET)
Johannes Schindelin wrote:
> Hi Takashi,
>
> On Sat, 28 Feb 2026, Takashi Yano wrote:
>
> > Previously, CSI6n was not handled correctly if the some sequences
> > are appended after the response for CSI6n. Especially, if the
> > appended sequence is a ESC sequence, which is longer than the
> > expected maximum length of the CSI6n response, the sequence will
> > not be written atomically.
> >
> > Moreover, when the terminal's CSI 6n response and subsequent data
> > (e.g. keystrokes) arrive in the same write buffer, master::write()
> > processes all of it inside the pcon_start loop and returns early.
> > Bytes after the 'R' terminator go through per-byte line_edit() in
> > that loop instead of falling through to the `nat` pipe fast path
> > or the normal bulk `line_edit()` call. Due to this behaviour,
> > the chance of code conversion to the terminal code page for the
> > subsequent data in `to_be_read_from_nat_pipe()` case, will be lost.
> >
> > Fix this by breaking out of the loop when 'R' is found and letting
> > the remaining data fall through to the normal write paths, which
> > are now reachable because `pcon_start` has been cleared.
> >
> > Fixes: f20641789427 ("Cygwin: pty: Reduce unecessary input transfer.")
> > Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
> > Co-authored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
>
> Heh, I would have been fine with a mere Reviewed-by ;-)
>
> > Reviewed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> > ---
> > winsup/cygwin/fhandler/pty.cc | 47 +++++++++++++++++++----------------
> > 1 file changed, 25 insertions(+), 22 deletions(-)
> >
> > diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
> > index 838be4a2b..34a87c6dc 100644
> > --- a/winsup/cygwin/fhandler/pty.cc
> > +++ b/winsup/cygwin/fhandler/pty.cc
> > @@ -2137,6 +2137,8 @@ fhandler_pty_master::close (int flag)
> > ssize_t
> > fhandler_pty_master::write (const void *ptr, size_t len)
> > {
> > + size_t towrite = len;
> > +
> > ssize_t ret;
> > char *p = (char *) ptr;
> > termios &ti = tc ()->ti;
> > @@ -2160,6 +2162,7 @@ fhandler_pty_master::write (const void *ptr, size_t len)
> >
> > DWORD n;
> > WaitForSingleObject (input_mutex, mutex_timeout);
> > + towrite = 0;
>
> I originally suggested to initialize `towrite` to 0 instead of `len`
> above.
>
> The reason why it needs to be initialized to `len`, and only be zeroed
> inside the `if (get_ttyp ()->pcon_start)` block is that you are using
> `towrite` instead of `len` in the "fall-through" block _after_ the `if
> (get_ttyp ()->pcon_start)` block, which you now fall through if the `R`
> was encountered.
>
> And you use `towrite` instead of `len` there in that fall-through block in
> the UTF-8/code page conversion, but you still use the original `len` as
> return value from that method as well as for the `line_edit()` fall-back.
>
> I found this a bit hard to follow.
>
> Wouldn't it be easier to introduce a new variable `size_t orig_ret = len`
> instead of `towrite`, return `orig_ret` instead of len (and using it in
> the `line_edit()` call, too), and then adjust `len` instead of assigning
> `towrite`?
Using len in line_edit() fall-through was a bug. Thanks for pointing this
out. I'll submit a fixed version as v3 patch.
> > for (size_t i = 0; i < len; i++)
> > {
> > if (p[i] == '\033')
> > @@ -2171,32 +2174,33 @@ fhandler_pty_master::write (const void *ptr, size_t len)
> > }
> > if (state == 1)
> > {
> > - if (ixput < wpbuf_len)
> > - wpbuf[ixput++] = p[i];
> > - else
> > + if (ixput == wpbuf_len)
> > {
> > if (!get_ttyp ()->req_xfer_input)
> > WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
> > ixput = 0;
> > - wpbuf[ixput++] = p[i];
> > }
> > + wpbuf[ixput++] = p[i];
>
> Okay, this is correct, a simple refactoring. But it does distract from the
> purpose of the patch a bit (and makes reviewing slightly more confusing
> than necessary), as it is unrelated.
Yeah, indeed. I'll remove this refactoring from this patch.
> > }
> > else
> > line_edit (p + i, 1, ti, &ret);
> > if (state == 1 && p[i] == 'R')
> > state = 2;
> > - }
> > - if (state == 2)
> > - {
> > - /* req_xfer_input is true if "ESC[6n" was sent just for
> > - triggering transfer_input() in master. In this case,
> > - the responce sequence should not be written. */
> > - if (!get_ttyp ()->req_xfer_input)
> > - WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
> > - ixput = 0;
> > - state = 0;
> > - get_ttyp ()->req_xfer_input = false;
> > - get_ttyp ()->pcon_start = false;
> > + if (state == 2)
> > + {
> > + /* req_xfer_input is true if "ESC[6n" was sent just for
> > + triggering transfer_input() in master. In this case,
> > + the response sequence should not be written. */
> > + if (!get_ttyp ()->req_xfer_input)
> > + WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
> > + towrite = len - i - 1;
> > + ptr = p + i + 1;
> > + ixput = 0;
> > + state = 0;
> > + get_ttyp ()->req_xfer_input = false;
> > + get_ttyp ()->pcon_start = false;
> > + break;
> > + }
>
> Okay, that makes sense to me, in case we reach state 2, we want to change
> `towrite` and no longer `return len` below, but instead move on to writing
> the remainder to the `nat` pipe. It is a bit unfortunate that this
> refactor makes the diff a bit harder to read than I like.
To me, breaking on 'state == 2' makes more sense than before...
This might just come down to personal preference, though.
As for readability of the patch, I agree with you.
--
Takashi Yano <takashi.yano@nifty.ne.jp>
More information about the Cygwin-patches
mailing list