1
0

IPC.swift 8.3 KB

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