88package kotlinx.io.files
99
1010import kotlinx.cinterop.Arena
11+ import kotlinx.cinterop.CFunction
12+ import kotlinx.cinterop.CPointed
13+ import kotlinx.cinterop.CPointer
1114import kotlinx.cinterop.ExperimentalForeignApi
1215import kotlinx.cinterop.alloc
1316import kotlinx.cinterop.allocArray
1417import kotlinx.cinterop.convert
15- import kotlinx.cinterop.cstr
18+ import kotlinx.cinterop.invoke
1619import kotlinx.cinterop.memScoped
1720import kotlinx.cinterop.ptr
21+ import kotlinx.cinterop.reinterpret
1822import kotlinx.cinterop.toKString
23+ import kotlinx.cinterop.wcstr
1924import kotlinx.io.IOException
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.posix.size_t
2526import platform.windows.CloseHandle
27+ import platform.windows.CreateDirectoryW
2628import platform.windows.ERROR_FILE_NOT_FOUND
2729import platform.windows.ERROR_NO_MORE_FILES
30+ import platform.windows.FALSE
2831import platform.windows.FindClose
2932import platform.windows.FindFirstFileW
3033import platform.windows.FindNextFileW
3134import platform.windows.GetFullPathNameW
3235import platform.windows.GetLastError
36+ import platform.windows.GetProcAddress
3337import platform.windows.HANDLE
38+ import platform.windows.HMODULE
39+ import platform.windows.HRESULT
3440import platform.windows.INVALID_HANDLE_VALUE
41+ import platform.windows.LoadLibraryW
42+ import platform.windows.MAX_PATH
3543import platform.windows.MOVEFILE_REPLACE_EXISTING
36- import platform.windows.MoveFileExA
44+ import platform.windows.MoveFileExW
45+ import platform.windows.PWSTR
46+ import platform.windows.PathFindFileNameW
3747import platform.windows.PathIsRelativeW
48+ import platform.windows.PathIsRootW
3849import platform.windows.TRUE
3950import platform.windows.WCHARVar
4051import platform.windows.WIN32_FIND_DATAW
52+ import kotlin.experimental.ExperimentalNativeApi
53+
54+ private typealias PathCchRemoveFileSpecFunc = CPointer <CFunction <(PWSTR , size_t) - > HRESULT >>
55+
56+ @OptIn(ExperimentalNativeApi ::class )
57+ private val kernelBaseDll = LoadLibraryW (" kernelbase.dll" ) ? : run {
58+ terminateWithUnhandledException(RuntimeException (" kernelbase_dll is not supported: ${formatWin32ErrorMessage()} " ))
59+ }
60+
61+ @OptIn(ExperimentalNativeApi ::class )
62+ private fun <T : CPointed > getProcAddressOrFailed (module : HMODULE , name : String ): CPointer <T > {
63+ val pointer = GetProcAddress (kernelBaseDll, " PathCchRemoveFileSpec" ) ? : terminateWithUnhandledException(
64+ UnsupportedOperationException (" Failed to get proc: $name : ${formatWin32ErrorMessage()} " ),
65+ )
66+ return pointer.reinterpret()
67+ }
68+
69+ // Available since Windows 8 / Windows Server 2012, long path and UNC path supported
70+ private val PathCchRemoveFileSpec : PathCchRemoveFileSpecFunc =
71+ getProcAddressOrFailed(kernelBaseDll, " PathCchRemoveFileSpec" )
4172
4273internal actual fun atomicMoveImpl (source : Path , destination : Path ) {
43- if (MoveFileExA (source.path, destination.path, MOVEFILE_REPLACE_EXISTING .convert()) == 0 ) {
44- // TODO: get formatted error message
45- throw IOException (" Move failed with error code: ${GetLastError ()} " )
74+ if (MoveFileExW (source.path, destination.path, MOVEFILE_REPLACE_EXISTING .convert()) == 0 ) {
75+ throw IOException (" Move failed with error code: ${formatWin32ErrorMessage()} " )
4676 }
4777}
4878
4979internal actual fun dirnameImpl (path : String ): String {
50- if (! path.contains(UnixPathSeparator ) && ! path.contains(WindowsPathSeparator )) {
51- return " "
52- }
5380 memScoped {
54- return dirname(path.cstr.ptr)?.toKString() ? : " "
81+ val p = path.wcstr.ptr
82+ // we don't care the result, even it failed.
83+ PathCchRemoveFileSpec .invoke(p, path.length.convert())
84+ return p.toKString()
5585 }
5686}
5787
5888internal actual fun basenameImpl (path : String ): String {
59- memScoped {
60- return basename(path.cstr.ptr)?.toKString() ? : " "
61- }
89+ if (PathIsRootW (path) == TRUE ) return " "
90+ return PathFindFileNameW (path)?.toKString() ? : " "
6291}
6392
6493internal actual fun isAbsoluteImpl (path : String ): Boolean {
@@ -72,39 +101,46 @@ internal actual fun isAbsoluteImpl(path: String): Boolean {
72101}
73102
74103internal actual fun mkdirImpl (path : String ) {
75- if (mkdir (path) != 0 ) {
76- throw IOException (" mkdir failed: ${strerror(errno)?.toKString ()} " )
104+ if (CreateDirectoryW (path, null ) == FALSE ) {
105+ throw IOException (" mkdir failed: $path : ${formatWin32ErrorMessage ()}" )
77106 }
78107}
79108
80- private const val MAX_PATH_LENGTH = 32767
81-
82109internal actual fun realpathImpl (path : String ): String {
83110 memScoped {
84- val buffer = allocArray<WCHARVar >(MAX_PATH_LENGTH )
85- val len = GetFullPathNameW (path, MAX_PATH_LENGTH .convert(), buffer, null )
86- if (len == 0u ) throw IllegalStateException ()
87- return buffer.toKString()
111+ // in practice, MAX_PATH is enough for most cases
112+ var buf = allocArray<WCHARVar >(MAX_PATH )
113+ var r = GetFullPathNameW (path, MAX_PATH .convert(), buf, null )
114+ if (r >= MAX_PATH .toUInt()) {
115+ // if not, we will retry with the required size
116+ buf = allocArray<WCHARVar >(r.toInt())
117+ r = GetFullPathNameW (path, r, buf, null )
118+ }
119+ if (r == 0u ) {
120+ error(" GetFullPathNameW failed for $path : ${formatWin32ErrorMessage()} " )
121+ }
122+ return buf.toKString()
88123 }
89124}
90125
91- internal actual class OpaqueDirEntry (directory : String ) : AutoCloseable {
126+ internal actual class OpaqueDirEntry (private val directory : String ) : AutoCloseable {
92127 private val arena = Arena ()
93128 private val data = arena.alloc<WIN32_FIND_DATAW >()
94129 private var handle: HANDLE ? = INVALID_HANDLE_VALUE
95130 private var firstName: String? = null
96131
97132 init {
98133 try {
99- val directory0 = if (directory.endsWith(' /' ) || directory.endsWith(' \\ ' )) " $directory *" else " $directory /*"
134+ // since the root
135+ val directory0 =
136+ if (directory.endsWith(UnixPathSeparator ) || directory.endsWith(WindowsPathSeparator )) " $directory *" else " $directory /*"
100137 handle = FindFirstFileW (directory0, data.ptr)
101138 if (handle != INVALID_HANDLE_VALUE ) {
102139 firstName = data.cFileName.toKString()
103140 } 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 )" )
141+ val e = GetLastError ()
142+ if (e != ERROR_FILE_NOT_FOUND .toUInt()) {
143+ throw IOException (" Can't open directory $directory : ${formatWin32ErrorMessage(e)} " )
108144 }
109145 }
110146 } catch (th: Throwable ) {
@@ -130,8 +166,7 @@ internal actual class OpaqueDirEntry(directory: String) : AutoCloseable {
130166 if (le == ERROR_NO_MORE_FILES .toUInt()) {
131167 return null
132168 }
133- val strerr = formatWin32ErrorMessage(le)
134- throw IOException (" Can't readdir: $le ($strerr )" )
169+ throw IOException (" Can't readdir from $directory : ${formatWin32ErrorMessage(le)} " )
135170 }
136171
137172 actual override fun close () {
0 commit comments