| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- //
- // Copyright 2026 Aarav Ravindra Kharade
- //
- // 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.
- //
- import ark.sys.libc
- #if canImport(Glibc) || canImport(Musl)
- // 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: ark_errno(), message: String(cString: strerror(ark_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 = Int(ark_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 = Int(ark_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 {
- /// Execute a unit of work on a new detached pthread.
- public static func executeOnCorePool(_ work: @escaping () -> Void) {
- // Box the closure into a heap-allocated context
- let ctx = UnsafeMutablePointer<(() -> Void)>.allocate(capacity: 1)
- ctx.initialize(to: work)
- var thread: pthread_t?
- let result = pthread_create(&thread, nil, { arg -> UnsafeMutableRawPointer? in
- let fn = arg!.assumingMemoryBound(to: (() -> Void).self)
- fn.pointee()
- fn.deinitialize(count: 1)
- fn.deallocate()
- return nil
- }, ctx)
- if result == 0 {
- if let t = thread {
- pthread_detach(t)
- }
- } else {
- ctx.deinitialize(count: 1)
- ctx.deallocate()
- }
- }
- /// 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 = readFileContents("/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
- }
- /// Returns available system RAM in bytes by reading /proc/meminfo.
- public static func getAvailableMemory() -> Int64 {
- if let data = readFileContents("/proc/meminfo") {
- for line in data.split(separator: "\n") {
- if line.hasPrefix("MemAvailable:") {
- let parts = line.split(separator: " ").compactMap { Int64($0) }
- if let kb = parts.first {
- return kb * 1024
- }
- }
- }
- }
- return 0
- }
- /// Returns used memory in bytes (total - available).
- public static func getUsedMemory() -> Int64 {
- let total = getTotalMemory()
- let available = getAvailableMemory()
- return max(0, total - available)
- }
- /// Returns the CPU usage as a percentage (0-100).
- /// Reads /proc/stat twice with a 100ms interval and computes the delta.
- public static func getCPUUsage() -> Double {
- func readCPUStat() -> (idle: Int64, total: Int64)? {
- guard let data = readFileContents("/proc/stat") else { return nil }
- let lines = data.split(separator: "\n")
- guard let cpuLine = lines.first(where: { $0.hasPrefix("cpu ") }) else { return nil }
- let fields = cpuLine.split(separator: " ").dropFirst().compactMap { Int64($0) }
- guard fields.count >= 4 else { return nil }
- let idle = fields[3] + (fields.count > 4 ? fields[4] : 0) // idle + iowait
- let total = fields.reduce(0, +)
- return (idle, total)
- }
- guard let first = readCPUStat() else { return 0.0 }
- usleep(100_000) // 100ms
- guard let second = readCPUStat() else { return 0.0 }
- let deltaIdle = second.idle - first.idle
- let deltaTotal = second.total - first.total
- guard deltaTotal > 0 else { return 0.0 }
- return Double(deltaTotal - deltaIdle) / Double(deltaTotal) * 100.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: UnsafeMutablePointer<pthread_mutex_t> = {
- let ptr = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
- pthread_mutex_init(ptr, nil)
- return ptr
- }()
- /// 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) {
- var ts = timespec()
- clock_gettime(CLOCK_REALTIME, &ts)
- var tmBuf = tm()
- var secs = ts.tv_sec
- gmtime_r(&secs, &tmBuf)
- var timeBuf = [CChar](repeating: 0, count: 32)
- strftime(&timeBuf, 32, "%Y-%m-%d %H:%M:%S +0000", &tmBuf)
- let timestamp = String(cString: timeBuf)
- let formatted = "[\(timestamp)] [\(level.rawValue)] \(message)"
- pthread_mutex_lock(lock)
- defer { pthread_mutex_unlock(lock) }
- // 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)
- fflush(stdout)
- // Append to persistent log files synchronously
- if let fp = fopen(logFilePath, "a") {
- fputs(formatted + "\n", fp)
- fflush(fp)
- fclose(fp)
- }
- if let fp2 = fopen("/boot/arkos.log", "a") {
- fputs(formatted + "\n", fp2)
- fflush(fp2)
- fclose(fp2)
- }
- }
- /// Dump all buffered log entries in chronological order.
- public static func dumpLogs() -> [String] {
- pthread_mutex_lock(lock)
- defer { pthread_mutex_unlock(lock) }
- 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 {
- return 100 // QEMU fallback — no physical battery
- }
- /// Returns the current charging state by reading the battery status file.
- public static func getChargeState() -> ChargeState {
- return .full // QEMU fallback
- }
- /// Returns true if an AC adapter is connected.
- public static func isACConnected() -> Bool {
- 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] {
- return []
- }
- /// Returns detailed info about all non-loopback network interfaces.
- public static func getAllInterfaces() -> [InterfaceInfo] {
- return []
- }
- /// 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] {
- return []
- }
- }
- // ── 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 {
- return false
- }
- /// Returns the names of all detected Bluetooth HCI adapters.
- public static func getAdapters() -> [String] {
- 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 fMode = (mode == O_RDONLY) ? "r" : "w+"
- guard let fp = fopen(path, fMode) else {
- throw SyscallError.fromErrno()
- }
- let fd = fileno(fp)
- if fd < 0 {
- throw SyscallError.fromErrno()
- }
- self.raw = fd
- }
- /// Closes the underlying file descriptor.
- public func closeFd() {
- close(raw)
- }
- }
|