IPC.swift 8.3 KB

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