[newlib-cygwin] Cygwin: pty: detect pcon-backed pty for non-Cygwin-spawned children
Takashi Yano via Cygwin-cvs <[email protected]> Wed, 24 Jun 2026 13:11:15 +0000 (GMT)
| Newsgroups | gmane.os.cygwin.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3D6eed1ef7486= 9e113c01c97a226fb11a1bad21e40 commit 6eed1ef74869e113c01c97a226fb11a1bad21e40 Author: Johannes Schindelin <[email protected]> Date: Thu Apr 30 15:04:04 2026 +0000 Cygwin: pty: detect pcon-backed pty for non-Cygwin-spawned children =20 When a Cygwin process (e.g. `bash` under MinTTY) spawns a native Win32 child (e.g. `git.exe`) with pseudo console support enabled, the child gets a pseudo console that bridges the pty. If that native child then spawns a Cygwin grandchild (e.g. `vim`, `less`), the grandchild inherits the pseudo console's console handles. In `init_std_file_from_handle()`, the grandchild's msys2-runtime sees `GetConsoleScreenBufferInfo()` succeed on those handles and, with no valid `ctty` set, falls back to `FH_CONSOLE` and gives the process `cons0` instead of connecting to the pty. =20 This causes scrollback clobbering in MinTTY because alternate screen sequences (`ESC[?1049h` / `ESC[?1049l`) are handled by `fhandler_console`'s `save_restore()` against the pseudo console's buffer, which has no correspondence to MinTTY's scrollback. =20 Fix this in the existing console branch of `init_std_file_from_handle()`: when there is no valid `ctty` and we are about to fall back to `FH_CONSOLE`, first scan the shared tty table for an entry whose `pcon_activated` is set and whose `nat_pipe_owner_pid` is in our console's process list (via `GetConsoleProcessList`). If found, parse the device as that pty slave instead of as a real console. The handle is closed in either fallback path, matching the existing `FH_CONSOLE` behavior. `myself->ctty` is left untouched; the regular `fhandler_pty_slave::open_setup()` path will set it via `myself->set_ctty()` when the pty slave is opened. =20 The structure of `find_pcon_pty()` matters and is easy to get wrong in case a keen developer would like to refactor this code in the future. This code runs on every Cygwin process startup whose parent is non-Cygwin, so the common path (no pty with an active pseudo console) must remain free of expensive operations. Two pitfalls to avoid: filtering tty entries with `tty::exists()` looks correct but creates and destroys a named pipe per entry (128 entries on every call), and hoisting the `GetConsoleProcessList()` call out of the loop pays the cross-process cost even when no candidate exists. The current shape, a cheap shared-memory boolean check first and a lazily fetched process list only on the first candidate, keeps the common case at a handful of pointer reads. =20 Reported downstream at https://github.com/git-for-windows/git/issues/53= 03 and bisected to a Git for Windows release that upgraded the bundled msys2-runtime from 3.3.6 (no pseudo console code) to 3.4.6 (the new pseudo console architecture). =20 Fixes: bb4285206207 ("Cygwin: pty: Implement new pseudo console support= .") Assisted-by: Claude Opus 4.7 (1M context) Signed-off-by: Johannes Schindelin <[email protected]> Reviewed-by: Takashi Yano <[email protected]> Diff: --- winsup/cygwin/dtable.cc | 14 +++++++++++++- winsup/cygwin/local_includes/tty.h | 5 +++++ winsup/cygwin/release/3.7.0 | 4 ++++ winsup/cygwin/tty.cc | 37 ++++++++++++++++++++++++++++++++++= +++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/winsup/cygwin/dtable.cc b/winsup/cygwin/dtable.cc index f1832a169..e4d1cdf8f 100644 --- a/winsup/cygwin/dtable.cc +++ b/winsup/cygwin/dtable.cc @@ -326,7 +326,19 @@ dtable::init_std_file_from_handle (int fd, HANDLE hand= le) if (CTTY_IS_VALID (myself->ctty)) dev.parse (myself->ctty); else - dev.parse (FH_CONSOLE); + { + /* Check whether the inherited console is actually a pseudo + console bridging a pty. This happens when our non-Cygwin + parent was itself spawned by a Cygwin process from a pty + (e.g. bash spawning git.exe which then spawns vim). In + that case, connect to the pty slave instead of treating + the handle as a real console. */ + int pcon_minor =3D cygwin_shared->tty.find_pcon_pty (); + if (pcon_minor >=3D 0) + dev.parse (FHDEV (DEV_PTYS_MAJOR, pcon_minor)); + else + dev.parse (FH_CONSOLE); + } } else if (GetCommState (handle, &dcb)) /* FIXME: Not right - assumes ttyS0 */ diff --git a/winsup/cygwin/local_includes/tty.h b/winsup/cygwin/local_inclu= des/tty.h index 0adad03e6..507f7772e 100644 --- a/winsup/cygwin/local_includes/tty.h +++ b/winsup/cygwin/local_includes/tty.h @@ -181,6 +181,10 @@ public: void wait_fwd (); bool pty_input_state_eq (xfer_dir x) { return pty_input_state =3D=3D x; } bool nat_fg (pid_t pgid); + bool has_active_pcon () const + { return pcon_activated && switch_to_nat_pipe; } + bool has_pcon_and_owner (DWORD pid) const + { return pcon_activated && switch_to_nat_pipe && nat_pipe_owner_pid = =3D=3D pid; } friend class fhandler_pty_common; friend class fhandler_pty_master; friend class fhandler_pty_slave; @@ -199,6 +203,7 @@ public: int connect (int); void init (); tty_min *get_cttyp (); + int find_pcon_pty (); int attach (int n); static void init_session (); friend class lock_ttys; diff --git a/winsup/cygwin/release/3.7.0 b/winsup/cygwin/release/3.7.0 index bcc80cbfd..3fc32433e 100644 --- a/winsup/cygwin/release/3.7.0 +++ b/winsup/cygwin/release/3.7.0 @@ -21,3 +21,7 @@ What's new: =20 - Add support for OpenConsole.exe if available. This adds new features and fixes against pseudo console based on legacy conhost.exe. + +- Now, a Cygwin process started from a non=E2=80=91Cygwin process on a pse= udo console + runs on a pty rather than on the console device originating from the pse= udo + console. diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc index e8083dc1f..667aa2682 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -123,6 +123,43 @@ tty_list::init () } } =20 +/* Search for a pty whose pseudo console owns our console. + Return tty minor number or -1 if not found. + Called from init_std_file_from_handle() for processes started by + non-Cygwin parents to detect that inherited console handles are + from a pcon-backed pty. + + The cheap precondition (any tty with pcon active in shared memory) + short-circuits the common case where no pty has a pseudo console + active, avoiding the GetConsoleProcessList() LPC call entirely. */ +int +tty_list::find_pcon_pty () +{ + DWORD pids[128]; + DWORD count =3D 0; + bool got_pids =3D false; + + for (int i =3D 0; i < NTTYS; i++) + { + if (!ttys[i].has_active_pcon ()) + continue; + + /* Fetch the console process list lazily, only on first candidate. */ + if (!got_pids) + { + count =3D GetConsoleProcessList (pids, 128); + if (!count) + return -1; + got_pids =3D true; + } + + for (DWORD j =3D 0; j < count; j++) + if (ttys[i].has_pcon_and_owner (pids[j])) + return i; + } + return -1; +} + /* Search for a free tty and allocate it. Return tty number or -1 if error. */