From e7cf4cfdf914895ee61913b9bec0f6a75bfa03a3 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 22 Apr 2024 14:48:38 +0200 Subject: [PATCH] Replace stat call with NSFileManager API on Apple target (#298) Starting from May 1, 2024 developers have to explicitly declare why they use APIs allowing to access file timestamps in a privacy manifest file; otherwise, an app won't be accepted by the AppStore. The only restricted API we're currently using is stat, and since we don't extract any timestamps using it, we can safely replace it with NSFileManager::fileAttributesAtPath. For more details on the restriction imposed on timestamp-accessing APIs read https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api Closes #297 --- core/apple/src/files/FileSystemApple.kt | 14 ++++++++++- core/native/src/files/FileSystemNative.kt | 20 +++------------- core/nonApple/src/files/FileSystemNonApple.kt | 24 ++++++++++++++++--- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/core/apple/src/files/FileSystemApple.kt b/core/apple/src/files/FileSystemApple.kt index f7c07dca0..ed2632997 100644 --- a/core/apple/src/files/FileSystemApple.kt +++ b/core/apple/src/files/FileSystemApple.kt @@ -11,7 +11,7 @@ import kotlinx.cinterop.cstr import kotlinx.cinterop.memScoped import kotlinx.cinterop.toKString import kotlinx.io.IOException -import platform.Foundation.NSTemporaryDirectory +import platform.Foundation.* import platform.posix.* @@ -55,3 +55,15 @@ internal actual fun realpathImpl(path: String): String { free(res) } } + +internal actual fun metadataOrNullImpl(path: Path): FileMetadata? { + val attributes = NSFileManager.defaultManager().fileAttributesAtPath(path.path, traverseLink = true) ?: return null + val fileType = attributes[NSFileType] as String + val isFile = fileType == NSFileTypeRegular + val isDir = fileType == NSFileTypeDirectory + return FileMetadata( + isRegularFile = isFile, + isDirectory = isDir, + size = if (isFile) attributes[NSFileSize] as Long else -1 + ) +} diff --git a/core/native/src/files/FileSystemNative.kt b/core/native/src/files/FileSystemNative.kt index b7c0d5f18..05b4795b1 100644 --- a/core/native/src/files/FileSystemNative.kt +++ b/core/native/src/files/FileSystemNative.kt @@ -63,23 +63,7 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl() atomicMoveImpl(source, destination) } - @OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) - override fun metadataOrNull(path: Path): FileMetadata? { - memScoped { - val struct_stat = alloc() - if (stat(path.path, struct_stat.ptr) != 0) { - if (errno == ENOENT) return null - throw IOException("stat failed to ${path.path}: ${strerror(errno)?.toKString()}") - } - val mode = struct_stat.st_mode.toInt() - val isFile = (mode and S_IFMT) == S_IFREG - return FileMetadata( - isRegularFile = isFile, - isDirectory = (mode and S_IFMT) == S_IFDIR, - if (isFile) struct_stat.st_size.toLong() else -1L - ) - } - } + override fun metadataOrNull(path: Path): FileMetadata? = metadataOrNullImpl(path) override fun resolve(path: Path): Path { if (!exists(path)) throw FileNotFoundException(path.path) @@ -104,6 +88,8 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl() } } +internal expect fun metadataOrNullImpl(path: Path): FileMetadata? + internal expect fun atomicMoveImpl(source: Path, destination: Path) internal expect fun mkdirImpl(path: String) diff --git a/core/nonApple/src/files/FileSystemNonApple.kt b/core/nonApple/src/files/FileSystemNonApple.kt index 6c69cca3c..631aea1a2 100644 --- a/core/nonApple/src/files/FileSystemNonApple.kt +++ b/core/nonApple/src/files/FileSystemNonApple.kt @@ -5,10 +5,28 @@ package kotlinx.io.files -import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.toKString -import platform.posix.getenv +import kotlinx.cinterop.* +import kotlinx.io.IOException +import platform.posix.* @OptIn(ExperimentalForeignApi::class) public actual val SystemTemporaryDirectory: Path get() = Path(getenv("TMPDIR")?.toKString() ?: getenv("TMP")?.toKString() ?: "") + +@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) +internal actual fun metadataOrNullImpl(path: Path): FileMetadata? { + memScoped { + val struct_stat = alloc() + if (stat(path.path, struct_stat.ptr) != 0) { + if (errno == ENOENT) return null + throw IOException("stat failed to ${path.path}: ${strerror(errno)?.toKString()}") + } + val mode = struct_stat.st_mode.toInt() + val isFile = (mode and S_IFMT) == S_IFREG + return FileMetadata( + isRegularFile = isFile, + isDirectory = (mode and S_IFMT) == S_IFDIR, + if (isFile) struct_stat.st_size.toLong() else -1L + ) + } +}