├── .gitignore ├── .swift-format ├── Tests └── SwiftyR2Tests │ └── SwiftyR2Tests.swift ├── Package.swift ├── LICENSE.md └── Sources └── SwiftyR2 ├── R2Config.swift ├── R2IO.swift ├── R2IOAsyncAdapter.swift ├── R2Core.swift └── SwiftRIOPlugin.swift /.gitignore: -------------------------------------------------------------------------------- 1 | /.build/ 2 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "indentation": { 3 | "spaces": 4 4 | }, 5 | "lineLength": 140, 6 | "tabWidth": 4 7 | } 8 | -------------------------------------------------------------------------------- /Tests/SwiftyR2Tests/SwiftyR2Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import SwiftyR2 4 | 5 | final class SwiftyR2Tests: XCTestCase { 6 | 7 | func testCoreCreationAndSimpleCommand() { 8 | let core = R2Core() 9 | 10 | let output = core.cmd("?V") 11 | XCTAssertFalse(output.isEmpty, "Expected non-empty output from ?V command") 12 | XCTAssertTrue( 13 | output.lowercased().contains("radare2"), 14 | "Expected version output to mention radare2, got: \(output)" 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "SwiftyR2", 6 | platforms: [ 7 | .macOS(.v11), 8 | .iOS(.v12), 9 | ], 10 | products: [ 11 | .library( 12 | name: "SwiftyR2", 13 | targets: ["SwiftyR2"] 14 | ) 15 | ], 16 | targets: [ 17 | .binaryTarget( 18 | name: "Radare2", 19 | url: "https://build.frida.re/Radare2.xcframework.zip", 20 | checksum: "8474ba205817b86fb5a147b68a395e2c431e57e28d16633fe91f2787f3950f5d" 21 | ), 22 | 23 | .target( 24 | name: "SwiftyR2", 25 | dependencies: ["Radare2"] 26 | ), 27 | 28 | .testTarget( 29 | name: "SwiftyR2Tests", 30 | dependencies: ["SwiftyR2"] 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ole André Vadla Ravnås 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/SwiftyR2/R2Config.swift: -------------------------------------------------------------------------------- 1 | import Radare2 2 | 3 | public struct R2Config: Sendable { 4 | let raw: UnsafeMutablePointer 5 | 6 | let run: @Sendable (@escaping () -> Void) async -> Void 7 | 8 | @inline(__always) 9 | init(raw: UnsafeMutablePointer, run: @escaping @Sendable (@escaping () -> Void) async -> Void) { 10 | self.raw = raw 11 | self.run = run 12 | } 13 | 14 | public func set(_ key: String, bool value: Bool) async { 15 | await run { r_config_set_b(raw, key, value) } 16 | } 17 | 18 | public func set(_ key: String, int value: UInt64) async { 19 | await run { r_config_set_i(raw, key, value) } 20 | } 21 | 22 | public func set(_ key: String, int value: Int) async { 23 | await run { r_config_set_i(raw, key, UInt64(value)) } 24 | } 25 | 26 | public func set(_ key: String, string value: String) async { 27 | await run { r_config_set(raw, key, value) } 28 | } 29 | 30 | public func set(_ key: String, colorMode value: R2ColorMode) async { 31 | await run { r_config_set_i(raw, key, UInt64(value.rawValue)) } 32 | } 33 | } 34 | 35 | public enum R2ColorMode: Int32 { 36 | case disabled = 0 37 | case mode16 = 1 38 | case mode256 = 2 39 | case mode16M = 3 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SwiftyR2/R2IO.swift: -------------------------------------------------------------------------------- 1 | import Radare2 2 | 3 | public protocol R2IOProvider: AnyObject, Sendable { 4 | func supports(path: String, many: Bool) -> Bool 5 | func open(path: String, access: R2IOAccess, mode: Int32) throws -> R2IOFile 6 | } 7 | 8 | public protocol R2IOFile: AnyObject, Sendable { 9 | func close() throws 10 | func read(at offset: UInt64, count: Int) throws -> [UInt8] 11 | func write(at offset: UInt64, bytes: [UInt8]) throws -> Int 12 | func size() throws -> UInt64 13 | func setSize(_ size: UInt64) throws 14 | } 15 | 16 | public protocol R2IOAsyncProvider: AnyObject, Sendable { 17 | func supports(path: String, many: Bool) -> Bool 18 | func open(path: String, access: R2IOAccess, mode: Int32) async throws -> R2IOAsyncFile 19 | } 20 | 21 | public protocol R2IOAsyncFile: AnyObject, Sendable { 22 | func close() async throws 23 | func read(at offset: UInt64, count: Int) async throws -> [UInt8] 24 | func write(at offset: UInt64, bytes: [UInt8]) async throws -> Int 25 | func size() async throws -> UInt64 26 | func setSize(_ size: UInt64) async throws 27 | } 28 | 29 | public struct R2IOAccess: OptionSet { 30 | public let rawValue: Int32 31 | 32 | public static let none = R2IOAccess(rawValue: 0) 33 | public static let read = R2IOAccess(rawValue: 4) 34 | public static let write = R2IOAccess(rawValue: 2) 35 | public static let execute = R2IOAccess(rawValue: 1) 36 | 37 | public static let rw: R2IOAccess = [.read, .write] 38 | public static let rx: R2IOAccess = [.read, .execute] 39 | public static let wx: R2IOAccess = [.write, .execute] 40 | public static let rwx: R2IOAccess = [.read, .write, .execute] 41 | 42 | public static let shared = R2IOAccess(rawValue: 8) 43 | public static let priv = R2IOAccess(rawValue: 16) 44 | public static let access = R2IOAccess(rawValue: 32) 45 | public static let create = R2IOAccess(rawValue: 64) 46 | 47 | public init(rawValue: Int32) { self.rawValue = rawValue } 48 | 49 | public static func from(rw: Int32) -> R2IOAccess { 50 | R2IOAccess(rawValue: rw) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/SwiftyR2/R2IOAsyncAdapter.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | public final class R2IOAsyncProviderAdapter: R2IOProvider, @unchecked Sendable { 4 | private let asyncProvider: R2IOAsyncProvider 5 | 6 | public init(asyncProvider: R2IOAsyncProvider) { 7 | self.asyncProvider = asyncProvider 8 | } 9 | 10 | public func supports(path: String, many: Bool) -> Bool { 11 | asyncProvider.supports(path: path, many: many) 12 | } 13 | 14 | public func open(path: String, access: R2IOAccess, mode: Int32) throws -> R2IOFile { 15 | let asyncFile = try blockingFromAsyncThrowing { [self] in 16 | try await self.asyncProvider.open(path: path, access: access, mode: mode) 17 | } 18 | return R2IOAsyncFileAdapter(asyncFile: asyncFile) 19 | } 20 | } 21 | 22 | public final class R2IOAsyncFileAdapter: R2IOFile, @unchecked Sendable { 23 | private let asyncFile: R2IOAsyncFile 24 | 25 | public init(asyncFile: R2IOAsyncFile) { 26 | self.asyncFile = asyncFile 27 | } 28 | 29 | public func close() throws { 30 | _ = try blockingFromAsyncThrowing { [self] in 31 | try await self.asyncFile.close() 32 | } 33 | } 34 | 35 | public func read(at offset: UInt64, count: Int) throws -> [UInt8] { 36 | try blockingFromAsyncThrowing { [self] in 37 | try await self.asyncFile.read(at: offset, count: count) 38 | } 39 | } 40 | 41 | public func write(at offset: UInt64, bytes: [UInt8]) throws -> Int { 42 | try blockingFromAsyncThrowing { [self] in 43 | try await self.asyncFile.write(at: offset, bytes: bytes) 44 | } 45 | } 46 | 47 | public func size() throws -> UInt64 { 48 | try blockingFromAsyncThrowing { [self] in 49 | try await self.asyncFile.size() 50 | } 51 | } 52 | 53 | public func setSize(_ size: UInt64) throws { 54 | _ = try blockingFromAsyncThrowing { [self] in 55 | try await self.asyncFile.setSize(size) 56 | } 57 | } 58 | } 59 | 60 | private func blockingFromAsyncThrowing( 61 | _ body: @escaping () async throws -> T 62 | ) throws -> T { 63 | let sema = DispatchSemaphore(value: 0) 64 | var result: Result! 65 | 66 | Task.detached(priority: .userInitiated) { 67 | do { 68 | let value = try await body() 69 | result = .success(value) 70 | } catch { 71 | result = .failure(error) 72 | } 73 | sema.signal() 74 | } 75 | 76 | sema.wait() 77 | 78 | switch result! { 79 | case .success(let value): return value 80 | case .failure(let error): throw error 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/SwiftyR2/R2Core.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Radare2 3 | 4 | #if canImport(Darwin) 5 | import Darwin 6 | #else 7 | import Glibc 8 | #endif 9 | 10 | public final class R2Core: @unchecked Sendable { 11 | let core: UnsafeMutablePointer 12 | public let config: R2Config 13 | 14 | private var retainedProviders: [AnyObject] = [] 15 | private let executor: CoreThreadExecutor 16 | 17 | public static func create() async -> R2Core { 18 | let executor = CoreThreadExecutor() 19 | 20 | let core: UnsafeMutablePointer = await withCheckedContinuation { cont in 21 | executor.submit { 22 | cont.resume(returning: r_core_new()!) 23 | } 24 | } 25 | 26 | return R2Core(core: core, executor: executor) 27 | } 28 | 29 | private init(core: UnsafeMutablePointer, executor: CoreThreadExecutor) { 30 | self.core = core 31 | self.executor = executor 32 | self.config = R2Config( 33 | raw: core.pointee.config!, 34 | run: { [executor] job in 35 | await withCheckedContinuation { cont in 36 | executor.submit { 37 | job() 38 | cont.resume() 39 | } 40 | } 41 | } 42 | ) 43 | } 44 | 45 | deinit { 46 | let core = self.core 47 | let executor = self.executor 48 | 49 | executor.submit { [executor] in 50 | _r2io_coreWillDeinit(core: core) 51 | r_core_free(core) 52 | 53 | executor.stop() 54 | } 55 | } 56 | 57 | public func setColorLimit(_ limit: R2ColorMode) async { 58 | await runVoid { 59 | self.core.pointee.cons.pointee.context.pointee.color_limit = limit.rawValue 60 | } 61 | } 62 | 63 | public func applyTheme(_ theme: String) async { 64 | await runVoid { 65 | // Two hacks: 66 | // - Use r_core_cmd() as r_core_cmd_str() prevents theme updates from being applied, due to the cons push/pop. 67 | // - Reset first (ecd), as cmd_load_theme() sets cmdfilter to "ec ", which means any ecd command in the theme gets ignored. 68 | r_core_cmd(self.core, "ecd; eco \(theme)", false) 69 | } 70 | } 71 | 72 | @discardableResult 73 | public func openFile( 74 | uri: String, 75 | access: R2IOAccess = .rwx, 76 | loadAddress: UInt64 = 0 77 | ) async -> UnsafeMutablePointer? { 78 | await run { r_core_file_open(self.core, uri, access.rawValue, loadAddress) } 79 | } 80 | 81 | @discardableResult 82 | public func binLoad( 83 | uri: String, 84 | loadAddress: UInt64 = 0 85 | ) async -> Bool { 86 | await run { r_core_bin_load(self.core, uri, loadAddress) } 87 | } 88 | 89 | @discardableResult 90 | public func cmd(_ command: String) async -> String { 91 | await run { 92 | let cStr = r_core_cmd_str(self.core, command)! 93 | defer { free(cStr) } 94 | return String(cString: cStr) 95 | } 96 | } 97 | 98 | public func registerIOPlugin( 99 | provider: R2IOProvider, 100 | uriSchemes: [String] 101 | ) async { 102 | await runVoid { 103 | _r2io_installPlugin(core: self.core, provider: provider, uriSchemes: uriSchemes) 104 | self.retainedProviders.append(provider as AnyObject) 105 | } 106 | } 107 | 108 | public func registerIOPlugin( 109 | asyncProvider: R2IOAsyncProvider, 110 | uriSchemes: [String] 111 | ) async { 112 | let adapter = R2IOAsyncProviderAdapter(asyncProvider: asyncProvider) 113 | 114 | await runVoid { 115 | _r2io_installPlugin(core: self.core, provider: adapter, uriSchemes: uriSchemes) 116 | self.retainedProviders.append(adapter) 117 | } 118 | } 119 | 120 | @inline(__always) 121 | func run(_ job: @escaping () -> T) async -> T { 122 | await withCheckedContinuation { cont in 123 | executor.submit { 124 | cont.resume(returning: job()) 125 | } 126 | } 127 | } 128 | 129 | @inline(__always) 130 | func runVoid(_ job: @escaping () -> Void) async { 131 | await withCheckedContinuation { cont in 132 | executor.submit { 133 | job() 134 | cont.resume() 135 | } 136 | } 137 | } 138 | } 139 | 140 | private final class CoreThreadExecutor { 141 | private let condition = NSCondition() 142 | private var jobs: [() -> Void] = [] 143 | private var stopped = false 144 | 145 | private var thread: Thread! 146 | private var pthreadID: pthread_t? = nil 147 | 148 | init() { 149 | thread = Thread { [weak self] in 150 | self?.runLoop() 151 | } 152 | thread.name = "org.radare.swiftyr2.core" 153 | thread.qualityOfService = .userInitiated 154 | thread.start() 155 | } 156 | 157 | deinit { 158 | stop() 159 | } 160 | 161 | private func runLoop() { 162 | pthreadID = pthread_self() 163 | 164 | while true { 165 | condition.lock() 166 | while jobs.isEmpty && !stopped { 167 | condition.wait() 168 | } 169 | 170 | if stopped && jobs.isEmpty { 171 | condition.unlock() 172 | return 173 | } 174 | 175 | let job = jobs.removeFirst() 176 | condition.unlock() 177 | 178 | autoreleasepool { 179 | job() 180 | } 181 | } 182 | } 183 | 184 | private func isOnCoreThread() -> Bool { 185 | guard let tid = pthreadID else { return false } 186 | return pthread_equal(pthread_self(), tid) != 0 187 | } 188 | 189 | func submit(_ job: @escaping () -> Void) { 190 | if isOnCoreThread() { 191 | job() 192 | return 193 | } 194 | 195 | condition.lock() 196 | jobs.append(job) 197 | condition.signal() 198 | condition.unlock() 199 | } 200 | 201 | func stop() { 202 | condition.lock() 203 | stopped = true 204 | condition.signal() 205 | condition.unlock() 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Sources/SwiftyR2/SwiftRIOPlugin.swift: -------------------------------------------------------------------------------- 1 | import Radare2 2 | 3 | internal func _r2io_installPlugin( 4 | core: UnsafeMutablePointer, 5 | provider: R2IOProvider, 6 | uriSchemes: [String] 7 | ) { 8 | let mgr = pluginManager() 9 | 10 | let io = core.pointee.io! 11 | let state = mgr.state(for: io) 12 | 13 | state.providers.append(provider) 14 | 15 | state.uriSchemes.append(contentsOf: uriSchemes) 16 | var seenPerCore = Set() 17 | state.uriSchemes = state.uriSchemes.filter { seenPerCore.insert($0).inserted } 18 | 19 | mgr.recomputeGlobalURIs() 20 | if mgr.globalURIs.isEmpty { 21 | swiftRIOPlugin.uris = nil 22 | } else { 23 | let joined = mgr.globalURIs.joined(separator: ",") 24 | swiftRIOPlugin.uris = UnsafePointer(strdup(joined)) 25 | } 26 | 27 | if !state.isRegistered { 28 | r_io_plugin_add(io, &swiftRIOPlugin) 29 | state.isRegistered = true 30 | } 31 | } 32 | 33 | internal func _r2io_coreWillDeinit(core: UnsafeMutablePointer) { 34 | let mgr = pluginManager() 35 | if let io = core.pointee.io { 36 | mgr.removeState(for: io) 37 | mgr.recomputeGlobalURIs() 38 | if mgr.globalURIs.isEmpty { 39 | swiftRIOPlugin.uris = nil 40 | } else { 41 | let joined = mgr.globalURIs.joined(separator: ",") 42 | swiftRIOPlugin.uris = UnsafePointer(strdup(joined)) 43 | } 44 | } 45 | } 46 | 47 | private func pluginManager() -> PluginManager { 48 | if let raw = swiftRIOPlugin.data { 49 | return Unmanaged.fromOpaque(raw).takeUnretainedValue() 50 | } 51 | 52 | let mgr = PluginManager() 53 | let raw = Unmanaged.passRetained(mgr).toOpaque() 54 | swiftRIOPlugin.data = raw 55 | return mgr 56 | } 57 | 58 | private final class PluginManager { 59 | var cores: [UInt: CoreState] = [:] 60 | var globalURIs: [String] = [] 61 | 62 | func state(for io: UnsafeMutablePointer) -> CoreState { 63 | let key = UInt(bitPattern: UnsafeRawPointer(io)) 64 | if let existing = cores[key] { 65 | return existing 66 | } 67 | let new = CoreState() 68 | cores[key] = new 69 | return new 70 | } 71 | 72 | func removeState(for io: UnsafeMutablePointer) { 73 | let key = UInt(bitPattern: UnsafeRawPointer(io)) 74 | cores.removeValue(forKey: key) 75 | } 76 | 77 | func recomputeGlobalURIs() { 78 | var all: [String] = [] 79 | var seen = Set() 80 | for state in cores.values { 81 | for u in state.uriSchemes where seen.insert(u).inserted { 82 | all.append(u) 83 | } 84 | } 85 | globalURIs = all 86 | } 87 | } 88 | 89 | private final class CoreState { 90 | var isRegistered: Bool = false 91 | var uriSchemes: [String] = [] 92 | var providers: [R2IOProvider] = [] 93 | } 94 | 95 | private var swiftRIOPlugin: RIOPlugin = { 96 | let meta = makeMeta() 97 | 98 | return RIOPlugin( 99 | meta: meta, 100 | data: nil, 101 | uris: nil, 102 | listener: nil, 103 | isdbg: false, 104 | system: nil, 105 | open: swift_rio_open, 106 | open_many: nil, 107 | read: swift_rio_read, 108 | seek: swift_rio_seek, 109 | write: swift_rio_write, 110 | close: swift_rio_close, 111 | is_blockdevice: nil, 112 | is_chardevice: nil, 113 | getpid: nil, 114 | gettid: nil, 115 | getbase: nil, 116 | resize: swift_rio_resize, 117 | extend: nil, 118 | accept: nil, 119 | create: nil, 120 | check: swift_rio_check 121 | ) 122 | }() 123 | 124 | private func makeMeta() -> RPluginMeta { 125 | var meta = RPluginMeta() 126 | meta.name = strdup("swift-io") 127 | meta.desc = strdup("Swift-based radare2 IO plugin") 128 | meta.author = strdup("SwiftyR2") 129 | meta.version = strdup("1.0.0") 130 | meta.license = strdup("MIT") 131 | meta.contact = strdup("https://github.com/frida/SwiftyR2") 132 | meta.copyright = strdup("(c) 2025 SwiftyR2") 133 | return meta 134 | } 135 | 136 | private let swift_rio_check: 137 | @convention(c) ( 138 | UnsafeMutablePointer?, 139 | UnsafePointer?, 140 | Bool 141 | ) -> Bool = { io, pathname, many in 142 | let path = String(cString: pathname!) 143 | return chooseProvider(io: io!, path: path, many: many) != nil 144 | } 145 | 146 | private let swift_rio_open: 147 | @convention(c) ( 148 | UnsafeMutablePointer?, 149 | UnsafePointer?, 150 | Int32, 151 | Int32 152 | ) -> UnsafeMutablePointer? = { io, pathname, rw, mode in 153 | let io = io! 154 | let pathname = pathname! 155 | 156 | let path = String(cString: pathname) 157 | let access = R2IOAccess.from(rw: rw) 158 | 159 | guard let provider = chooseProvider(io: io, path: path, many: false) else { 160 | return nil 161 | } 162 | 163 | let file: R2IOFile 164 | do { 165 | file = try provider.open(path: path, access: access, mode: mode) 166 | } catch { 167 | return nil 168 | } 169 | 170 | let box = FileBox(file: file, offset: 0) 171 | let opaque = UnsafeMutableRawPointer(Unmanaged.passRetained(box).toOpaque()) 172 | 173 | return r_io_desc_new(io, &swiftRIOPlugin, path, rw, mode, opaque) 174 | } 175 | 176 | private func chooseProvider( 177 | io: UnsafeMutablePointer, 178 | path: String, 179 | many: Bool 180 | ) -> R2IOProvider? { 181 | let mgr = pluginManager() 182 | let state = mgr.state(for: io) 183 | for p in state.providers { 184 | if p.supports(path: path, many: many) { 185 | return p 186 | } 187 | } 188 | return nil 189 | } 190 | 191 | private let swift_rio_close: @convention(c) (UnsafeMutablePointer?) -> Bool = { fd in 192 | let fd = fd! 193 | let data = fd.pointee.data! 194 | let box = Unmanaged.fromOpaque(data).takeRetainedValue() 195 | fd.pointee.data = nil 196 | 197 | try? box.file.close() 198 | return true 199 | } 200 | 201 | private let swift_rio_read: 202 | @convention(c) ( 203 | UnsafeMutablePointer?, 204 | UnsafeMutablePointer?, 205 | UnsafeMutablePointer?, 206 | Int32 207 | ) -> Int32 = { io, fd, buf, count in 208 | let io = io! 209 | let buf = buf! 210 | 211 | let box = fileBox(from: fd) 212 | let requested = Int(count) 213 | 214 | let bytes: [UInt8] 215 | do { 216 | bytes = try box.file.read(at: box.offset, count: requested) 217 | } catch { 218 | return -1 219 | } 220 | 221 | let n = min(bytes.count, requested) 222 | 223 | if n > 0 { 224 | bytes.withUnsafeBufferPointer { src in 225 | memcpy(buf, src.baseAddress!, n) 226 | } 227 | } 228 | 229 | box.offset &+= UInt64(n) 230 | io.pointee.off = box.offset 231 | 232 | return Int32(n) 233 | } 234 | 235 | private let swift_rio_write: 236 | @convention(c) ( 237 | UnsafeMutablePointer?, 238 | UnsafeMutablePointer?, 239 | UnsafePointer?, 240 | Int32 241 | ) -> Int32 = { io, fd, buf, count in 242 | let io = io! 243 | let buf = buf! 244 | 245 | let box = fileBox(from: fd) 246 | let len = Int(count) 247 | 248 | var bytes = [UInt8](repeating: 0, count: len) 249 | bytes.withUnsafeMutableBufferPointer { dst in 250 | memcpy(dst.baseAddress!, buf, len) 251 | } 252 | 253 | let written: Int 254 | do { 255 | written = try box.file.write(at: box.offset, bytes: bytes) 256 | } catch { 257 | return -1 258 | } 259 | 260 | box.offset &+= UInt64(written) 261 | io.pointee.off = box.offset 262 | 263 | return Int32(written) 264 | } 265 | 266 | private let swift_rio_seek: 267 | @convention(c) ( 268 | UnsafeMutablePointer?, 269 | UnsafeMutablePointer?, 270 | UInt64, 271 | Int32 272 | ) -> UInt64 = { io, fd, rawOffset, whence in 273 | let io = io! 274 | let box = fileBox(from: fd) 275 | 276 | let sizeU: UInt64 277 | do { 278 | sizeU = try box.file.size() 279 | } catch { 280 | return UInt64.max 281 | } 282 | 283 | let size: Int64 = sizeU > UInt64(Int64.max) ? Int64.max : Int64(sizeU) 284 | 285 | let curU = box.offset 286 | let cur: Int64 = curU > UInt64(Int64.max) ? Int64.max : Int64(curU) 287 | 288 | let signedOffset = Int64(bitPattern: rawOffset) 289 | 290 | var new: Int64 291 | 292 | switch whence { 293 | case SEEK_SET: 294 | new = signedOffset 295 | case SEEK_CUR: 296 | new = cur &+ signedOffset 297 | case SEEK_END: 298 | new = size &+ signedOffset 299 | default: 300 | return UInt64.max 301 | } 302 | 303 | if new < 0 { new = 0 } 304 | if new > size { new = size } 305 | 306 | let newU = UInt64(new) 307 | box.offset = newU 308 | io.pointee.off = newU 309 | 310 | return newU 311 | } 312 | 313 | private let swift_rio_resize: 314 | @convention(c) ( 315 | UnsafeMutablePointer?, 316 | UnsafeMutablePointer?, 317 | UInt64 318 | ) -> Bool = { io, fd, size in 319 | let io = io! 320 | let box = fileBox(from: fd) 321 | 322 | do { 323 | try box.file.setSize(size) 324 | let newSize = try box.file.size() 325 | if box.offset > newSize { 326 | box.offset = newSize 327 | io.pointee.off = newSize 328 | } 329 | return true 330 | } catch { 331 | return false 332 | } 333 | } 334 | 335 | private final class FileBox { 336 | let file: R2IOFile 337 | var offset: UInt64 338 | 339 | init(file: R2IOFile, offset: UInt64) { 340 | self.file = file 341 | self.offset = offset 342 | } 343 | } 344 | 345 | private func fileBox(from fd: UnsafeMutablePointer?) -> FileBox { 346 | let desc = fd! 347 | let data = desc.pointee.data! 348 | return Unmanaged.fromOpaque(data).takeUnretainedValue() 349 | } 350 | --------------------------------------------------------------------------------