[newlib-cygwin] Cygwin: pty: Apply line_edit() for transferred input to to_cyg
Takashi Yano
tyan0@sourceware.org
Sun Mar 29 00:44:00 GMT 2026
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=a0b38a81b9be482a0058e8f4f5477eeae2f2c012
commit a0b38a81b9be482a0058e8f4f5477eeae2f2c012
Author: Takashi Yano <takashi.yano@nifty.ne.jp>
Date: Tue Mar 17 11:33:21 2026 +0900
Cygwin: pty: Apply line_edit() for transferred input to to_cyg
When keystrokes travel through the nat pipe during a native process
session, they bypass POSIX line discipline entirely. When they are
transferred back to the cyg pipe at cleanup (via
`transfer_input(to_cyg)`), they arrive as raw bytes. If the
terminal is in canonical mode at that point, VERASE and VKILL
characters in those raw bytes have no effect because `line_edit()`
was never applied to them. The result: backspace typed while a
native process was running fails to erase the preceding character
once the input reaches bash's readline.
The fix applies `line_edit()` to the transferred bytes before they
reach the reading process. The right place to do this is the
master's forward thread (`pty_master_fwd_thread()`), because it
runs in the master process alongside `fhandler_pty_master::write()`
and shares access to the readahead buffer and `line_edit()` state.
Calling `line_edit()` from the slave (where `transfer_input()` runs)
would not work because that state belongs to the master.
To coordinate: `transfer_input(to_cyg)` writes the raw bytes to
the cyg pipe's slave end (`to_slave`), then signals a new
cross-process event (`input_transferred_to_cyg`) and spin-waits
for the forward thread to clear it. The forward thread is converted
from synchronous to overlapped I/O so it can wait on both the
`from_slave_nat` read completion and the transfer event
simultaneously. When the event fires, it reads the transferred
bytes from the cyg pipe's master end (`from_master`), processes
them through `line_edit()`, and clears the event.
The spin-wait in `transfer_input()` holds `input_mutex` (from its
caller), which blocks `fhandler_pty_master::write()` from injecting
new keystrokes until the forward thread has finished applying
`line_edit()` to the transferred bytes.
Fixes: 10d083c745dd ("Cygwin: pty: Inherit typeahead data between two input pipes.")
Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
Reviewed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Diff:
---
winsup/cygwin/fhandler/pty.cc | 143 +++++++++++++++++++++++---------
winsup/cygwin/local_includes/fhandler.h | 10 ++-
winsup/cygwin/local_includes/tty.h | 1 +
3 files changed, 113 insertions(+), 41 deletions(-)
diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
index 257630345..90c7a9710 100644
--- a/winsup/cygwin/fhandler/pty.cc
+++ b/winsup/cygwin/fhandler/pty.cc
@@ -210,6 +210,7 @@ atexit_func (void)
{
ptys->get_handle_nat (),
ptys->get_input_available_event (),
+ ptys->input_transferred_to_cyg,
ptys->input_mutex,
ptys->pipe_sw_mutex
};
@@ -739,7 +740,7 @@ fhandler_pty_slave::open (int flags, mode_t)
{
&from_master_nat_local, &input_available_event, &input_mutex, &inuse,
&output_mutex, &to_master_nat_local, &pty_owner, &to_master_local,
- &from_master_local, &pipe_sw_mutex,
+ &from_master_local, &pipe_sw_mutex, &input_transferred_to_cyg,
NULL
};
@@ -779,6 +780,12 @@ fhandler_pty_slave::open (int flags, mode_t)
errmsg = "open input event failed, %E";
goto err;
}
+ shared_name (buf, INPUT_TRANSFERRED_EVENT, get_minor ());
+ if (!(input_transferred_to_cyg = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf)))
+ {
+ errmsg = "open input transferred event failed, %E";
+ goto err;
+ }
/* FIXME: Needs a method to eliminate tty races */
{
@@ -993,6 +1000,8 @@ fhandler_pty_slave::close (int flag)
termios_printf ("CloseHandle (inuse), %E");
if (!ForceCloseHandle (input_available_event))
termios_printf ("CloseHandle (input_available_event<%p>), %E", input_available_event);
+ if (!ForceCloseHandle (input_transferred_to_cyg))
+ termios_printf ("CloseHandle (input_transferred_to_cyg<%p>), %E", input_transferred_to_cyg);
if (!ForceCloseHandle (get_output_handle_nat ()))
termios_printf ("CloseHandle (get_output_handle_nat ()<%p>), %E",
get_output_handle_nat ());
@@ -1101,7 +1110,8 @@ fhandler_pty_slave::reset_switch_to_nat_pipe (void)
WaitForSingleObject (input_mutex, mutex_timeout);
acquire_attach_mutex (mutex_timeout);
transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
- input_available_event);
+ input_available_event,
+ input_transferred_to_cyg);
release_attach_mutex ();
ReleaseMutex (input_mutex);
}
@@ -1250,14 +1260,14 @@ fhandler_pty_slave::mask_switch_to_nat_pipe (bool mask, bool xfer)
{
acquire_attach_mutex (mutex_timeout);
transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
- input_available_event);
+ input_available_event, input_transferred_to_cyg);
release_attach_mutex ();
}
else if (!mask && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
{
acquire_attach_mutex (mutex_timeout);
transfer_input (tty::to_nat, get_handle (), get_ttyp (),
- input_available_event);
+ input_available_event, input_transferred_to_cyg);
release_attach_mutex ();
}
}
@@ -1835,11 +1845,15 @@ fhandler_pty_slave::fch_open_handles (bool chown)
shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
input_available_event = OpenEvent (READ_CONTROL | write_access,
TRUE, buf);
+ shared_name (buf, INPUT_TRANSFERRED_EVENT, get_minor ());
+ input_transferred_to_cyg = OpenEvent (READ_CONTROL | write_access,
+ TRUE, buf);
output_mutex = get_ttyp ()->open_output_mutex (write_access);
input_mutex = get_ttyp ()->open_input_mutex (write_access);
pipe_sw_mutex = get_ttyp ()->open_mutex (PIPE_SW_MUTEX, write_access);
inuse = get_ttyp ()->open_inuse (write_access);
- if (!input_available_event || !output_mutex || !input_mutex || !inuse)
+ if (!input_available_event || !output_mutex || !input_mutex || !inuse
+ || !input_transferred_to_cyg)
{
__seterrno ();
return false;
@@ -1856,11 +1870,13 @@ fhandler_pty_slave::fch_set_sd (security_descriptor &sd, bool chown)
get_object_sd (input_available_event, sd_old);
if (!set_object_sd (input_available_event, sd, chown)
+ && !set_object_sd (input_transferred_to_cyg, sd, chown)
&& !set_object_sd (output_mutex, sd, chown)
&& !set_object_sd (input_mutex, sd, chown)
&& !set_object_sd (inuse, sd, chown))
return 0;
set_object_sd (input_available_event, sd_old, chown);
+ set_object_sd (input_transferred_to_cyg, sd_old, chown);
set_object_sd (output_mutex, sd_old, chown);
set_object_sd (input_mutex, sd_old, chown);
set_object_sd (inuse, sd_old, chown);
@@ -1873,6 +1889,7 @@ void
fhandler_pty_slave::fch_close_handles ()
{
close_maybe (input_available_event);
+ close_maybe (input_transferred_to_cyg);
close_maybe (output_mutex);
close_maybe (input_mutex);
close_maybe (inuse);
@@ -2125,6 +2142,9 @@ fhandler_pty_master::close (int flag)
if (!ForceCloseHandle (input_available_event))
termios_printf ("CloseHandle (input_available_event<%p>), %E",
input_available_event);
+ if (!ForceCloseHandle (input_transferred_to_cyg))
+ termios_printf ("CloseHandle (input_transferred_to_cyg<%p>), %E",
+ input_transferred_to_cyg);
/* The from_master must be closed last so that the same pty is not
allocated before cleaning up the other corresponding instances. */
@@ -2228,7 +2248,8 @@ fhandler_pty_master::write (const void *ptr, size_t len)
acquire_attach_mutex (mutex_timeout);
fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
get_ttyp (),
- input_available_event);
+ input_available_event,
+ input_transferred_to_cyg);
release_attach_mutex ();
ReleaseMutex (input_mutex);
}
@@ -2338,7 +2359,8 @@ fhandler_pty_master::write (const void *ptr, size_t len)
{
acquire_attach_mutex (mutex_timeout);
fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
- get_ttyp (), input_available_event);
+ get_ttyp (), input_available_event,
+ input_transferred_to_cyg);
release_attach_mutex ();
}
@@ -2699,6 +2721,26 @@ reply:
return 0;
}
+void
+fhandler_pty_master::apply_line_edit_to_transferred_input ()
+{
+ /* cyg pipe is fhandler_pty_common::pipesize (128K) depth, so memory
+ allocated by w_get() (128K) is enough here. */
+ tmp_pathbuf tp;
+ char *buf = (char *) tp.w_get ();
+ DWORD n;
+ ReadFile (from_master, buf, NT_MAX_PATH * 2, &n, NULL);
+ char *p = buf;
+ while (n)
+ {
+ ssize_t ret;
+ line_edit (p, n, get_ttyp ()->ti, &ret);
+ n -= ret;
+ p += ret;
+ }
+ SetEvent (input_available_event);
+}
+
static DWORD
pty_master_thread (VOID *arg)
{
@@ -2883,19 +2925,37 @@ fhandler_pty_master::pty_master_fwd_thread (const master_fwd_thread_param_t *p)
char *outbuf = tp.c_get ();
char *mbbuf = tp.c_get ();
static mbstate_t mbp;
+ OVERLAPPED ov = {0, };
+ ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ HANDLE w[2] = {ov.hEvent, p->input_transferred_to_cyg};
termios_printf ("Started.");
for (;;)
{
p->ttyp->fwd_last_time = GetTickCount64 ();
- DWORD n;
- p->ttyp->fwd_not_empty =
- ::bytes_available (n, p->from_slave_nat) && n;
- if (!ReadFile (p->from_slave_nat, outbuf, NT_MAX_PATH, &rlen, NULL))
+ if (!ReadFile (p->from_slave_nat, outbuf, NT_MAX_PATH, NULL, &ov)
+ && GetLastError () != ERROR_IO_PENDING)
{
termios_printf ("ReadFile for forwarding failed, %E");
break;
}
+wait_event:
+ switch (WaitForMultipleObjects (2, w, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ GetOverlappedResult (p->from_slave_nat, &ov, &rlen, FALSE);
+ ResetEvent (ov.hEvent);
+ break;
+ case WAIT_OBJECT_0 + 1:
+ p->master->apply_line_edit_to_transferred_input ();
+ ResetEvent (p->input_transferred_to_cyg);
+ goto wait_event;
+ default:
+ /* Not expected to happen */
+ debug_printf ("WaitForMultipleObjects() returns unexpectedly.");
+ Sleep (10);
+ goto wait_event;
+ }
if (p->ttyp->stop_fwd_thread)
break;
ssize_t wlen = rlen;
@@ -3021,7 +3081,8 @@ fhandler_pty_master::setup ()
char pipename[sizeof ("ptyNNNN-from-master-nat")];
__small_sprintf (pipename, "pty%d-to-master-nat", unit);
res = fhandler_pipe::create (&sec_none, &from_slave_nat, &to_master_nat,
- fhandler_pty_common::pipesize, pipename, 0);
+ fhandler_pty_common::pipesize, pipename,
+ FILE_FLAG_OVERLAPPED);
if (res)
{
errstr = "output pipe for non-cygwin apps";
@@ -3082,6 +3143,10 @@ fhandler_pty_master::setup ()
&sa, TRUE))
|| GetLastError () == ERROR_ALREADY_EXISTS)
goto err;
+ if (!(input_transferred_to_cyg = t.get_event (errstr = INPUT_TRANSFERRED_EVENT,
+ &sa, TRUE))
+ || GetLastError () == ERROR_ALREADY_EXISTS)
+ goto err;
char buf[MAX_PATH];
errstr = shared_name (buf, OUTPUT_MUTEX, unit);
@@ -3159,6 +3224,7 @@ err:
close_maybe (get_handle ());
close_maybe (get_output_handle ());
close_maybe (input_available_event);
+ close_maybe (input_transferred_to_cyg);
close_maybe (output_mutex);
close_maybe (input_mutex);
close_maybe (from_master_nat);
@@ -3964,6 +4030,8 @@ fhandler_pty_master::get_master_fwd_thread_param (master_fwd_thread_param_t *p)
p->from_slave_nat = from_slave_nat;
p->output_mutex = output_mutex;
p->ttyp = get_ttyp ();
+ p->input_transferred_to_cyg = input_transferred_to_cyg;
+ p->master = this;
SetEvent (thread_param_copied_event);
}
@@ -3971,7 +4039,8 @@ fhandler_pty_master::get_master_fwd_thread_param (master_fwd_thread_param_t *p)
#define CTRL_PRESSED (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)
void
fhandler_pty_slave::transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp,
- HANDLE input_available_event)
+ HANDLE input_available_event,
+ HANDLE input_transferred_to_cyg)
{
HANDLE to;
if (dir == tty::to_nat)
@@ -4093,26 +4162,8 @@ fhandler_pty_slave::transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp,
ptr = mbbuf;
len = nlen;
}
- /* Call WriteFile() line by line */
- char *p0 = ptr;
- char *p_cr = (char *) memchr (p0, '\r', len - (p0 - ptr));
- char *p_lf = (char *) memchr (p0, '\n', len - (p0 - ptr));
- while (p_cr || p_lf)
- {
- char *p1 =
- p_cr ? (p_lf ? ((p_cr + 1 == p_lf)
- ? p_lf : min(p_cr, p_lf)) : p_cr) : p_lf;
- *p1 = '\n';
- n = p1 - p0 + 1;
- if (n && WriteFile (to, p0, n, &n, NULL) && n)
- transfered = true;
- p0 = p1 + 1;
- p_cr = (char *) memchr (p0, '\r', len - (p0 - ptr));
- p_lf = (char *) memchr (p0, '\n', len - (p0 - ptr));
- }
- n = len - (p0 - ptr);
- if (n && WriteFile (to, p0, n, &n, NULL) && n)
- transfered = true;
+ if (len && WriteFile (to, ptr, len, &n, NULL) && n)
+ transfered = true;;
}
}
else
@@ -4151,13 +4202,20 @@ fhandler_pty_slave::transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp,
}
CloseHandle (to);
+ ttyp->pty_input_state = dir;
/* Fix input_available_event which indicates availability in cyg pipe. */
if (dir == tty::to_nat) /* all data is transfered to nat pipe,
so no data available in cyg pipe. */
ResetEvent (input_available_event);
else if (transfered) /* There is data transfered to cyg pipe. */
- SetEvent (input_available_event);
- ttyp->pty_input_state = dir;
+ {
+ SetEvent (input_transferred_to_cyg);
+ /* Wait for line_edit() to be applied to the data in the cyg pipe.
+ Holding input mutex while waiting here is necessary to
+ prevent mixing transferred input and new master::write() input. */
+ while (IsEventSignalled (input_transferred_to_cyg))
+ yield ();
+ }
ttyp->discard_input = false;
}
@@ -4177,6 +4235,9 @@ fhandler_pty_slave::get_duplicated_handle_set (handle_set_t *p)
DuplicateHandle (GetCurrentProcess (), input_available_event,
GetCurrentProcess (), &p->input_available_event,
0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), input_transferred_to_cyg,
+ GetCurrentProcess (), &p->input_transferred_to_cyg,
+ 0, 0, DUPLICATE_SAME_ACCESS);
DuplicateHandle (GetCurrentProcess (), input_mutex,
GetCurrentProcess (), &p->input_mutex,
0, 0, DUPLICATE_SAME_ACCESS);
@@ -4192,6 +4253,8 @@ fhandler_pty_slave::close_handle_set (handle_set_t *p)
p->from_master_nat = NULL;
CloseHandle (p->input_available_event);
p->input_available_event = NULL;
+ CloseHandle (p->input_transferred_to_cyg);
+ p->input_transferred_to_cyg = NULL;
CloseHandle (p->input_mutex);
p->input_mutex = NULL;
CloseHandle (p->pipe_sw_mutex);
@@ -4227,7 +4290,7 @@ fhandler_pty_slave::setup_for_non_cygwin_app (bool nopcon,
WaitForSingleObject (input_mutex, mutex_timeout);
acquire_attach_mutex (mutex_timeout);
transfer_input (tty::to_nat, get_handle (), get_ttyp (),
- input_available_event);
+ input_available_event, input_transferred_to_cyg);
release_attach_mutex ();
ReleaseMutex (input_mutex);
}
@@ -4249,7 +4312,8 @@ fhandler_pty_slave::cleanup_for_non_cygwin_app (handle_set_t *p, tty *ttyp,
WaitForSingleObject (p->input_mutex, mutex_timeout);
acquire_attach_mutex (mutex_timeout);
transfer_input (tty::to_cyg, p->from_master_nat, ttyp,
- p->input_available_event);
+ p->input_available_event,
+ p->input_transferred_to_cyg);
release_attach_mutex ();
ReleaseMutex (p->input_mutex);
}
@@ -4275,7 +4339,7 @@ fhandler_pty_slave::setpgid_aux (pid_t pid)
WaitForSingleObject (input_mutex, mutex_timeout);
acquire_attach_mutex (mutex_timeout);
transfer_input (tty::to_nat, get_handle (), get_ttyp (),
- input_available_event);
+ input_available_event, input_transferred_to_cyg);
release_attach_mutex ();
ReleaseMutex (input_mutex);
}
@@ -4301,7 +4365,8 @@ fhandler_pty_slave::setpgid_aux (pid_t pid)
}
else
acquire_attach_mutex (mutex_timeout);
- transfer_input (tty::to_cyg, from, get_ttyp (), input_available_event);
+ transfer_input (tty::to_cyg, from, get_ttyp (), input_available_event,
+ input_transferred_to_cyg);
if (attach_restore)
resume_from_temporarily_attach (resume_pid);
else
diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_includes/fhandler.h
index 7ea04a26c..5d3bf5eca 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -2012,6 +2012,7 @@ class fhandler_termios: public fhandler_base
{
HANDLE from_master_nat;
HANDLE input_available_event;
+ HANDLE input_transferred_to_cyg;
HANDLE input_mutex;
HANDLE pipe_sw_mutex;
};
@@ -2385,13 +2386,14 @@ class fhandler_pty_common: public fhandler_termios
fhandler_pty_common ()
: fhandler_termios (),
output_mutex (NULL), input_mutex (NULL), pipe_sw_mutex (NULL),
- input_available_event (NULL)
+ input_available_event (NULL), input_transferred_to_cyg (NULL)
{
pc.file_attributes (FILE_ATTRIBUTE_NORMAL);
}
static const unsigned pipesize = 128 * 1024;
HANDLE output_mutex, input_mutex, pipe_sw_mutex;
HANDLE input_available_event;
+ HANDLE input_transferred_to_cyg;
bool use_archetype () const {return true;}
DWORD __acquire_output_mutex (const char *fn, int ln, DWORD ms);
@@ -2514,7 +2516,8 @@ class fhandler_pty_slave: public fhandler_pty_common
void setup_locale (void);
void create_invisible_console (void);
static void transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp,
- HANDLE input_available_event);
+ HANDLE input_available_event,
+ HANDLE input_transferred_to_cyg);
HANDLE get_input_available_event (void) { return input_available_event; }
bool pcon_activated (void) { return get_ttyp ()->pcon_activated; }
void cleanup_before_exit ();
@@ -2549,8 +2552,10 @@ public:
struct master_fwd_thread_param_t {
HANDLE to_master;
HANDLE from_slave_nat;
+ HANDLE input_transferred_to_cyg;
HANDLE output_mutex;
tty *ttyp;
+ fhandler_pty_master *master;
};
private:
int pktmode; // non-zero if pty in a packet mode.
@@ -2629,6 +2634,7 @@ public:
void get_master_thread_param (master_thread_param_t *p);
void get_master_fwd_thread_param (master_fwd_thread_param_t *p);
bool need_send_ctrl_c_event ();
+ void apply_line_edit_to_transferred_input ();
};
class fhandler_dev_null: public fhandler_base
diff --git a/winsup/cygwin/local_includes/tty.h b/winsup/cygwin/local_includes/tty.h
index 9485e24c5..cd1e202f1 100644
--- a/winsup/cygwin/local_includes/tty.h
+++ b/winsup/cygwin/local_includes/tty.h
@@ -18,6 +18,7 @@ details. */
/* Input/Output/ioctl events */
#define INPUT_AVAILABLE_EVENT "cygtty.input.avail"
+#define INPUT_TRANSFERRED_EVENT "cygtty.input.xfer"
#define OUTPUT_MUTEX "cygtty.output.mutex"
#define INPUT_MUTEX "cygtty.input.mutex"
#define PIPE_SW_MUTEX "cygtty.pipe_sw.mutex"
More information about the Cygwin-cvs
mailing list