diff --git a/msys2-runtime/0048-Add-AGENTS.md-with-comprehensive-project-context-for.patch b/msys2-runtime/0048-Add-AGENTS.md-with-comprehensive-project-context-for.patch new file mode 100644 index 00000000000..9190af9c799 --- /dev/null +++ b/msys2-runtime/0048-Add-AGENTS.md-with-comprehensive-project-context-for.patch @@ -0,0 +1,331 @@ +From 1e0ff3720728a1bd8788a836476a3e83665aa9aa Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Fri, 20 Feb 2026 12:17:50 +0100 +Subject: [PATCH 48/N] Add AGENTS.md with comprehensive project context for AI + agents +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This file documents the layered fork structure of this repository +(Cygwin → MSYS2 → Git for Windows), the merging-rebase strategy that +keeps the main branch fast-forwarding, the build system and its +bootstrap chicken-and-egg nature (msys-2.0.dll is the POSIX emulation +layer that its own GCC depends on), the CI pipeline, key directories +and files, development guidelines, and external resources. + +The intent is to give AI coding agents enough context to work +competently on this codebase without hallucinating about its structure +or purpose. + +Assisted-by: Claude Opus 4.6 +Signed-off-by: Johannes Schindelin +--- + AGENTS.md | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 298 insertions(+) + create mode 100644 AGENTS.md + +diff --git a/AGENTS.md b/AGENTS.md +new file mode 100644 +index 0000000..177fb5e +--- /dev/null ++++ b/AGENTS.md +@@ -0,0 +1,298 @@ ++# Guidelines for AI Agents Working on This Codebase ++ ++## Project Overview ++ ++This repository is the **Git for Windows fork** of the **MSYS2 runtime**, which is itself a fork of the **Cygwin runtime**. The runtime provides a POSIX emulation layer on Windows, producing `msys-2.0.dll` (analogous to Cygwin's `cygwin1.dll`). It is the foundational component that allows Unix-style programs (bash, coreutils, etc.) to run on Windows within the MSYS2 and Git for Windows ecosystems. ++ ++### The Layered Fork Structure ++ ++There are three layers of this project, each building on the one below: ++ ++1. **Cygwin** (`git://sourceware.org/git/newlib-cygwin.git`, releases at https://cygwin.com): The upstream project. Cygwin is a POSIX-compatible environment for Windows consisting of a DLL (`cygwin1.dll`) that provides substantial POSIX API functionality, plus a collection of GNU and Open Source tools. The Cygwin project releases versioned tags (e.g., `cygwin-3.6.6`) from the `cygwin/cygwin` GitHub mirror. ++ ++2. **MSYS2** (`https://github.com/msys2/msys2-runtime`): The MSYS2 project rebases its own patches on top of each Cygwin release. MSYS2 maintains branches named `msys2-X.Y.Z` (e.g., `msys2-3.6.6`) where the Cygwin code is the base and MSYS2-specific patches are applied on top. These patches implement features like POSIX-to-Windows path conversion (`msys2_path_conv.cc`), the `MSYS` environment variable for controlling runtime behavior, pseudo-console support toggling, and adaptations needed for MSYS2's focus on building native Windows software (as opposed to Cygwin's focus on running Unix software on Windows as-is). ++ ++3. **Git for Windows** (`https://github.com/git-for-windows/msys2-runtime`, this repository): Git for Windows maintains a "merging rebase" on top of the MSYS2 patches. The `main` branch uses a special strategy where it always fast-forwards. Each rebase to a new upstream version starts with a "fake merge" commit (message: `Start the merging-rebase to cygwin-X.Y.Z`) that merges previous `main` using the `-s ours` strategy. This ensures the branch always fast-forwards despite being rebased. Git for Windows' own patches (on top of MSYS2's patches) address issues specific to Git's usage patterns, such as Ctrl+C signal handling, SSH hang fixes, and console output correctness. ++ ++### Key Relationships ++ ++- **Cygwin → MSYS2**: MSYS2 rebases onto each Cygwin release. When Cygwin releases version X.Y.Z, an `msys2-X.Y.Z` branch is created with MSYS2 patches rebased on top. ++- **MSYS2 → Git for Windows**: Git for Windows performs a merging rebase that first merges in the MSYS2 patches, then rebases its own patches on top. ++- The `main` branch in this repository (git-for-windows/msys2-runtime) is the Git for Windows branch, not Cygwin's or MSYS2's. ++ ++## Repository Structure ++ ++### Key Directories ++ ++- **`winsup/cygwin/`**: The core of the Cygwin/MSYS2 runtime. This is where `msys-2.0.dll` (the POSIX emulation DLL) is built. Most development work happens here. Key files include: ++ - `dcrt0.cc`: Runtime initialization ++ - `spawn.cc`: Process spawning ++ - `path.cc`: Path handling ++ - `fork.cc`: fork() implementation ++ - `exceptions.cc`: Signal handling ++ - `msys2_path_conv.cc` / `msys2_path_conv.h`: MSYS2-specific POSIX-to-Windows path conversion (CC0-licensed) ++ - `environ.cc`: Environment variable handling, including the `MSYS` environment variable ++ - `fhandler/`: File handler implementations for various device types ++ - `local_includes/`: Internal headers ++ - `release/`: Version history files (one per Cygwin release version) ++- **`winsup/utils/`**: Cygwin/MSYS2 utility programs (mount, cygpath, etc.) ++- **`newlib/`**: The C library (newlib) used by the runtime ++- **`ui-tests/`**: AutoHotKey-based integration tests that test the runtime in real terminal scenarios ++- **`.github/workflows/`**: CI configuration ++ ++## Build System ++ ++### The Chicken-and-Egg Problem ++ ++The MSYS2 runtime (`msys-2.0.dll`) is itself the POSIX emulation layer that the MSYS2 toolchain (GCC, binutils, etc.) depends on. The MSYS2 environment's own GCC links against `msys-2.0.dll` to provide POSIX semantics. This means you need a working MSYS2 runtime to compile a new MSYS2 runtime — a classic bootstrap problem. ++ ++In practice, this is resolved by using an existing MSYS2 installation to build the new version. The CI workflow (`.github/workflows/build.yaml`) installs MSYS2 via the `msys2/setup-msys2` action, then builds the new runtime within that environment. ++ ++### Build Dependencies ++ ++Building requires MSYS2 packages: `msys2-devel`, `base-devel`, `autotools`, `cocom`, `gcc`, `gettext-devel`, `libiconv-devel`, `make`, `mingw-w64-cross-crt`, `mingw-w64-cross-gcc`, `mingw-w64-cross-zlib`, `perl`, `zlib-devel`. These are all **msys** packages (they link against `msys-2.0.dll`), not native MinGW packages. ++ ++### Building in the Git for Windows SDK ++ ++The Git for Windows SDK provides a complete MSYS2 environment with all necessary build dependencies pre-installed. The source tree is typically located at `/usr/src/MSYS2-packages/msys2-runtime/src/msys2-runtime` inside the SDK. ++ ++**Critical: PATH ordering.** The build must use the MSYS2 toolchain, not any MinGW toolchain that might be on the PATH. Before building, ensure: ++ ++```bash ++export PATH=/usr/bin:/mingw64/bin:/mingw32/bin:$PATH ++``` ++ ++If MinGW's GCC is found first, the build will fail because MinGW tools do not link against `msys-2.0.dll` and cannot produce the runtime DLL. ++ ++### Build Commands ++ ++```bash ++# Generate autotools files ++(cd winsup && ./autogen.sh) ++ ++# Configure (the --with-msys2-runtime-commit flag embeds the commit hash) ++./configure --disable-dependency-tracking --with-msys2-runtime-commit="$(git rev-parse HEAD)" ++ ++# Build ++make -j8 ++``` ++ ++For quick rebuilds of just the DLL during development: ++```bash ++# Rebuild only msys-2.0.dll ++make -C ../build-x86_64-pc-msys/x*/winsup/cygwin -j15 new-msys-2.0.dll ++``` ++ ++The build output is `new-msys-2.0.dll` in the build directory. This is a staging name to avoid overwriting the running DLL. ++ ++### Testing a Locally-Built DLL ++ ++You cannot replace the SDK's own `msys-2.0.dll` while running inside the SDK — the DLL is loaded by every MSYS2 process including your shell. Instead, copy the built DLL into a separate installation such as a Portable Git: ++ ++```bash ++cp new-msys-2.0.dll /path/to/PortableGit/usr/bin/msys-2.0.dll ++``` ++ ++Then run tests using that Portable Git's mintty/bash. Back up the original DLL first. ++ ++The `build-and-copy.sh` helper script in the repository root can reconfigure, rebuild, and copy `msys-2.0.dll` to a target location. ++ ++### Internal API Constraints ++ ++Code inside `msys-2.0.dll` cannot use the full C runtime or C++ standard library freely. Key limitations: ++ ++- **`__small_sprintf`** is used instead of `sprintf`. It does NOT support `%lld` (64-bit integers) or floating-point format specifiers. For 64-bit values, split into high/low 32-bit halves and print as two `%u` values. ++- **Memory allocation** in low-level code (e.g., DLL initialization, atexit handlers) should use `HeapAlloc(GetProcessHeap(), ...)` to avoid circular dependencies with the Cygwin malloc. ++ ++### CI Pipeline ++ ++The CI (`.github/workflows/build.yaml`) does the following: ++1. **Build**: Compiles the runtime on `windows-latest` using MSYS2 ++2. **Minimal SDK artifact**: Creates a minimal Git for Windows SDK with the just-built runtime, used for testing Git itself ++3. **Test minimal SDK**: Runs Git's test suite against the new runtime ++4. **UI tests**: AutoHotKey-based integration tests for terminal behavior (Ctrl+C interrupts, SSH operations, etc.) ++5. **MSYS2 tests**: Runs the MSYS2 project's own test suite across multiple environments and compilers ++ ++## Git Branch and Rebase Workflow ++ ++### The Merging Rebase Strategy ++ ++Git for Windows uses a "merging rebase" to maintain a fast-forwarding `main` branch. The key insight is a "fake merge" commit that: ++ ++1. Starts from the new upstream commit (Cygwin tag) ++2. Merges in the previous `main` using `-s ours` (takes NO changes from previous main, only the tree from upstream) ++3. This makes `main` a parent of the new commit, so the result is a fast-forward from previous `main` ++4. Patches are then rebased on top of this fake merge ++ ++The commit message follows a strict format: `Start the merging-rebase to cygwin-X.Y.Z`. This is machine-parseable — `git rev-parse 'main^{/^Start.the.merging-rebase}'` finds the most recent such commit. ++ ++### History of Merging Rebases ++ ++The repository has been continuously rebased through Cygwin versions from 3.3.x through the current 3.6.6. Each rebase is visible as a `Start the merging-rebase to cygwin-X.Y.Z` commit on `main`. ++ ++### Key Branches ++ ++- `main`: Git for Windows' branch (fast-forwarding, contains merging-rebase commits) ++- `cygwin-X_Y-branch` (e.g., `cygwin-3_6-branch`): Tracking branches for upstream Cygwin ++- `cygwin/main`: Upstream Cygwin's main branch ++- Various feature branches for specific fixes (e.g., `fix-ctrl+c-again`, `fix-ssh-hangs-reloaded`) ++ ++### Key Remotes ++ ++- `cygwin`: The upstream Cygwin repository (`git://sourceware.org/git/newlib-cygwin.git`) ++- `msys2`: The MSYS2 fork (`https://github.com/msys2/msys2-runtime`) ++- `git-for-windows`: This repository (`https://github.com/git-for-windows/msys2-runtime`) ++- `dscho`: Johannes Schindelin's fork (primary maintainer) ++ ++## Development Guidelines ++ ++### Language and Style ++ ++The runtime is written in **C++** (with some C). The code uses Cygwin's existing coding conventions. When modifying files under `winsup/cygwin/`: ++- Follow the existing indentation and brace style of each file ++- Cygwin code uses 8-space tabs in many files ++- MSYS2-specific additions (like `msys2_path_conv.cc`) may use different conventions ++ ++### Making Changes ++ ++Most changes for Git for Windows purposes are in `winsup/cygwin/`. Common areas of modification: ++- Signal handling (`exceptions.cc`, `sigproc.cc`) ++- Process spawning (`spawn.cc`) ++- PTY/console handling (`fhandler/` directory, `termios.cc`) ++- Path conversion (`msys2_path_conv.cc`, `path.cc`) ++- Environment handling (`environ.cc`) ++ ++### Testing ++ ++- The CI builds the runtime and runs Git's entire test suite against it ++- UI tests in `ui-tests/` test real terminal scenarios using AutoHotKey ++- MSYS2's own test suite is run across multiple compiler/environment combinations ++- For local testing, build the DLL and copy it to replace `msys-2.0.dll` in an MSYS2 installation ++ ++### Commit Discipline ++ ++- One logical change per commit ++- Commit messages should explain context, intent, and justification in prose (not bullet points) ++- For the rebase workflow, commit messages follow specific patterns (e.g., `Start the merging-rebase to ...`) that tooling depends on — do not alter these patterns ++ ++## PTY Architecture — Pipes, State Machine, and Input Routing ++ ++This section documents the internal architecture of the pseudo-terminal (PTY) implementation in `winsup/cygwin/fhandler/pty.cc`. Understanding this is essential for debugging any issue involving terminal input/output, keystroke handling, signal delivery, and process foreground/background transitions. ++ ++### Background: Why This Matters ++ ++The pseudo console support in the Cygwin runtime is one of the most intricate subsystems in this codebase. It bridges two fundamentally different models of terminal I/O — POSIX and Win32 console — across multiple processes that share state through shared memory. The implementation is ambitious and evolving; the complexity of the interactions between pipe switching, pseudo console lifecycle, cross-process mutexes, and foreground process detection means that changes in one area can have subtle, hard-to-diagnose effects elsewhere. Historically, bug fixes in this area have occasionally introduced new regressions, which is simply a reflection of how difficult the problem space is. Any AI agent working on PTY-related issues should take the time to understand the full picture before proposing changes, and should be especially careful about mutex acquisition order, state transitions that span process boundaries, and the distinction between the two pipe pairs described below. ++ ++### The Two Pipe Pairs ++ ++Each PTY has **two independent pipe pairs** for input, serving different consumers: ++ ++1. **Cygwin (cyg) pipe**: `to_slave` / `from_master` ++ - Used when a **Cygwin/MSYS2 process** (e.g., bash) is in the foreground. ++ - Input goes through `line_edit()` (in `termios.cc`) which handles line discipline (echo, canonical mode, special characters) before being written via `accept_input()`. ++ - The slave reads from `from_master` (aliased as `get_handle()` on the slave side). ++ ++2. **Native (nat) pipe**: `to_slave_nat` / `from_master_nat` ++ - Used when a **non-Cygwin (native Windows) process** (e.g., `powershell.exe`, `cmd.exe`, a MinGW program) is in the foreground. ++ - When the pseudo console (pcon) is active, `CreatePseudoConsole()` wraps this pipe pair. The Windows `conhost.exe` process reads from `from_master_nat` and provides console input semantics to the native app. ++ - The master writes directly to `to_slave_nat` via `WriteFile()`, bypassing `line_edit()`. ++ ++For **output**, there is a corresponding pair (`to_master` / `to_master_nat`) plus a forwarding thread (`master_fwd_thread`) that copies output from the nat pipe's slave side (`from_slave_nat`) to the cyg pipe's master side (`to_master`), so the terminal emulator (mintty) always reads from one place. ++ ++### The Pseudo Console (pcon) ++ ++When `MSYS=disable_pcon` is NOT set (the default), the runtime uses Windows' `CreatePseudoConsole()` API to give native console applications a real console to talk to. The pseudo console is created on demand when a non-Cygwin process becomes the foreground process, and torn down when it exits. This is what allows programs like `cmd.exe`, `powershell.exe`, or any MinGW-built program to work correctly inside a mintty terminal, which has no native Win32 console of its own. ++ ++The pcon lifecycle is managed across process boundaries: the slave process (running the non-Cygwin app) and the master process (the terminal emulator) both participate. This cross-process coordination is the source of much of the complexity. ++ ++Key state fields in the `tty` structure (shared memory, in `tty.h`): ++ ++- **`pcon_activated`** (`bool`): True when a pseudo console is currently active. ++- **`pcon_start`** (`bool`): True during pseudo console initialization. ++- **`pcon_start_pid`** (`pid_t`): PID of the process that initiated pcon setup. ++ ++### The Input State Machine ++ ++The field **`pty_input_state`** (type `xfer_dir`, in `tty.h:137`) tracks which pipe pair currently "owns" the input. It has two values: ++ ++- **`to_cyg`**: Input is flowing to the Cygwin pipe. The master's `write()` uses the `line_edit()` → `accept_input()` path, which writes to `to_slave` (cyg pipe). ++- **`to_nat`**: Input is flowing to the native pipe. The master's `write()` writes directly to `to_slave_nat` (nat pipe), or through the pseudo console. ++ ++The state transitions happen via **`transfer_input()`** (pty.cc, around line 3905), which: ++1. Reads all pending data from the "source" pipe (the one being abandoned). ++2. Writes that data into the "destination" pipe (the one being switched to). ++3. Sets `pty_input_state` to the new direction. ++ ++This ensures data already buffered in one pipe is not lost when switching. **Any code that changes `pty_input_state` without calling `transfer_input()` risks losing or reordering data.** This invariant is critical and has been the root cause of past bugs. ++ ++### Related State Fields ++ ++- **`switch_to_nat_pipe`** (`bool`): Set to true when a non-Cygwin process is detected in the foreground. This is a prerequisite for `to_be_read_from_nat_pipe()` returning true. ++- **`nat_pipe_owner_pid`** (`DWORD`): PID of the process that "owns" the nat pipe setup. Used to detect when the owner has exited (for cleanup). ++ ++### The `to_be_read_from_nat_pipe()` Function ++ ++This function (pty.cc, around line 1288) determines whether the current foreground process is a native (non-Cygwin) app. It checks: ++ ++1. `switch_to_nat_pipe` must be true. ++2. A named event `TTY_SLAVE_READING` must NOT exist (its existence means a Cygwin process is actively reading from the slave, indicating a Cygwin foreground). ++3. `nat_fg(pgid)` returns true (the foreground process group contains a native process). ++ ++**This function reads shared state without holding any mutex.** Its return value can therefore change between consecutive calls within the same function, which is an important consideration for callers that make multiple decisions based on the foreground state. ++ ++### Mutexes and Synchronization ++ ++Two cross-process named mutexes protect different aspects of the PTY state. Understanding which mutex protects what — and the fact that they are independent — is essential for diagnosing race conditions. ++ ++- **`input_mutex`**: Protects the input data path. Held by `master::write()` while routing input to a pipe, by `transfer_input()` while moving data between pipes, and by `line_edit()` / `accept_input()`. ++- **`pipe_sw_mutex`**: Protects pipe switching state — creation/destruction of the pseudo console, changes to `switch_to_nat_pipe`, `nat_pipe_owner_pid`. This is a DIFFERENT mutex from `input_mutex`. ++ ++Because these are separate mutexes, it is possible for one process to modify the pipe switching state (under `pipe_sw_mutex`) while another process is in the middle of writing input (under `input_mutex`). Any code that modifies `pty_input_state` or `pcon_activated` must carefully consider whether it also needs `input_mutex` to avoid creating a window where the master's write path makes inconsistent decisions. ++ ++Additionally, because these are **cross-process** named mutexes, they are shared via the kernel between the master (terminal emulator) and slave (bash and its children) processes. Operations that look local in the source code actually have system-wide synchronization effects. ++ ++### The `master::write()` Input Routing (pty.cc, around line 2240) ++ ++When the terminal emulator (mintty) sends a keystroke, it calls `master::write()`. After acquiring `input_mutex`, the function decides which path to take: ++ ++1. **Path 1 — pcon+nat** (line ~2245): If `to_be_read_from_nat_pipe()` AND `pcon_activated` AND `pty_input_state == to_nat` → write directly to `to_slave_nat`. This is the fast path for native apps with pcon. ++ ++2. **Path 2 — non-pcon transfer** (line ~2288): If `to_be_read_from_nat_pipe()` AND NOT `pcon_activated` AND `pty_input_state == to_cyg` → call `transfer_input(to_nat)` to move cyg pipe data to nat pipe, then fall through to line_edit. ++ ++3. **Path 3 — line_edit** (line ~2300): The default/fallthrough path. Calls `line_edit()` which processes the input through terminal line discipline and then calls `accept_input()`, which writes to either the cyg or nat pipe based on the current `pty_input_state`. ++ ++The conditions checked at each step involve multiple shared-memory fields (`to_be_read_from_nat_pipe()`, `pcon_activated`, `pty_input_state`). If any of these fields changes between consecutive calls to `master::write()` — or worse, between the check and the write within a single call — input can end up in the wrong pipe. ++ ++### Key Functions for State Transitions ++ ++- **`setup_for_non_cygwin_app()`** (~line 4150): Called when a non-Cygwin process becomes foreground. Sets up the pseudo console and switches input to nat pipe. ++- **`cleanup_for_non_cygwin_app()`** (~line 4184): Called when the non-Cygwin process exits. Tears down pcon, transfers input back to cyg pipe. ++- **`reset_switch_to_nat_pipe()`** (~line 1091): Cleanup function called from various slave-side operations (e.g., `bg_check()`, `setpgid_aux()`). Detects when the nat pipe owner has exited and resets state. This function is particularly subtle because it runs in the slave process and modifies shared state that the master relies on. ++- **`mask_switch_to_nat_pipe()`** (~line 1249): Temporarily masks/unmasks the nat pipe switching. Used when a Cygwin process starts/stops reading from the slave. ++- **`setpgid_aux()`** (~line 4214): Called when the foreground process group changes. May trigger pipe switching. ++ ++### Debugging Tips ++ ++When investigating PTY-related bugs, keep these patterns in mind: ++ ++- **Data in two pipes**: If characters are lost, duplicated, or reordered, check whether data ended up split across the cyg and nat pipes due to a state transition during input. ++- **Cross-process state changes**: The master and slave processes share state through the `tty` structure in shared memory. A state change in the slave (e.g., `reset_switch_to_nat_pipe()`) is immediately visible to the master, without any notification. Look for races where the master reads state, acts on it, but the state changed between the read and the action. ++- **Mutex coverage gaps**: Check whether every modification of `pty_input_state`, `pcon_activated`, and `switch_to_nat_pipe` is protected by the appropriate mutex. The existence of two separate mutexes (`input_mutex` and `pipe_sw_mutex`) means that holding one does not protect against changes guarded by the other. ++- **`transfer_input()` must accompany state changes**: Whenever `pty_input_state` is changed, any data buffered in the old pipe must be transferred to the new one. Forgetting this step causes data loss or reordering. ++- **Tracing**: For timing-sensitive bugs, in-process tracing with lock-free per-thread buffers (using Windows TLS and `QueryPerformanceCounter`) is effective. Avoid file I/O during reproduction — accumulate in memory and dump at process exit. See the `ui-tests/` directory for AutoHotKey-based reproducers that can drive mintty programmatically. ++ ++## Packaging ++ ++The MSYS2 runtime is packaged as an **msys** package (`msys2-runtime`) using `makepkg` with a `PKGBUILD` recipe in the `msys2/MSYS2-packages` repository. The package definition lives at `msys2-runtime/PKGBUILD` in that repository. ++ ++## External Resources ++ ++- **Cygwin project**: https://cygwin.com — upstream source, FAQ, user's guide ++- **Cygwin source**: https://github.com/cygwin/cygwin (mirror of `sourceware.org/git/newlib-cygwin.git`) ++- **Cygwin announcements**: https://inbox.sourceware.org/cygwin-announce — release announcements ++- **MSYS2 project**: https://www.msys2.org — documentation, package management ++- **MSYS2 runtime source**: https://github.com/msys2/msys2-runtime ++- **MSYS2 packages**: https://github.com/msys2/MSYS2-packages — package recipes including `msys2-runtime` ++- **Git for Windows**: https://gitforwindows.org ++- **Git for Windows runtime**: https://github.com/git-for-windows/msys2-runtime (this repository) ++- **MSYS2 environments**: https://www.msys2.org/docs/environments/ — explains MSYS vs UCRT64 vs CLANG64 etc. diff --git a/msys2-runtime/0049-Cygwin-pty-Fix-nat-pipe-hand-over-when-pcon-is-disab.patch b/msys2-runtime/0049-Cygwin-pty-Fix-nat-pipe-hand-over-when-pcon-is-disab.patch new file mode 100644 index 00000000000..36fff34801e --- /dev/null +++ b/msys2-runtime/0049-Cygwin-pty-Fix-nat-pipe-hand-over-when-pcon-is-disab.patch @@ -0,0 +1,59 @@ +From 3d8be6bd4d3d9086c57856cf80c03258fdd16327 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Tue, 3 Mar 2026 22:18:22 +0900 +Subject: [PATCH 49/N] Cygwin: pty: Fix nat pipe hand-over when pcon is + disabled + +The nat pipe ownership hand-over mechanism relies on the console +process list - the set of processes attached to a console, enumerable +via `GetConsoleProcessList()`. For non-cygwin process in pcon_activated +case, this list contains all processes attached to the pseudo console. +Otherwise, it contains all processes attached to the invisible console. + +04f386e9af (Cygwin: console: Inherit pcon hand over from parent pty, +2024-10-31) added a last-resort fallback in `get_winpid_to_hand_over()` +that hands nat pipe ownership to any process in the console process +list, including Cygwin processes. This fallback is needed when a +Cygwin process on the pseudo console (that might be exec'ed from non- +cygwin process) must take over management of an active pseudo console +after the original owner exits. + +When the pseudo console is disabled, this fallback incorrectly finds a +Cygwin process (such as the shell) and assigns it nat pipe ownership, +because both the original nat pipe owner and the shell are assosiated +with the same invisible console. Since there is no console for that +process to manage, ownership never gets released, input stays stuck on +the nat pipe. + +Only the third (last-resort) call in the cascade needs guarding: the +first two calls filter for native (non-Cygwin) processes via the `nat` +parameter, and handing ownership to another native process is fine +regardless of pcon state. It is only the fallback to Cygwin processes +that is dangerous without an active pseudo console. + +Guard the fallback with a `pcon_activated` check, since handing nat +pipe ownership to a Cygwin process only makes sense when there is an +active pseudo console for it to manage. + +Fixes: 04f386e9af99 ("Cygwin: console: Inherit pcon hand over from parent pty") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Cherry-picked-from: 699c6892f1 (Cygwin: pty: Fix nat pipe hand-over when pcon is disabled, 2026-03-03) +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/pty.cc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc +index 90f5867..e07bbfe 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -3559,7 +3559,7 @@ fhandler_pty_slave::get_winpid_to_hand_over (tty *ttyp, + if (!switch_to) + switch_to = get_console_process_id (current_pid, + false, true, false, true); +- if (!switch_to) ++ if (!switch_to && ttyp->pcon_activated) + switch_to = get_console_process_id (current_pid, + false, false, false, false); + } diff --git a/msys2-runtime/0050-Cygwin-console-Release-pipe_sw_mutex-in-pcon_hand_ov.patch b/msys2-runtime/0050-Cygwin-console-Release-pipe_sw_mutex-in-pcon_hand_ov.patch new file mode 100644 index 00000000000..0dc951cc68d --- /dev/null +++ b/msys2-runtime/0050-Cygwin-console-Release-pipe_sw_mutex-in-pcon_hand_ov.patch @@ -0,0 +1,50 @@ +From a0f8f2baa5454daaa3d751ab07bf0fee205c2b13 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Tue, 24 Mar 2026 11:25:40 +0900 +Subject: [PATCH 50/N] Cygwin: console: Release pipe_sw_mutex in + pcon_hand_over_proc() + +Currently, pipe_sw_mutex is held in the process which is running +in console inherited from pseudo console until the process ends. +Due to this behaviour, the process may cause deadlock when it +attempts to acquire input_mutex in set_input_mode() called via +close_ctty(). This deadlock occurs because the pty master +acquire input_mutex first and acquire pipe_sw_mutex next while +the process exiting acquire pipe_sw_mutex first. + +To avoid this deadlock, this patch releases pipe_sw_mutex in +pcon_hand_over_proc(). In addition, pointless pipe_sw_mutex +acquire/release is drppped in pcon_hand_over_proc(). + +Fixes: 04f386e9af99 ("Cygwin: console: Inherit pcon hand over from parent pty") +Signed-off-by: Takashi Yano +Reviewed-by: Corinna Vinschen +Cherry-picked-from: 9ef8e3ad3b (Cygwin: console: Release pipe_sw_mutex in pcon_hand_over_proc(), 2026-03-24) +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/console.cc | 5 +---- + 1 file changed, 1 insertion(+), 4 deletions(-) + +diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc +index 831df4f..9e11611 100644 +--- a/winsup/cygwin/fhandler/console.cc ++++ b/winsup/cygwin/fhandler/console.cc +@@ -1946,8 +1946,6 @@ fhandler_console::pcon_hand_over_proc (void) + char buf[MAX_PATH]; + shared_name (buf, PIPE_SW_MUTEX, parent_pty); + HANDLE mtx = OpenMutex (MAXIMUM_ALLOWED, FALSE, buf); +- WaitForSingleObject (mtx, INFINITE); +- ReleaseMutex (mtx); + DWORD res = WaitForSingleObject (mtx, INFINITE); + if (res == WAIT_OBJECT_0 || res == WAIT_ABANDONED) + { +@@ -1958,8 +1956,7 @@ fhandler_console::pcon_hand_over_proc (void) + } + else + system_printf("Acquiring pcon_ho_mutex failed."); +- /* Do not release the mutex. +- Hold onto the mutex until this process completes. */ ++ ReleaseMutex (mtx); + } + + bool diff --git a/msys2-runtime/0051-Cygwin-pty-Fix-input-transfer-when-multiple-non-cygw.patch b/msys2-runtime/0051-Cygwin-pty-Fix-input-transfer-when-multiple-non-cygw.patch new file mode 100644 index 00000000000..f0b98159f6c --- /dev/null +++ b/msys2-runtime/0051-Cygwin-pty-Fix-input-transfer-when-multiple-non-cygw.patch @@ -0,0 +1,78 @@ +From 79ae2b09ed0b2dd826a333857246e7cb963922d8 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:59:29 +0900 +Subject: [PATCH 51/N] Cygwin: pty: Fix input transfer when multiple + non-cygwin apps exist + +Cygwin maintains POSIX line discipline for its own processes: +input goes through `line_edit()` before reaching the reading process. +Native (non-Cygwin) processes must not receive line-edited input; +they expect raw console input instead. To support both, the PTY keeps +two independent pipe pairs for input: a "cyg" pipe for Cygwin processes +and a "nat" pipe for native ones. The runtime switches between the two +as the foreground process changes. + +The PTY tracks which process "owns" the nat pipe session via the +shared-memory field `nat_pipe_owner_pid`. Only one process is the +owner at any time. When `setup_for_non_cygwin_app()` finds that the +current owner is still alive, it leaves ownership with that process +rather than claiming it for the new one. + +This means that a Cygwin-spawned native process can go through +`cleanup_for_non_cygwin_app()` without being the nat pipe owner. +Before this fix, that cleanup called `transfer_input(to_cyg)` +unconditionally, draining the pseudo console's input buffer even +though another process still owned the session. Keystrokes that the +user had typed were moved to the cyg pipe prematurely, so the actual +owner found an empty console input buffer and appeared to lose all +input. + +When looking for the next owner of the console in +`cleanup_for_non_cygwin_app()` (via `get_winpid_to_hand_over()`), +and when transferring the input back to the cyg pipe, guard both with +a `nat_pipe_owner_self()` check so that only the actual owner performs +these operations. Non-owner processes skip straight to detaching from +the pseudo console without disturbing the input buffer. + +Fixes: f9542a2e8e75 ("Cygwin: pty: Re-fix the last bug regarding nat-pipe.") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Applied-from: https://inbox.sourceware.org/cygwin-patches/20260328110050.1928-1-takashi.yano@nifty.ne.jp/ +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/pty.cc | 21 ++++++++++++--------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc +index e07bbfe..387fa17 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -4140,16 +4140,19 @@ fhandler_pty_slave::cleanup_for_non_cygwin_app (handle_set_t *p, tty *ttyp, + { + ttyp->wait_fwd (); + WaitForSingleObject (p->pipe_sw_mutex, INFINITE); +- DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to); +- if ((!switch_to && (ttyp->pcon_activated || stdin_is_ptys)) +- && ttyp->pty_input_state_eq (tty::to_nat)) ++ if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid)) + { +- 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); +- release_attach_mutex (); +- ReleaseMutex (p->input_mutex); ++ DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to); ++ if ((!switch_to && (ttyp->pcon_activated || stdin_is_ptys)) ++ && ttyp->pty_input_state_eq (tty::to_nat)) ++ { ++ 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); ++ release_attach_mutex (); ++ ReleaseMutex (p->input_mutex); ++ } + } + if (ttyp->pcon_activated) + close_pseudoconsole (ttyp, force_switch_to); diff --git a/msys2-runtime/0052-Cygwin-console-Fix-master-thread.patch b/msys2-runtime/0052-Cygwin-console-Fix-master-thread.patch new file mode 100644 index 00000000000..f2da3aedaec --- /dev/null +++ b/msys2-runtime/0052-Cygwin-console-Fix-master-thread.patch @@ -0,0 +1,50 @@ +From 10aff7d8f7857e1385025d5b44aec6402543aa6e Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:45 +0900 +Subject: [PATCH 52/N] Cygwin: console: Fix master thread + +In Windows 11, key event with wRepeatCount == 0 is fixed-up to +wRepeatCount == 1 in conhost.exe. +https://github.com/microsoft/terminal/blob/v1.25.622.0/src/host/inputBuffer.cpp#L406 + +The console master thread (`cons_master_thread`) reads INPUT_RECORDs +from the console input buffer, processes signal-generating events, +and writes the remaining records back. After the writeback, it peeks +the buffer and uses `inrec_eq()` to verify that conhost stored the +records faithfully. On Windows 11, conhost normalizes `wRepeatCount` +from 0 to 1 on readback, causing `inrec_eq()` to report a mismatch +and triggering an unnecessary fixup path. Treat 0 and 1 as equivalent +for comparison purposes. + +Addresses: https://github.com/git-for-windows/git/issues/5632 +Fixes: ff4440fcf768 ("Cygwin: console: Introduce new thread which handles input signal.") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/console.cc | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc +index 9e11611..80d6e34 100644 +--- a/winsup/cygwin/fhandler/console.cc ++++ b/winsup/cygwin/fhandler/console.cc +@@ -318,9 +318,17 @@ inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b, DWORD n) + written event. Therefore they are ignored. */ + const KEY_EVENT_RECORD *ak = &a[i].Event.KeyEvent; + const KEY_EVENT_RECORD *bk = &b[i].Event.KeyEvent; ++ /* On Windows 11, conhost normalizes wRepeatCount from 0 to 1 ++ on readback. Treat them as equivalent for comparison. */ ++ WORD r1 = ak->wRepeatCount; ++ WORD r2 = bk->wRepeatCount; ++ if (r1 == 0) ++ r1 = 1; ++ if (r2 == 0) ++ r2 = 1; + if (ak->bKeyDown != bk->bKeyDown + || ak->uChar.UnicodeChar != bk->uChar.UnicodeChar +- || ak->wRepeatCount != bk->wRepeatCount) ++ || r1 != r2) + return false; + } + else if (a[i].EventType == MOUSE_EVENT) diff --git a/msys2-runtime/0053-Cygwin-pty-Add-workaround-for-handling-of-backspace-.patch b/msys2-runtime/0053-Cygwin-pty-Add-workaround-for-handling-of-backspace-.patch new file mode 100644 index 00000000000..2e1139c82dd --- /dev/null +++ b/msys2-runtime/0053-Cygwin-pty-Add-workaround-for-handling-of-backspace-.patch @@ -0,0 +1,204 @@ +From 4f08a7575975001f8e6f09f90d446f91a1b15c11 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:46 +0900 +Subject: [PATCH 53/N] Cygwin: pty: Add workaround for handling of backspace + when pcon enabled + +In Windows 11, pseudo console has an undesired key conversion that +the Ctrl-H is translated into Ctrl-Backspace (not Backspace). +The reverse VT input path in conhost's `_DoControlCharacter()` maps +the byte 0x08 to a Ctrl+Backspace key event (VK_BACK with +LEFT_CTRL_PRESSED and character 0x7F). This was introduced in PR #3935 +(Jan 2020) to make Ctrl+Backspace delete whole words. In September +2022, PR #13894 rewrote the forward path to properly implement DECBKM +(Backarrow Key Mode), but the reverse path was never updated to match, +breaking the roundtrip. + +Due to this behaviour, inrec_eq() in cons_master_thread() fails to +compare backspace/Ctrl-H events in the input record sequence. This +patch is a workaround for the issue that replaces Ctrl-H with backspace +(0x7f), which will be translated into Ctrl-H in pseudo console. + +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/console.cc | 12 +++- + winsup/cygwin/fhandler/pty.cc | 78 ++++++++++++++++++++++--- + winsup/cygwin/local_includes/fhandler.h | 2 + + 3 files changed, 82 insertions(+), 10 deletions(-) + +diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc +index 80d6e34..20c20de 100644 +--- a/winsup/cygwin/fhandler/console.cc ++++ b/winsup/cygwin/fhandler/console.cc +@@ -318,6 +318,16 @@ inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b, DWORD n) + written event. Therefore they are ignored. */ + const KEY_EVENT_RECORD *ak = &a[i].Event.KeyEvent; + const KEY_EVENT_RECORD *bk = &b[i].Event.KeyEvent; ++ WCHAR c1 = ak->uChar.UnicodeChar; ++ WCHAR c2 = bk->uChar.UnicodeChar; ++ if (inside_pcon) ++ { ++ /* Workaround for pseudo console in Windows 11 */ ++ if (c1 == 8) /* Ctrl-H */ ++ c1 = 127; /* Backspace */ ++ if (c2 == 8) /* Ctrl-H */ ++ c2 = 127; /* Backspace */ ++ } + /* On Windows 11, conhost normalizes wRepeatCount from 0 to 1 + on readback. Treat them as equivalent for comparison. */ + WORD r1 = ak->wRepeatCount; +@@ -327,7 +337,7 @@ inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b, DWORD n) + if (r2 == 0) + r2 = 1; + if (ak->bKeyDown != bk->bKeyDown +- || ak->uChar.UnicodeChar != bk->uChar.UnicodeChar ++ || c1 != c2 + || r1 != r2) + return false; + } +diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc +index 387fa17..292e071 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -1933,7 +1933,8 @@ fhandler_pty_master::fhandler_pty_master (int unit, dev_t via) + master_thread (NULL), from_master_nat (NULL), to_master_nat (NULL), + from_slave_nat (NULL), to_slave_nat (NULL), echo_r (NULL), echo_w (NULL), + dwProcessId (0), to_master (NULL), from_master (NULL), +- master_fwd_thread (NULL) ++ master_fwd_thread (NULL), h_pcon_in_dupped (NULL), ++ nat_pipe_owner_pid_dupped (0) + { + dev_referred_via = via; + if (unit >= 0) +@@ -2114,6 +2115,10 @@ fhandler_pty_master::close (int flag) + termios_printf ("error closing from_master %p, %E", from_master); + from_master = NULL; + ++ if (h_pcon_in_dupped) ++ ForceCloseHandle (h_pcon_in_dupped); ++ h_pcon_in_dupped = NULL; ++ + return 0; + } + +@@ -2215,28 +2220,77 @@ fhandler_pty_master::write (const void *ptr, size_t len) + { /* Reaches here when non-cygwin app is foreground and pseudo console + is activated. */ + tmp_pathbuf tp; +- char *buf = (char *) ptr; ++ char *buf = tp.c_get (); + size_t nlen = len; + if (get_ttyp ()->term_code_page != CP_UTF8) + { + static mbstate_t mbp; +- buf = tp.c_get (); + nlen = NT_MAX_PATH; + convert_mb_str (CP_UTF8, buf, &nlen, + get_ttyp ()->term_code_page, (const char *) ptr, len, + &mbp); + } ++ else ++ memcpy (buf, ptr, nlen); + +- for (size_t i = 0; i < nlen; i++) ++ if (get_ttyp ()->nat_pipe_owner_pid != nat_pipe_owner_pid_dupped) ++ { ++ if (!nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid)) ++ { ++ if (h_pcon_in_dupped) ++ ForceCloseHandle (h_pcon_in_dupped); ++ h_pcon_in_dupped = NULL; ++ nat_pipe_owner_pid_dupped = 0; ++ HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, ++ get_ttyp ()->nat_pipe_owner_pid); ++ if (pcon_owner) ++ { ++ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in, ++ GetCurrentProcess (), &h_pcon_in_dupped, ++ 0, FALSE, DUPLICATE_SAME_ACCESS); ++ nat_pipe_owner_pid_dupped = get_ttyp ()->nat_pipe_owner_pid; ++ CloseHandle (pcon_owner); ++ } ++ } ++ else ++ { ++ h_pcon_in_dupped = get_ttyp ()->h_pcon_in; ++ nat_pipe_owner_pid_dupped = get_ttyp ()->nat_pipe_owner_pid; ++ } ++ } ++ ++ /* Retrieve console mode */ ++ DWORD cons_mode = ENABLE_VIRTUAL_TERMINAL_INPUT; ++ if (h_pcon_in_dupped && memchr (buf, '\010' /* Ctrl-H */, nlen)) ++ { ++ if (!nat_pipe_owner_self (nat_pipe_owner_pid_dupped)) ++ { ++ DWORD resume_pid = ++ attach_console_temporarily (nat_pipe_owner_pid_dupped); ++ GetConsoleMode (h_pcon_in_dupped, &cons_mode); ++ resume_from_temporarily_attach (resume_pid); ++ } ++ else ++ GetConsoleMode (h_pcon_in_dupped, &cons_mode); ++ } ++ ++ len = nlen; ++ for (size_t i = 0, j = 0; i < len; i++) + { + process_sig_state r = process_sigs (buf[i], get_ttyp (), this); +- if (r == done_with_debugger) ++ if (r != done_with_debugger) + { +- for (size_t j = i; j < nlen - 1; j++) +- buf[j] = buf[j + 1]; +- nlen--; +- i--; ++ char c = buf[i]; ++ /* Workaround for pseudo console in Windows 11 */ ++ if (!(cons_mode & ENABLE_VIRTUAL_TERMINAL_INPUT)) ++ /* Undesired backspace conversion in pseudo console does ++ not happen if ENABLE_VIRTUAL_TERMINAL_INPUT is set. */ ++ if (c == '\010') /* Ctrl-H */ ++ c = '\177'; /* Backspace */ ++ buf[j++] = c; + } ++ else ++ nlen--; + } + + DWORD n; +@@ -3105,6 +3159,8 @@ fhandler_pty_master::fixup_after_fork (HANDLE parent) + from_slave_nat = arch->from_slave_nat; + to_slave_nat = arch->to_slave_nat; + #endif ++ h_pcon_in_dupped = NULL; ++ nat_pipe_owner_pid_dupped = 0; + report_tty_counts (this, "inherited master", ""); + } + +@@ -3958,6 +4014,10 @@ fhandler_pty_slave::transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp, + if (r[i].EventType == KEY_EVENT && r[i].Event.KeyEvent.bKeyDown) + { + DWORD ctrl_key_state = r[i].Event.KeyEvent.dwControlKeyState; ++ if (r[i].Event.KeyEvent.uChar.AsciiChar == '\010' /* Ctrl-H */ ++ && !(ctrl_key_state & ALT_PRESSED)) ++ /* Workaround for pseudo console in Windows 11 */ ++ r[i].Event.KeyEvent.uChar.AsciiChar = '\177'; /* Backspace */ + if (r[i].Event.KeyEvent.uChar.AsciiChar) + { + if ((ctrl_key_state & ALT_PRESSED) +diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_includes/fhandler.h +index 9fa7389..a4feeec 100644 +--- a/winsup/cygwin/local_includes/fhandler.h ++++ b/winsup/cygwin/local_includes/fhandler.h +@@ -2560,6 +2560,8 @@ private: + HANDLE thread_param_copied_event; + HANDLE helper_goodbye; + HANDLE helper_h_process; ++ HANDLE h_pcon_in_dupped; ++ DWORD nat_pipe_owner_pid_dupped; + + public: + HANDLE get_echo_handle () const { return echo_r; } diff --git a/msys2-runtime/0054-Cygwin-console-Use-input_mutex-in-the-parent-PTY-in-.patch b/msys2-runtime/0054-Cygwin-console-Use-input_mutex-in-the-parent-PTY-in-.patch new file mode 100644 index 00000000000..ec038ff334e --- /dev/null +++ b/msys2-runtime/0054-Cygwin-console-Use-input_mutex-in-the-parent-PTY-in-.patch @@ -0,0 +1,98 @@ +From b9d762742c58aa226f12346f1916488d482efa45 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:47 +0900 +Subject: [PATCH 54/N] Cygwin: console: Use input_mutex in the parent PTY in + master thread + +If the console is originating from pseudo console, the input into +console is coming from PTY master. This is because: + +When the pseudo console is active, and a cygwin process is started +from non-cygwin process, `cons_master_thread()` runs inside the +Cygwin process that inherited the pseudo console from its parent +PTY. It reads all `INPUT_RECORD`s from the console input buffer via +`ReadConsoleInputW()`, processes signal-generating events (e.g. Ctrl+C), +and writes the remaining records back via `WriteConsoleInputW()`. +Meanwhile, the PTY master process (e.g. mintty) calls +`fhandler_pty_master::write()`, which writes keystrokes to `to_slave_nat` +(one end of the nat pipe). Conhost reads from the other end of that +pipe, parses the byte stream through its VT input path, and inserts +the resulting `INPUT_RECORD`s into the console input buffer. + +If `cons_master_thread()` reads the buffer and removes a signal record +while conhost is simultaneously inserting new records from the PTY +master's write, the verify step (`inrec_eq()`) finds records in the +buffer that were not part of the original read, reports a mismatch, and +enters the fixup path. That fixup path itself can disturb the record +order, turning what was merely an interference into an actual problem. +Acquiring the PTY's `input_mutex` in `cons_master_thread()` prevents +`fhandler_pty_master::write()` from feeding new bytes into the pipe +while the read-process-writeback-verify cycle is in progress. + +Use parent input_mutex as well as input_mutex in console device in +cons_master_thread(). + +Fixes: 04f386e9af99 ("Cygwin: console: Inherit pcon hand over from parent pty") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/console.cc | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc +index 20c20de..6220a91 100644 +--- a/winsup/cygwin/fhandler/console.cc ++++ b/winsup/cygwin/fhandler/console.cc +@@ -63,6 +63,7 @@ fhandler_console::console_state NO_COPY + static bool NO_COPY inside_pcon_checked = false; + static bool NO_COPY inside_pcon = false; + static int NO_COPY parent_pty; ++static HANDLE NO_COPY parent_pty_input_mutex = NULL; + + bool NO_COPY fhandler_console::invisible_console; + +@@ -465,6 +466,8 @@ fhandler_console::cons_master_thread (handle_set_t *p, tty *ttyp) + continue; + } + total_read = 0; ++ if (inside_pcon && parent_pty_input_mutex) ++ WaitForSingleObject (parent_pty_input_mutex, mutex_timeout); + switch (cygwait (p->input_handle, (DWORD) 0)) + { + case WAIT_OBJECT_0: +@@ -489,6 +492,8 @@ fhandler_console::cons_master_thread (handle_set_t *p, tty *ttyp) + default: /* Error */ + free (input_rec); + free (input_tmp); ++ if (inside_pcon && parent_pty_input_mutex) ++ ReleaseMutex (parent_pty_input_mutex); + ReleaseMutex (p->input_mutex); + return; + } +@@ -666,6 +671,8 @@ remove_record: + while (true); + } + skip_writeback: ++ if (inside_pcon && parent_pty_input_mutex) ++ ReleaseMutex (parent_pty_input_mutex); + ReleaseMutex (p->input_mutex); + cygwait (40); + } +@@ -1949,6 +1956,8 @@ fhandler_console::setup_pcon_hand_over () + inside_pcon = true; + atexit (fhandler_console::pcon_hand_over_proc); + parent_pty = i; ++ parent_pty_input_mutex = ++ cygwin_shared->tty[i]->open_input_mutex (MAXIMUM_ALLOWED); + break; + } + } +@@ -1975,6 +1984,7 @@ fhandler_console::pcon_hand_over_proc (void) + else + system_printf("Acquiring pcon_ho_mutex failed."); + ReleaseMutex (mtx); ++ ForceCloseHandle (parent_pty_input_mutex); + } + + bool diff --git a/msys2-runtime/0055-Cygwin-pty-Apply-line_edit-for-transferred-input-to-.patch b/msys2-runtime/0055-Cygwin-pty-Apply-line_edit-for-transferred-input-to-.patch new file mode 100644 index 00000000000..644f45d8bf6 --- /dev/null +++ b/msys2-runtime/0055-Cygwin-pty-Apply-line_edit-for-transferred-input-to-.patch @@ -0,0 +1,483 @@ +From 15c111b068c5b712ac0793a6e23557022c9761f1 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:48 +0900 +Subject: [PATCH 55/N] 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 +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + 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 292e071..58fd996 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -209,6 +209,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 + }; +@@ -738,7 +739,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 + }; + +@@ -778,6 +779,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 */ + { +@@ -992,6 +999,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 ()); +@@ -1100,7 +1109,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); + } +@@ -1249,14 +1259,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 (); + } + } +@@ -1818,11 +1828,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; +@@ -1839,11 +1853,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); +@@ -1856,6 +1872,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); +@@ -2108,6 +2125,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. */ +@@ -2202,7 +2222,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); + } +@@ -2311,7 +2332,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 (); + } + +@@ -2667,6 +2689,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) + { +@@ -2690,19 +2732,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; +@@ -2981,7 +3041,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"; +@@ -3042,6 +3103,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); +@@ -3119,6 +3184,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); +@@ -3924,6 +3990,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); + } + +@@ -3931,7 +3999,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) +@@ -4053,26 +4122,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 +@@ -4111,13 +4162,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; + } + +@@ -4137,6 +4195,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); +@@ -4152,6 +4213,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); +@@ -4187,7 +4250,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); + } +@@ -4209,7 +4272,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); + } +@@ -4235,7 +4299,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); + } +@@ -4261,7 +4325,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 a4feeec..95423ed 100644 +--- a/winsup/cygwin/local_includes/fhandler.h ++++ b/winsup/cygwin/local_includes/fhandler.h +@@ -2011,6 +2011,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; + }; +@@ -2382,13 +2383,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); +@@ -2510,7 +2512,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 (); +@@ -2545,8 +2548,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. +@@ -2625,6 +2630,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 754ee90..7d80ab4 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" diff --git a/msys2-runtime/0056-Cygwin-pty-Guard-get_winpid_to_hand_over-with-attach.patch b/msys2-runtime/0056-Cygwin-pty-Guard-get_winpid_to_hand_over-with-attach.patch new file mode 100644 index 00000000000..f61eed5ef9f --- /dev/null +++ b/msys2-runtime/0056-Cygwin-pty-Guard-get_winpid_to_hand_over-with-attach.patch @@ -0,0 +1,99 @@ +From 9e78b35e7566edad51c22de8688af3eaa23cb31b Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:49 +0900 +Subject: [PATCH 56/N] Cygwin: pty: Guard get_winpid_to_hand_over() with + attach_mutex + +The master process (e.g. mintty) temporarily attaches to the pseudo +console's conhost in `transfer_input()` so it can read +INPUT_RECORDs via `ReadConsoleInputA()`. During that brief window, +`get_console_process_id()` inside `get_winpid_to_hand_over()` calls +`GetConsoleProcessList()`, which sees the master among the console's +attached processes and may select it as the handover target. That is +wrong because the master will detach immediately after the read. + +Until now, `attach_mutex` was a process-local unnamed mutex, so +the slave's `get_winpid_to_hand_over()` could not serialize with +the master's temporary attachment. Make `attach_mutex` a +cross-process named mutex (`ATTACH_MUTEX`) shared within the PTY, +and acquire it around the `get_console_process_id()` calls in +`get_winpid_to_hand_over()`. This ensures the console process list +enumeration never observes the master while it is temporarily +attached. + +Fixes: 1e6c51d74136 ("Cygwin: pty: Reorganize the code path of setting up and closing pcon.") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/pty.cc | 16 ++++++++++++++-- + winsup/cygwin/local_includes/tty.h | 1 + + 2 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc +index 58fd996..d6817cb 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -773,6 +773,12 @@ fhandler_pty_slave::open (int flags, mode_t) + errmsg = "open pipe switch mutex failed, %E"; + goto err; + } ++ if (!(attach_mutex ++ = get_ttyp ()->open_mutex (ATTACH_MUTEX, MAXIMUM_ALLOWED))) ++ { ++ errmsg = "open attach mutex failed, %E"; ++ goto err; ++ } + shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ()); + if (!(input_available_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf))) + { +@@ -2493,6 +2499,9 @@ void + fhandler_pty_slave::fixup_after_fork (HANDLE parent) + { + create_invisible_console (); ++ /* attach_mutex is initialized not only in the fork() case, but also in ++ the exec() case, since fixup_after_exec() calls fixup_after_fork(). */ ++ attach_mutex = get_ttyp ()->open_mutex (ATTACH_MUTEX, MAXIMUM_ALLOWED); + + // fork_fixup (parent, inuse, "inuse"); + // fhandler_pty_common::fixup_after_fork (parent); +@@ -3121,8 +3130,9 @@ fhandler_pty_master::setup () + if (!(pipe_sw_mutex = CreateMutex (&sa, FALSE, buf))) + goto err; + +- if (!attach_mutex) +- attach_mutex = CreateMutex (&sec_none_nih, FALSE, NULL); ++ errstr = shared_name (buf, ATTACH_MUTEX, unit); ++ if (!(attach_mutex = CreateMutex (&sa, FALSE, buf))) ++ goto err; + + /* Create master control pipe which allows the master to duplicate + the pty pipe handles to processes which deserve it. */ +@@ -3676,6 +3686,7 @@ fhandler_pty_slave::get_winpid_to_hand_over (tty *ttyp, + DWORD current_pid = myself->exec_dwProcessId ?: myself->dwProcessId; + if (ttyp->nat_pipe_owner_pid == GetCurrentProcessId ()) + current_pid = GetCurrentProcessId (); ++ acquire_attach_mutex (mutex_timeout); + switch_to = get_console_process_id (current_pid, + false, true, true, true); + if (!switch_to) +@@ -3684,6 +3695,7 @@ fhandler_pty_slave::get_winpid_to_hand_over (tty *ttyp, + if (!switch_to && ttyp->pcon_activated) + switch_to = get_console_process_id (current_pid, + false, false, false, false); ++ release_attach_mutex (); + } + return switch_to; + } +diff --git a/winsup/cygwin/local_includes/tty.h b/winsup/cygwin/local_includes/tty.h +index 7d80ab4..6e70a74 100644 +--- a/winsup/cygwin/local_includes/tty.h ++++ b/winsup/cygwin/local_includes/tty.h +@@ -22,6 +22,7 @@ details. */ + #define OUTPUT_MUTEX "cygtty.output.mutex" + #define INPUT_MUTEX "cygtty.input.mutex" + #define PIPE_SW_MUTEX "cygtty.pipe_sw.mutex" ++#define ATTACH_MUTEX "cygtty.attach.mutex" + #define TTY_SLAVE_ALIVE "cygtty.slave_alive" + #define TTY_SLAVE_READING "cygtty.slave_reading" + diff --git a/msys2-runtime/0057-Cygwin-pty-Guard-to_be_read_from_nat_pipe-by-pipe_sw.patch b/msys2-runtime/0057-Cygwin-pty-Guard-to_be_read_from_nat_pipe-by-pipe_sw.patch new file mode 100644 index 00000000000..f45fa1d40da --- /dev/null +++ b/msys2-runtime/0057-Cygwin-pty-Guard-to_be_read_from_nat_pipe-by-pipe_sw.patch @@ -0,0 +1,165 @@ +From d2355119f7c6fafc74a4c8595f15baffb6b5d364 Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:50 +0900 +Subject: [PATCH 57/N] Cygwin: pty: Guard to_be_read_from_nat_pipe() by + pipe_sw_mutex + +`to_be_read_from_nat_pipe()` reads several shared-memory fields +(`switch_to_nat_pipe`, `pcon_activated`, `pty_input_state`) to +decide whether keystrokes should go to the nat pipe. It is called +from `master::write()` on every keystroke. Without synchronization, +the slave can be in the middle of a pipe switch (changing these +fields in `setup_for_non_cygwin_app()`, `cleanup_for_non_cygwin_app()`, +or `setpgid_aux()`) while the master reads a half-updated snapshot, +making an inconsistent routing decision that sends keystrokes to the +wrong pipe. + +Guard `to_be_read_from_nat_pipe()` with `pipe_sw_mutex` so it +always reads a consistent state. The spin-wait at entry handles the +pseudo console initialization case: when `pipe_sw_mutex` is held by +the slave during `setup_pseudoconsole()` and `pcon_start` is set, +the function returns false immediately, routing keystrokes to the +cyg pipe through `line_edit()` where the CSI6n response handler +expects them. + +Acquiring `pipe_sw_mutex` inside `to_be_read_from_nat_pipe()` +creates a lock ordering constraint: `master::write()` holds +`input_mutex` before calling `to_be_read_from_nat_pipe()`, so the +master's lock order is `input_mutex` then `pipe_sw_mutex`. +Previously, `cleanup_for_non_cygwin_app()` and `setpgid_aux()` +acquired `pipe_sw_mutex` first and then `input_mutex` (for +`transfer_input()`), which is the reverse order and would deadlock. +Restructure both functions to release `pipe_sw_mutex` before +acquiring `input_mutex`, maintaining a consistent lock order +throughout. + +Fixes: bb4285206207 ("Cygwin: pty: Implement new pseudo console support.") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/pty.cc | 48 +++++++++++++++++++++++++---------- + 1 file changed, 34 insertions(+), 14 deletions(-) + +diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc +index d6817cb..4a628ea 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -1283,22 +1283,42 @@ fhandler_pty_slave::mask_switch_to_nat_pipe (bool mask, bool xfer) + bool + fhandler_pty_common::to_be_read_from_nat_pipe (void) + { ++ /* If the slave is in setup_pseudoconsole(), pipe_sw_mutex cannot ++ be acquired because the slave has it. In this case pcon_start ++ will be asserted. During pcon_start, other input than response ++ to CSI6n should be go to cyg-pipe. So, wait for pcon_start and ++ return false. */ ++ while (WaitForSingleObject (pipe_sw_mutex, 0) == WAIT_TIMEOUT) ++ if (get_ttyp ()->pcon_start || get_ttyp ()->pcon_start_pid) ++ return false; ++ else ++ yield (); ++ ++ bool ret = false; + if (!get_ttyp ()->switch_to_nat_pipe) +- return false; ++ goto out; + +- char name[MAX_PATH]; +- shared_name (name, TTY_SLAVE_READING, get_minor ()); +- HANDLE masked = OpenEvent (READ_CONTROL, FALSE, name); +- CloseHandle (masked); ++ { ++ char name[MAX_PATH]; ++ shared_name (name, TTY_SLAVE_READING, get_minor ()); ++ HANDLE masked = OpenEvent (READ_CONTROL, FALSE, name); ++ CloseHandle (masked); + +- if (masked) /* The foreground process is cygwin process */ +- return false; ++ if (masked) /* The foreground process is cygwin process */ ++ goto out; ++ } + + if (!pinfo (get_ttyp ()->getpgid ())) + /* GDB may set invalid process group for non-cygwin process. */ +- return true; ++ { ++ ret = true; ++ goto out; ++ } + +- return get_ttyp ()->nat_fg (get_ttyp ()->getpgid ()); ++ ret = get_ttyp ()->nat_fg (get_ttyp ()->getpgid ()); ++out: ++ ReleaseMutex (pipe_sw_mutex); ++ return ret; + } + + void +@@ -3901,7 +3921,6 @@ fhandler_pty_slave::term_has_pcon_cap (const WCHAR *env) + goto maybe_dumb; + + /* Check if terminal has CSI6n */ +- WaitForSingleObject (pipe_sw_mutex, INFINITE); + WaitForSingleObject (input_mutex, mutex_timeout); + /* Set pcon_activated and pcon_start so that the response + will sent to io_handle_nat rather than io_handle. */ +@@ -3937,7 +3956,6 @@ fhandler_pty_slave::term_has_pcon_cap (const WCHAR *env) + while (len); + get_ttyp ()->pcon_activated = false; + get_ttyp ()->nat_pipe_owner_pid = 0; +- ReleaseMutex (pipe_sw_mutex); + if (len == 0) + goto not_has_csi6n; + +@@ -3953,7 +3971,6 @@ not_has_csi6n: + get_ttyp ()->pcon_start = false; + get_ttyp ()->pcon_activated = false; + ReleaseMutex (input_mutex); +- ReleaseMutex (pipe_sw_mutex); + maybe_dumb: + get_ttyp ()->pcon_cap_checked = true; + return false; +@@ -4274,7 +4291,6 @@ fhandler_pty_slave::cleanup_for_non_cygwin_app (handle_set_t *p, tty *ttyp, + DWORD force_switch_to) + { + ttyp->wait_fwd (); +- WaitForSingleObject (p->pipe_sw_mutex, INFINITE); + if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid)) + { + DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to); +@@ -4290,6 +4306,7 @@ fhandler_pty_slave::cleanup_for_non_cygwin_app (handle_set_t *p, tty *ttyp, + ReleaseMutex (p->input_mutex); + } + } ++ WaitForSingleObject (p->pipe_sw_mutex, INFINITE); + if (ttyp->pcon_activated) + close_pseudoconsole (ttyp, force_switch_to); + else +@@ -4308,6 +4325,7 @@ fhandler_pty_slave::setpgid_aux (pid_t pid) + if (!was_nat_fg && nat_fg && get_ttyp ()->switch_to_nat_pipe + && get_ttyp ()->pty_input_state_eq (tty::to_cyg)) + { ++ ReleaseMutex (pipe_sw_mutex); + WaitForSingleObject (input_mutex, mutex_timeout); + acquire_attach_mutex (mutex_timeout); + transfer_input (tty::to_nat, get_handle (), get_ttyp (), +@@ -4318,6 +4336,7 @@ fhandler_pty_slave::setpgid_aux (pid_t pid) + else if (was_nat_fg && !nat_fg && get_ttyp ()->switch_to_nat_pipe + && get_ttyp ()->pty_input_state_eq (tty::to_nat)) + { ++ ReleaseMutex (pipe_sw_mutex); + bool attach_restore = false; + HANDLE from = get_handle_nat (); + DWORD resume_pid = 0; +@@ -4345,7 +4364,8 @@ fhandler_pty_slave::setpgid_aux (pid_t pid) + release_attach_mutex (); + ReleaseMutex (input_mutex); + } +- ReleaseMutex (pipe_sw_mutex); ++ else ++ ReleaseMutex (pipe_sw_mutex); + } + + bool diff --git a/msys2-runtime/0058-Cygwin-pty-Drop-nat_fg-check-from-to_be_read_from_na.patch b/msys2-runtime/0058-Cygwin-pty-Drop-nat_fg-check-from-to_be_read_from_na.patch new file mode 100644 index 00000000000..2e4421800dc --- /dev/null +++ b/msys2-runtime/0058-Cygwin-pty-Drop-nat_fg-check-from-to_be_read_from_na.patch @@ -0,0 +1,89 @@ +From 76f644d90e44d4d41a69ab4ca38e98c64a9b67ec Mon Sep 17 00:00:00 2001 +From: Takashi Yano +Date: Sat, 28 Mar 2026 19:55:51 +0900 +Subject: [PATCH 58/N] Cygwin: pty: Drop nat_fg() check from + to_be_read_from_nat_pipe() + +While a non-cygwin app has exited but the stub process has not yet +terminated, `nat_fg()` returns false because no non-cygwin app is +running. In this window, pty input goes to the cyg pipe. Due to +this, the keystroke order is swapped unexpectedly: + +1) start non-cygwin app +2) press 'a' ('a' goes to nat pipe) +3) non-cygwin app exits +4) press 'b' ('b' goes to cyg pipe) +5) the stub process for non-cygwin app transfers input in nat pipe + to cyg pipe ('a' goes to cyg pipe) +6) the result in the cyg pipe is "ba" + +Fix this by dropping the `nat_fg()` check from +`to_be_read_from_nat_pipe()`. The function now returns true when +`!pcon_start && switch_to_nat_pipe && !masked`. Each component has +a specific purpose: + +- `!pcon_start`: keystrokes go through the CSI6n response handler + during pseudo console initialization rather than the fast path. +- `switch_to_nat_pipe`: this session-level flag stays true from + `setup_for_non_cygwin_app()` through `cleanup_for_non_cygwin_app()`, + spanning the entire native process lifetime including the post-exit + cleanup window. +- `!masked` (`TTY_SLAVE_READING` event does not exist): keystrokes + go to the Cygwin pipe when a Cygwin process is actively reading + from the slave, since that process expects POSIX-processed input. + +Removing `nat_fg()` is safe because conhost's input buffer +accumulates keystrokes as INPUT_RECORDs during the post-exit +window, and `transfer_input(to_cyg)` in `cleanup_for_non_cygwin_app()` +reads them back via `ReadConsoleInputA()` and writes them to the +cyg pipe. Those transferred bytes then go through `line_edit()` in +the master's forward thread (via `input_transferred_to_cyg` from an +earlier patch in this series), ensuring proper POSIX line discipline +processing. + +Additionally, add a `nat_fg()` check to the disable_pcon transfer +path in `master::write()`. That transfer moves cyg pipe data to +the nat pipe when a Cygwin child exits and a native process +regains the foreground with pcon disabled. Without pcon, there is +no conhost buffer to accumulate keystrokes (the nat pipe is a raw +pipe), so keystrokes must only go there when a native process is +genuinely in the foreground and ready to read them. The `nat_fg()` +guard prevents the transfer from stealing readline's data during +the post-exit window. + +Fixes: f20641789427 ("Cygwin: pty: Reduce unecessary input transfer.") +Signed-off-by: Takashi Yano +Reviewed-by: Johannes Schindelin +Signed-off-by: Johannes Schindelin +--- + winsup/cygwin/fhandler/pty.cc | 9 ++------- + 1 file changed, 2 insertions(+), 7 deletions(-) + +diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc +index 4a628ea..07904d9 100644 +--- a/winsup/cygwin/fhandler/pty.cc ++++ b/winsup/cygwin/fhandler/pty.cc +@@ -1308,14 +1308,8 @@ fhandler_pty_common::to_be_read_from_nat_pipe (void) + goto out; + } + +- if (!pinfo (get_ttyp ()->getpgid ())) +- /* GDB may set invalid process group for non-cygwin process. */ +- { +- ret = true; +- goto out; +- } ++ ret = true; /* !pcon_start && switch_to_nat_pipe && !masked */ + +- ret = get_ttyp ()->nat_fg (get_ttyp ()->getpgid ()); + out: + ReleaseMutex (pipe_sw_mutex); + return ret; +@@ -2354,6 +2348,7 @@ fhandler_pty_master::write (const void *ptr, size_t len) + /* This input transfer is needed when cygwin-app which is started from + non-cygwin app is terminated if pseudo console is disabled. */ + if (to_be_read_from_nat_pipe () && !get_ttyp ()->pcon_activated ++ && get_ttyp ()->nat_fg (get_ttyp ()->getpgid ()) + && get_ttyp ()->pty_input_state == tty::to_cyg) + { + acquire_attach_mutex (mutex_timeout); diff --git a/msys2-runtime/0059-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch b/msys2-runtime/0059-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch new file mode 100644 index 00000000000..b36d5a8ea60 --- /dev/null +++ b/msys2-runtime/0059-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch @@ -0,0 +1,117 @@ +From 4dff1e2b6a978028c9ba7217f20d2057ae0444eb Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Thu, 26 Feb 2026 15:11:05 +0100 +Subject: [PATCH 59/N] fixup! Add AGENTS.md with comprehensive project context + for AI agents + +Update the PTY architecture section to reflect the lessons learned +from investigating and fixing the character reordering bug +(git-for-windows/git#5632). + +The master::write() input routing description previously documented +three code paths, but the "non-pcon transfer" path (Path 2) was +removed as part of the fix because it stole readline's buffered data +from the cyg pipe during pseudo console oscillation gaps. Update +the section to describe the current two-path structure: the pcon+nat +fast path (which now flushes stale readahead before writing) and the +line_edit fallthrough (whose accept_input routing now includes a +pcon_activated guard). + +Add a new "Pseudo Console Oscillation" subsection documenting the +rapid pcon activate/deactivate cycles that occur when native +processes spawn short-lived Cygwin children. This phenomenon was +the root cause of the reordering bug and is a critical pattern for +future PTY debugging. + +Correct the transfer_input() guidance: the previous text stated that +transfer_input() "must accompany state changes" as an unconditional +invariant. In practice, per-keystroke transfers in master::write() +were actively harmful; the correct transfer points are setpgid_aux() +at process-group boundaries and cleanup_for_non_cygwin_app() at +session end. + +Update the reset_switch_to_nat_pipe() description to reflect the +restructured guard that prevents bg_check() from prematurely tearing +down active pseudo console sessions when bash itself is the +nat_pipe_owner. + +Add the Cygwin mailing list archives (cygwin, cygwin-patches, +cygwin-developers) to the external resources section, since commit +messages in this codebase frequently reference discussions on those +lists. + +Assisted-by: Claude Opus 4.6 +Signed-off-by: Johannes Schindelin +--- + AGENTS.md | 28 ++++++++++++++++++++-------- + 1 file changed, 20 insertions(+), 8 deletions(-) + +diff --git a/AGENTS.md b/AGENTS.md +index 177fb5e..547cf8e 100644 +--- a/AGENTS.md ++++ b/AGENTS.md +@@ -223,7 +223,7 @@ The state transitions happen via **`transfer_input()`** (pty.cc, around line 390 + 2. Writes that data into the "destination" pipe (the one being switched to). + 3. Sets `pty_input_state` to the new direction. + +-This ensures data already buffered in one pipe is not lost when switching. **Any code that changes `pty_input_state` without calling `transfer_input()` risks losing or reordering data.** This invariant is critical and has been the root cause of past bugs. ++This ensures data already buffered in one pipe is not lost when switching. **However, `transfer_input()` is only correct at process-group boundaries** — specifically in `setpgid_aux()` (when the foreground changes) and `cleanup_for_non_cygwin_app()` (when a native session ends). Calling `transfer_input()` on every keystroke in `master::write()` was historically a source of bugs: during pseudo console oscillation (see below), per-keystroke transfers would steal readline's buffered data from the cyg pipe and push it to the nat pipe, causing character reordering. The correct approach is to let `setpgid_aux()` handle the transfer at the moment of the actual process-group change, not to anticipate it in the master. + + ### Related State Fields + +@@ -253,21 +253,31 @@ Additionally, because these are **cross-process** named mutexes, they are shared + + ### The `master::write()` Input Routing (pty.cc, around line 2240) + +-When the terminal emulator (mintty) sends a keystroke, it calls `master::write()`. After acquiring `input_mutex`, the function decides which path to take: ++When the terminal emulator (mintty) sends a keystroke, it calls `master::write()`. After acquiring `input_mutex`, the function decides which code path to take: + +-1. **Path 1 — pcon+nat** (line ~2245): If `to_be_read_from_nat_pipe()` AND `pcon_activated` AND `pty_input_state == to_nat` → write directly to `to_slave_nat`. This is the fast path for native apps with pcon. ++1. **Code path 1 — pcon+nat fast path** (line ~2237): If `to_be_read_from_nat_pipe()` AND `pcon_activated` AND `pty_input_state == to_nat` → flush any stale readahead via `accept_input()`, then write directly to `to_slave_nat` via `WriteFile()`. This is the fast path for native apps with pcon active. The readahead flush is necessary because a prior `master::write()` call may have gone through `line_edit()` during a brief oscillation gap, leaving data in the readahead buffer that would otherwise be emitted out of order. + +-2. **Path 2 — non-pcon transfer** (line ~2288): If `to_be_read_from_nat_pipe()` AND NOT `pcon_activated` AND `pty_input_state == to_cyg` → call `transfer_input(to_nat)` to move cyg pipe data to nat pipe, then fall through to line_edit. +- +-3. **Path 3 — line_edit** (line ~2300): The default/fallthrough path. Calls `line_edit()` which processes the input through terminal line discipline and then calls `accept_input()`, which writes to either the cyg or nat pipe based on the current `pty_input_state`. ++2. **Code path 2 — line_edit** (line ~2275): The default/fallthrough path. Calls `line_edit()` which processes the input through terminal line discipline and then calls `accept_input()`, which writes to either the cyg or nat pipe based on the current `pty_input_state`. The `accept_input()` routing includes a `!pcon_activated` guard: it only routes to the nat pipe when pcon is NOT active, matching the documented invariant that direct nat pipe writes are for when "pseudo console is not enabled." + + The conditions checked at each step involve multiple shared-memory fields (`to_be_read_from_nat_pipe()`, `pcon_activated`, `pty_input_state`). If any of these fields changes between consecutive calls to `master::write()` — or worse, between the check and the write within a single call — input can end up in the wrong pipe. + ++### Pseudo Console Oscillation ++ ++When a native process spawns short-lived Cygwin children (e.g. `git.exe` calling `cygpath` via `--format`), the pseudo console activates and deactivates in rapid succession: ++ ++1. Native process in foreground: `pcon_activated=true`, `pty_input_state=to_nat` ++2. Cygwin child starts: `setpgid_aux()` fires, transfers data to cyg pipe, `pcon_activated=false`, `pty_input_state=to_cyg` ++3. Cygwin child exits (milliseconds later): native process regains foreground, pcon reactivates ++ ++A single command can cause dozens of such cycles per second. This "oscillation" is the root cause of the character reordering bug fixed on the `fix-jumbled-character-order` branch (see git-for-windows/git#5632). During each gap (step 2), `master::write()` must correctly route keystrokes without stealing data from readline's buffer in the cyg pipe. ++ ++The key insight: during the oscillation gap, `switch_to_nat_pipe` remains true (the native process is still alive) even though `pcon_activated` is false. This means `to_be_read_from_nat_pipe()` returns true, which historically caused several code paths to prematurely transfer data from the cyg pipe to the nat pipe. Those transfer code paths have been removed — `setpgid_aux()` in the slave is now the sole authority for pipe transfers at process-group boundaries. ++ + ### Key Functions for State Transitions + + - **`setup_for_non_cygwin_app()`** (~line 4150): Called when a non-Cygwin process becomes foreground. Sets up the pseudo console and switches input to nat pipe. + - **`cleanup_for_non_cygwin_app()`** (~line 4184): Called when the non-Cygwin process exits. Tears down pcon, transfers input back to cyg pipe. +-- **`reset_switch_to_nat_pipe()`** (~line 1091): Cleanup function called from various slave-side operations (e.g., `bg_check()`, `setpgid_aux()`). Detects when the nat pipe owner has exited and resets state. This function is particularly subtle because it runs in the slave process and modifies shared state that the master relies on. ++- **`reset_switch_to_nat_pipe()`** (~line 1091): Cleanup function called from various slave-side operations (e.g., `bg_check()`, `setpgid_aux()`). Detects when the nat pipe owner has exited and resets state. This function is particularly subtle because it runs in the slave process and modifies shared state that the master relies on. Note: the guard logic checks `process_alive()` first, then handles two sub-cases — when another process owns the nat pipe (return early), and when bash itself is the owner (return early if `pcon_activated` or `switch_to_nat_pipe` is still set, indicating the native session is ongoing). Without this two-level guard, `bg_check()` can tear down active pcon sessions, amplifying oscillation. + - **`mask_switch_to_nat_pipe()`** (~line 1249): Temporarily masks/unmasks the nat pipe switching. Used when a Cygwin process starts/stops reading from the slave. + - **`setpgid_aux()`** (~line 4214): Called when the foreground process group changes. May trigger pipe switching. + +@@ -278,7 +288,8 @@ When investigating PTY-related bugs, keep these patterns in mind: + - **Data in two pipes**: If characters are lost, duplicated, or reordered, check whether data ended up split across the cyg and nat pipes due to a state transition during input. + - **Cross-process state changes**: The master and slave processes share state through the `tty` structure in shared memory. A state change in the slave (e.g., `reset_switch_to_nat_pipe()`) is immediately visible to the master, without any notification. Look for races where the master reads state, acts on it, but the state changed between the read and the action. + - **Mutex coverage gaps**: Check whether every modification of `pty_input_state`, `pcon_activated`, and `switch_to_nat_pipe` is protected by the appropriate mutex. The existence of two separate mutexes (`input_mutex` and `pipe_sw_mutex`) means that holding one does not protect against changes guarded by the other. +-- **`transfer_input()` must accompany state changes**: Whenever `pty_input_state` is changed, any data buffered in the old pipe must be transferred to the new one. Forgetting this step causes data loss or reordering. ++- **`transfer_input()` is correct only at process-group boundaries**: The proper places for `transfer_input()` are `setpgid_aux()` (foreground change) and `cleanup_for_non_cygwin_app()` (session end). Per-keystroke transfers in `master::write()` were historically a source of character reordering — they would steal readline's buffered data from the cyg pipe during pseudo console oscillation gaps. If you see a `transfer_input()` call in `master::write()`, question whether it is genuinely needed or whether `setpgid_aux()` already handles the case. ++- **Pseudo console oscillation**: When characters are lost or reordered and the scenario involves a native process spawning Cygwin children, suspect pcon oscillation. The oscillation happens because each Cygwin child start/exit triggers a pcon teardown/setup cycle, and shared-memory flags (`pcon_activated`, `switch_to_nat_pipe`, `pty_input_state`) change rapidly without synchronization with the master's `input_mutex`. Tracing the state transitions across processes is essential for diagnosis. + - **Tracing**: For timing-sensitive bugs, in-process tracing with lock-free per-thread buffers (using Windows TLS and `QueryPerformanceCounter`) is effective. Avoid file I/O during reproduction — accumulate in memory and dump at process exit. See the `ui-tests/` directory for AutoHotKey-based reproducers that can drive mintty programmatically. + + ## Packaging +@@ -290,6 +301,7 @@ The MSYS2 runtime is packaged as an **msys** package (`msys2-runtime`) using `ma + - **Cygwin project**: https://cygwin.com — upstream source, FAQ, user's guide + - **Cygwin source**: https://github.com/cygwin/cygwin (mirror of `sourceware.org/git/newlib-cygwin.git`) + - **Cygwin announcements**: https://inbox.sourceware.org/cygwin-announce — release announcements ++- **Cygwin mailing lists**: https://inbox.sourceware.org/cygwin/ (general), https://inbox.sourceware.org/cygwin-patches/ (patches), https://inbox.sourceware.org/cygwin-developers/ (internals) — essential for understanding why specific code was added; commit messages often reference these discussions + - **MSYS2 project**: https://www.msys2.org — documentation, package management + - **MSYS2 runtime source**: https://github.com/msys2/msys2-runtime + - **MSYS2 packages**: https://github.com/msys2/MSYS2-packages — package recipes including `msys2-runtime` diff --git a/msys2-runtime/0060-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch b/msys2-runtime/0060-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch new file mode 100644 index 00000000000..eb2ef715ef2 --- /dev/null +++ b/msys2-runtime/0060-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch @@ -0,0 +1,36 @@ +From 6c1ab7b075b1e62a44344b546916b9127ee8b125 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Thu, 26 Feb 2026 18:56:32 +0100 +Subject: [PATCH 60/N] fixup! Add AGENTS.md with comprehensive project context + for AI agents + +Document the Cygwin commit message conventions (subject prefix, Fixes +and Addresses trailers, trailer ordering) as observed in upstream +commits by Corinna Vinschen and Takashi Yano. + +Assisted-by: Claude Opus 4.6 +Signed-off-by: Johannes Schindelin +--- + AGENTS.md | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/AGENTS.md b/AGENTS.md +index 547cf8e..67b3945 100644 +--- a/AGENTS.md ++++ b/AGENTS.md +@@ -175,6 +175,15 @@ Most changes for Git for Windows purposes are in `winsup/cygwin/`. Common areas + - Commit messages should explain context, intent, and justification in prose (not bullet points) + - For the rebase workflow, commit messages follow specific patterns (e.g., `Start the merging-rebase to ...`) that tooling depends on — do not alter these patterns + ++### Cygwin Commit Message Format ++ ++Commits that modify code under `winsup/cygwin/` should follow the Cygwin project's commit message conventions, as established by the upstream maintainers (Corinna Vinschen, Takashi Yano, et al.): ++ ++- **Subject prefix**: `Cygwin: : `, where `` is the subsystem (e.g. `pty`, `flock`, `termios`, `uinfo`, `path`, `spawn`). Example: `Cygwin: pty: Fix jumbled keystrokes by removing the per-keystroke pipe transfer`. Both upper-case and lower-case after the prefix are used upstream; there is no strict rule. ++- **`Fixes:` trailer**: When a commit fixes a bug introduced by a specific earlier commit, reference it with `Fixes: <12-char-hash> ("")`. Example: `Fixes: acc44e09d1d0 ("Cygwin: pty: Add missing input transfer when switch_to_pcon_in state.")` ++- **`Addresses:` trailer**: Reference the user-visible bug report URL. Example: `Addresses: https://github.com/git-for-windows/git/issues/5632` ++- **Trailer ordering**: `Addresses:`, then `Fixes:`, then `Assisted-by:` / `Reviewed-by:` / `Reported-by:`, then `Signed-off-by:` last — following the pattern seen in upstream Cygwin commits. ++ + ## PTY Architecture — Pipes, State Machine, and Input Routing + + This section documents the internal architecture of the pseudo-terminal (PTY) implementation in `winsup/cygwin/fhandler/pty.cc`. Understanding this is essential for debugging any issue involving terminal input/output, keystroke handling, signal delivery, and process foreground/background transitions. diff --git a/msys2-runtime/0061-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch b/msys2-runtime/0061-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch new file mode 100644 index 00000000000..4e3fdfde8b5 --- /dev/null +++ b/msys2-runtime/0061-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch @@ -0,0 +1,237 @@ +From fb42d71358dd896ab324c52970f7d03f9ab0dfe5 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Fri, 27 Mar 2026 09:47:14 +0100 +Subject: [PATCH 61/N] fixup! Add AGENTS.md with comprehensive project context + for AI agents + +The PTY architecture section contained several claims that were proven +incorrect by Takashi Yano's v7 patch series for the keystroke reordering bug +(https://inbox.sourceware.org/cygwin-patches/20260325130453.62246-1-takashi.yano@nifty.ne.jp/). +This session (https://github.com/git-for-windows/msys2-runtime/pull/124) spent +multiple weeks attempting to fix the bug with a 4-patch series that was +ultimately replaced entirely by Takashi's 9-patch series (merged via +c6d1b52813). The AGENTS.md content reflected the incorrect mental model that +led to our wrong patches. + +The most consequential error was the claim that transfer_input() calls in +master::write() "steal readline's data" and should be removed. In fact, those +transfers are essential: when a native process does not read stdin, keystrokes +accumulate in conhost's console input buffer as INPUT_RECORD events. At +cleanup, transfer_input(to_cyg) reads them back via ReadConsoleInputA() and +writes them to the cyg pipe for bash's readline. Removing the transfers lost +data instead of fixing the reordering. + +The reordering itself was caused by split delivery: some keystrokes went to the +nat pipe (fast path) while others leaked to the cyg pipe (line_edit +fallthrough) during transitions. The bytes that went directly to the cyg pipe +arrived at readline before the bytes that were transferred from the nat pipe at +cleanup, producing scrambled output. Takashi's fix was to drop the nat_fg() +check from to_be_read_from_nat_pipe() (v7 7/7), ensuring keystrokes always go +to the nat pipe while switch_to_nat_pipe is set, and to add pipe_sw_mutex +synchronization to to_be_read_from_nat_pipe() (v7 6/7) so the master cannot +read inconsistent state during pipe switching. + +Key corrections to AGENTS.md: + +The "Designed Keystroke Lifecycle" section is entirely new. It documents the +full roundtrip that keystrokes take when a native process is in the foreground: +master writes to nat pipe, conhost stores INPUT_RECORDs, cleanup transfers them +back to cyg pipe, readline receives them. This roundtrip was completely absent +from the previous version and its absence led directly to the wrong diagnosis +(we assumed keystrokes sent to conhost were "consumed" and "lost"). + +The to_be_read_from_nat_pipe() description previously stated that the function +checks nat_fg(). The corrected version explicitly states that nat_fg() must NOT +be checked, because doing so creates a gap between native process exit and +cleanup where keystrokes fall through to the cyg pipe, causing the split +delivery that produces reordering. + +The transfer_input() description previously warned that it "is only correct at +process-group boundaries" and that "per-keystroke transfers in master::write() +were historically a source of character reordering." Both claims were wrong. +The transfers in master::write() (specifically the pcon_start completion block) +serve the essential purpose of moving typeahead to the nat pipe after pcon +initialization. Takashi's v7 4/7 adds an input_transferred_to_cyg +synchronization event so that transferred bytes go through line_edit() in the +forward thread, ensuring proper line discipline processing. + +The reset_switch_to_nat_pipe() description previously documented a "two-level +guard" that checked pcon_activated and switch_to_nat_pipe when the owner is +self. That was our incorrect patch 3/4. Takashi's correct design is simpler: +only return early if a DIFFERENT process owns the nat pipe and is alive. When +the owner is self or dead, proceed to cleanup. + +The oscillation section previously claimed that "transfer code paths have been +removed" and that "setpgid_aux() in the slave is now the sole authority for +pipe transfers." Both were wrong. The correct design principle is that +keystrokes must always go to the nat pipe while switch_to_nat_pipe is set, +regardless of oscillation. The transfers remain in place and are essential. + +The mutex section now documents attach_mutex (the third cross-process mutex, +used by get_winpid_to_hand_over() per Takashi's v7 5/7) and notes the +consistent lock ordering (pipe_sw_mutex first, then input_mutex). + +The debugging tips now include "study existing upstream patches before writing +fixes" and "never remove transfer_input() calls without understanding what they +transfer," both learned the hard way from this session's failures. + +Assisted-by: Claude Opus 4.6 (1M context) +Signed-off-by: Johannes Schindelin +--- + AGENTS.md | 88 ++++++++++++++++++++++++++++++++++--------------------- + 1 file changed, 55 insertions(+), 33 deletions(-) + +diff --git a/AGENTS.md b/AGENTS.md +index 67b3945..e05270a 100644 +--- a/AGENTS.md ++++ b/AGENTS.md +@@ -208,6 +208,29 @@ Each PTY has **two independent pipe pairs** for input, serving different consume + + For **output**, there is a corresponding pair (`to_master` / `to_master_nat`) plus a forwarding thread (`master_fwd_thread`) that copies output from the nat pipe's slave side (`from_slave_nat`) to the cyg pipe's master side (`to_master`), so the terminal emulator (mintty) always reads from one place. + ++### The Designed Keystroke Lifecycle ++ ++Understanding the full lifecycle of a keystroke is essential. The design intent is that **no keystroke is ever lost**, regardless of what the foreground process does with it. The lifecycle differs between Cygwin and native foreground processes: ++ ++**When a Cygwin process is in the foreground (e.g., bash):** ++1. Terminal emulator writes keystroke via `master::write()` ++2. `master::write()` calls `line_edit()` which applies POSIX line discipline ++3. `accept_input()` writes processed bytes to the cyg pipe ++4. The Cygwin slave (bash) reads from the cyg pipe ++ ++**When a native process is in the foreground with pcon active:** ++1. Terminal emulator writes keystroke via `master::write()` ++2. `master::write()` fast path writes directly to `to_slave_nat` (nat pipe) ++3. Conhost (the pseudo console host) reads from the nat pipe, converts the byte stream to `INPUT_RECORD` events, and stores them in its console input buffer ++4. If the native process reads stdin: it gets `INPUT_RECORD` events via `ReadConsoleInput()` ++5. If the native process does NOT read stdin (common for background tasks): the `INPUT_RECORD` events accumulate in conhost's buffer ++6. When the native process exits: `cleanup_for_non_cygwin_app()` calls `transfer_input(to_cyg)`, which reads all pending `INPUT_RECORD` events from the console buffer via `ReadConsoleInputA()`, converts them back to bytes, and writes them to the cyg pipe ++7. Bash's readline then receives these bytes as if they had been typed directly ++ ++**Step 6 is critical and easy to overlook.** Keystrokes that go to the nat pipe during a native process's lifetime are NOT consumed by the native app (unless it explicitly reads them). They accumulate in conhost's input buffer and are transferred back to bash at cleanup. The transfer happens via `ReadConsoleInputA()` (raw event reads, not cooked/line-edited), so backspaces, escape sequences, and control characters are preserved as-is. ++ ++**Consequence for debugging:** If keystrokes appear reordered at bash's readline after a native process exits, the problem is that some bytes went to the cyg pipe (directly to readline) while others went to the nat pipe (and were transferred back later). The bytes that went directly arrive first; the transferred bytes arrive second. This split delivery causes reordering. The fix must ensure that ALL keystrokes go through the same pipe during the native process's lifetime. ++ + ### The Pseudo Console (pcon) + + When `MSYS=disable_pcon` is NOT set (the default), the runtime uses Windows' `CreatePseudoConsole()` API to give native console applications a real console to talk to. The pseudo console is created on demand when a non-Cygwin process becomes the foreground process, and torn down when it exits. This is what allows programs like `cmd.exe`, `powershell.exe`, or any MinGW-built program to work correctly inside a mintty terminal, which has no native Win32 console of its own. +@@ -217,58 +240,57 @@ The pcon lifecycle is managed across process boundaries: the slave process (runn + Key state fields in the `tty` structure (shared memory, in `tty.h`): + + - **`pcon_activated`** (`bool`): True when a pseudo console is currently active. +-- **`pcon_start`** (`bool`): True during pseudo console initialization. ++- **`pcon_start`** (`bool`): True during pseudo console initialization (CSI6n exchange). + - **`pcon_start_pid`** (`pid_t`): PID of the process that initiated pcon setup. + + ### The Input State Machine + + The field **`pty_input_state`** (type `xfer_dir`, in `tty.h:137`) tracks which pipe pair currently "owns" the input. It has two values: + +-- **`to_cyg`**: Input is flowing to the Cygwin pipe. The master's `write()` uses the `line_edit()` → `accept_input()` path, which writes to `to_slave` (cyg pipe). ++- **`to_cyg`**: Input is flowing to the Cygwin pipe. The master's `write()` uses the `line_edit()` -> `accept_input()` path, which writes to `to_slave` (cyg pipe). + - **`to_nat`**: Input is flowing to the native pipe. The master's `write()` writes directly to `to_slave_nat` (nat pipe), or through the pseudo console. + +-The state transitions happen via **`transfer_input()`** (pty.cc, around line 3905), which: ++The state transitions happen via **`transfer_input()`**, which: + 1. Reads all pending data from the "source" pipe (the one being abandoned). + 2. Writes that data into the "destination" pipe (the one being switched to). + 3. Sets `pty_input_state` to the new direction. + +-This ensures data already buffered in one pipe is not lost when switching. **However, `transfer_input()` is only correct at process-group boundaries** — specifically in `setpgid_aux()` (when the foreground changes) and `cleanup_for_non_cygwin_app()` (when a native session ends). Calling `transfer_input()` on every keystroke in `master::write()` was historically a source of bugs: during pseudo console oscillation (see below), per-keystroke transfers would steal readline's buffered data from the cyg pipe and push it to the nat pipe, causing character reordering. The correct approach is to let `setpgid_aux()` handle the transfer at the moment of the actual process-group change, not to anticipate it in the master. ++This ensures data already buffered in one pipe is not lost when switching. ++ ++**When transferred input goes to the cyg pipe (to_cyg direction),** it must pass through `line_edit()` to apply POSIX line discipline. This is handled by the `input_transferred_to_cyg` event: the slave signals this event after the transfer, and the master's forward thread wakes up, reads the transferred bytes from the cyg pipe, and processes them through `line_edit()`. This ensures consistent line discipline regardless of whether input arrived via direct typing or via transfer. + + ### Related State Fields + +-- **`switch_to_nat_pipe`** (`bool`): Set to true when a non-Cygwin process is detected in the foreground. This is a prerequisite for `to_be_read_from_nat_pipe()` returning true. ++- **`switch_to_nat_pipe`** (`bool`): Set to true when a non-Cygwin process is detected in the foreground. This is a prerequisite for `to_be_read_from_nat_pipe()` returning true. It stays true for the entire duration of the native session, including during brief transitions when `pcon_activated` may flicker. + - **`nat_pipe_owner_pid`** (`DWORD`): PID of the process that "owns" the nat pipe setup. Used to detect when the owner has exited (for cleanup). + + ### The `to_be_read_from_nat_pipe()` Function + +-This function (pty.cc, around line 1288) determines whether the current foreground process is a native (non-Cygwin) app. It checks: ++This function determines whether keystroke input should go to the nat pipe. Its design intent is simple: return true whenever a native process session is active (`switch_to_nat_pipe` is set) and no Cygwin process is actively reading from the slave (the `TTY_SLAVE_READING` event does not exist). + +-1. `switch_to_nat_pipe` must be true. +-2. A named event `TTY_SLAVE_READING` must NOT exist (its existence means a Cygwin process is actively reading from the slave, indicating a Cygwin foreground). +-3. `nat_fg(pgid)` returns true (the foreground process group contains a native process). ++The function is synchronized with `pipe_sw_mutex` to avoid reading inconsistent state during pipe switching. If the mutex cannot be acquired and `pcon_start` is set (meaning pseudo console initialization is in progress), the function returns false so that the CSI6n response bytes go through `line_edit()` to the cyg pipe where the initialization code expects them. + +-**This function reads shared state without holding any mutex.** Its return value can therefore change between consecutive calls within the same function, which is an important consideration for callers that make multiple decisions based on the foreground state. ++**Important design principle:** This function should NOT check `nat_fg()` (whether the native process is still in the foreground process group). Such a check creates a gap between native process exit and cleanup where keystrokes fall through to `line_edit()` (cyg pipe) instead of going to the nat pipe. This gap causes keystroke reordering: bytes that go directly to the cyg pipe during the gap arrive at readline before bytes that are transferred from the nat pipe at cleanup. The correct approach is to keep routing to the nat pipe as long as `switch_to_nat_pipe` is set, regardless of the native process's foreground status. The `switch_to_nat_pipe` flag is only cleared during cleanup, after `transfer_input(to_cyg)` has moved all pending data back to the cyg pipe. + + ### Mutexes and Synchronization + +-Two cross-process named mutexes protect different aspects of the PTY state. Understanding which mutex protects what — and the fact that they are independent — is essential for diagnosing race conditions. ++Three cross-process named mutexes protect different aspects of the PTY state: + + - **`input_mutex`**: Protects the input data path. Held by `master::write()` while routing input to a pipe, by `transfer_input()` while moving data between pipes, and by `line_edit()` / `accept_input()`. +-- **`pipe_sw_mutex`**: Protects pipe switching state — creation/destruction of the pseudo console, changes to `switch_to_nat_pipe`, `nat_pipe_owner_pid`. This is a DIFFERENT mutex from `input_mutex`. +- +-Because these are separate mutexes, it is possible for one process to modify the pipe switching state (under `pipe_sw_mutex`) while another process is in the middle of writing input (under `input_mutex`). Any code that modifies `pty_input_state` or `pcon_activated` must carefully consider whether it also needs `input_mutex` to avoid creating a window where the master's write path makes inconsistent decisions. ++- **`pipe_sw_mutex`**: Protects pipe switching state — creation/destruction of the pseudo console, changes to `switch_to_nat_pipe`, `nat_pipe_owner_pid`. Also acquired by `to_be_read_from_nat_pipe()` to read consistent state. The consistent lock ordering is: `pipe_sw_mutex` first, then `input_mutex`. ++- **`attach_mutex`**: Protects console attachment/detachment operations. Used during `transfer_input()` to prevent races when reading console input records via `ReadConsoleInputA()`, and in `get_winpid_to_hand_over()` to prevent the master process from being misidentified during temporary console attachment. + +-Additionally, because these are **cross-process** named mutexes, they are shared via the kernel between the master (terminal emulator) and slave (bash and its children) processes. Operations that look local in the source code actually have system-wide synchronization effects. ++Because these are **cross-process** named mutexes, they are shared via the kernel between the master (terminal emulator) and slave (bash and its children) processes. Operations that look local in the source code actually have system-wide synchronization effects. + +-### The `master::write()` Input Routing (pty.cc, around line 2240) ++### The `master::write()` Input Routing + +-When the terminal emulator (mintty) sends a keystroke, it calls `master::write()`. After acquiring `input_mutex`, the function decides which code path to take: ++When the terminal emulator (mintty) sends a keystroke, it calls `master::write()`. The function has three code paths: + +-1. **Code path 1 — pcon+nat fast path** (line ~2237): If `to_be_read_from_nat_pipe()` AND `pcon_activated` AND `pty_input_state == to_nat` → flush any stale readahead via `accept_input()`, then write directly to `to_slave_nat` via `WriteFile()`. This is the fast path for native apps with pcon active. The readahead flush is necessary because a prior `master::write()` call may have gone through `line_edit()` during a brief oscillation gap, leaving data in the readahead buffer that would otherwise be emitted out of order. ++1. **pcon_start handler**: Active during pseudo console initialization (CSI6n exchange). Accumulates ESC sequence bytes and routes the CSI6n response to the slave. Non-response bytes go through `line_edit()`. This path is only active during the brief initialization window. + +-2. **Code path 2 — line_edit** (line ~2275): The default/fallthrough path. Calls `line_edit()` which processes the input through terminal line discipline and then calls `accept_input()`, which writes to either the cyg or nat pipe based on the current `pty_input_state`. The `accept_input()` routing includes a `!pcon_activated` guard: it only routes to the nat pipe when pcon is NOT active, matching the documented invariant that direct nat pipe writes are for when "pseudo console is not enabled." ++2. **Fast path** (pcon+nat): Active when `to_be_read_from_nat_pipe()` AND `pcon_activated` AND `pty_input_state == to_nat`. Writes directly to `to_slave_nat` via `WriteFile()`, with signal processing and charset conversion. This is the steady-state path for native apps. + +-The conditions checked at each step involve multiple shared-memory fields (`to_be_read_from_nat_pipe()`, `pcon_activated`, `pty_input_state`). If any of these fields changes between consecutive calls to `master::write()` — or worse, between the check and the write within a single call — input can end up in the wrong pipe. ++3. **Fallthrough** (`line_edit`): All other cases. Input goes through POSIX line discipline and `accept_input()` routes to the appropriate pipe based on `pty_input_state`. + + ### Pseudo Console Oscillation + +@@ -278,28 +300,28 @@ When a native process spawns short-lived Cygwin children (e.g. `git.exe` calling + 2. Cygwin child starts: `setpgid_aux()` fires, transfers data to cyg pipe, `pcon_activated=false`, `pty_input_state=to_cyg` + 3. Cygwin child exits (milliseconds later): native process regains foreground, pcon reactivates + +-A single command can cause dozens of such cycles per second. This "oscillation" is the root cause of the character reordering bug fixed on the `fix-jumbled-character-order` branch (see git-for-windows/git#5632). During each gap (step 2), `master::write()` must correctly route keystrokes without stealing data from readline's buffer in the cyg pipe. ++**The key design principle for handling oscillation:** keystrokes must always go to the nat pipe while `switch_to_nat_pipe` is true, regardless of `pcon_activated` or foreground status flickering. When keystrokes reach the nat pipe while the pcon is temporarily deactivated, they go through the raw pipe (not via conhost). When `transfer_input` runs at cleanup, it moves them back. This is safe because the keystrokes stay in the nat pipe in chronological order. + +-The key insight: during the oscillation gap, `switch_to_nat_pipe` remains true (the native process is still alive) even though `pcon_activated` is false. This means `to_be_read_from_nat_pipe()` returns true, which historically caused several code paths to prematurely transfer data from the cyg pipe to the nat pipe. Those transfer code paths have been removed — `setpgid_aux()` in the slave is now the sole authority for pipe transfers at process-group boundaries. ++The bugs that cause keystroke reordering are always of the form: some bytes go to the cyg pipe (via `line_edit` fallthrough) while others go to the nat pipe (via the fast path), and the two sets arrive at bash's readline in the wrong order. The fix is to prevent the split: either ALL bytes go to one pipe, or the routing decision is properly synchronized so that no bytes leak to the wrong pipe. + + ### Key Functions for State Transitions + +-- **`setup_for_non_cygwin_app()`** (~line 4150): Called when a non-Cygwin process becomes foreground. Sets up the pseudo console and switches input to nat pipe. +-- **`cleanup_for_non_cygwin_app()`** (~line 4184): Called when the non-Cygwin process exits. Tears down pcon, transfers input back to cyg pipe. +-- **`reset_switch_to_nat_pipe()`** (~line 1091): Cleanup function called from various slave-side operations (e.g., `bg_check()`, `setpgid_aux()`). Detects when the nat pipe owner has exited and resets state. This function is particularly subtle because it runs in the slave process and modifies shared state that the master relies on. Note: the guard logic checks `process_alive()` first, then handles two sub-cases — when another process owns the nat pipe (return early), and when bash itself is the owner (return early if `pcon_activated` or `switch_to_nat_pipe` is still set, indicating the native session is ongoing). Without this two-level guard, `bg_check()` can tear down active pcon sessions, amplifying oscillation. +-- **`mask_switch_to_nat_pipe()`** (~line 1249): Temporarily masks/unmasks the nat pipe switching. Used when a Cygwin process starts/stops reading from the slave. +-- **`setpgid_aux()`** (~line 4214): Called when the foreground process group changes. May trigger pipe switching. ++- **`setup_for_non_cygwin_app()`**: Called when a non-Cygwin process is about to be spawned. Sets up the pseudo console and switches input to nat pipe. Holds `pipe_sw_mutex` during the entire setup to prevent the master from seeing inconsistent state. ++- **`cleanup_for_non_cygwin_app()`**: Called when the non-Cygwin process exits. First calls `transfer_input(to_cyg)` to move all pending input from the nat pipe (conhost's console buffer) back to the cyg pipe. Then tears down the pcon via `close_pseudoconsole()`. The transfer must happen BEFORE the pcon is closed (while the console is still accessible). ++- **`reset_switch_to_nat_pipe()`**: Cleanup function called from `bg_check()` and `setpgid_aux()`. Detects when the nat pipe owner has exited and resets state. Only performs cleanup when no other process owns the nat pipe and the owner is dead. Does NOT clean up when the owner is self (bash) or alive, to avoid tearing down active sessions. ++- **`transfer_input()`**: Moves pending data between the cyg and nat pipes. When transferring to cyg with pcon active, reads `INPUT_RECORD` events from the console via `ReadConsoleInputA()`. When transferring to cyg, signals `input_transferred_to_cyg` so the master's forward thread can apply `line_edit()` to the transferred bytes. ++- **`setpgid_aux()`**: Called when the foreground process group changes. Triggers `transfer_input` in the appropriate direction. Releases `pipe_sw_mutex` before acquiring `input_mutex` to maintain consistent lock ordering. + + ### Debugging Tips + + When investigating PTY-related bugs, keep these patterns in mind: + +-- **Data in two pipes**: If characters are lost, duplicated, or reordered, check whether data ended up split across the cyg and nat pipes due to a state transition during input. +-- **Cross-process state changes**: The master and slave processes share state through the `tty` structure in shared memory. A state change in the slave (e.g., `reset_switch_to_nat_pipe()`) is immediately visible to the master, without any notification. Look for races where the master reads state, acts on it, but the state changed between the read and the action. +-- **Mutex coverage gaps**: Check whether every modification of `pty_input_state`, `pcon_activated`, and `switch_to_nat_pipe` is protected by the appropriate mutex. The existence of two separate mutexes (`input_mutex` and `pipe_sw_mutex`) means that holding one does not protect against changes guarded by the other. +-- **`transfer_input()` is correct only at process-group boundaries**: The proper places for `transfer_input()` are `setpgid_aux()` (foreground change) and `cleanup_for_non_cygwin_app()` (session end). Per-keystroke transfers in `master::write()` were historically a source of character reordering — they would steal readline's buffered data from the cyg pipe during pseudo console oscillation gaps. If you see a `transfer_input()` call in `master::write()`, question whether it is genuinely needed or whether `setpgid_aux()` already handles the case. +-- **Pseudo console oscillation**: When characters are lost or reordered and the scenario involves a native process spawning Cygwin children, suspect pcon oscillation. The oscillation happens because each Cygwin child start/exit triggers a pcon teardown/setup cycle, and shared-memory flags (`pcon_activated`, `switch_to_nat_pipe`, `pty_input_state`) change rapidly without synchronization with the master's `input_mutex`. Tracing the state transitions across processes is essential for diagnosis. +-- **Tracing**: For timing-sensitive bugs, in-process tracing with lock-free per-thread buffers (using Windows TLS and `QueryPerformanceCounter`) is effective. Avoid file I/O during reproduction — accumulate in memory and dump at process exit. See the `ui-tests/` directory for AutoHotKey-based reproducers that can drive mintty programmatically. ++- **Trace the full keystroke lifecycle**: Do not stop at "the keystroke goes to pipe X." Follow it all the way to where bash's readline receives it, including any `transfer_input` calls at cleanup. The most common bugs involve bytes being split across the two pipes and arriving at readline out of order. ++- **Check the routing decision in `to_be_read_from_nat_pipe()`**: This function is the gatekeeper for all routing decisions. If it returns the wrong value, keystrokes go to the wrong pipe. Verify that it holds `pipe_sw_mutex` while reading state, and that it does not have unnecessary checks (like `nat_fg()`) that create gaps during transitions. ++- **Study existing upstream patches before writing fixes**: Takashi Yano is the upstream PTY maintainer and understands the state machine deeply. When he proposes patches on cygwin-patches@, apply and test his full series before attempting alternative fixes. His patches form cohesive sets where individual patches depend on each other for correct behavior. Cherry-picking individual patches from his series will break invariants. ++- **Never remove `transfer_input()` calls without understanding what they transfer**: The transfers at `setpgid_aux()`, `cleanup_for_non_cygwin_app()`, and the pcon_start completion block each serve specific purposes. Removing them loses data. The correct fix for reordering bugs is to ensure keystrokes consistently go to one pipe (typically by fixing the routing decision), not to remove the transfer that reunites split data. ++- **The `pcon_start` handler is only for CSI6n**: During pcon initialization, `pcon_start=true` tells `master::write()` to enter a special handler that accumulates the CSI6n response. Non-CSI bytes in this handler go through `line_edit()` to the cyg pipe. This is correct and intentional: during the brief CSI6n exchange, the pcon is not yet ready to receive user input, so `line_edit()` buffers it for bash. The pcon_start handler is NOT a general-purpose input router and should not be modified to route bytes to the nat pipe. ++- **Tracing**: For timing-sensitive bugs, use a memory-mapped ring buffer (not per-event file I/O, which changes timings). The master process (mintty) is a MinGW program; C++ static destructors in msys-2.0.dll do NOT fire when it exits. Use `CreateFileMapping` + `MapViewOfFile` for trace buffers that persist after process termination. Use `QueryPerformanceCounter` for microsecond timestamps. Trace across both master and slave processes using separate per-PID files. + + ## Packaging + diff --git a/msys2-runtime/PKGBUILD b/msys2-runtime/PKGBUILD index a7447a161f7..2ec936687dc 100644 --- a/msys2-runtime/PKGBUILD +++ b/msys2-runtime/PKGBUILD @@ -4,7 +4,7 @@ pkgbase=msys2-runtime pkgname=('msys2-runtime' 'msys2-runtime-devel') pkgver=3.6.7 -pkgrel=3 +pkgrel=4 pkgdesc="Cygwin POSIX emulation engine" arch=('x86_64') url="https://www.cygwin.com/" @@ -74,9 +74,23 @@ source=('msys2-runtime'::git+https://github.com/cygwin/cygwin#tag=cygwin-${pkgve 0044-Use-MB_CUR_MAX-6-by-default.patch 0045-msys2-runtime-restore-fast-path-for-current-user-pri.patch 0046-Change-the-default-base-address-for-x86_64.patch - 0047-fixup-Use-MB_CUR_MAX-6-by-default.patch) + 0047-fixup-Use-MB_CUR_MAX-6-by-default.patch + 0048-Add-AGENTS.md-with-comprehensive-project-context-for.patch + 0049-Cygwin-pty-Fix-nat-pipe-hand-over-when-pcon-is-disab.patch + 0050-Cygwin-console-Release-pipe_sw_mutex-in-pcon_hand_ov.patch + 0051-Cygwin-pty-Fix-input-transfer-when-multiple-non-cygw.patch + 0052-Cygwin-console-Fix-master-thread.patch + 0053-Cygwin-pty-Add-workaround-for-handling-of-backspace-.patch + 0054-Cygwin-console-Use-input_mutex-in-the-parent-PTY-in-.patch + 0055-Cygwin-pty-Apply-line_edit-for-transferred-input-to-.patch + 0056-Cygwin-pty-Guard-get_winpid_to_hand_over-with-attach.patch + 0057-Cygwin-pty-Guard-to_be_read_from_nat_pipe-by-pipe_sw.patch + 0058-Cygwin-pty-Drop-nat_fg-check-from-to_be_read_from_na.patch + 0059-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch + 0060-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch + 0061-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch) sha256sums=('4ecc8ac443ed3f0568e9b2f95aafa9f412669466bfb0dd5cfbb194b572538916' - 'c31268394bbe6094e590b5c019aaf2e25f5d5fa4c2cb0bc37a01218af9c4bd14' + '6af07981e82b3f1910a1573643e62cac3b7402705a19fbf996af8c75ca9af4a7' 'fd5fca07f5febedd4be73af01613847568aec1e6dd1f0aa39658f9c946ae2812' 'c5d502a01a7206ef26572674cc081e71c11e4796b1ffae7ee06c8afd546abf53' '432af98a8df514b3dbf2c6d6a82f19b561b40b109466f8eecc2c74ad5cc03b86' @@ -123,7 +137,21 @@ sha256sums=('4ecc8ac443ed3f0568e9b2f95aafa9f412669466bfb0dd5cfbb194b572538916' '4898546fae614b07aae22beba2246a9bcb909f9486d3423321172920d165db2d' '4d6e3eb36d418eb967d63087a48420d48c911c156631989465351fc0f345f289' 'c89414545dacfedc30bb48c54ff1f6a89b32a8cfceb6342ef1050bfb12fff20e' - 'bcf9839f2fda676542c85c08d6f612b4c854b2a273f96473d8d49b371014edb8') + 'bcf9839f2fda676542c85c08d6f612b4c854b2a273f96473d8d49b371014edb8' + '266e9602353e81aa95d829de0a1a4fc0f3ad3ec5d1a110f2f32c94d345b47a34' + 'f58beb7ece28eb3c054e451fc82f38ae8105caf779539927b964f398a3728daa' + '90f222e70003f62f5c49e24ffa881dda74e00e4becac2e71d20fbb56ef461c4f' + '2daa6a88915f124f66feb8bd2dfe80ed5f10d00fa16e76d015b0d89a5bd4313b' + '2fd54ad7ce72786dceea533fc208cb14dbe36b00c1709edfc34993d3a15038c9' + 'd5de4f78e00702c2d65ccc80f0fbd10d5da597ccb6e5ac78fd0a69b26ddb9857' + 'df4a8c5b4ab59ec902902a46b069337f4d67d3b6f7540a98ff89c881f4d6fee3' + '8d51d9c94aa199e000da1faaa8658e3f7d525dad92d6704a43b392140dd9ce70' + '51a8493b83462e2fa1e0c605a6e0cd5c435b38eeec60cacdcc32a87de1a3ea33' + '328a0b94e0f567bbb1fd3ba7c030652dde8ea9a59fd4e37a6023d29623815d66' + '162c510934b42c1a5935d2a20ffcf65ab009f4d59330c8d02cb6a7376e59e788' + 'cccaedb75f95603a8b77885ae3535827eac14657e32b5dc2521b14db28ea23c2' + '63d6a614115d40fa38a89683e2067f82e2e0c0fdafab8af4d0e83517d889e321' + '7e6a65f17a61b434b01cf726fb709c0e8e9b6a3873cdc5ddfe8225bcac8f65c3') # Helper macros to help make tasks easier # apply_patch_with_msg() { @@ -225,7 +253,21 @@ prepare() { 0044-Use-MB_CUR_MAX-6-by-default.patch \ 0045-msys2-runtime-restore-fast-path-for-current-user-pri.patch \ 0046-Change-the-default-base-address-for-x86_64.patch \ - 0047-fixup-Use-MB_CUR_MAX-6-by-default.patch + 0047-fixup-Use-MB_CUR_MAX-6-by-default.patch \ + 0048-Add-AGENTS.md-with-comprehensive-project-context-for.patch \ + 0049-Cygwin-pty-Fix-nat-pipe-hand-over-when-pcon-is-disab.patch \ + 0050-Cygwin-console-Release-pipe_sw_mutex-in-pcon_hand_ov.patch \ + 0051-Cygwin-pty-Fix-input-transfer-when-multiple-non-cygw.patch \ + 0052-Cygwin-console-Fix-master-thread.patch \ + 0053-Cygwin-pty-Add-workaround-for-handling-of-backspace-.patch \ + 0054-Cygwin-console-Use-input_mutex-in-the-parent-PTY-in-.patch \ + 0055-Cygwin-pty-Apply-line_edit-for-transferred-input-to-.patch \ + 0056-Cygwin-pty-Guard-get_winpid_to_hand_over-with-attach.patch \ + 0057-Cygwin-pty-Guard-to_be_read_from_nat_pipe-by-pipe_sw.patch \ + 0058-Cygwin-pty-Drop-nat_fg-check-from-to_be_read_from_na.patch \ + 0059-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch \ + 0060-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch \ + 0061-fixup-Add-AGENTS.md-with-comprehensive-project-conte.patch } build() { diff --git a/msys2-runtime/msys2-runtime.commit b/msys2-runtime/msys2-runtime.commit index b8ed8e78896..c24967e655b 100644 --- a/msys2-runtime/msys2-runtime.commit +++ b/msys2-runtime/msys2-runtime.commit @@ -1 +1 @@ -4c22b036e83e1a1fe71637a9455da3d9c8838d77 +fb42d71358dd896ab324c52970f7d03f9ab0dfe5