Skip to content

windows: delegate Read, Write, Fd, Name to the file passed to newMaster#88

Open
vagokuln-msft wants to merge 2 commits into
containerd:mainfrom
vagokuln-msft:fix/console-file-delegation
Open

windows: delegate Read, Write, Fd, Name to the file passed to newMaster#88
vagokuln-msft wants to merge 2 commits into
containerd:mainfrom
vagokuln-msft:fix/console-file-delegation

Conversation

@vagokuln-msft

@vagokuln-msft vagokuln-msft commented Feb 26, 2026

Copy link
Copy Markdown

Previously, the Windows master struct hardcoded os.Stdin for Read/Fd and os.Stdout for Write, ignoring the file passed to ConsoleFromFile. This meant callers that created a console from os.Stderr (or any other valid file) would silently operate on the wrong stream.

This PR stores the file in the master struct and delegates Read, Write, Fd, and Name to it, matching the Unix implementation.

Changes:

  • Added f File field to the master struct
  • Read() delegates to m.f.Read() instead of os.Stdin.Read()
  • Write() delegates to m.f.Write() instead of os.Stdout.Write()
  • Fd() returns m.f.Fd() instead of the hardcoded stdin handle
  • Name() returns m.f.Name() instead of a hardcoded string
  • newMaster() stores f in the struct

Behavior change — Name():
On Windows, Name() previously returned a fixed "console" string. It now returns the name of the underlying file the console was created from (for example "/dev/stdin" / "/dev/stdout"), consistent with the Unix implementation. Callers that depended on the literal "console" value should be aware of this change.

Note on console-mode operations:
The console-mode operations (SetRaw, Reset, Size, DisableEcho) still act on the process's standard handles rather than f alone. On Windows the standard streams share a single underlying console object, so mode and size queries apply to that console as a whole. This is documented on newMaster.

Tests:

  • TestMaster_DelegatesToFile: in-process, pipe-backed test verifying master delegates Write and Read to its file (the regression test for Windows: stderr output leaks into stdout #83, where output written to a console created from a non-stdout stream leaked to stdout), plus assertions that Fd()/Name() track the provided file and Fd() is not os.Stdout's descriptor. Runs anywhere — no attached console required.
  • TestConsoleFromFile_Delegation: end-to-end subprocess test using CONIN$/CONOUT$ to exercise the public API with real console handles; the subprocess's stderr is captured so failures surface with detail.

Fixes #83

Previously, the Windows master struct hardcoded os.Stdin for Read/Fd
and os.Stdout for Write, ignoring the file passed to ConsoleFromFile.
This meant callers that created a console from os.Stderr (or any other
valid file) would silently operate on the wrong stream.

Store the file in the master struct and delegate Read, Write, Fd, and
Name to it, matching the Unix implementation.

Fixes containerd#83

Signed-off-by: Varun Gokulnath <vagokuln@microsoft.com>
@vagokuln-msft vagokuln-msft force-pushed the fix/console-file-delegation branch from 515d88e to 820dfc5 Compare February 26, 2026 00:05
@apurv15

apurv15 commented Feb 26, 2026

Copy link
Copy Markdown

@estesp @dmcgowan @thaJeztah do you have any thoughts on this PR?

@cpuguy83 cpuguy83 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks correct at least.

@vagokuln-msft

Copy link
Copy Markdown
Author

Ping, @estesp @dmcgowan @thaJeztah

@vagokuln-msft

Copy link
Copy Markdown
Author

@copilot Help tag the maintainers to get a sign-off for this PR. It has been sitting for a while

@cpuguy83 cpuguy83 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review from Claude Opus 4.8

Delegating Read/Write/Fd/Name to the stored file is the right fix and matches the Unix implementation. A few things before merging:

  1. The reported bug isn't tested. #83 is about Write hitting the wrong stream, but both tests only assert Fd() and Name(). Add an in-process test that constructs a master from a pipe/buffer-backed file and asserts Write goes to that file, not os.Stdout. Unlike the E2E test, it would run in CI.

  2. TestNewMaster_FileDelegation is near-tautological. Fd() returns m.f.Fd(), so m.Fd() == tt.file.Fd() is mostly f.Fd() == f.Fd(). It catches a revert to uintptr(m.in) but covers neither the public ConsoleFromFile path nor the I/O methods.

  3. The E2E test won't run in CI and hides failures. OpenFile("CONIN$"/"CONOUT$") fails headless, so it skips. On failure the subprocess writes to os.Stderr, wired to conout, so the parent only reports exit status 1 with no detail. If kept, capture the subprocess output (CombinedOutput/buffer) into t.Fatalf.

  4. Incomplete delegation. Only the I/O methods follow f; initStdios(), SetRaw(), Reset(), Size(), and DisableEcho() still operate on global os.Std{in,out,err}. A console from os.Stderr reports Fd() == stderr and writes to stderr, but Size() queries stdout and SetRaw() flips all three. Either extend the fix to track f or document the limitation.

  5. Name() behavior change. Now returns /dev/stdout etc. instead of "console", and the explanatory comment was dropped. Aligns with Unix, but call it out in the description.

- Add in-process pipe-based test verifying master delegates Write and Read to its file (regression test for containerd#83 where output leaked to stdout).

- Replace the tautological Fd/Name test with meaningful pipe-based assertions that also verify Fd() is not os.Stdout's descriptor.

- Capture the E2E subprocess's stderr so failures are reported with detail.

- Document on newMaster that console-mode operations act on the process's shared console object, not f alone.

- Restore an explanatory comment on Name() noting it now returns the underlying file name.

Signed-off-by: Varun Gokulnath <vagokuln@microsoft.com>
@vagokuln-msft

Copy link
Copy Markdown
Author

Thanks for the review! All five points are addressed in the latest commit:

  1. In-process Write/Read regression test — Added TestMaster_DelegatesToFile, which backs a master with an os.Pipe and asserts that Write/Read actually flow through the provided file. The WriteGoesToFile subtest is the direct regression test for Windows: stderr output leaks into stdout #83 (it fails if Write ever leaks to os.Stdout again) and runs anywhere without an attached console.

  2. Replaced the tautological Fd/Name test — The old test compared m.Fd()/m.Name() against the same file it was built from. The new FdAndName subtest uses a pipe and additionally asserts Fd() != os.Stdout.Fd(), so it would catch a regression back to the hardcoded handle.

  3. Capture subprocess outputTestConsoleFromFile_Delegation now wires the subprocess's stderr to a bytes.Buffer and includes it in the failure message, so E2E failures are diagnosable.

  4. Documented the limitation — Added a doc comment on newMaster noting that the console-mode operations (SetRaw, Reset, Size, DisableEcho) act on the process's standard handles rather than f alone, because Windows standard streams share one underlying console object.

  5. Name() behavior change — Restored an explanatory comment on Name() and called the change out in the PR description: it now returns the underlying file name (consistent with Unix) instead of the previous fixed "console" string.

go vet ./... and go test ./... pass locally on Windows.

@vagokuln-msft

Copy link
Copy Markdown
Author

Friendly ping for a review when you get a chance 🙏 — @AkihiroSuda @fuweid

This is a small Windows-only fix for #83 (output written to a console created from a non-stdout stream leaked to stdout). CI is green, and go vet ./... / go test ./... pass locally on Windows. The earlier review feedback has been addressed in the latest commit, and the PR description calls out the one behavior change (Name() now returns the underlying file name on Windows, consistent with Unix). Happy to make any adjustments.

@cpuguy83 cpuguy83 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: stderr output leaks into stdout

3 participants