Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement FileHandle on native Windows #933

Merged
merged 1 commit into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions okio/src/commonTest/kotlin/okio/AbstractFileSystemTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -838,8 +838,10 @@ abstract class AbstractFileSystemTest(

private fun supportsFileHandle(): Boolean {
return when (fileSystem::class.simpleName) {
"JvmSystemFileSystem", "NioSystemFileSystem", "FakeFileSystem" -> true
"PosixFileSystem" -> !windowsLimitations
"FakeFileSystem",
"JvmSystemFileSystem",
"NioSystemFileSystem",
"PosixFileSystem" -> true
else -> false
}
}
Expand Down
91 changes: 36 additions & 55 deletions okio/src/mingwX64Main/kotlin/okio/-WindowsPosixVariant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ package okio
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.ByteVarOf
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.CValuesRef
import kotlinx.cinterop.UIntVar
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
import okio.Path.Companion.toPath
import platform.posix.EACCES
import platform.posix.ENOENT
Expand All @@ -32,23 +29,25 @@ import platform.posix.PATH_MAX
import platform.posix.S_IFDIR
import platform.posix.S_IFMT
import platform.posix.S_IFREG
import platform.posix._chsize_s
import platform.posix._fstat64
import platform.posix._fullpath
import platform.posix._stat64
import platform.posix.errno
import platform.posix.fileno
import platform.posix.fread
import platform.posix.free
import platform.posix.fwrite
import platform.posix.mkdir
import platform.posix.remove
import platform.posix.rmdir
import platform.windows.CreateFileA
import platform.windows.FILE_ATTRIBUTE_NORMAL
import platform.windows.FILE_SHARE_WRITE
import platform.windows.GENERIC_READ
import platform.windows.GENERIC_WRITE
import platform.windows.INVALID_HANDLE_VALUE
import platform.windows.MOVEFILE_REPLACE_EXISTING
import platform.windows.MoveFileExA
import platform.windows.ReadFile
import platform.windows.WriteFile
import platform.windows._OVERLAPPED
import platform.windows.OPEN_ALWAYS
import platform.windows.OPEN_EXISTING

internal actual val PLATFORM_DIRECTORY_SEPARATOR = "\\"

Expand Down Expand Up @@ -129,54 +128,36 @@ internal actual fun variantFwrite(
return fwrite(source, 1, byteCount.toULong(), file).toUInt()
}

internal actual fun variantPread(
file: CPointer<FILE>,
target: CValuesRef<*>,
byteCount: Int,
offset: Long
): Int {
memScoped {
val overlapped = alloc<_OVERLAPPED>()
overlapped.Offset = offset.toUInt()
overlapped.OffsetHigh = (offset ushr 32).toUInt()
val bytesRead = alloc<UIntVar>()
if (ReadFile(file, target.getPointer(this), byteCount.toUInt(), bytesRead.ptr, overlapped.ptr) == 0) {
throw lastErrorToIOException()
}
return bytesRead.value.toInt()
}
}

internal actual fun variantPwrite(
file: CPointer<FILE>,
source: CValuesRef<*>,
byteCount: Int,
offset: Long
): Int {
memScoped {
val overlapped = alloc<_OVERLAPPED>()
overlapped.Offset = offset.toUInt()
overlapped.OffsetHigh = (offset ushr 32).toUInt()
val bytesWritten = alloc<UIntVar>()
if (WriteFile(file, source.getPointer(this), byteCount.toUInt(), bytesWritten.ptr, overlapped.ptr) == 0) {
throw lastErrorToIOException()
}
return bytesWritten.value.toInt()
}
}

internal actual fun variantSize(file: CPointer<FILE>): Long {
memScoped {
val stat = alloc<_stat64>()
if (_fstat64(fileno(file), stat.ptr) != 0) {
throw errnoToIOException(errno)
}
return stat.st_size
@ExperimentalFileSystem
internal actual fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle {
val openFile = CreateFileA(
lpFileName = file.toString(),
dwDesiredAccess = GENERIC_READ,
dwShareMode = FILE_SHARE_WRITE,
lpSecurityAttributes = null,
dwCreationDisposition = OPEN_EXISTING,
dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
hTemplateFile = null
)
if (openFile == INVALID_HANDLE_VALUE) {
throw lastErrorToIOException()
}
return WindowsFileHandle(false, openFile)
}

internal actual fun variantResize(file: CPointer<FILE>, size: Long) {
if (_chsize_s(fileno(file), size) != 0) {
throw errnoToIOException(errno)
@ExperimentalFileSystem
internal actual fun PosixFileSystem.variantOpenReadWrite(file: Path): FileHandle {
val openFile = CreateFileA(
lpFileName = file.toString(),
dwDesiredAccess = GENERIC_READ or GENERIC_WRITE.toUInt(),
dwShareMode = FILE_SHARE_WRITE,
lpSecurityAttributes = null,
dwCreationDisposition = OPEN_ALWAYS,
dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
hTemplateFile = null
)
if (openFile == INVALID_HANDLE_VALUE) {
throw lastErrorToIOException()
}
return WindowsFileHandle(true, openFile)
}
160 changes: 160 additions & 0 deletions okio/src/mingwX64Main/kotlin/okio/WindowsFileHandle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright (C) 2021 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio

import kotlinx.cinterop.CValuesRef
import kotlinx.cinterop.IntVar
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.usePinned
import kotlinx.cinterop.value
import platform.windows.CloseHandle
import platform.windows.ERROR_HANDLE_EOF
import platform.windows.FILE_BEGIN
import platform.windows.FlushFileBuffers
import platform.windows.GetFileSizeEx
import platform.windows.GetLastError
import platform.windows.HANDLE
import platform.windows.LARGE_INTEGER
import platform.windows.ReadFile
import platform.windows.SetEndOfFile
import platform.windows.SetFilePointer
import platform.windows.WriteFile
import platform.windows._OVERLAPPED

@ExperimentalFileSystem
internal class WindowsFileHandle(
readWrite: Boolean,
private val file: HANDLE?
) : FileHandle(readWrite) {
override fun size(): Long {
memScoped {
val result = alloc<LARGE_INTEGER>()
if (GetFileSizeEx(file, result.ptr) == 0) {
throw lastErrorToIOException()
}
return result.toLong()
}
}

override fun protectedRead(
fileOffset: Long,
array: ByteArray,
arrayOffset: Int,
byteCount: Int
): Int {
val bytesRead = array.usePinned { pinned ->
variantPread(pinned.addressOf(arrayOffset), byteCount, fileOffset)
}
if (bytesRead == 0) return -1
return bytesRead
}

fun variantPread(
target: CValuesRef<*>,
byteCount: Int,
offset: Long
): Int {
memScoped {
val overlapped = alloc<_OVERLAPPED>()
overlapped.Offset = offset.toUInt()
overlapped.OffsetHigh = (offset ushr 32).toUInt()
val readFileResult = ReadFile(
hFile = file,
lpBuffer = target.getPointer(this),
nNumberOfBytesToRead = byteCount.toUInt(),
lpNumberOfBytesRead = null,
lpOverlapped = overlapped.ptr
)
if (readFileResult == 0 && GetLastError().toInt() != ERROR_HANDLE_EOF) {
throw lastErrorToIOException()
}
return overlapped.InternalHigh.toInt()
}
}

override fun protectedWrite(
fileOffset: Long,
array: ByteArray,
arrayOffset: Int,
byteCount: Int
) {
val bytesWritten = array.usePinned { pinned ->
variantPwrite(pinned.addressOf(arrayOffset), byteCount, fileOffset)
}
if (bytesWritten != byteCount) throw IOException("bytesWritten=$bytesWritten")
}

fun variantPwrite(
source: CValuesRef<*>,
byteCount: Int,
offset: Long
): Int {
memScoped {
val overlapped = alloc<_OVERLAPPED>()
overlapped.Offset = offset.toUInt()
overlapped.OffsetHigh = (offset ushr 32).toUInt()
val writeFileResult = WriteFile(
hFile = file,
lpBuffer = source.getPointer(this),
nNumberOfBytesToWrite = byteCount.toUInt(),
lpNumberOfBytesWritten = null,
lpOverlapped = overlapped.ptr
)
if (writeFileResult == 0) {
throw lastErrorToIOException()
}
return overlapped.InternalHigh.toInt()
}
}

override fun protectedFlush() {
if (FlushFileBuffers(file) == 0) {
throw lastErrorToIOException()
}
}

override fun protectedResize(size: Long) {
memScoped {
val distanceToMoveHigh = alloc<IntVar>()
distanceToMoveHigh.value = (size ushr 32).toInt()
val movePointerResult = SetFilePointer(
hFile = file,
lDistanceToMove = size.toInt(),
lpDistanceToMoveHigh = distanceToMoveHigh.ptr,
dwMoveMethod = FILE_BEGIN
)
if (movePointerResult == 0U) {
throw lastErrorToIOException()
}
if (SetEndOfFile(file) == 0) {
throw lastErrorToIOException()
}
}
}

override fun protectedClose() {
if (CloseHandle(file) == 0) {
throw lastErrorToIOException()
}
}

private fun LARGE_INTEGER.toLong(): Long {
return (HighPart.toLong() shl 32) + (LowPart.toLong() and 0xffffffffL)
}
}
9 changes: 4 additions & 5 deletions okio/src/nativeMain/kotlin/okio/-PosixVariant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
*/
package okio

import kotlinx.cinterop.CPointer
import platform.posix.FILE

@ExperimentalFileSystem
internal expect fun PosixFileSystem.variantDelete(path: Path)

Expand All @@ -33,6 +30,8 @@ internal expect fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetad
@ExperimentalFileSystem
internal expect fun PosixFileSystem.variantMove(source: Path, target: Path)

internal expect fun variantSize(file: CPointer<FILE>): Long
@ExperimentalFileSystem
internal expect fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle

internal expect fun variantResize(file: CPointer<FILE>, size: Long)
@ExperimentalFileSystem
internal expect fun PosixFileSystem.variantOpenReadWrite(file: Path): FileHandle
15 changes: 0 additions & 15 deletions okio/src/nativeMain/kotlin/okio/-SizetVariant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package okio
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.ByteVarOf
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.CValuesRef
import platform.posix.FILE

internal expect fun variantFread(
Expand All @@ -32,17 +31,3 @@ internal expect fun variantFwrite(
byteCount: UInt,
file: CPointer<FILE>
): UInt

internal expect fun variantPread(
file: CPointer<FILE>,
target: CValuesRef<*>,
byteCount: Int,
offset: Long
): Int

internal expect fun variantPwrite(
file: CPointer<FILE>,
source: CValuesRef<*>,
byteCount: Int,
offset: Long
): Int
16 changes: 3 additions & 13 deletions okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ internal object PosixFileSystem : FileSystem() {

override fun canonicalize(path: Path) = variantCanonicalize(path)

override fun metadataOrNull(path: Path): FileMetadata? {
return variantMetadataOrNull(path)
}
override fun metadataOrNull(path: Path) = variantMetadataOrNull(path)

override fun list(dir: Path): List<Path> {
val opendir: CPointer<DIR> = opendir(dir.toString())
Expand Down Expand Up @@ -71,17 +69,9 @@ internal object PosixFileSystem : FileSystem() {
}
}

override fun openReadOnly(file: Path): FileHandle {
val openFile: CPointer<FILE> = fopen(file.toString(), "r")
?: throw errnoToIOException(errno)
return PosixFileHandle(false, openFile)
}
override fun openReadOnly(file: Path) = variantOpenReadOnly(file)

override fun openReadWrite(file: Path): FileHandle {
val openFile: CPointer<FILE> = fopen(file.toString(), "a+")
?: throw errnoToIOException(errno)
return PosixFileHandle(true, openFile)
}
override fun openReadWrite(file: Path) = variantOpenReadWrite(file)

override fun source(file: Path): Source {
val openFile: CPointer<FILE> = fopen(file.toString(), "r")
Expand Down
Loading