Description
Okio 3.18.1 consistently throws from FileHandle.resize(0L) on the Kotlin/Native mingwX64 target when resizing a non-empty file:
okio.IOException: The operation completed successfully.
This is not a transient file-system or concurrency failure. WindowsFileHandle.protectedResize() treats a SetFilePointer() return value of 0 as failure, but Windows documents that a successful move to file position 0 returns 0 because the return value is the low-order DWORD of the new position.
Minimal reproduction
https://github.com/czp3009/okio-mingw-file-handle-resize-reproduction
Core code:
fun main() {
val fileSystem = FileSystem.SYSTEM
val path = "test".toPath()
fileSystem.sink(path).buffer().use { sink ->
sink.writeUtf8("seed")
}
fileSystem.openReadWrite(path, mustExist = true).use { handle ->
handle.resize(0L)
}
check(fileSystem.metadata(path).size == 0L)
}
The Windows native process terminates with an uncaught exception at handle.resize(0L):
Uncaught Kotlin exception: okio.IOException: The operation completed successfully.
The final size assertion is not reached. The exact formatted message can depend on the thread's last-error value and the Windows language, but the erroneous exception is deterministic.
Root cause
The affected code is WindowsFileHandle.protectedResize():
val movePointerResult = SetFilePointer(
hFile = file,
lDistanceToMove = size.toInt(),
lpDistanceToMoveHigh = distanceToMoveHigh.ptr,
dwMoveMethod = FILE_BEGIN.toUInt(),
)
if (movePointerResult == 0U) {
throw lastErrorToIOException()
}
According to Microsoft's SetFilePointer documentation:
- On success, the return value is the low-order
DWORD of the new file-pointer position.
- On failure, the return value is
INVALID_SET_FILE_POINTER, not zero.
- Because
INVALID_SET_FILE_POINTER can itself be a valid low-order position, failure with a non-null high-order pointer must be checked as result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR.
For resize(0L), the call succeeds and returns the new low-order position, which is 0. The movePointerResult == 0U condition therefore enters the error branch after a successful API call. Okio throws before reaching SetEndOfFile.
The observed message is also explained by the Windows API contract:
GetLastError should only be read when the preceding function's return value indicates that error information is valid. A successful function does not necessarily clear the thread's last-error value.
ERROR_SUCCESS is value 0, with the system message The operation completed successfully.
- Okio's
lastErrorToIOException() formats that value with FormatMessageW and wraps the result in IOException.
The same incorrect condition also affects target positions whose low-order 32 bits are zero. resize(0L) is the smallest stable reproducer.
Suggested minimal fix
The failure condition should follow the documented contract instead of comparing the result with zero:
if (movePointerResult == 0xffffffffU && GetLastError() != 0U) {
throw lastErrorToIOException()
}
Description
Okio 3.18.1 consistently throws from
FileHandle.resize(0L)on the Kotlin/NativemingwX64target when resizing a non-empty file:This is not a transient file-system or concurrency failure.
WindowsFileHandle.protectedResize()treats aSetFilePointer()return value of0as failure, but Windows documents that a successful move to file position0returns0because the return value is the low-orderDWORDof the new position.Minimal reproduction
https://github.com/czp3009/okio-mingw-file-handle-resize-reproduction
Core code:
The Windows native process terminates with an uncaught exception at
handle.resize(0L):The final size assertion is not reached. The exact formatted message can depend on the thread's last-error value and the Windows language, but the erroneous exception is deterministic.
Root cause
The affected code is
WindowsFileHandle.protectedResize():According to Microsoft's
SetFilePointerdocumentation:DWORDof the new file-pointer position.INVALID_SET_FILE_POINTER, not zero.INVALID_SET_FILE_POINTERcan itself be a valid low-order position, failure with a non-null high-order pointer must be checked asresult == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR.For
resize(0L), the call succeeds and returns the new low-order position, which is0. ThemovePointerResult == 0Ucondition therefore enters the error branch after a successful API call. Okio throws before reachingSetEndOfFile.The observed message is also explained by the Windows API contract:
GetLastErrorshould only be read when the preceding function's return value indicates that error information is valid. A successful function does not necessarily clear the thread's last-error value.ERROR_SUCCESSis value0, with the system messageThe operation completed successfully.lastErrorToIOException()formats that value withFormatMessageWand wraps the result inIOException.The same incorrect condition also affects target positions whose low-order 32 bits are zero.
resize(0L)is the smallest stable reproducer.Suggested minimal fix
The failure condition should follow the documented contract instead of comparing the result with zero: