diff --git a/build-support/src/main/kotlin/platforms.kt b/build-support/src/main/kotlin/platforms.kt index ee233310e2..4783f14048 100644 --- a/build-support/src/main/kotlin/platforms.kt +++ b/build-support/src/main/kotlin/platforms.kt @@ -34,6 +34,10 @@ fun KotlinMultiplatformExtension.configureOrCreateOkioPlatforms() { } fun KotlinMultiplatformExtension.configureOrCreateNativePlatforms() { + androidNativeArm64() + androidNativeArm32() + androidNativeX64() + androidNativeX86() iosX64() iosArm64() iosSimulatorArm64() @@ -53,6 +57,13 @@ fun KotlinMultiplatformExtension.configureOrCreateNativePlatforms() { mingwX64() } +val androidNativeTargets = listOf( + "androidNativeArm64", + "androidNativeArm32", + "androidNativeX64", + "androidNativeX86", +) + val appleTargets = listOf( "iosArm64", "iosX64", @@ -78,7 +89,7 @@ val linuxTargets = listOf( "linuxArm64", ) -val nativeTargets = appleTargets + linuxTargets + mingwTargets +val nativeTargets = androidNativeTargets + appleTargets + linuxTargets + mingwTargets val wasmTargets = listOf( "wasmJs", diff --git a/okio/build.gradle.kts b/okio/build.gradle.kts index b2c5a29f57..f21e5c0e4b 100644 --- a/okio/build.gradle.kts +++ b/okio/build.gradle.kts @@ -40,7 +40,12 @@ plugins { * | | |-- watchosArm64 * | '-- linux * | |-- linuxX64 - * | '-- linuxArm64 + * | |-- linuxArm64 + * | '-- androidNative + * | |-- androidNativeArm64 + * | |-- androidNativeArm32 + * | |-- androidNativeX64 + * | |-- androidNativeX86 * '-- wasm * '-- wasmJs * '-- wasmWasi @@ -171,6 +176,7 @@ kotlin { children = linuxTargets, ).also { linuxMain -> linuxMain.dependsOn(nonAppleMain) + createSourceSet("androidNativeMain", parent = linuxMain, children = androidNativeTargets) } createSourceSet( name = "appleMain", @@ -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) @@ -210,7 +218,7 @@ kotlin { } targets.withType { - if (konanTarget.family == Family.LINUX) { + if (konanTarget.family == Family.LINUX || konanTarget.family == Family.ANDROID) { compilations["main"].cinterops.create("linux") { packageName("okio.internal.linux") headers( diff --git a/okio/src/androidNativeMain/kotlin/okio/internal/PosixDirectory.kt b/okio/src/androidNativeMain/kotlin/okio/internal/PosixDirectory.kt new file mode 100644 index 0000000000..6da1e6f318 --- /dev/null +++ b/okio/src/androidNativeMain/kotlin/okio/internal/PosixDirectory.kt @@ -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) +} diff --git a/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt b/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt index 4fdd049d59..bd54512cba 100644 --- a/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt +++ b/okio/src/linuxMain/kotlin/okio/LinuxPosixVariant.kt @@ -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 @@ -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() 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() + 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, diff --git a/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt b/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt index f090183e55..7f36276f5e 100644 --- a/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt +++ b/okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt @@ -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() { @@ -40,16 +36,15 @@ internal object PosixFileSystem : FileSystem() { override fun listOrNull(dir: Path): List? = list(dir, throwOnFailure = false) private fun list(dir: Path, throwOnFailure: Boolean): List? { - val opendir = opendir(dir.toString()) + val posixDir = openPosixDirectory(dir) ?: if (throwOnFailure) throw errnoToIOException(errno) else return null - - try { + posixDir.use { val result = mutableListOf() val buffer = Buffer() set_posix_errno(0) // If readdir() returns null it's either the end or an error. while (true) { - val dirent: CPointer = readdir(opendir) ?: break + val dirent = it.nextEntry() ?: break val childPath = buffer.writeNullTerminated( bytes = dirent[0].d_name, ).toPath(normalize = true) @@ -71,8 +66,6 @@ internal object PosixFileSystem : FileSystem() { result.sort() return result - } finally { - closedir(opendir) // Ignore errno from closedir. } } diff --git a/okio/src/nativeMain/kotlin/okio/internal/PosixDirectory.kt b/okio/src/nativeMain/kotlin/okio/internal/PosixDirectory.kt new file mode 100644 index 0000000000..31ad141cd2 --- /dev/null +++ b/okio/src/nativeMain/kotlin/okio/internal/PosixDirectory.kt @@ -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? + override fun close() +} + +internal expect fun openPosixDirectory(path: Path): PosixDirectory? diff --git a/okio/src/nativeNonAndroidMain/kotlin/okio/internal/PosixDirectory.kt b/okio/src/nativeNonAndroidMain/kotlin/okio/internal/PosixDirectory.kt new file mode 100644 index 0000000000..6da1e6f318 --- /dev/null +++ b/okio/src/nativeNonAndroidMain/kotlin/okio/internal/PosixDirectory.kt @@ -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) +} diff --git a/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt b/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt index 62e7849f7e..7f57bdd5f1 100644 --- a/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt +++ b/okio/src/unixMain/kotlin/okio/UnixFileHandle.kt @@ -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 @@ -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) } } diff --git a/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt b/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt index ce1a6f76bf..d29675ee6d 100644 --- a/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt +++ b/okio/src/unixMain/kotlin/okio/UnixPosixVariant.kt @@ -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 @@ -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, @@ -175,7 +176,7 @@ 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( @@ -183,7 +184,7 @@ internal fun variantPwrite( 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