| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- //
- // 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 CSystem
- #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. ArkFileDescriptor — 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(CSystem.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(CSystem.write(fd, buffer, count))
- if result < 0 {
- throw SyscallError.fromErrno()
- }
- return result
- }
- }
- // ── 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, component: String = "ark.log") {
- log(message, level: .info, component: component)
- }
- /// Log a message at a specific severity level.
- public static func log(_ message: String, level: Level, component: String = "ark.log") {
- 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)] [\(component)] \(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
- let fd1 = ark_open(logFilePath, O_CREAT | O_WRONLY | O_APPEND, 0o666)
- if fd1 >= 0 {
- let line = formatted + "\n"
- line.withCString { ptr in
- CSystem.write(fd1, ptr, strlen(ptr))
- }
- fsync(fd1)
- close(fd1)
- }
- let fd2 = ark_open("/boot/arkos.log", O_CREAT | O_WRONLY | O_APPEND, 0o666)
- if fd2 >= 0 {
- let line = formatted + "\n"
- line.withCString { ptr in
- CSystem.write(fd2, ptr, strlen(ptr))
- }
- fsync(fd2)
- close(fd2)
- }
- }
- /// 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, component: "ark.kernel.utils")
- #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, component: "ark.kernel.utils")
- #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 ArkFileDescriptor {
- 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 = ark_open2(path, mode)
- if fd < 0 {
- throw SyscallError.fromErrno()
- }
- self.raw = fd
- }
- /// Closes the underlying file descriptor.
- public func closeFd() {
- close(raw)
- }
- }
|