Skip to content

Fix 64-bit file offsets on Windows - #348

Open
YayoRazo wants to merge 1 commit into
apple:mainfrom
YayoRazo:fix/windows-64bit-file-offsets
Open

Fix 64-bit file offsets on Windows#348
YayoRazo wants to merge 1 commit into
apple:mainfrom
YayoRazo:fix/windows-64bit-file-offsets

Conversation

@YayoRazo

Copy link
Copy Markdown

Summary

On Windows, C off_t is long, i.e. 32-bit, so it cannot represent file offsets or sizes at or beyond 2 GiB. The internal offset typealias _COffT was unconditionally off_t, so FileDescriptor.seek, read/write at an absolute offset, and resize funneled a public Int64 through a 32-bit conversion.

Because Swift's numeric conversions are range-checked:

  • Any offset >= 2^31 trapped at runtime (process crash), so seeking, positioned reads/writes, or resizing anywhere past 2 GiB was impossible.
  • Even below the trap boundary, positioned I/O could never target a region past 4 GiB, because the OVERLAPPED high dword was computed from an already-32-bit value and was therefore always zero.

The Windows adapters already contained a 64-bit lseek overload backed by _lseeki64 and an OVERLAPPED/LARGE_INTEGER split, which is strong evidence the 64-bit path was intended and only the plumbing type was wrong.

Fix

  • Make _COffT 64-bit on Windows (Int64); it remains off_t on all other platforms, so there is no behavioral change off Windows.
  • Thread _COffT through system_lseek, system_pread, system_pwrite, and system_ftruncate.
  • Remove the now-redundant 32-bit _lseek overload so overload resolution selects the existing _lseeki64-backed one.
  • Use truncating conversions for the OVERLAPPED offset split, since DWORD(UInt32(offset)) would itself trap once offsets exceed 4 GiB.

Testing

  • Added testLargeFileOffsets (Windows), which seeks past 2 GiB and 4 GiB and performs a positioned write/read round-trip beyond 4 GiB using a sparse file so it allocates no real storage.
  • Verified the test fails without this change (it traps at the first seek past Int32.max) and passes with it.
  • Full swift test suite green on Windows (x86_64-unknown-windows-msvc, Swift 6.3.3); no changes to other platforms.

On Windows, C `off_t` is `long`, i.e. 32-bit, so it cannot represent file
offsets or sizes at or beyond 2 GiB. The offset typealias `_COffT` was
unconditionally `off_t`, so `FileDescriptor.seek`, `read`/`write` at an
absolute offset, and `resize` funneled a public `Int64` through a 32-bit
conversion. Because Swift's numeric conversions are range-checked, any
offset >= 2^31 trapped at runtime, and positioned I/O could never target a
region past 4 GiB (the `OVERLAPPED` high dword was always zero).

Make `_COffT` 64-bit on Windows and thread it through `system_lseek`,
`system_pread`, `system_pwrite`, and `system_ftruncate` (unchanged on other
platforms, where `_COffT` remains `off_t`). The Windows adapters already had
a 64-bit `lseek` overload backed by `_lseeki64`; the now-redundant 32-bit
`_lseek` overload is removed so overload resolution selects it. The
`OVERLAPPED` offset split now uses truncating conversions, since
`DWORD(UInt32(offset))` would itself trap once offsets exceed 4 GiB.
@YayoRazo
YayoRazo force-pushed the fix/windows-64bit-file-offsets branch from 47c4fd6 to cca1aac Compare July 15, 2026 18:24
@glessard
glessard requested a review from compnerd July 17, 2026 23:32
// seeking, positioned I/O, and resizing work on large files, matching the
// 64-bit `off_t` on Linux and Darwin. The Windows syscall adapters route
// this through the 64-bit `_lseeki64` / `OVERLAPPED` / `LARGE_INTEGER` APIs.
internal typealias _COffT = Int64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think I would slightly prefer _off64_t as the spelling. This is still Int64 under the hood, but more explicit and uses the libc spelling.

@inline(__always)
internal func pread(
_ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t
_ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: Int64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These can then use the same spelling

@glessard glessard left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this contribution. Could you edit the very wordy comments I reviewed as well as those in the new test to be shorter and more self-contained? There is a reference in the test comment to "the fix", which won't be a useful comment for future readers. "[something about specific values]. Both trapped before the fix." -> "Two values not representable by a 32-bit off_t."

Please target the release/1.7.0 release/1.8.x branch, as this is clearly a bug fix.

// offsets or sizes at or beyond 2 GiB. Use a 64-bit offset type so that
// seeking, positioned I/O, and resizing work on large files, matching the
// 64-bit `off_t` on Linux and Darwin. The Windows syscall adapters route
// this through the 64-bit `_lseeki64` / `OVERLAPPED` / `LARGE_INTEGER` APIs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you make this comment less wordy? It's probably enough to say that we use a 64-bit value because off_t is a 32-bit type on Windows.

Comment on lines +155 to +156
// Split the 64-bit offset into high/low DWORDs. Use truncating conversions:
// `DWORD(UInt32(offset))` would trap for any offset >= 4 GiB.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This comment isn't necessary. The truncating API is wordy for a reason!

Comment on lines +185 to +186
// Split the 64-bit offset into high/low DWORDs. Use truncating conversions:
// `DWORD(UInt32(offset))` would trap for any offset >= 4 GiB.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above

Comment on lines +363 to +364
DeviceIoControl(handle, FSCTL_SET_SPARSE, nil, 0, nil, 0,
&bytesReturned, nil),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Our coding style is to have all the parameters on one line, or to have one line per parameter.

Suggested change
DeviceIoControl(handle, FSCTL_SET_SPARSE, nil, 0, nil, 0,
&bytesReturned, nil),
DeviceIoControl(
handle, FSCTL_SET_SPARSE, nil, 0, nil, 0, &bytesReturned, nil
),

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.

3 participants