Skip to content

fix(remotefs): fix whole-file reads and writes on Windows - #479

Merged
kke merged 1 commit into
mainfrom
fix/winfs-readfile-copyto
Sep 24, 2026
Merged

kke merged 1 commit into
mainfrom
fix/winfs-readfile-copyto

Conversation

@kke

@kke kke commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

WinFS.ReadFile read through io.ReadAll. That doesn't use io.WriterTo; it calls Read with a 512-byte buffer that grows, and every Read is its own "r N" round trip to the rigrcp helper, which over WinRM means a command, a response poll and a payload transfer each. ReadFile now reads with CopyTo, a single "r -1". To support that, winFile.CopyTo returns (0, nil) for an empty file, as PosixFile.CopyTo already did, where it used to return io.EOF.

"r -1" itself was unsafe. The helper sent the remaining length as n and then ran Stream.CopyTo, which copies up to wherever the file ends by then. If the file grew in the meantime, the extra bytes were read as the next command's JSON reply. If it shrank, the Go side waited until the watchdog fired. A read that threw partway had its error JSON written into the data stream, where it was read as file content. The helper now copies exactly n bytes. After n has been sent there is no framing left to carry an error, so a short copy or a failed read ends the helper and the Go side reports the stream ending early.

WinFS.WriteFile never truncated. It opens with O_CREATE|O_TRUNC, and fMode turned any O_CREATE into FileMode.OpenOrCreate and dropped the O_TRUNC. Writing "ab" over "0123456789" left "ab23456789". O_CREATE|O_TRUNC is now FileMode.Create. The same mapping had more flag bugs:

  • O_APPEND mapped to FileMode.Append, and only when O_CREATE was absent. FileMode.Append creates a missing file and refuses read access, and O_CREATE|O_APPEND wrote from offset 0. The file is now opened with the mode the other flags call for, and each Write moves to the end of the file before writing, as O_APPEND does for an os.File. Reading still starts at the beginning. O_APPEND without O_CREATE on a missing file now fails, as os.OpenFile does.
  • fAccess checked the write flags before O_RDWR, so O_RDWR|O_TRUNC was opened write-only.
  • A read-only open with O_CREATE|O_EXCL or O_CREATE|O_TRUNC asked for read access, which .NET rejects for CreateNew and Create. A read-only open that needs write access for its mode, or for O_APPEND, now gets ReadWrite. Before, truncation and appending got write-only access, which left the handle unable to read.

The WinFS.OpenFile doc no longer claims O_EXCL is ignored, because it maps to CreateNew.

Refs #475

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Append handling and read-only exclusive creation do not preserve Go OpenFile semantics.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Improves Windows remote file correctness and reduces WinRM round trips.

Changes:

  • Streams whole-file reads in one rigrcp command.
  • Corrects Windows open-mode/access mappings.
  • Prevents growing or shrinking files from corrupting the rigrcp protocol.
File Description
remotefs/​winfs.go Optimizes ReadFile and documents append behavior.
remotefs/​winfs_test.go Adds Windows read, write, and append tests.
remotefs/​winfile.go Updates copy, access, mode, and append handling.
remotefs/​winfile_internal_test.go Tests flag mappings.
remotefs/​rigrcp.ps1 Copies the announced byte count exactly.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread remotefs/winfile.go
Comment thread remotefs/winfile.go Outdated
WinFS.ReadFile read through io.ReadAll. That doesn't use io.WriterTo; it
calls Read with a 512-byte buffer that grows, and every Read is its own
"r N" round trip to the rigrcp helper, which over WinRM means a command,
a response poll and a payload transfer each. ReadFile now reads with
CopyTo, a single "r -1". To support that, winFile.CopyTo returns (0, nil)
for an empty file, as PosixFile.CopyTo already did, where it used to
return io.EOF.

"r -1" itself was unsafe. The helper sent the remaining length as n and
then ran Stream.CopyTo, which copies up to wherever the file ends by
then. If the file grew in the meantime, the extra bytes were read as the
next command's JSON reply. If it shrank, the Go side waited until the
watchdog fired. A read that threw partway had its error JSON written
into the data stream, where it was read as file content. The helper now
copies exactly n bytes. After n has been sent there is no framing left
to carry an error, so a short copy or a failed read ends the helper and
the Go side reports the stream ending early.

WinFS.WriteFile never truncated. It opens with O_CREATE|O_TRUNC, and
fMode turned any O_CREATE into FileMode.OpenOrCreate and dropped the
O_TRUNC. Writing "ab" over "0123456789" left "ab23456789". O_CREATE|O_TRUNC
is now FileMode.Create. The same mapping had more flag bugs:

- O_APPEND mapped to FileMode.Append, and only when O_CREATE was absent.
  FileMode.Append creates a missing file and refuses read access, and
  O_CREATE|O_APPEND wrote from offset 0. The file is now opened with the
  mode the other flags call for, and each Write moves to the end of the
  file before writing, as O_APPEND does for an os.File. Reading still
  starts at the beginning. O_APPEND without O_CREATE on a missing file
  now fails, as os.OpenFile does.
- fAccess checked the write flags before O_RDWR, so O_RDWR|O_TRUNC was
  opened write-only.
- A read-only open with O_CREATE|O_EXCL or O_CREATE|O_TRUNC asked for
  read access, which .NET rejects for CreateNew and Create. A read-only
  open that needs write access for its mode, or for O_APPEND, now gets
  ReadWrite. Before, truncation and appending got write-only access,
  which left the handle unable to read.

The WinFS.OpenFile doc no longer claims O_EXCL is ignored, because it
maps to CreateNew.

Refs #475

Signed-off-by: Kimmo Lehto <klehto@mirantis.com>
@kke
kke force-pushed the fix/winfs-readfile-copyto branch from 890303f to 571c9b0 Compare September 24, 2026 07:14
@kke
kke requested a balanced review from Copilot September 24, 2026 07:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The implementation matches the stated behavior and includes focused regression coverage.

Review effort: Balanced
Findings: None

Resolved since last review (2)

@kke
kke marked this pull request as ready for review September 24, 2026 07:39
@kke
kke merged commit b08bdf5 into main Sep 24, 2026
18 checks passed
@kke
kke deleted the fix/winfs-readfile-copyto branch September 24, 2026 07:40
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.

2 participants