Skip to content
Open
13 changes: 12 additions & 1 deletion build-support/src/main/kotlin/platforms.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ fun KotlinMultiplatformExtension.configureOrCreateOkioPlatforms() {
}

fun KotlinMultiplatformExtension.configureOrCreateNativePlatforms() {
androidNativeArm64()
androidNativeArm32()
androidNativeX64()
androidNativeX86()
iosX64()
iosArm64()
iosSimulatorArm64()
Expand All @@ -53,6 +57,13 @@ fun KotlinMultiplatformExtension.configureOrCreateNativePlatforms() {
mingwX64()
}

val androidNativeTargets = listOf(
"androidNativeArm64",
"androidNativeArm32",
"androidNativeX64",
"androidNativeX86",
)

val appleTargets = listOf(
"iosArm64",
"iosX64",
Expand All @@ -78,7 +89,7 @@ val linuxTargets = listOf(
"linuxArm64",
)

val nativeTargets = appleTargets + linuxTargets + mingwTargets
val nativeTargets = androidNativeTargets + appleTargets + linuxTargets + mingwTargets

val wasmTargets = listOf(
"wasmJs",
Expand Down
14 changes: 11 additions & 3 deletions okio/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ plugins {
* | | |-- watchosArm64
* | '-- linux
* | |-- linuxX64
* | '-- linuxArm64
* | |-- linuxArm64
* | '-- androidNative
* | |-- androidNativeArm64
* | |-- androidNativeArm32
* | |-- androidNativeX64
* | |-- androidNativeX86
* '-- wasm
* '-- wasmJs
* '-- wasmWasi
Expand Down Expand Up @@ -171,6 +176,7 @@ kotlin {
children = linuxTargets,
).also { linuxMain ->
linuxMain.dependsOn(nonAppleMain)
createSourceSet("androidNativeMain", parent = linuxMain, children = androidNativeTargets)
}
createSourceSet(
name = "appleMain",
Expand All @@ -184,9 +190,11 @@ kotlin {
)
}
}

createSourceSet("nativeNonAndroidMain", parent = nativeMain, children = appleTargets + mingwTargets + linuxTargets)
}

createSourceSet("nativeTest", parent = commonTest, children = mingwTargets + linuxTargets)
createSourceSet("nativeTest", parent = commonTest, children = androidNativeTargets + mingwTargets + linuxTargets)
.also { nativeTest ->
nativeTest.dependsOn(nonJvmTest)
nativeTest.dependsOn(nonWasmTest)
Expand All @@ -210,7 +218,7 @@ kotlin {
}

targets.withType<KotlinNativeTarget> {
if (konanTarget.family == Family.LINUX) {
if (konanTarget.family == Family.LINUX || konanTarget.family == Family.ANDROID) {
compilations["main"].cinterops.create("linux") {
packageName("okio.internal.linux")
headers(
Expand Down
36 changes: 36 additions & 0 deletions okio/src/androidNativeMain/kotlin/okio/internal/PosixDirectory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2026 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio.internal

import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.reinterpret
import okio.Closeable
import okio.Path
import platform.posix.closedir
import platform.posix.opendir
import platform.posix.readdir

internal actual value class PosixDirectory(private val dir: COpaquePointer) : Closeable {
actual fun nextEntry() = readdir(dir.reinterpret())
actual override fun close() {
closedir(dir.reinterpret()) // Ignore errno from closedir.
}
}

@Suppress("NOTHING_TO_INLINE")
internal actual inline fun openPosixDirectory(path: Path): PosixDirectory? {
return opendir(path.toString())?.let(::PosixDirectory)
}
8 changes: 5 additions & 3 deletions okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package okio

import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.alloc
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import okio.internal.linux.AT_FDCWD
Expand All @@ -40,18 +41,19 @@ import platform.posix.syscall
* Prefer `statx()` if it's available. Fall back to `stat()` which doesn't have a field for
* `createdAt`.
*/
@OptIn(UnsafeNumber::class)
internal actual fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetadata? {
memScoped {
val statx = alloc<statx>()
val result = syscall(
__NR_statx.toLong(),
__NR_statx.convert(),
AT_FDCWD,
path.toString(),
AT_SYMLINK_NOFOLLOW,
STATX_BASIC_STATS or STATX_BTIME,
statx.ptr,
)
if (result == 0L) {
).convert<Int>()
if (result == 0) {
return FileMetadata(
isRegularFile = statx.stx_mode.toInt() and S_IFMT == S_IFREG,
isDirectory = statx.stx_mode.toInt() and S_IFMT == S_IFDIR,
Expand Down
15 changes: 4 additions & 11 deletions okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@
*/
package okio

import kotlinx.cinterop.CPointer
import kotlinx.cinterop.get
import okio.Path.Companion.toPath
import okio.internal.openPosixDirectory
import okio.internal.toPath
import platform.posix.EEXIST
import platform.posix.closedir
import platform.posix.dirent
import platform.posix.errno
import platform.posix.opendir
import platform.posix.readdir
import platform.posix.set_posix_errno

internal object PosixFileSystem : FileSystem() {
Expand All @@ -40,16 +36,15 @@ internal object PosixFileSystem : FileSystem() {
override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)

private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
val opendir = opendir(dir.toString())
val posixDir = openPosixDirectory(dir)
?: if (throwOnFailure) throw errnoToIOException(errno) else return null

try {
posixDir.use {
val result = mutableListOf<Path>()
val buffer = Buffer()

set_posix_errno(0) // If readdir() returns null it's either the end or an error.
while (true) {
val dirent: CPointer<dirent> = readdir(opendir) ?: break
val dirent = it.nextEntry() ?: break
val childPath = buffer.writeNullTerminated(
bytes = dirent[0].d_name,
).toPath(normalize = true)
Expand All @@ -71,8 +66,6 @@ internal object PosixFileSystem : FileSystem() {

result.sort()
return result
} finally {
closedir(opendir) // Ignore errno from closedir.
}
}

Expand Down
37 changes: 37 additions & 0 deletions okio/src/nativeMain/kotlin/okio/internal/PosixDirectory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2026 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio.internal

import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import okio.Closeable
import okio.Path
import platform.posix.dirent

/**
* `platform.posix.DIR` is not available on Android Native, so the standard
* POSIX directory APIs (`opendir`, `readdir`, `closedir`) cannot be used
* directly.
*
* [PosixDirectory] provides platform-specific implementation
* for `DIR`-related functionality.
*/
internal expect value class PosixDirectory(private val dir: COpaquePointer) : Closeable {
fun nextEntry(): CPointer<dirent>?
override fun close()
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the cleanest working approach so far.
I probably should’ve marked this PR as a draft from the beginning…

Man, everything feels way more complicated than it needs to be.

Why doesn’t platform.posix just expose DIR on Android?!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Seems like these should all be value classes to avoid the memory allocation. Does anything prevent that?

Copy link
Copy Markdown
Contributor Author

@MohammedKHC MohammedKHC Feb 11, 2026

Choose a reason for hiding this comment

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

@JakeWharton
The problem is that the actual implementation needs to store

private val dir = opendir(path.toString())

And if you meant that the value should be DIR. Then this is kinda complicated.
as we cannot use expect/actual for DIR in only two source sets. (androidNative, nativeNonAndroid)

that's because this code will not compile under nativeNonAndroid

actual typealias DIR = platform.posix.DIR

It will fail with an error cannot create a typealias to a typealias.
As platform.posix.DIR is a typealias on Linux.

we could create a new nativeNonLinux source set that will exclude androidNative and linux.

What do you think?

EDIT: I just realized that we could do this

internal expect value class PosixDirectory(private val dir: COpaquePointer) : Closeable {
  fun nextEntry(): CPointer<dirent>?
  override fun close()
}

internal expect fun openPosixDirectory(path: Path): PosixDirectory?

and

internal actual value class PosixDirectory(private val dir: COpaquePointer) : Closeable {
  actual fun nextEntry() = readdir(dir.reinterpret())
  actual override fun close() {
    closedir(dir.reinterpret()) // Ignore errno from closedir.
  }
}

internal actual fun openPosixDirectory(path: Path): PosixDirectory? {
  return opendir(path.toString())?.let(::PosixDirectory)
}

Does this seem good?
And also we can make openPosixDirectory inline.
Do you think it's worth it?
And btw, I really hate the Kotlin compiler advising me to not use inline on functions..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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


internal expect fun openPosixDirectory(path: Path): PosixDirectory?
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2026 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio.internal

import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.reinterpret
import okio.Closeable
import okio.Path
import platform.posix.closedir
import platform.posix.opendir
import platform.posix.readdir

internal actual value class PosixDirectory(private val dir: COpaquePointer) : Closeable {
actual fun nextEntry() = readdir(dir.reinterpret())
actual override fun close() {
closedir(dir.reinterpret()) // Ignore errno from closedir.
}
}

@Suppress("NOTHING_TO_INLINE")
internal actual inline fun openPosixDirectory(path: Path): PosixDirectory? {
return opendir(path.toString())?.let(::PosixDirectory)
}
5 changes: 4 additions & 1 deletion okio/src/unixMain/kotlin/okio/UnixFileHandle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
package okio

import kotlinx.cinterop.CPointer
import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.alloc
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.usePinned
Expand Down Expand Up @@ -84,8 +86,9 @@ internal class UnixFileHandle(
}
}

@OptIn(UnsafeNumber::class)
override fun protectedResize(size: Long) {
if (ftruncate(fileno(file), size) == -1) {
if (ftruncate(fileno(file), size.convert()) == -1) {
throw errnoToIOException(errno)
}
}
Expand Down
7 changes: 4 additions & 3 deletions okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import okio.Path.Companion.toPath
import okio.internal.toPath
import platform.posix.DEFFILEMODE
import platform.posix.ENOENT
import platform.posix.FILE
import platform.posix.O_CREAT
Expand Down Expand Up @@ -129,6 +128,8 @@ internal actual fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle
return UnixFileHandle(false, openFile)
}

internal const val DEFFILEMODE = 0b110110110 /* octal 666 */

internal actual fun PosixFileSystem.variantOpenReadWrite(
file: Path,
mustCreate: Boolean,
Expand Down Expand Up @@ -175,15 +176,15 @@ internal fun variantPread(
target: CValuesRef<*>,
byteCount: Int,
offset: Long,
): Int = pread(fileno(file), target, byteCount.convert(), offset).convert()
): Int = pread(fileno(file), target, byteCount.convert(), offset.convert()).convert()

@OptIn(UnsafeNumber::class)
internal fun variantPwrite(
file: CPointer<FILE>,
source: CValuesRef<*>,
byteCount: Int,
offset: Long,
): Int = pwrite(fileno(file), source, byteCount.convert(), offset).convert()
): Int = pwrite(fileno(file), source, byteCount.convert(), offset.convert()).convert()

@OptIn(UnsafeNumber::class)
internal val timespec.epochMillis: Long
Expand Down