[newlib-cygwin] Cygwin: pty: Apply line_edit() for transferred input to to_cyg
Takashi Yano via Cygwin-cvs <[email protected]> Sun, 29 Mar 2026 00:44:00 +0000 (GMT)
| Newsgroups | gmane.os.cygwin.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3Da0b38a81b9b= e482a0058e8f4f5477eeae2f2c012 commit a0b38a81b9be482a0058e8f4f5477eeae2f2c012 Author: Takashi Yano <[email protected]> Date: Tue Mar 17 11:33:21 2026 +0900 Cygwin: pty: Apply line_edit() for transferred input to to_cyg =20 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. =20 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. =20 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. =20 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. =20 Fixes: 10d083c745dd ("Cygwin: pty: Inherit typeahead data between two i= nput pipes.") Signed-off-by: Takashi Yano <[email protected]> Reviewed-by: Johannes Schindelin <[email protected]> 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 }; =20 @@ -779,6 +780,12 @@ fhandler_pty_slave::open (int flags, mode_t) errmsg =3D "open input event failed, %E"; goto err; } + shared_name (buf, INPUT_TRANSFERRED_EVENT, get_minor ()); + if (!(input_transferred_to_cyg =3D OpenEvent (MAXIMUM_ALLOWED, TRUE, buf= ))) + { + errmsg =3D "open input transferred event failed, %E"; + goto err; + } =20 /* 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_a= vailable_event); + if (!ForceCloseHandle (input_transferred_to_cyg)) + termios_printf ("CloseHandle (input_transferred_to_cyg<%p>), %E", inpu= t_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 m= ask, 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 =3D OpenEvent (READ_CONTROL | write_access, TRUE, buf); + shared_name (buf, INPUT_TRANSFERRED_EVENT, get_minor ()); + input_transferred_to_cyg =3D OpenEvent (READ_CONTROL | write_access, + TRUE, buf); output_mutex =3D get_ttyp ()->open_output_mutex (write_access); input_mutex =3D get_ttyp ()->open_input_mutex (write_access); pipe_sw_mutex =3D get_ttyp ()->open_mutex (PIPE_SW_MUTEX, write_access); inuse =3D 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) =20 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); =20 /* 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 l= en) 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 l= en) { 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 (); } =20 @@ -2699,6 +2721,26 @@ reply: return 0; } =20 +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 =3D (char *) tp.w_get (); + DWORD n; + ReadFile (from_master, buf, NT_MAX_PATH * 2, &n, NULL); + char *p =3D buf; + while (n) + { + ssize_t ret; + line_edit (p, n, get_ttyp ()->ti, &ret); + n -=3D ret; + p +=3D ret; + } + SetEvent (input_available_event); +} + static DWORD pty_master_thread (VOID *arg) { @@ -2883,19 +2925,37 @@ fhandler_pty_master::pty_master_fwd_thread (const m= aster_fwd_thread_param_t *p) char *outbuf =3D tp.c_get (); char *mbbuf =3D tp.c_get (); static mbstate_t mbp; + OVERLAPPED ov =3D {0, }; + ov.hEvent =3D CreateEvent(NULL, TRUE, FALSE, NULL); + HANDLE w[2] =3D {ov.hEvent, p->input_transferred_to_cyg}; =20 termios_printf ("Started."); for (;;) { p->ttyp->fwd_last_time =3D GetTickCount64 (); - DWORD n; - p->ttyp->fwd_not_empty =3D - ::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 () !=3D 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 =3D 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 =3D fhandler_pipe::create (&sec_none, &from_slave_nat, &to_master_na= t, - fhandler_pty_common::pipesize, pipename, 0); + fhandler_pty_common::pipesize, pipename, + FILE_FLAG_OVERLAPPED); if (res) { errstr =3D "output pipe for non-cygwin apps"; @@ -3082,6 +3143,10 @@ fhandler_pty_master::setup () &sa, TRUE)) || GetLastError () =3D=3D ERROR_ALREADY_EXISTS) goto err; + if (!(input_transferred_to_cyg =3D t.get_event (errstr =3D INPUT_TRANSFE= RRED_EVENT, + &sa, TRUE)) + || GetLastError () =3D=3D ERROR_ALREADY_EXISTS) + goto err; =20 char buf[MAX_PATH]; errstr =3D 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 (mas= ter_fwd_thread_param_t *p) p->from_slave_nat =3D from_slave_nat; p->output_mutex =3D output_mutex; p->ttyp =3D get_ttyp (); + p->input_transferred_to_cyg =3D input_transferred_to_cyg; + p->master =3D this; SetEvent (thread_param_copied_event); } =20 @@ -3971,7 +4039,8 @@ fhandler_pty_master::get_master_fwd_thread_param (mas= ter_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 *t= typ, - HANDLE input_available_event) + HANDLE input_available_event, + HANDLE input_transferred_to_cyg) { HANDLE to; if (dir =3D=3D tty::to_nat) @@ -4093,26 +4162,8 @@ fhandler_pty_slave::transfer_input (tty::xfer_dir di= r, HANDLE from, tty *ttyp, ptr =3D mbbuf; len =3D nlen; } - /* Call WriteFile() line by line */ - char *p0 =3D ptr; - char *p_cr =3D (char *) memchr (p0, '\r', len - (p0 - ptr)); - char *p_lf =3D (char *) memchr (p0, '\n', len - (p0 - ptr)); - while (p_cr || p_lf) - { - char *p1 =3D - p_cr ? (p_lf ? ((p_cr + 1 =3D=3D p_lf) - ? p_lf : min(p_cr, p_lf)) : p_cr) : p_lf; - *p1 =3D '\n'; - n =3D p1 - p0 + 1; - if (n && WriteFile (to, p0, n, &n, NULL) && n) - transfered =3D true; - p0 =3D p1 + 1; - p_cr =3D (char *) memchr (p0, '\r', len - (p0 - ptr)); - p_lf =3D (char *) memchr (p0, '\n', len - (p0 - ptr)); - } - n =3D len - (p0 - ptr); - if (n && WriteFile (to, p0, n, &n, NULL) && n) - transfered =3D true; + if (len && WriteFile (to, ptr, len, &n, NULL) && n) + transfered =3D true;; } } else @@ -4151,13 +4202,20 @@ fhandler_pty_slave::transfer_input (tty::xfer_dir d= ir, HANDLE from, tty *ttyp, } CloseHandle (to); =20 + ttyp->pty_input_state =3D dir; /* Fix input_available_event which indicates availability in cyg pipe. */ if (dir =3D=3D 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 =3D 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 =3D false; } =20 @@ -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 =3D NULL; CloseHandle (p->input_available_event); p->input_available_event =3D NULL; + CloseHandle (p->input_transferred_to_cyg); + p->input_transferred_to_cyg =3D NULL; CloseHandle (p->input_mutex); p->input_mutex =3D NULL; CloseHandle (p->pipe_sw_mutex); @@ -4227,7 +4290,7 @@ fhandler_pty_slave::setup_for_non_cygwin_app (bool no= pcon, 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 (handl= e_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_even= t); + transfer_input (tty::to_cyg, from, get_ttyp (), input_available_even= t, + 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 =3D 128 * 1024; HANDLE output_mutex, input_mutex, pipe_sw_mutex; HANDLE input_available_event; + HANDLE input_transferred_to_cyg; =20 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 (); }; =20 class fhandler_dev_null: public fhandler_base diff --git a/winsup/cygwin/local_includes/tty.h b/winsup/cygwin/local_inclu= des/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 */ =20 #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"