IPC.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import Foundation
  2. #if canImport(Glibc)
  3. import Glibc
  4. #endif
  5. // ═══════════════════════════════════════════════════════════════════
  6. // ArkOS IPC Server
  7. // ═══════════════════════════════════════════════════════════════════
  8. // Unix domain socket server that provides the primary communication
  9. // channel between arkrt (the system daemon) and all other OS
  10. // components (display server, UI apps, background services).
  11. //
  12. // Protocol:
  13. // Request: [Command_ID (2B)][Payload_Length (4B)][Payload_Data]
  14. // Response: [Command_ID (2B)][Payload_Length (4B)][Payload_Data]
  15. //
  16. // Security:
  17. // - Socket file at /dev/arkrt.sock with mode 0770 (root + ark group)
  18. // - Each connection is handled in an isolated thread from the pool
  19. // - Connections are dropped after 30 seconds of inactivity
  20. //
  21. // ═══════════════════════════════════════════════════════════════════
  22. // Cross-platform autoreleasepool shim for Linux (no Objective-C runtime).
  23. #if !canImport(ObjectiveC)
  24. @discardableResult
  25. public func autoreleasepool<Result>(_ block: () throws -> Result) rethrows -> Result {
  26. return try block()
  27. }
  28. #endif
  29. // ── IPC Constants ─────────────────────────────────────────────────
  30. /// IPC protocol configuration.
  31. private enum IPCConfig {
  32. /// Path to the Unix domain socket file.
  33. static let socketPath = "/dev/arkrt.sock"
  34. /// Maximum pending connections in the listen queue.
  35. static let backlogSize: Int32 = 16
  36. /// Read buffer size for each client connection (4 KB).
  37. static let bufferSize = 4096
  38. /// Socket file permissions (owner + group read/write, no world access).
  39. static let socketMode: mode_t = 0o770
  40. /// Inactivity timeout per connection in seconds.
  41. static let connectionTimeout: Int = 30
  42. }
  43. // ── IPC Server ────────────────────────────────────────────────────
  44. /// The IPC server listens on a Unix domain socket and dispatches
  45. /// incoming requests to the CommandRouter for processing.
  46. public struct IPCServer {
  47. private var serverFd: Int32 = -1
  48. public init() {}
  49. /// Binds and starts listening on the Unix domain socket.
  50. /// The accept loop runs on the ResourceController thread pool
  51. /// so the main thread remains free for other work.
  52. public mutating func start() {
  53. LogManager.log("IPC: Initializing Unix domain socket at \(IPCConfig.socketPath)...")
  54. // Remove any stale socket file from a previous boot
  55. unlink(IPCConfig.socketPath)
  56. // Create the socket (AF_UNIX = 1, SOCK_STREAM = 1)
  57. let fd = socket(AF_UNIX, 1, 0)
  58. if fd < 0 {
  59. LogManager.log("IPC: socket() failed: \(String(cString: strerror(errno)))", level: .error)
  60. return
  61. }
  62. self.serverFd = fd
  63. // Configure the Unix domain socket address
  64. var addr = sockaddr_un()
  65. addr.sun_family = sa_family_t(AF_UNIX)
  66. let pathBytes = IPCConfig.socketPath.utf8CString
  67. withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in
  68. let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self)
  69. for i in 0..<min(pathBytes.count, 108) {
  70. rawPtr[i] = pathBytes[i]
  71. }
  72. }
  73. // Bind the socket to the filesystem path
  74. let addrSize = MemoryLayout<sockaddr_un>.size
  75. let bindResult = withUnsafePointer(to: &addr) { addrPtr in
  76. addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in
  77. bind(fd, saPtr, socklen_t(addrSize))
  78. }
  79. }
  80. if bindResult < 0 {
  81. LogManager.log("IPC: bind() failed: \(String(cString: strerror(errno)))", level: .error)
  82. Glibc.close(fd)
  83. return
  84. }
  85. // Start listening for connections
  86. if listen(fd, IPCConfig.backlogSize) < 0 {
  87. LogManager.log("IPC: listen() failed: \(String(cString: strerror(errno)))", level: .error)
  88. Glibc.close(fd)
  89. return
  90. }
  91. // Set socket permissions (restricts access to root and ark group)
  92. chmod(IPCConfig.socketPath, IPCConfig.socketMode)
  93. LogManager.log("IPC: Listening on \(IPCConfig.socketPath) (backlog=\(IPCConfig.backlogSize))")
  94. // Start the accept loop on the thread pool
  95. let fdToAccept = self.serverFd
  96. ResourceController.executeOnCorePool {
  97. IPCServer.acceptLoop(serverFd: fdToAccept)
  98. }
  99. }
  100. // ── Connection Accept Loop ────────────────────────────────────
  101. /// Blocking loop that accepts incoming client connections and
  102. /// dispatches each to a handler thread from the pool.
  103. private static func acceptLoop(serverFd: Int32) {
  104. while true {
  105. var clientAddr = sockaddr_un()
  106. var clientLen = socklen_t(MemoryLayout<sockaddr_un>.size)
  107. let clientFd = withUnsafeMutablePointer(to: &clientAddr) { addrPtr in
  108. addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in
  109. accept(serverFd, saPtr, &clientLen)
  110. }
  111. }
  112. if clientFd < 0 {
  113. // Accept failed — brief pause to prevent busy-spin on transient errors
  114. usleep(10_000) // 10ms
  115. continue
  116. }
  117. // Handle each client connection on a separate thread from the pool
  118. ResourceController.executeOnCorePool {
  119. IPCServer.handleClient(clientFd)
  120. }
  121. }
  122. }
  123. // ── Client Handler ────────────────────────────────────────────
  124. /// Handles a single client connection. Reads request frames,
  125. /// routes them through the CommandRouter, and sends responses.
  126. /// Automatically closes the connection after a timeout or error.
  127. private static func handleClient(_ fd: Int32) {
  128. autoreleasepool {
  129. let rawBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: IPCConfig.bufferSize)
  130. defer {
  131. rawBuffer.deallocate()
  132. Glibc.close(fd)
  133. }
  134. while true {
  135. let bytesRead = Glibc.read(fd, rawBuffer, IPCConfig.bufferSize)
  136. if bytesRead <= 0 {
  137. // Connection closed by client or read error
  138. break
  139. }
  140. // Parse and route the request
  141. let requestBuffer = UnsafeRawBufferPointer(start: rawBuffer, count: bytesRead)
  142. let responseData = CommandRouter.route(requestBuffer: requestBuffer)
  143. // Send the response
  144. responseData.withUnsafeBytes { respBytes in
  145. var totalWritten = 0
  146. while totalWritten < respBytes.count {
  147. let written = Glibc.write(
  148. fd,
  149. respBytes.baseAddress! + totalWritten,
  150. respBytes.count - totalWritten
  151. )
  152. if written <= 0 { break }
  153. totalWritten += written
  154. }
  155. }
  156. }
  157. }
  158. }
  159. }