Skip to content

Commit c28df84

Browse files
committed
Support path resolution
1 parent e9a90bc commit c28df84

File tree

9 files changed

+82
-8
lines changed

9 files changed

+82
-8
lines changed

core/apple/src/files/FileSystemApple.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,12 @@ internal actual fun mkdirImpl(path: String) {
4343
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
4444
}
4545
}
46+
47+
internal actual fun realpathImpl(path: String): String {
48+
val res = realpath(path, null) ?: throw IllegalStateException()
49+
try {
50+
return res.toKString()
51+
} finally {
52+
free(res)
53+
}
54+
}

core/common/src/files/FileSystem.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ public sealed interface FileSystem {
129129
* @param path the path to get the metadata for.
130130
*/
131131
public fun metadataOrNull(path: Path): FileMetadata?
132+
133+
/**
134+
* Returns an absolute path to the same file or directory the [path] is pointing to
135+
* where all symbolic links are solved, extra path separators and references to current (`.`) or
136+
* parent (`..`) directories are removed.
137+
* If the [path] is a relative path then it'll be resolved against current working directory.
138+
* If there is no file or directory to which [path] point then [FileNotFoundException] will be thrown.
139+
*
140+
* @param path the path to resolve.
141+
* @return a resolved path.
142+
* @throws FileNotFoundException if there is no file or directory corresponding to the specified path.
143+
*/
144+
public fun resolve(path: Path): Path
132145
}
133146

134147
internal abstract class SystemFileSystemImpl : FileSystem

core/common/test/files/SmokeFileTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,26 @@ class SmokeFileTest {
336336
}
337337
assertEquals("second third",
338338
SystemFileSystem.source(path).buffered().use { it.readString() })
339+
}
340+
341+
@Test
342+
fun resolve() {
343+
assertFailsWith<FileNotFoundException> { SystemFileSystem.resolve(createTempPath()) }
344+
345+
val root = createTempPath()
346+
SystemFileSystem.createDirectories(Path(root, "a", "b"))
347+
val tgt = Path(root, "c", "d")
348+
SystemFileSystem.createDirectories(tgt)
339349

350+
val src = Path(root, "a", "..", "a", ".", "b", "..", "..", "c", ".", "d")
351+
try {
352+
assertEquals(SystemFileSystem.resolve(tgt), SystemFileSystem.resolve(src))
353+
} finally {
354+
SystemFileSystem.delete(Path(root, "a", "b"))
355+
SystemFileSystem.delete(Path(root, "a"))
356+
SystemFileSystem.delete(Path(root, "c", "d"))
357+
SystemFileSystem.delete(Path(root, "c"))
358+
}
340359
}
341360

342361
private fun constructAbsolutePath(vararg parts: String): String {

core/js/src/files/FileSystemJs.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
115115
check(buffer !== null) { "Module 'buffer' was not found" }
116116
return FileSink(path, append)
117117
}
118+
119+
override fun resolve(path: Path): Path {
120+
check(fs !== null) { "Module 'fs' was not found" }
121+
if (!exists(path)) throw FileNotFoundException(path.path)
122+
return Path(fs.realpathSync.native(path.path) as String)
123+
}
118124
}
119125

120126
public actual val SystemTemporaryDirectory: Path

core/jvm/src/files/FileSystemJvm.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
9191
override fun source(path: Path): RawSource = FileInputStream(path.file).asSource()
9292

9393
override fun sink(path: Path, append: Boolean): RawSink = FileOutputStream(path.file, append).asSink()
94+
95+
override fun resolve(path: Path): Path {
96+
if (!path.file.exists()) throw FileNotFoundException()
97+
return Path(path.file.canonicalFile)
98+
}
9499
}
95100

96101
@JvmField

core/mingw/src/files/FileSystemMingw.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ package kotlinx.io.files
1010
import kotlinx.cinterop.*
1111
import kotlinx.io.IOException
1212
import platform.posix.*
13-
import platform.windows.GetLastError
14-
import platform.windows.MOVEFILE_REPLACE_EXISTING
15-
import platform.windows.MoveFileExA
16-
import platform.windows.PathIsRelativeA
13+
import platform.windows.*
1714

1815
internal actual fun atomicMoveImpl(source: Path, destination: Path) {
1916
if (MoveFileExA(source.path, destination.path, MOVEFILE_REPLACE_EXISTING.convert()) == 0) {
@@ -44,3 +41,14 @@ internal actual fun mkdirImpl(path: String) {
4441
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
4542
}
4643
}
44+
45+
private const val MAX_PATH_LENGTH = 32767
46+
47+
internal actual fun realpathImpl(path: String): String {
48+
memScoped {
49+
val buffer = allocArray<CHARVar>(MAX_PATH_LENGTH)
50+
val len = GetFullPathNameA(path, MAX_PATH_LENGTH.convert(), buffer, null)
51+
if (len == 0u) throw IllegalStateException()
52+
return buffer.toKString()
53+
}
54+
}

core/native/src/files/FileSystemNative.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
8080
}
8181
}
8282

83+
override fun resolve(path: Path): Path {
84+
if (!exists(path)) throw FileNotFoundException(path.path)
85+
return Path(realpathImpl(path.path))
86+
}
87+
8388
override fun source(path: Path): RawSource {
8489
val openFile: CPointer<FILE>? = fopen(path.path, "rb")
8590
if (openFile == null) {
@@ -102,6 +107,8 @@ internal expect fun atomicMoveImpl(source: Path, destination: Path)
102107

103108
internal expect fun mkdirImpl(path: String)
104109

110+
internal expect fun realpathImpl(path: String): String
111+
105112
public actual open class FileNotFoundException actual constructor(
106113
message: String?
107114
) : IOException(message)

core/unix/src/files/FileSystemUnix.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,23 @@ import kotlinx.cinterop.UnsafeNumber
1212
import kotlinx.cinterop.convert
1313
import kotlinx.cinterop.toKString
1414
import kotlinx.io.IOException
15-
import platform.posix.errno
16-
import platform.posix.mkdir
17-
import platform.posix.rename
18-
import platform.posix.strerror
15+
import platform.posix.*
1916

2017
internal actual fun atomicMoveImpl(source: Path, destination: Path) {
2118
if (rename(source.path, destination.path) != 0) {
2219
throw IOException("Move failed: ${strerror(errno)?.toKString()}")
2320
}
2421
}
2522

23+
internal actual fun realpathImpl(path: String): String {
24+
val result = realpath(path, null) ?: throw IllegalStateException()
25+
try {
26+
return result.toKString()
27+
} finally {
28+
free(result)
29+
}
30+
}
31+
2632
internal actual fun mkdirImpl(path: String) {
2733
if (mkdir(path, PermissionAllowAll.convert()) != 0) {
2834
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")

core/wasm/src/files/FileSystemWasm.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
3030

3131
override fun metadataOrNull(path: Path): FileMetadata = unsupported()
3232

33+
override fun resolve(path: Path): Path = unsupported()
3334
}
3435

3536
public actual open class FileNotFoundException actual constructor(

0 commit comments

Comments
 (0)