Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,18 @@ jobs:

- uses: gradle/actions/setup-gradle@v6

- name: Build Android native test binary
run: ./gradlew :okio:androidNativeX64TestBinaries

- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 24
script: ./gradlew :okio-assetfilesystem:connectedCheck
arch: x86_64
script: |
adb push okio/build/bin/androidNativeX64/debugTest/test.kexe /data/local/tmp
adb push okio-testing-support /data/local/tmp/okio/okio-testing-support
adb shell "OKIO_ROOT=/data/local/tmp/okio /data/local/tmp/test.kexe"
./gradlew :okio-assetfilesystem:connectedCheck

loom:
runs-on: ubuntu-latest
Expand Down
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
2 changes: 1 addition & 1 deletion okio-fakefilesystem/api/okio-fakefilesystem.klib.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Klib ABI Dump
// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ abstract class AbstractFileSystemTest(
private val isNodeJsFileSystem = fileSystem::class.simpleName?.startsWith("NodeJs") ?: false
private val isWasiFileSystem = fileSystem::class.simpleName?.startsWith("Wasi") ?: false
private val isWrappingJimFileSystem = this::class.simpleName?.contains("JimFileSystem") ?: false
private val isAndroidNativeFileSystem = fileSystem::class.simpleName?.startsWith("Posix") ?: false &&
getEnv("ANDROID_DATA") != null

@Test
fun doesNotExistsWithInvalidPathDoesNotThrow() {
Expand Down Expand Up @@ -283,6 +285,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listOnRelativePathWhichIsNotDotReturnsRelativePaths() {
if (isNodeJsFileSystem) return
if (isAndroidNativeFileSystem) return

val apiDir = "api".toPath()
val expectedFiles = listOf(
Expand Down Expand Up @@ -336,6 +339,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listOrNullOnRelativePathWhichIsNotDotReturnsRelativePaths() {
if (isNodeJsFileSystem) return
if (isAndroidNativeFileSystem) return

val apiDir = "api".toPath()
val expectedFiles = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ expect fun isBrowser(): Boolean
* The file system that GitHub actions gives us doesn't do anything when we `touch` a file.
*/
val fileSystemHasGoodMetadata: Boolean
get() = getEnv("GITHUB_WORKSPACE") == null
get() = getEnv("GITHUB_WORKSPACE") == null && getEnv("ANDROID_DATA") == null

val FileMetadata.createdAt: Instant?
get() {
Expand Down
4 changes: 2 additions & 2 deletions okio/api/okio.klib.api
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Klib ABI Dump
// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Alias: native => [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Alias: native => [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Alias: apple => [iosArm64, iosSimulatorArm64, iosX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
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,8 @@ kotlin {
children = linuxTargets,
).also { linuxMain ->
linuxMain.dependsOn(nonAppleMain)
createSourceSet("linuxNonAndroidMain", parent = linuxMain, children = linuxTargets)
createSourceSet("androidNativeMain", parent = linuxMain, children = androidNativeTargets)
}
createSourceSet(
name = "appleMain",
Expand All @@ -183,10 +190,11 @@ kotlin {
children = appleTargets - "macosX64",
)
}
createSourceSet("unixNonAndroidMain", parent = unixMain, children = appleTargets + 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
11 changes: 11 additions & 0 deletions okio/src/androidNativeMain/kotlin/okio/AndroidPosixVariant.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package okio

import kotlin.concurrent.Volatile
import platform.posix.android_get_device_api_level

/**
* Checked eagerly because on older Android versions (API < 30)
* seccomp blocks the syscall with SIGSYS, crashing the app instead of returning ENOSYS.
**/
@Volatile
internal actual var isStatXSupported = android_get_device_api_level() >= 30
35 changes: 35 additions & 0 deletions okio/src/androidNativeMain/kotlin/okio/internal/PosixDirectory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 cnames.structs.DIR
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.reinterpret
import okio.Path
import platform.posix.closedir
import platform.posix.opendir
import platform.posix.readdir

private class AndroidPosixDirectory(private val dir: CPointer<DIR>) : PosixDirectory {
override fun nextEntry() = readdir(dir.reinterpret())
override fun close() {
closedir(dir.reinterpret()) // Ignore errno from closedir.
}
}

internal actual fun openPosixDirectory(path: Path): PosixDirectory? {
return opendir(path.toString())?.let(::AndroidPosixDirectory)
}
83 changes: 47 additions & 36 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 @@ -36,51 +37,61 @@ import platform.posix.lstat
import platform.posix.stat
import platform.posix.syscall

internal expect var isStatXSupported: Boolean

/**
* 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(),
AT_FDCWD,
path.toString(),
AT_SYMLINK_NOFOLLOW,
STATX_BASIC_STATS or STATX_BTIME,
statx.ptr,
)
if (result == 0L) {
return FileMetadata(
isRegularFile = statx.stx_mode.toInt() and S_IFMT == S_IFREG,
isDirectory = statx.stx_mode.toInt() and S_IFMT == S_IFDIR,
symlinkTarget = symlinkTarget(statx.stx_mode.toInt(), path),
size = statx.stx_size.toLong(),
createdAtMillis = when {
statx.stx_mask and STATX_BTIME != 0U -> statx.stx_btime.epochMillis
else -> statx.stx_mtime.epochMillis
},
lastModifiedAtMillis = statx.stx_mtime.epochMillis,
lastAccessedAtMillis = statx.stx_atime.epochMillis,
)
}

// Recover if statx() isn't available. It first appeared in Linux in 4.11 (2017-04-30) and
// Android in API 30 (2020-09-08).
if (errno == ENOSYS) {
val stat = alloc<stat>()
if (lstat(path.toString(), stat.ptr) == 0) {
if (isStatXSupported) {
val statx = alloc<statx>()
val result = syscall(
__NR_statx.convert(),
AT_FDCWD,
path.toString(),
AT_SYMLINK_NOFOLLOW,
STATX_BASIC_STATS or STATX_BTIME,
statx.ptr,
).convert<Int>()
if (result == 0) {
return FileMetadata(
isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
symlinkTarget = symlinkTarget(stat.st_mode.toInt(), path),
size = stat.st_size,
createdAtMillis = stat.st_mtim.epochMillis,
lastModifiedAtMillis = stat.st_mtim.epochMillis,
lastAccessedAtMillis = stat.st_atim.epochMillis,
isRegularFile = statx.stx_mode.toInt() and S_IFMT == S_IFREG,
isDirectory = statx.stx_mode.toInt() and S_IFMT == S_IFDIR,
symlinkTarget = symlinkTarget(statx.stx_mode.toInt(), path),
size = statx.stx_size.toLong(),
createdAtMillis = when {
statx.stx_mask and STATX_BTIME != 0U -> statx.stx_btime.epochMillis
else -> statx.stx_mtime.epochMillis
},
lastModifiedAtMillis = statx.stx_mtime.epochMillis,
lastAccessedAtMillis = statx.stx_atime.epochMillis,
)
}

// Recover if statx() isn't available. It first appeared in Linux in 4.11 (2017-04-30) and
// Android in API 30 (2020-09-08).
if (errno == ENOSYS) {
isStatXSupported = false
} else {
if (errno == ENOENT) return null
throw errnoToIOException(errno)
}
}

val stat = alloc<stat>()
if (lstat(path.toString(), stat.ptr) == 0) {
return FileMetadata(
isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
symlinkTarget = symlinkTarget(stat.st_mode.toInt(), path),
size = stat.st_size,
createdAtMillis = stat.st_mtim.epochMillis,
lastModifiedAtMillis = stat.st_mtim.epochMillis,
lastAccessedAtMillis = stat.st_atim.epochMillis,
)
}

if (errno == ENOENT) return null
Expand Down
6 changes: 6 additions & 0 deletions okio/src/linuxNonAndroidMain/kotlin/okio/LinuxPosixVariant.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package okio

import kotlin.concurrent.Volatile

@Volatile
internal actual var isStatXSupported = true
2 changes: 0 additions & 2 deletions okio/src/nativeMain/kotlin/okio/PosixFileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
*/
package okio

import kotlinx.cinterop.get
import okio.Path.Companion.toPath
import okio.internal.toPath
import platform.posix.EEXIST
import platform.posix.errno

Expand Down
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
Loading
Loading