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

Support path resolution #228

Merged
merged 5 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
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.
shanshin marked this conversation as resolved.
Show resolved Hide resolved
*/
public fun resolve(path: Path): Path
shanshin marked this conversation as resolved.
Show resolved Hide resolved
}

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

@Test
fun resolve() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I need to add more tests: to check for an exception if the path does not exist, to check for an absolute path, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the test for absolute path, all other cases are already there.

It would be nice to test symlink resolution, but we don't have an API for links yet.

assertFailsWith<FileNotFoundException> { SystemFileSystem.resolve(createTempPath()) }

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 {
assertEquals(SystemFileSystem.resolve(tgt), SystemFileSystem.resolve(src))
} finally {
SystemFileSystem.delete(Path(root, "a", "b"))
SystemFileSystem.delete(Path(root, "a"))
SystemFileSystem.delete(Path(root, "c", "d"))
SystemFileSystem.delete(Path(root, "c"))
}

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

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()
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