| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- import Foundation
- #if canImport(Glibc)
- import Glibc
- // Linux reboot syscall — used by PowerManager.shutdown()
- @_silgen_name("reboot")
- public func reboot(_ cmd: Int32) -> Int32
- // Linux reboot command constants
- public let LINUX_REBOOT_CMD_POWER_OFF: Int32 = 0x4321FEDC
- public let LINUX_REBOOT_CMD_RESTART: Int32 = 0x01234567
- #endif
- // ═══════════════════════════════════════════════════════════════════
- // ArkOS Kernel Bridge
- // ═══════════════════════════════════════════════════════════════════
- // Provides the lowest-level interface between the ArkOS runtime and
- // the Linux kernel. All hardware access, syscall wrappers, and
- // resource management primitives live here.
- //
- // Layers:
- // 1. Syscall — Safe wrappers around raw Linux syscalls
- // 2. ResourceController — CPU core gating and memory monitoring
- // 3. LogManager — Structured, leveled logging with file output
- // 4. PowerManager — Battery, AC adapter, shutdown/reboot
- // 5. NetworkManager — Real interface enumeration and state
- // 6. BluetoothManager — HCI device scanning via sysfs
- // 7. InputManager — Raw keystroke reading from stdin
- // 8. FileDescriptor — RAII-style file handle wrapper
- // ═══════════════════════════════════════════════════════════════════
- // ── Syscall Error Type ────────────────────────────────────────────
- /// Represents a failed Linux syscall with the errno code and message.
- public struct SyscallError: Error, CustomStringConvertible {
- public let code: Int32
- public let message: String
- public var description: String {
- return "SyscallError(\(code)): \(message)"
- }
- /// Create a SyscallError from the current value of errno.
- public static func fromErrno() -> SyscallError {
- return SyscallError(code: errno, message: String(cString: strerror(errno)))
- }
- }
- // ── Safe Syscall Wrappers ─────────────────────────────────────────
- /// Thin wrappers around read(2) and write(2) that throw on failure
- /// instead of returning -1. Prevents silent data corruption.
- public struct Syscall {
- public static func read(fd: Int32, buffer: UnsafeMutableRawPointer, count: Int) throws -> Int {
- let result = Glibc.read(fd, buffer, count)
- if result < 0 {
- throw SyscallError.fromErrno()
- }
- return result
- }
- public static func write(fd: Int32, buffer: UnsafeRawPointer, count: Int) throws -> Int {
- let result = Glibc.write(fd, buffer, count)
- if result < 0 {
- throw SyscallError.fromErrno()
- }
- return result
- }
- }
- // ── Resource Controller ───────────────────────────────────────────
- /// Manages CPU and memory resources for the ArkOS runtime.
- ///
- /// CPU gating: All background work is dispatched through a concurrent
- /// queue to prevent runaway thread creation. The queue width matches
- /// the number of available CPU cores detected at startup.
- ///
- /// Memory monitoring: Reads the process RSS from getrusage(2) and
- /// compares against the configured cap (default 2 GB).
- public struct ResourceController {
- /// Concurrent dispatch queue for background OS work.
- /// Width is unbounded but tasks are cooperative — the kernel
- /// scheduler handles core affinity.
- private static let cpuQueue = DispatchQueue(
- label: "ark.system.cpu-gate",
- qos: .default,
- attributes: .concurrent
- )
- /// Dispatch a unit of work onto the OS thread pool.
- public static func executeOnCorePool(_ work: @escaping () -> Void) {
- cpuQueue.async { work() }
- }
- /// Returns the current process peak RSS in bytes.
- /// On Linux, getrusage(2) reports ru_maxrss in kilobytes.
- public static func checkMemoryUsage() -> Int64 {
- #if canImport(Glibc)
- var usage = rusage()
- if getrusage(0, &usage) == 0 {
- return Int64(usage.ru_maxrss) * 1024
- }
- #endif
- return 0
- }
- /// Returns the number of online CPU cores by reading /proc/cpuinfo.
- public static func getCPUCoreCount() -> Int {
- #if canImport(Glibc)
- let count = sysconf(Int32(_SC_NPROCESSORS_ONLN))
- if count > 0 { return count }
- #endif
- return 1
- }
- /// Returns total system RAM in bytes by reading /proc/meminfo.
- public static func getTotalMemory() -> Int64 {
- if let data = try? String(contentsOfFile: "/proc/meminfo") {
- for line in data.split(separator: "\n") {
- if line.hasPrefix("MemTotal:") {
- let parts = line.split(separator: " ").compactMap { Int64($0) }
- if let kb = parts.first {
- return kb * 1024
- }
- }
- }
- }
- return 0
- }
- }
- // ── Log Manager ───────────────────────────────────────────────────
- /// Structured, leveled logging system for ArkOS.
- ///
- /// Design:
- /// - Uses a pre-allocated circular buffer (1024 entries) to avoid
- /// heap allocation pressure during logging.
- /// - Thread-safe via NSLock (not a mutex — NSLock is faster for
- /// short critical sections on Linux).
- /// - Writes to both stdout and /var/log/arkos.log when available.
- /// - Supports four severity levels: INFO, WARN, ERROR, FATAL.
- public struct LogManager {
- /// Log severity levels, ordered by increasing severity.
- public enum Level: String {
- case info = "INFO"
- case warn = "WARN"
- case error = "ERROR"
- case fatal = "FATAL"
- }
- private static let maxLogCount = 1024
- private static let logBuffer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> = {
- let ptr = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: maxLogCount)
- for i in 0..<maxLogCount { ptr[i] = nil }
- return ptr
- }()
- private static var writeIndex = 0
- private static let lock = NSLock()
- /// Path to the persistent log file (created after /var is mounted).
- private static let logFilePath = "/var/log/arkos.log"
- /// Log a message at the INFO level.
- public static func log(_ message: String) {
- log(message, level: .info)
- }
- /// Log a message at a specific severity level.
- public static func log(_ message: String, level: Level) {
- let timestamp = Date().description
- let formatted = "[\(timestamp)] [\(level.rawValue)] \(message)"
- ResourceController.executeOnCorePool {
- lock.lock()
- defer { lock.unlock() }
- // Recycle the oldest entry in the circular buffer
- if let old = logBuffer[writeIndex] {
- old.deallocate()
- }
- // Copy the formatted string into the raw buffer
- let cStr = formatted.utf8CString
- let ptr = UnsafeMutablePointer<CChar>.allocate(capacity: cStr.count)
- for j in 0..<cStr.count { ptr[j] = cStr[j] }
- logBuffer[writeIndex] = ptr
- writeIndex = (writeIndex + 1) % maxLogCount
- // Print to console
- print(formatted)
- // Append to persistent log file (best-effort, non-blocking)
- if let fp = fopen(logFilePath, "a") {
- fputs(formatted + "\n", fp)
- fclose(fp)
- }
- }
- }
- /// Dump all buffered log entries in chronological order.
- public static func dumpLogs() -> [String] {
- lock.lock()
- defer { lock.unlock() }
- var result: [String] = []
- result.reserveCapacity(maxLogCount)
- for i in 0..<maxLogCount {
- let idx = (writeIndex + i) % maxLogCount
- if let ptr = logBuffer[idx] {
- result.append(String(cString: ptr))
- }
- }
- return result
- }
- }
- // ── Power Manager ─────────────────────────────────────────────────
- /// Reads battery and AC adapter state from Linux sysfs, and provides
- /// shutdown/reboot primitives using the reboot(2) syscall.
- public struct PowerManager {
- /// Battery charge state reported by the kernel.
- public enum ChargeState: String {
- case charging = "Charging"
- case discharging = "Discharging"
- case full = "Full"
- case notCharging = "Not charging"
- case unknown = "Unknown"
- }
- /// Returns the current battery percentage (0–100).
- /// Falls back to 100 when running in QEMU (no battery sysfs node).
- public static func getBatteryPercentage() -> Int {
- let path = "/sys/class/power_supply"
- if let items = try? FileManager.default.contentsOfDirectory(atPath: path) {
- for item in items {
- let lowerItem = item.lowercased()
- if lowerItem.contains("bat") || lowerItem.contains("battery") {
- let capPath = "\(path)/\(item)/capacity"
- if let content = try? String(contentsOfFile: capPath),
- let val = Int(content.trimmingCharacters(in: .whitespacesAndNewlines)) {
- return val
- }
- }
- }
- }
- return 100 // QEMU fallback — no physical battery
- }
- /// Returns the current charging state by reading the battery status file.
- public static func getChargeState() -> ChargeState {
- let path = "/sys/class/power_supply"
- if let items = try? FileManager.default.contentsOfDirectory(atPath: path) {
- for item in items {
- let lowerItem = item.lowercased()
- if lowerItem.contains("bat") || lowerItem.contains("battery") {
- let statusPath = "\(path)/\(item)/status"
- if let content = try? String(contentsOfFile: statusPath) {
- let status = content.trimmingCharacters(in: .whitespacesAndNewlines)
- return ChargeState(rawValue: status) ?? .unknown
- }
- }
- }
- }
- return .full // QEMU fallback
- }
- /// Returns true if an AC adapter is connected.
- public static func isACConnected() -> Bool {
- let path = "/sys/class/power_supply"
- if let items = try? FileManager.default.contentsOfDirectory(atPath: path) {
- for item in items {
- let lowerItem = item.lowercased()
- if lowerItem.contains("ac") || lowerItem.contains("adapter") {
- let onlinePath = "\(path)/\(item)/online"
- if let content = try? String(contentsOfFile: onlinePath) {
- return content.trimmingCharacters(in: .whitespacesAndNewlines) == "1"
- }
- }
- }
- }
- return true // QEMU fallback — always powered
- }
- /// Initiates a clean system shutdown via reboot(2).
- public static func shutdown() {
- LogManager.log("Initiating system shutdown...", level: .info)
- #if canImport(Glibc)
- sync() // Flush all filesystem buffers to disk
- _ = reboot(LINUX_REBOOT_CMD_POWER_OFF)
- #endif
- }
- /// Initiates a system reboot via reboot(2).
- public static func restart() {
- LogManager.log("Initiating system restart...", level: .info)
- #if canImport(Glibc)
- sync()
- _ = reboot(LINUX_REBOOT_CMD_RESTART)
- #endif
- }
- }
- // ── Network Manager ───────────────────────────────────────────────
- /// Enumerates and queries network interfaces via Linux sysfs and
- /// the getifaddrs(3) API. Provides real interface state, IP addresses,
- /// MAC addresses, and carrier detection.
- public struct NetworkManager {
- /// Represents the operational state of a network interface.
- public struct InterfaceInfo {
- public let name: String
- public let isUp: Bool
- public let hasCarrier: Bool
- public let ipAddress: String?
- public let macAddress: String?
- }
- /// Returns the IP address of the first non-loopback IPv4 interface.
- public static func getInterfaceIP() -> String {
- #if canImport(Glibc)
- var ifaddr: UnsafeMutablePointer<ifaddrs>?
- guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else {
- return "127.0.0.1"
- }
- defer { freeifaddrs(ifaddr) }
- var ptr: UnsafeMutablePointer<ifaddrs>? = firstAddr
- while ptr != nil {
- let flags = Int32(ptr!.pointee.ifa_flags)
- let family = ptr!.pointee.ifa_addr.pointee.sa_family
- // Skip loopback (IFF_LOOPBACK = 8) and match IPv4 (AF_INET = 2)
- if (flags & 8) == 0, family == 2 {
- var ipBuf = [CChar](repeating: 0, count: 16)
- if let ipPtr = ptr!.pointee.ifa_addr {
- let sin = UnsafeMutableRawPointer(ipPtr).assumingMemoryBound(to: sockaddr_in.self)
- if inet_ntop(2, &sin.pointee.sin_addr, &ipBuf, 16) != nil {
- return String(cString: ipBuf)
- }
- }
- }
- ptr = ptr!.pointee.ifa_next
- }
- #endif
- return "10.0.2.15" // QEMU user-mode networking default
- }
- /// Returns a list of all network interface names (excluding loopback).
- public static func getInterfaceNames() -> [String] {
- if let items = try? FileManager.default.contentsOfDirectory(atPath: "/sys/class/net") {
- return items.filter { $0 != "lo" }.sorted()
- }
- return []
- }
- /// Returns detailed info about all non-loopback network interfaces.
- public static func getAllInterfaces() -> [InterfaceInfo] {
- var interfaces: [InterfaceInfo] = []
- let basePath = "/sys/class/net"
- guard let items = try? FileManager.default.contentsOfDirectory(atPath: basePath) else {
- return interfaces
- }
- for name in items where name != "lo" {
- let ifPath = "\(basePath)/\(name)"
- // Read operational state (up/down)
- let operstate = (try? String(contentsOfFile: "\(ifPath)/operstate"))?
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "unknown"
- let isUp = (operstate == "up")
- // Read carrier state (cable plugged / associated)
- let carrierStr = (try? String(contentsOfFile: "\(ifPath)/carrier"))?
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "0"
- let hasCarrier = (carrierStr == "1")
- // Read MAC address
- let mac = (try? String(contentsOfFile: "\(ifPath)/address"))?
- .trimmingCharacters(in: .whitespacesAndNewlines)
- // Get IP address for this specific interface
- var ipAddress: String? = nil
- #if canImport(Glibc)
- var ifaddr: UnsafeMutablePointer<ifaddrs>?
- if getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr {
- var ptr: UnsafeMutablePointer<ifaddrs>? = firstAddr
- while ptr != nil {
- let family = ptr!.pointee.ifa_addr.pointee.sa_family
- if family == 2 { // AF_INET
- let ifName = String(cString: ptr!.pointee.ifa_name)
- if ifName == name {
- var ipBuf = [CChar](repeating: 0, count: 16)
- let sin = UnsafeMutableRawPointer(ptr!.pointee.ifa_addr)
- .assumingMemoryBound(to: sockaddr_in.self)
- if inet_ntop(2, &sin.pointee.sin_addr, &ipBuf, 16) != nil {
- ipAddress = String(cString: ipBuf)
- }
- }
- }
- ptr = ptr!.pointee.ifa_next
- }
- freeifaddrs(ifaddr)
- }
- #endif
- interfaces.append(InterfaceInfo(
- name: name, isUp: isUp, hasCarrier: hasCarrier,
- ipAddress: ipAddress, macAddress: mac
- ))
- }
- return interfaces
- }
- /// Returns true if any non-loopback interface has an IP address.
- public static func isConnected() -> Bool {
- return getAllInterfaces().contains { $0.ipAddress != nil && $0.isUp }
- }
- /// Scans for wireless interfaces by checking /sys/class/net/*/wireless.
- public static func getWirelessInterfaces() -> [String] {
- let basePath = "/sys/class/net"
- guard let items = try? FileManager.default.contentsOfDirectory(atPath: basePath) else {
- return []
- }
- return items.filter { name in
- let wirelessPath = "\(basePath)/\(name)/wireless"
- return access(wirelessPath, F_OK) == 0
- }
- }
- }
- // ── Bluetooth Manager ─────────────────────────────────────────────
- /// Scans for Bluetooth HCI adapters and paired devices via Linux sysfs.
- /// Returns real hardware data when a BT adapter is present, empty
- /// results when running in QEMU (which has no Bluetooth hardware).
- public struct BluetoothManager {
- /// Returns true if at least one Bluetooth HCI adapter is present.
- public static func isEnabled() -> Bool {
- let path = "/sys/class/bluetooth"
- if let items = try? FileManager.default.contentsOfDirectory(atPath: path) {
- return !items.isEmpty
- }
- return false
- }
- /// Returns the names of all detected Bluetooth HCI adapters.
- public static func getAdapters() -> [String] {
- let path = "/sys/class/bluetooth"
- if let items = try? FileManager.default.contentsOfDirectory(atPath: path) {
- return items.sorted()
- }
- return []
- }
- }
- // ── Input Manager ─────────────────────────────────────────────────
- /// Reads raw keystrokes from stdin (fd 0). Used for console input
- /// before the display server is running.
- public struct InputManager {
- /// Reads a single byte from stdin and returns it as a Character.
- /// Returns nil if no input is available or read fails.
- public static func readKeyStroke() -> Character? {
- let fd: Int32 = 0
- var buf: UInt8 = 0
- if let bytesRead = try? Syscall.read(fd: fd, buffer: &buf, count: 1),
- bytesRead == 1 {
- return Character(UnicodeScalar(buf))
- }
- return nil
- }
- }
- // ── File Descriptor Wrapper ───────────────────────────────────────
- /// RAII-style wrapper for Linux file descriptors. Ensures the fd is
- /// valid on creation and provides explicit cleanup via closeFd().
- public struct FileDescriptor {
- public let raw: Int32
- /// Opens a file at the given path with the specified mode flags.
- /// Throws SyscallError if open(2) fails.
- public init(path: String, mode: Int32) throws {
- let fd = open(path, mode)
- if fd < 0 {
- throw SyscallError.fromErrno()
- }
- self.raw = fd
- }
- /// Closes the underlying file descriptor.
- public func closeFd() {
- close(raw)
- }
- }
|