@@ -25,10 +25,6 @@ import TerminalProgress
2525public struct ClientContainer : Sendable , Codable {
2626 static let serviceIdentifier = " com.apple.container.apiserver "
2727
28- private var sandboxClient : SandboxClient {
29- SandboxClient ( id: configuration. id, runtime: configuration. runtimeHandler)
30- }
31-
3228 /// Identifier of the container.
3329 public var id : String {
3430 configuration. id
@@ -58,14 +54,10 @@ public struct ClientContainer: Sendable, Codable {
5854 self . status = snapshot. status
5955 self . networks = snapshot. networks
6056 }
61-
62- public var initProcess : ClientProcess {
63- ClientProcessImpl ( containerId: self . id, client: self . sandboxClient)
64- }
6557}
6658
6759extension ClientContainer {
68- private static func newClient ( ) -> XPCClient {
60+ private static func newXPCClient ( ) -> XPCClient {
6961 XPCClient ( service: serviceIdentifier)
7062 }
7163
@@ -84,8 +76,8 @@ extension ClientContainer {
8476 kernel: Kernel
8577 ) async throws -> ClientContainer {
8678 do {
87- let client = Self . newClient ( )
88- let request = XPCMessage ( route: . createContainer )
79+ let client = Self . newXPCClient ( )
80+ let request = XPCMessage ( route: . containerCreate )
8981
9082 let data = try JSONEncoder ( ) . encode ( configuration)
9183 let kdata = try JSONEncoder ( ) . encode ( kernel)
@@ -107,8 +99,8 @@ extension ClientContainer {
10799
108100 public static func list( ) async throws -> [ ClientContainer ] {
109101 do {
110- let client = Self . newClient ( )
111- let request = XPCMessage ( route: . listContainer )
102+ let client = Self . newXPCClient ( )
103+ let request = XPCMessage ( route: . containerList )
112104
113105 let response = try await xpcSend (
114106 client: client,
@@ -145,16 +137,72 @@ extension ClientContainer {
145137
146138extension ClientContainer {
147139 public func bootstrap( stdio: [ FileHandle ? ] ) async throws -> ClientProcess {
148- let client = self . sandboxClient
149- try await client. bootstrap ( stdio: stdio)
150- return ClientProcessImpl ( containerId: self . id, client: self . sandboxClient)
140+ let request = XPCMessage ( route: . containerBootstrap)
141+ let client = Self . newXPCClient ( )
142+
143+ for (i, h) in stdio. enumerated ( ) {
144+ let key : XPCKeys = try {
145+ switch i {
146+ case 0 : . stdin
147+ case 1 : . stdout
148+ case 2 : . stderr
149+ default :
150+ throw ContainerizationError ( . invalidArgument, message: " invalid fd \( i) " )
151+ }
152+ } ( )
153+
154+ if let h {
155+ request. set ( key: key, value: h)
156+ }
157+ }
158+
159+ do {
160+ request. set ( key: . id, value: self . id)
161+ try await client. send ( request)
162+ return ClientProcessImpl ( containerId: self . id, xpcClient: client)
163+ } catch {
164+ throw ContainerizationError (
165+ . internalError,
166+ message: " failed to bootstrap container " ,
167+ cause: error
168+ )
169+ }
170+ }
171+
172+ public func kill( _ signal: Int32 ) async throws {
173+ do {
174+ let request = XPCMessage ( route: . containerKill)
175+ request. set ( key: . id, value: self . id)
176+ request. set ( key: . processIdentifier, value: self . id)
177+ request. set ( key: . signal, value: Int64 ( signal) )
178+
179+ let client = Self . newXPCClient ( )
180+ try await client. send ( request)
181+ } catch {
182+ throw ContainerizationError (
183+ . internalError,
184+ message: " failed to kill container " ,
185+ cause: error
186+ )
187+ }
151188 }
152189
153190 /// Stop the container and all processes currently executing inside.
154191 public func stop( opts: ContainerStopOptions = ContainerStopOptions . default) async throws {
155192 do {
156- let client = self . sandboxClient
157- try await client. stop ( options: opts)
193+ let client = Self . newXPCClient ( )
194+ let request = XPCMessage ( route: . containerStop)
195+ let data = try JSONEncoder ( ) . encode ( opts)
196+ request. set ( key: . id, value: self . id)
197+ request. set ( key: . stopOptions, value: data)
198+
199+ // Stop is somewhat more prone to hanging than other commands given it
200+ // has quite a bit of `wait()`'s down the chain to make sure the container actually
201+ // exited. To combat a potential hang, lets timeout if we don't return in a small
202+ // time period after the actual stop timeout sent via .stopOptions (the time
203+ // until we send SIGKILL after SIGTERM if the container still hasn't exited).
204+ let responseTimeout = Duration ( . seconds( Int64 ( opts. timeoutInSeconds + 3 ) ) )
205+ try await client. send ( request, responseTimeout: responseTimeout)
158206 } catch {
159207 throw ContainerizationError (
160208 . internalError,
@@ -167,8 +215,8 @@ extension ClientContainer {
167215 /// Delete the container along with any resources.
168216 public func delete( force: Bool = false ) async throws {
169217 do {
170- let client = XPCClient ( service : Self . serviceIdentifier )
171- let request = XPCMessage ( route: . deleteContainer )
218+ let client = Self . newXPCClient ( )
219+ let request = XPCMessage ( route: . containerDelete )
172220 request. set ( key: . id, value: self . id)
173221 request. set ( key: . forceDelete, value: force)
174222 try await client. send ( request)
@@ -180,46 +228,53 @@ extension ClientContainer {
180228 )
181229 }
182230 }
183- }
184231
185- extension ClientContainer {
186- /// Execute a new process inside a running container .
232+ /// Create a new process inside a running container. The process is in a
233+ /// created state and must still be started .
187234 public func createProcess(
188235 id: String ,
189236 configuration: ProcessConfiguration ,
190237 stdio: [ FileHandle ? ]
191238 ) async throws -> ClientProcess {
192239 do {
193- let client = self . sandboxClient
194- try await client. createProcess ( id, config: configuration, stdio: stdio)
195- return ClientProcessImpl ( containerId: self . id, processId: id, client: client)
196- } catch {
197- throw ContainerizationError (
198- . internalError,
199- message: " failed to exec in container " ,
200- cause: error
201- )
202- }
203- }
240+ let request = XPCMessage ( route: . containerCreateProcess)
241+ request. set ( key: . id, value: self . id)
242+ request. set ( key: . processIdentifier, value: id)
204243
205- /// Send or "kill" a signal to the initial process of the container.
206- /// Kill does not wait for the process to exit, it only delivers the signal.
207- public func kill( _ signal: Int32 ) async throws {
208- do {
209- let client = self . sandboxClient
210- try await client. kill ( self . id, signal: Int64 ( signal) )
244+ let data = try JSONEncoder ( ) . encode ( configuration)
245+ request. set ( key: . processConfig, value: data)
246+
247+ for (i, h) in stdio. enumerated ( ) {
248+ let key : XPCKeys = try {
249+ switch i {
250+ case 0 : . stdin
251+ case 1 : . stdout
252+ case 2 : . stderr
253+ default :
254+ throw ContainerizationError ( . invalidArgument, message: " invalid fd \( i) " )
255+ }
256+ } ( )
257+
258+ if let h {
259+ request. set ( key: key, value: h)
260+ }
261+ }
262+
263+ let client = Self . newXPCClient ( )
264+ try await client. send ( request)
265+ return ClientProcessImpl ( containerId: self . id, processId: id, xpcClient: client)
211266 } catch {
212267 throw ContainerizationError (
213268 . internalError,
214- message: " failed to kill container \( self . id ) " ,
269+ message: " failed to create process in container " ,
215270 cause: error
216271 )
217272 }
218273 }
219274
220275 public func logs( ) async throws -> [ FileHandle ] {
221276 do {
222- let client = XPCClient ( service : Self . serviceIdentifier )
277+ let client = Self . newXPCClient ( )
223278 let request = XPCMessage ( route: . containerLogs)
224279 request. set ( key: . id, value: self . id)
225280
@@ -242,15 +297,27 @@ extension ClientContainer {
242297 }
243298
244299 public func dial( _ port: UInt32 ) async throws -> FileHandle {
300+ let request = XPCMessage ( route: . containerDial)
301+ request. set ( key: . id, value: self . id)
302+ request. set ( key: . port, value: UInt64 ( port) )
303+
304+ let client = Self . newXPCClient ( )
305+ let response : XPCMessage
245306 do {
246- let client = self . sandboxClient
247- return try await client. dial ( port)
307+ response = try await client. send ( request)
248308 } catch {
249309 throw ContainerizationError (
250310 . internalError,
251- message: " failed to dial \( port) in container \( self . id ) " ,
311+ message: " failed to dial port \( port) on container " ,
252312 cause: error
253313 )
254314 }
315+ guard let fh = response. fileHandle ( key: . fd) else {
316+ throw ContainerizationError (
317+ . internalError,
318+ message: " failed to get fd for vsock port \( port) "
319+ )
320+ }
321+ return fh
255322 }
256323}
0 commit comments