Skip to content

Commit eba0b52

Browse files
committed
Fix Windows target path encoding problem
1 parent 6fd8697 commit eba0b52

File tree

6 files changed

+211
-57
lines changed

6 files changed

+211
-57
lines changed

build-logic/src/main/kotlin/kotlinx/io/conventions/kotlinx-io-multiplatform.gradle.kts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ kotlin {
9797
group("androidNative")
9898
}
9999
}
100-
101-
group("nativeNonAndroid") {
102-
group("apple")
103-
group("mingw")
104-
group("linux")
105-
}
106100
}
107101
group("nodeFilesystemShared") {
108102
withJs()

core/apple/src/files/FileSystemApple.kt

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,29 @@
66

77
package kotlinx.io.files
88

9-
import kotlinx.cinterop.*
9+
import kotlinx.cinterop.CPointer
10+
import kotlinx.cinterop.ExperimentalForeignApi
11+
import kotlinx.cinterop.cstr
12+
import kotlinx.cinterop.get
13+
import kotlinx.cinterop.memScoped
14+
import kotlinx.cinterop.toKString
1015
import kotlinx.io.IOException
11-
import platform.Foundation.*
12-
import platform.posix.*
16+
import platform.Foundation.NSFileManager
17+
import platform.Foundation.NSFileSize
18+
import platform.Foundation.NSFileType
19+
import platform.Foundation.NSFileTypeDirectory
20+
import platform.Foundation.NSFileTypeRegular
21+
import platform.Foundation.NSTemporaryDirectory
22+
import platform.posix.DIR
23+
import platform.posix.basename
24+
import platform.posix.closedir
25+
import platform.posix.dirname
26+
import platform.posix.errno
27+
import platform.posix.free
28+
import platform.posix.mkdir
29+
import platform.posix.realpath
30+
import platform.posix.rename
31+
import platform.posix.strerror
1332

1433

1534
internal actual fun atomicMoveImpl(source: Path, destination: Path) {
@@ -64,3 +83,30 @@ internal actual fun metadataOrNullImpl(path: Path): FileMetadata? {
6483
size = if (isFile) attributes[NSFileSize] as Long else -1
6584
)
6685
}
86+
87+
@OptIn(ExperimentalForeignApi::class)
88+
internal actual class OpaqueDirEntry(private val dir: CPointer<DIR>) : AutoCloseable {
89+
actual fun readdir(): String? {
90+
val entry = platform.posix.readdir(dir) ?: return null
91+
return entry[0].d_name.toKString()
92+
}
93+
94+
actual override fun close() {
95+
if (closedir(dir) != 0) {
96+
val err = errno
97+
val strerr = strerror(err)?.toKString() ?: "unknown error"
98+
throw IOException("closedir failed with errno $err ($strerr)")
99+
}
100+
}
101+
}
102+
103+
@OptIn(ExperimentalForeignApi::class)
104+
internal actual fun opendir(path: String): OpaqueDirEntry {
105+
val dirent = platform.posix.opendir(path)
106+
if (dirent != null) return OpaqueDirEntry(dirent)
107+
108+
val err = errno
109+
val strerr = strerror(err)?.toKString() ?: "unknown error"
110+
throw IOException("Can't open directory $path: $err ($strerr)")
111+
}
112+

core/linux/src/files/FileSystemLinux.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55

66
package kotlinx.io.files
77

8+
import kotlinx.cinterop.CPointer
89
import kotlinx.cinterop.ExperimentalForeignApi
910
import kotlinx.cinterop.cstr
11+
import kotlinx.cinterop.get
1012
import kotlinx.cinterop.memScoped
1113
import kotlinx.cinterop.toKString
14+
import kotlinx.io.IOException
15+
import platform.posix.DIR
1216
import platform.posix.__xpg_basename
17+
import platform.posix.closedir
1318
import platform.posix.dirname
19+
import platform.posix.errno
20+
import platform.posix.strerror
1421

1522
@OptIn(ExperimentalForeignApi::class)
1623
internal actual fun dirnameImpl(path: String): String {
@@ -30,3 +37,30 @@ internal actual fun basenameImpl(path: String): String {
3037
}
3138

3239
internal actual fun isAbsoluteImpl(path: String): Boolean = path.startsWith('/')
40+
41+
42+
@OptIn(ExperimentalForeignApi::class)
43+
internal actual class OpaqueDirEntry(private val dir: CPointer<DIR>) : AutoCloseable {
44+
actual fun readdir(): String? {
45+
val entry = platform.posix.readdir(dir) ?: return null
46+
return entry[0].d_name.toKString()
47+
}
48+
49+
actual override fun close() {
50+
if (closedir(dir) != 0) {
51+
val err = errno
52+
val strerr = strerror(err)?.toKString() ?: "unknown error"
53+
throw IOException("closedir failed with errno $err ($strerr)")
54+
}
55+
}
56+
}
57+
58+
@OptIn(ExperimentalForeignApi::class)
59+
internal actual fun opendir(path: String): OpaqueDirEntry {
60+
val dirent = platform.posix.opendir(path)
61+
if (dirent != null) return OpaqueDirEntry(dirent)
62+
63+
val err = errno
64+
val strerr = strerror(err)?.toKString() ?: "unknown error"
65+
throw IOException("Can't open directory $path: $err ($strerr)")
66+
}

core/mingw/src/files/Error.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package kotlinx.io.files
2+
3+
import kotlinx.cinterop.ExperimentalForeignApi
4+
import kotlinx.cinterop.alloc
5+
import kotlinx.cinterop.memScoped
6+
import kotlinx.cinterop.ptr
7+
import kotlinx.cinterop.reinterpret
8+
import kotlinx.cinterop.toKString
9+
import kotlinx.cinterop.value
10+
import platform.windows.FORMAT_MESSAGE_ALLOCATE_BUFFER
11+
import platform.windows.FORMAT_MESSAGE_FROM_SYSTEM
12+
import platform.windows.FORMAT_MESSAGE_IGNORE_INSERTS
13+
import platform.windows.FormatMessageW
14+
import platform.windows.LPWSTRVar
15+
import platform.windows.LocalFree
16+
17+
@OptIn(ExperimentalForeignApi::class)
18+
internal fun formatWin32ErrorMessage(code: UInt): String {
19+
memScoped {
20+
val r = alloc<LPWSTRVar>()
21+
val n = FormatMessageW(
22+
dwFlags = (FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_FROM_SYSTEM).toUInt(),
23+
lpSource = null,
24+
dwMessageId = code,
25+
dwLanguageId = 0u,
26+
lpBuffer = r.ptr.reinterpret(),
27+
nSize = 0u,
28+
Arguments = null,
29+
)
30+
if (n == 0u) {
31+
throw RuntimeException("Error formatting error: $code")
32+
}
33+
val s = r.value?.toKString().orEmpty()
34+
LocalFree(r.value)
35+
return s
36+
}
37+
38+
}

core/mingw/src/files/FileSystemMingw.kt

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,37 @@
77

88
package kotlinx.io.files
99

10-
import kotlinx.cinterop.*
10+
import kotlinx.cinterop.Arena
11+
import kotlinx.cinterop.ExperimentalForeignApi
12+
import kotlinx.cinterop.alloc
13+
import kotlinx.cinterop.allocArray
14+
import kotlinx.cinterop.convert
15+
import kotlinx.cinterop.cstr
16+
import kotlinx.cinterop.memScoped
17+
import kotlinx.cinterop.ptr
18+
import kotlinx.cinterop.toKString
1119
import kotlinx.io.IOException
12-
import platform.posix.*
13-
import platform.windows.*
20+
import platform.posix.basename
21+
import platform.posix.dirname
22+
import platform.posix.errno
23+
import platform.posix.mkdir
24+
import platform.posix.strerror
25+
import platform.windows.CloseHandle
26+
import platform.windows.ERROR_FILE_NOT_FOUND
27+
import platform.windows.ERROR_NO_MORE_FILES
28+
import platform.windows.FindClose
29+
import platform.windows.FindFirstFileW
30+
import platform.windows.FindNextFileW
31+
import platform.windows.GetFullPathNameW
32+
import platform.windows.GetLastError
33+
import platform.windows.HANDLE
34+
import platform.windows.INVALID_HANDLE_VALUE
35+
import platform.windows.MOVEFILE_REPLACE_EXISTING
36+
import platform.windows.MoveFileExA
37+
import platform.windows.PathIsRelativeW
38+
import platform.windows.TRUE
39+
import platform.windows.WCHARVar
40+
import platform.windows.WIN32_FIND_DATAW
1441

1542
internal actual fun atomicMoveImpl(source: Path, destination: Path) {
1643
if (MoveFileExA(source.path, destination.path, MOVEFILE_REPLACE_EXISTING.convert()) == 0) {
@@ -41,7 +68,7 @@ internal actual fun isAbsoluteImpl(path: String): Boolean {
4168
val next = path[2]
4269
return next == WindowsPathSeparator || next == SystemPathSeparator
4370
}
44-
return PathIsRelativeA(path) == 0
71+
return PathIsRelativeW(path) == 0
4572
}
4673

4774
internal actual fun mkdirImpl(path: String) {
@@ -54,9 +81,66 @@ private const val MAX_PATH_LENGTH = 32767
5481

5582
internal actual fun realpathImpl(path: String): String {
5683
memScoped {
57-
val buffer = allocArray<CHARVar>(MAX_PATH_LENGTH)
58-
val len = GetFullPathNameA(path, MAX_PATH_LENGTH.convert(), buffer, null)
84+
val buffer = allocArray<WCHARVar>(MAX_PATH_LENGTH)
85+
val len = GetFullPathNameW(path, MAX_PATH_LENGTH.convert(), buffer, null)
5986
if (len == 0u) throw IllegalStateException()
6087
return buffer.toKString()
6188
}
6289
}
90+
91+
internal actual class OpaqueDirEntry(directory: String) : AutoCloseable {
92+
private val arena = Arena()
93+
private val data = arena.alloc<WIN32_FIND_DATAW>()
94+
private var handle: HANDLE? = INVALID_HANDLE_VALUE
95+
private var firstName: String? = null
96+
97+
init {
98+
try {
99+
val directory0 = if (directory.endsWith('/') || directory.endsWith('\\')) "$directory*" else "$directory/*"
100+
handle = FindFirstFileW(directory0, data.ptr)
101+
if (handle != INVALID_HANDLE_VALUE) {
102+
firstName = data.cFileName.toKString()
103+
} else {
104+
val le = GetLastError()
105+
if (le != ERROR_FILE_NOT_FOUND.toUInt()) {
106+
val strerr = formatWin32ErrorMessage(le)
107+
throw IOException("Can't open directory $directory: $le ($strerr)")
108+
}
109+
}
110+
} catch (th: Throwable) {
111+
if (handle != INVALID_HANDLE_VALUE) {
112+
CloseHandle(handle)
113+
}
114+
arena.clear()
115+
throw th
116+
}
117+
}
118+
119+
actual fun readdir(): String? {
120+
if (firstName != null) {
121+
return firstName.also { firstName = null }
122+
}
123+
if (handle == INVALID_HANDLE_VALUE) {
124+
return null
125+
}
126+
if (FindNextFileW(handle, data.ptr) == TRUE) {
127+
return data.cFileName.toKString()
128+
}
129+
val le = GetLastError()
130+
if (le == ERROR_NO_MORE_FILES.toUInt()) {
131+
return null
132+
}
133+
val strerr = formatWin32ErrorMessage(le)
134+
throw IOException("Can't readdir: $le ($strerr)")
135+
}
136+
137+
actual override fun close() {
138+
if (handle != INVALID_HANDLE_VALUE) {
139+
FindClose(handle)
140+
}
141+
arena.clear()
142+
}
143+
144+
}
145+
146+
internal actual fun opendir(path: String): OpaqueDirEntry = OpaqueDirEntry(path)

core/nativeNonAndroid/src/files/FileSystemNativeNonAndroid.kt

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)