| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- //
- // 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<Result>(_ 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..<min(pathBytes.count, 108) {
- rawPtr[i] = pathBytes[i]
- }
- }
- // Bind the socket to the filesystem path
- let addrSize = MemoryLayout<sockaddr_un>.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<sockaddr_un>.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<UInt8>.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
- }
- }
- }
- }
- }
- }
|