// // 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 // ═══════════════════════════════════════════════════════════════════ // ArkOS IPC Server // ═══════════════════════════════════════════════════════════════════ // Unix domain socket server that provides the primary communication // channel between arkrt (the system daemon) and all other OS // components (display server, UI apps, background services). // // Protocol: // Request: [Command_ID (2B)][Payload_Length (4B)][Payload_Data] // Response: [Command_ID (2B)][Payload_Length (4B)][Payload_Data] // // Security: // - Socket file at /dev/arkrt.sock with mode 0770 (root + ark group) // - Each connection is handled in an isolated thread from the pool // - Connections are dropped after 30 seconds of inactivity // // ═══════════════════════════════════════════════════════════════════ // Cross-platform autoreleasepool shim for Linux (no Objective-C runtime). #if !canImport(ObjectiveC) @discardableResult public func autoreleasepool(_ block: () throws -> Result) rethrows -> Result { return try block() } #endif // ── IPC Constants ───────────────────────────────────────────────── /// IPC protocol configuration. private enum IPCConfig { /// Path to the Unix domain socket file. static let socketPath = "/dev/arkrt.sock" /// Maximum pending connections in the listen queue. static let backlogSize: Int32 = 16 /// Read buffer size for each client connection (4 KB). static let bufferSize = 4096 /// Socket file permissions (owner + group read/write, no world access). public static let socketMode: mode_t = 0o770 } // ── IPC Server ──────────────────────────────────────────────────── /// The IPC server listens on a Unix domain socket and dispatches /// incoming requests to the CommandRouter for processing. public class IPCServer { private var serverFd: Int32 = -1 public init() {} /// Binds and starts listening on the Unix domain socket. /// The accept loop runs on the ResourceController thread pool /// so the main thread remains free for other work. public func start() { LogManager.log("Initializing Unix domain socket at \(IPCConfig.socketPath)...", component: "ark.sys.service") // Remove any stale socket file from a previous boot unlink(IPCConfig.socketPath) // Create the socket (AF_UNIX = 1, SOCK_STREAM = 1) #if canImport(Glibc) let fd = socket(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0) #else let fd = socket(AF_UNIX, Int32(SOCK_STREAM), 0) #endif if fd < 0 { LogManager.log("socket() failed: failed", level: .error, component: "ark.sys.service") return } self.serverFd = fd // Configure the Unix domain socket address var addr = sockaddr_un() addr.sun_family = sa_family_t(AF_UNIX) let pathBytes = IPCConfig.socketPath.utf8CString withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self) for i in 0...size let bindResult = withUnsafePointer(to: &addr) { addrPtr in addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in bind(fd, saPtr, socklen_t(addrSize)) } } if bindResult < 0 { LogManager.log("bind() failed: failed", level: .error, component: "ark.sys.service") close(fd) return } // Start listening for connections if listen(fd, IPCConfig.backlogSize) < 0 { LogManager.log("listen() failed: failed", level: .error, component: "ark.sys.service") close(fd) return } // Set socket permissions (restricts access to root and ark group) chmod(IPCConfig.socketPath, IPCConfig.socketMode) LogManager.log("Listening on \(IPCConfig.socketPath) (backlog=\(IPCConfig.backlogSize))", component: "ark.sys.service") // Start the accept loop on the thread pool let fdToAccept = self.serverFd ResourceController.executeOnCorePool { IPCServer.acceptLoop(serverFd: fdToAccept) } } // ── Connection Accept Loop ──────────────────────────────────── /// Blocking loop that accepts incoming client connections and /// dispatches each to a handler thread from the pool. private static func acceptLoop(serverFd: Int32) { while true { var clientAddr = sockaddr_un() var clientLen = socklen_t(MemoryLayout.size) let clientFd = withUnsafeMutablePointer(to: &clientAddr) { addrPtr in addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in accept(serverFd, saPtr, &clientLen) } } if clientFd < 0 { // Accept failed — brief pause to prevent busy-spin on transient errors usleep(10_000) // 10ms continue } // Handle each client connection on a separate thread from the pool ResourceController.executeOnCorePool { IPCServer.handleClient(clientFd) } } } // ── Client Handler ──────────────────────────────────────────── /// Handles a single client connection. Reads request frames, /// routes them through the CommandRouter, and sends responses. /// Automatically closes the connection after a timeout or error. private static func handleClient(_ fd: Int32) { autoreleasepool { let rawBuffer = UnsafeMutablePointer.allocate(capacity: IPCConfig.bufferSize) defer { rawBuffer.deallocate() close(fd) } while true { let bytesRead = read(fd, rawBuffer, IPCConfig.bufferSize) if bytesRead <= 0 { // Connection closed by client or read error break } // Parse and route the request let requestBuffer = UnsafeRawBufferPointer(start: rawBuffer, count: bytesRead) let responseData = CommandRouter.route(requestBuffer: requestBuffer) // Send the response responseData.withUnsafeBytes { respBytes in var totalWritten = 0 while totalWritten < respBytes.count { let written = write( fd, respBytes.baseAddress! + totalWritten, respBytes.count - totalWritten ) if written <= 0 { break } totalWritten += written } } } } } }