[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