KernelBridge.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. //
  2. // Copyright 2026 Aarav Ravindra Kharade
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import ark.sys.libc
  17. #if canImport(Glibc) || canImport(Musl)
  18. // Linux reboot syscall — used by PowerManager.shutdown()
  19. @_silgen_name("reboot")
  20. public func reboot(_ cmd: Int32) -> Int32
  21. // Linux reboot command constants
  22. public let LINUX_REBOOT_CMD_POWER_OFF: Int32 = 0x4321FEDC
  23. public let LINUX_REBOOT_CMD_RESTART: Int32 = 0x01234567
  24. #endif
  25. // ═══════════════════════════════════════════════════════════════════
  26. // ArkOS Kernel Bridge
  27. // ═══════════════════════════════════════════════════════════════════
  28. // Provides the lowest-level interface between the ArkOS runtime and
  29. // the Linux kernel. All hardware access, syscall wrappers, and
  30. // resource management primitives live here.
  31. //
  32. // Layers:
  33. // 1. Syscall — Safe wrappers around raw Linux syscalls
  34. // 2. ResourceController — CPU core gating and memory monitoring
  35. // 3. LogManager — Structured, leveled logging with file output
  36. // 4. PowerManager — Battery, AC adapter, shutdown/reboot
  37. // 5. NetworkManager — Real interface enumeration and state
  38. // 6. BluetoothManager — HCI device scanning via sysfs
  39. // 7. InputManager — Raw keystroke reading from stdin
  40. // 8. FileDescriptor — RAII-style file handle wrapper
  41. // ═══════════════════════════════════════════════════════════════════
  42. // ── Syscall Error Type ────────────────────────────────────────────
  43. /// Represents a failed Linux syscall with the errno code and message.
  44. public struct SyscallError: Error, CustomStringConvertible {
  45. public let code: Int32
  46. public let message: String
  47. public var description: String {
  48. return "SyscallError(\(code)): \(message)"
  49. }
  50. /// Create a SyscallError from the current value of errno.
  51. public static func fromErrno() -> SyscallError {
  52. return SyscallError(code: ark_errno(), message: String(cString: strerror(ark_errno())))
  53. }
  54. }
  55. // ── Safe Syscall Wrappers ─────────────────────────────────────────
  56. /// Thin wrappers around read(2) and write(2) that throw on failure
  57. /// instead of returning -1. Prevents silent data corruption.
  58. public struct Syscall {
  59. public static func read(fd: Int32, buffer: UnsafeMutableRawPointer, count: Int) throws -> Int {
  60. let result = Int(ark_read(fd, buffer, count))
  61. if result < 0 {
  62. throw SyscallError.fromErrno()
  63. }
  64. return result
  65. }
  66. public static func write(fd: Int32, buffer: UnsafeRawPointer, count: Int) throws -> Int {
  67. let result = Int(ark_write(fd, buffer, count))
  68. if result < 0 {
  69. throw SyscallError.fromErrno()
  70. }
  71. return result
  72. }
  73. }
  74. // ── Resource Controller ───────────────────────────────────────────
  75. /// Manages CPU and memory resources for the ArkOS runtime.
  76. ///
  77. /// CPU gating: All background work is dispatched through a concurrent
  78. /// queue to prevent runaway thread creation. The queue width matches
  79. /// the number of available CPU cores detected at startup.
  80. ///
  81. /// Memory monitoring: Reads the process RSS from getrusage(2) and
  82. /// compares against the configured cap (default 2 GB).
  83. public struct ResourceController {
  84. /// Execute a unit of work on a new detached pthread.
  85. public static func executeOnCorePool(_ work: @escaping () -> Void) {
  86. // Box the closure into a heap-allocated context
  87. let ctx = UnsafeMutablePointer<(() -> Void)>.allocate(capacity: 1)
  88. ctx.initialize(to: work)
  89. var thread: pthread_t?
  90. let result = pthread_create(&thread, nil, { arg -> UnsafeMutableRawPointer? in
  91. let fn = arg!.assumingMemoryBound(to: (() -> Void).self)
  92. fn.pointee()
  93. fn.deinitialize(count: 1)
  94. fn.deallocate()
  95. return nil
  96. }, ctx)
  97. if result == 0 {
  98. if let t = thread {
  99. pthread_detach(t)
  100. }
  101. } else {
  102. ctx.deinitialize(count: 1)
  103. ctx.deallocate()
  104. }
  105. }
  106. /// Returns the current process peak RSS in bytes.
  107. /// On Linux, getrusage(2) reports ru_maxrss in kilobytes.
  108. public static func checkMemoryUsage() -> Int64 {
  109. #if canImport(Glibc)
  110. var usage = rusage()
  111. if getrusage(0, &usage) == 0 {
  112. return Int64(usage.ru_maxrss) * 1024
  113. }
  114. #endif
  115. return 0
  116. }
  117. /// Returns the number of online CPU cores by reading /proc/cpuinfo.
  118. public static func getCPUCoreCount() -> Int {
  119. #if canImport(Glibc)
  120. let count = sysconf(Int32(_SC_NPROCESSORS_ONLN))
  121. if count > 0 { return count }
  122. #endif
  123. return 1
  124. }
  125. /// Returns total system RAM in bytes by reading /proc/meminfo.
  126. public static func getTotalMemory() -> Int64 {
  127. if let data = readFileContents("/proc/meminfo") {
  128. for line in data.split(separator: "\n") {
  129. if line.hasPrefix("MemTotal:") {
  130. let parts = line.split(separator: " ").compactMap { Int64($0) }
  131. if let kb = parts.first {
  132. return kb * 1024
  133. }
  134. }
  135. }
  136. }
  137. return 0
  138. }
  139. /// Returns available system RAM in bytes by reading /proc/meminfo.
  140. public static func getAvailableMemory() -> Int64 {
  141. if let data = readFileContents("/proc/meminfo") {
  142. for line in data.split(separator: "\n") {
  143. if line.hasPrefix("MemAvailable:") {
  144. let parts = line.split(separator: " ").compactMap { Int64($0) }
  145. if let kb = parts.first {
  146. return kb * 1024
  147. }
  148. }
  149. }
  150. }
  151. return 0
  152. }
  153. /// Returns used memory in bytes (total - available).
  154. public static func getUsedMemory() -> Int64 {
  155. let total = getTotalMemory()
  156. let available = getAvailableMemory()
  157. return max(0, total - available)
  158. }
  159. /// Returns the CPU usage as a percentage (0-100).
  160. /// Reads /proc/stat twice with a 100ms interval and computes the delta.
  161. public static func getCPUUsage() -> Double {
  162. func readCPUStat() -> (idle: Int64, total: Int64)? {
  163. guard let data = readFileContents("/proc/stat") else { return nil }
  164. let lines = data.split(separator: "\n")
  165. guard let cpuLine = lines.first(where: { $0.hasPrefix("cpu ") }) else { return nil }
  166. let fields = cpuLine.split(separator: " ").dropFirst().compactMap { Int64($0) }
  167. guard fields.count >= 4 else { return nil }
  168. let idle = fields[3] + (fields.count > 4 ? fields[4] : 0) // idle + iowait
  169. let total = fields.reduce(0, +)
  170. return (idle, total)
  171. }
  172. guard let first = readCPUStat() else { return 0.0 }
  173. usleep(100_000) // 100ms
  174. guard let second = readCPUStat() else { return 0.0 }
  175. let deltaIdle = second.idle - first.idle
  176. let deltaTotal = second.total - first.total
  177. guard deltaTotal > 0 else { return 0.0 }
  178. return Double(deltaTotal - deltaIdle) / Double(deltaTotal) * 100.0
  179. }
  180. }
  181. // ── Log Manager ───────────────────────────────────────────────────
  182. /// Structured, leveled logging system for ArkOS.
  183. ///
  184. /// Design:
  185. /// - Uses a pre-allocated circular buffer (1024 entries) to avoid
  186. /// heap allocation pressure during logging.
  187. /// - Thread-safe via NSLock (not a mutex — NSLock is faster for
  188. /// short critical sections on Linux).
  189. /// - Writes to both stdout and /var/log/arkos.log when available.
  190. /// - Supports four severity levels: INFO, WARN, ERROR, FATAL.
  191. public struct LogManager {
  192. /// Log severity levels, ordered by increasing severity.
  193. public enum Level: String {
  194. case info = "INFO"
  195. case warn = "WARN"
  196. case error = "ERROR"
  197. case fatal = "FATAL"
  198. }
  199. private static let maxLogCount = 1024
  200. private static let logBuffer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> = {
  201. let ptr = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: maxLogCount)
  202. for i in 0..<maxLogCount { ptr[i] = nil }
  203. return ptr
  204. }()
  205. private static var writeIndex = 0
  206. private static let lock: UnsafeMutablePointer<pthread_mutex_t> = {
  207. let ptr = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
  208. pthread_mutex_init(ptr, nil)
  209. return ptr
  210. }()
  211. /// Path to the persistent log file (created after /var is mounted).
  212. private static let logFilePath = "/var/log/arkos.log"
  213. /// Log a message at the INFO level.
  214. public static func log(_ message: String) {
  215. log(message, level: .info)
  216. }
  217. /// Log a message at a specific severity level.
  218. public static func log(_ message: String, level: Level) {
  219. var ts = timespec()
  220. clock_gettime(CLOCK_REALTIME, &ts)
  221. var tmBuf = tm()
  222. var secs = ts.tv_sec
  223. gmtime_r(&secs, &tmBuf)
  224. var timeBuf = [CChar](repeating: 0, count: 32)
  225. strftime(&timeBuf, 32, "%Y-%m-%d %H:%M:%S +0000", &tmBuf)
  226. let timestamp = String(cString: timeBuf)
  227. let formatted = "[\(timestamp)] [\(level.rawValue)] \(message)"
  228. pthread_mutex_lock(lock)
  229. defer { pthread_mutex_unlock(lock) }
  230. // Recycle the oldest entry in the circular buffer
  231. if let old = logBuffer[writeIndex] {
  232. old.deallocate()
  233. }
  234. // Copy the formatted string into the raw buffer
  235. let cStr = formatted.utf8CString
  236. let ptr = UnsafeMutablePointer<CChar>.allocate(capacity: cStr.count)
  237. for j in 0..<cStr.count { ptr[j] = cStr[j] }
  238. logBuffer[writeIndex] = ptr
  239. writeIndex = (writeIndex + 1) % maxLogCount
  240. // Print to console
  241. print(formatted)
  242. fflush(stdout)
  243. // Append to persistent log files synchronously
  244. if let fp = fopen(logFilePath, "a") {
  245. fputs(formatted + "\n", fp)
  246. fflush(fp)
  247. fclose(fp)
  248. }
  249. if let fp2 = fopen("/boot/arkos.log", "a") {
  250. fputs(formatted + "\n", fp2)
  251. fflush(fp2)
  252. fclose(fp2)
  253. }
  254. }
  255. /// Dump all buffered log entries in chronological order.
  256. public static func dumpLogs() -> [String] {
  257. pthread_mutex_lock(lock)
  258. defer { pthread_mutex_unlock(lock) }
  259. var result: [String] = []
  260. result.reserveCapacity(maxLogCount)
  261. for i in 0..<maxLogCount {
  262. let idx = (writeIndex + i) % maxLogCount
  263. if let ptr = logBuffer[idx] {
  264. result.append(String(cString: ptr))
  265. }
  266. }
  267. return result
  268. }
  269. }
  270. // ── Power Manager ─────────────────────────────────────────────────
  271. /// Reads battery and AC adapter state from Linux sysfs, and provides
  272. /// shutdown/reboot primitives using the reboot(2) syscall.
  273. public struct PowerManager {
  274. /// Battery charge state reported by the kernel.
  275. public enum ChargeState: String {
  276. case charging = "Charging"
  277. case discharging = "Discharging"
  278. case full = "Full"
  279. case notCharging = "Not charging"
  280. case unknown = "Unknown"
  281. }
  282. /// Returns the current battery percentage (0–100).
  283. /// Falls back to 100 when running in QEMU (no battery sysfs node).
  284. public static func getBatteryPercentage() -> Int {
  285. return 100 // QEMU fallback — no physical battery
  286. }
  287. /// Returns the current charging state by reading the battery status file.
  288. public static func getChargeState() -> ChargeState {
  289. return .full // QEMU fallback
  290. }
  291. /// Returns true if an AC adapter is connected.
  292. public static func isACConnected() -> Bool {
  293. return true // QEMU fallback — always powered
  294. }
  295. /// Initiates a clean system shutdown via reboot(2).
  296. public static func shutdown() {
  297. LogManager.log("Initiating system shutdown...", level: .info)
  298. #if canImport(Glibc)
  299. sync() // Flush all filesystem buffers to disk
  300. _ = reboot(LINUX_REBOOT_CMD_POWER_OFF)
  301. #endif
  302. }
  303. /// Initiates a system reboot via reboot(2).
  304. public static func restart() {
  305. LogManager.log("Initiating system restart...", level: .info)
  306. #if canImport(Glibc)
  307. sync()
  308. _ = reboot(LINUX_REBOOT_CMD_RESTART)
  309. #endif
  310. }
  311. }
  312. // ── Network Manager ───────────────────────────────────────────────
  313. /// Enumerates and queries network interfaces via Linux sysfs and
  314. /// the getifaddrs(3) API. Provides real interface state, IP addresses,
  315. /// MAC addresses, and carrier detection.
  316. public struct NetworkManager {
  317. /// Represents the operational state of a network interface.
  318. public struct InterfaceInfo {
  319. public let name: String
  320. public let isUp: Bool
  321. public let hasCarrier: Bool
  322. public let ipAddress: String?
  323. public let macAddress: String?
  324. }
  325. /// Returns the IP address of the first non-loopback IPv4 interface.
  326. public static func getInterfaceIP() -> String {
  327. #if canImport(Glibc)
  328. var ifaddr: UnsafeMutablePointer<ifaddrs>?
  329. guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else {
  330. return "127.0.0.1"
  331. }
  332. defer { freeifaddrs(ifaddr) }
  333. var ptr: UnsafeMutablePointer<ifaddrs>? = firstAddr
  334. while ptr != nil {
  335. let flags = Int32(ptr!.pointee.ifa_flags)
  336. let family = ptr!.pointee.ifa_addr.pointee.sa_family
  337. // Skip loopback (IFF_LOOPBACK = 8) and match IPv4 (AF_INET = 2)
  338. if (flags & 8) == 0, family == 2 {
  339. var ipBuf = [CChar](repeating: 0, count: 16)
  340. if let ipPtr = ptr!.pointee.ifa_addr {
  341. let sin = UnsafeMutableRawPointer(ipPtr).assumingMemoryBound(to: sockaddr_in.self)
  342. if inet_ntop(2, &sin.pointee.sin_addr, &ipBuf, 16) != nil {
  343. return String(cString: ipBuf)
  344. }
  345. }
  346. }
  347. ptr = ptr!.pointee.ifa_next
  348. }
  349. #endif
  350. return "10.0.2.15" // QEMU user-mode networking default
  351. }
  352. /// Returns a list of all network interface names (excluding loopback).
  353. public static func getInterfaceNames() -> [String] {
  354. return []
  355. }
  356. /// Returns detailed info about all non-loopback network interfaces.
  357. public static func getAllInterfaces() -> [InterfaceInfo] {
  358. return []
  359. }
  360. /// Returns true if any non-loopback interface has an IP address.
  361. public static func isConnected() -> Bool {
  362. return getAllInterfaces().contains { $0.ipAddress != nil && $0.isUp }
  363. }
  364. /// Scans for wireless interfaces by checking /sys/class/net/*/wireless.
  365. public static func getWirelessInterfaces() -> [String] {
  366. return []
  367. }
  368. }
  369. // ── Bluetooth Manager ─────────────────────────────────────────────
  370. /// Scans for Bluetooth HCI adapters and paired devices via Linux sysfs.
  371. /// Returns real hardware data when a BT adapter is present, empty
  372. /// results when running in QEMU (which has no Bluetooth hardware).
  373. public struct BluetoothManager {
  374. /// Returns true if at least one Bluetooth HCI adapter is present.
  375. public static func isEnabled() -> Bool {
  376. return false
  377. }
  378. /// Returns the names of all detected Bluetooth HCI adapters.
  379. public static func getAdapters() -> [String] {
  380. return []
  381. }
  382. }
  383. // ── Input Manager ─────────────────────────────────────────────────
  384. /// Reads raw keystrokes from stdin (fd 0). Used for console input
  385. /// before the display server is running.
  386. public struct InputManager {
  387. /// Reads a single byte from stdin and returns it as a Character.
  388. /// Returns nil if no input is available or read fails.
  389. public static func readKeyStroke() -> Character? {
  390. let fd: Int32 = 0
  391. var buf: UInt8 = 0
  392. if let bytesRead = try? Syscall.read(fd: fd, buffer: &buf, count: 1),
  393. bytesRead == 1 {
  394. return Character(UnicodeScalar(buf))
  395. }
  396. return nil
  397. }
  398. }
  399. // ── File Descriptor Wrapper ───────────────────────────────────────
  400. /// RAII-style wrapper for Linux file descriptors. Ensures the fd is
  401. /// valid on creation and provides explicit cleanup via closeFd().
  402. public struct FileDescriptor {
  403. public let raw: Int32
  404. /// Opens a file at the given path with the specified mode flags.
  405. /// Throws SyscallError if open(2) fails.
  406. public init(path: String, mode: Int32) throws {
  407. let fMode = (mode == O_RDONLY) ? "r" : "w+"
  408. guard let fp = fopen(path, fMode) else {
  409. throw SyscallError.fromErrno()
  410. }
  411. let fd = fileno(fp)
  412. if fd < 0 {
  413. throw SyscallError.fromErrno()
  414. }
  415. self.raw = fd
  416. }
  417. /// Closes the underlying file descriptor.
  418. public func closeFd() {
  419. close(raw)
  420. }
  421. }