Skip to content

Commit

Permalink
Support path resolution (#228)
Browse files Browse the repository at this point in the history
Fixes #223
  • Loading branch information
fzhinkin committed Oct 18, 2023
1 parent 5d8982c commit f85bebd
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 8 deletions.
1 change: 1 addition & 0 deletions core/api/kotlinx-io-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public abstract interface class kotlinx/io/files/FileSystem {
public static synthetic fun delete$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)V
public abstract fun exists (Lkotlinx/io/files/Path;)Z
public abstract fun metadataOrNull (Lkotlinx/io/files/Path;)Lkotlinx/io/files/FileMetadata;
public abstract fun resolve (Lkotlinx/io/files/Path;)Lkotlinx/io/files/Path;
public abstract fun sink (Lkotlinx/io/files/Path;Z)Lkotlinx/io/RawSink;
public static synthetic fun sink$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)Lkotlinx/io/RawSink;
public abstract fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/RawSource;
Expand Down
9 changes: 9 additions & 0 deletions core/apple/src/files/FileSystemApple.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ internal actual fun mkdirImpl(path: String) {
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
}
}

internal actual fun realpathImpl(path: String): String {
val res = realpath(path, null) ?: throw IllegalStateException()
try {
return res.toKString()
} finally {
free(res)
}
}
13 changes: 13 additions & 0 deletions core/common/src/files/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@ public sealed interface FileSystem {
* @param path the path to get the metadata for.
*/
public fun metadataOrNull(path: Path): FileMetadata?

/**
* Returns an absolute path to the same file or directory the [path] is pointing to.
* All symbolic links are solved, extra path separators and references to current (`.`) or
* parent (`..`) directories are removed.
* If the [path] is a relative path then it'll be resolved against current working directory.
* If there is no file or directory to which the [path] is pointing to then [FileNotFoundException] will be thrown.
*
* @param path the path to resolve.
* @return a resolved path.
* @throws FileNotFoundException if there is no file or directory corresponding to the specified path.
*/
public fun resolve(path: Path): Path
}

internal abstract class SystemFileSystemImpl : FileSystem
Expand Down
33 changes: 33 additions & 0 deletions core/common/test/files/SmokeFileTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,40 @@ class SmokeFileTest {
}
assertEquals("second third",
SystemFileSystem.source(path).buffered().use { it.readString() })
}

@Test
fun resolve() {
assertFailsWith<FileNotFoundException>("Non-existing path resolution should fail") {
SystemFileSystem.resolve(createTempPath())
}

val cwd = SystemFileSystem.resolve(Path("."))
val parentRel = Path("..")
assertEquals(cwd.parent, SystemFileSystem.resolve(parentRel))

assertEquals(cwd, SystemFileSystem.resolve(cwd),
"Absolute path resolution should not alter the path")

// root
// |-> a -> b
// |-> c -> d
val root = createTempPath()
SystemFileSystem.createDirectories(Path(root, "a", "b"))
val tgt = Path(root, "c", "d")
SystemFileSystem.createDirectories(tgt)

val src = Path(root, "a", "..", "a", ".", "b", "..", "..", "c", ".", "d")
try {
// root/a/../a/./b/../../c/./d -> root/c/d
assertEquals(SystemFileSystem.resolve(tgt), SystemFileSystem.resolve(src))
} finally {
// TODO: remove as soon as recursive file removal is implemented
SystemFileSystem.delete(Path(root, "a", "b"))
SystemFileSystem.delete(Path(root, "a"))
SystemFileSystem.delete(Path(root, "c", "d"))
SystemFileSystem.delete(Path(root, "c"))
}
}

private fun constructAbsolutePath(vararg parts: String): String {
Expand Down
6 changes: 6 additions & 0 deletions core/js/src/files/FileSystemJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
check(buffer !== null) { "Module 'buffer' was not found" }
return FileSink(path, append)
}

override fun resolve(path: Path): Path {
check(fs !== null) { "Module 'fs' was not found" }
if (!exists(path)) throw FileNotFoundException(path.path)
return Path(fs.realpathSync.native(path.path) as String)
}
}

public actual val SystemTemporaryDirectory: Path
Expand Down
5 changes: 5 additions & 0 deletions core/jvm/src/files/FileSystemJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
override fun source(path: Path): RawSource = FileInputStream(path.file).asSource()

override fun sink(path: Path, append: Boolean): RawSink = FileOutputStream(path.file, append).asSink()

override fun resolve(path: Path): Path {
if (!path.file.exists()) throw FileNotFoundException(path.file.absolutePath)
return Path(path.file.canonicalFile)
}
}

@JvmField
Expand Down
16 changes: 12 additions & 4 deletions core/mingw/src/files/FileSystemMingw.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ package kotlinx.io.files
import kotlinx.cinterop.*
import kotlinx.io.IOException
import platform.posix.*
import platform.windows.GetLastError
import platform.windows.MOVEFILE_REPLACE_EXISTING
import platform.windows.MoveFileExA
import platform.windows.PathIsRelativeA
import platform.windows.*

private const val WindowsPathSeparator: Char = '\\'

Expand Down Expand Up @@ -49,3 +46,14 @@ internal actual fun mkdirImpl(path: String) {
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
}
}

private const val MAX_PATH_LENGTH = 32767

internal actual fun realpathImpl(path: String): String {
memScoped {
val buffer = allocArray<CHARVar>(MAX_PATH_LENGTH)
val len = GetFullPathNameA(path, MAX_PATH_LENGTH.convert(), buffer, null)
if (len == 0u) throw IllegalStateException()
return buffer.toKString()
}
}
7 changes: 7 additions & 0 deletions core/native/src/files/FileSystemNative.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()
}
}

override fun resolve(path: Path): Path {
if (!exists(path)) throw FileNotFoundException(path.path)
return Path(realpathImpl(path.path))
}

override fun source(path: Path): RawSource {
val openFile: CPointer<FILE>? = fopen(path.path, "rb")
if (openFile == null) {
Expand All @@ -102,6 +107,8 @@ internal expect fun atomicMoveImpl(source: Path, destination: Path)

internal expect fun mkdirImpl(path: String)

internal expect fun realpathImpl(path: String): String

public actual open class FileNotFoundException actual constructor(
message: String?
) : IOException(message)
Expand Down
14 changes: 10 additions & 4 deletions core/unix/src/files/FileSystemUnix.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.convert
import kotlinx.cinterop.toKString
import kotlinx.io.IOException
import platform.posix.errno
import platform.posix.mkdir
import platform.posix.rename
import platform.posix.strerror
import platform.posix.*

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

internal actual fun realpathImpl(path: String): String {
val result = realpath(path, null) ?: throw IllegalStateException()
try {
return result.toKString()
} finally {
free(result)
}
}

internal actual fun mkdirImpl(path: String) {
if (mkdir(path, PermissionAllowAll.convert()) != 0) {
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
Expand Down
1 change: 1 addition & 0 deletions core/wasm/src/files/FileSystemWasm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl()

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

override fun resolve(path: Path): Path = unsupported()
}

public actual open class FileNotFoundException actual constructor(
Expand Down

0 comments on commit f85bebd

Please sign in to comment.