Skip to content

Commit f254f48

Browse files
authored
LinuxContainer: Add ability to close stdin (#201)
This allows the user the ability to raise an EOF for the containers stdin. Today there's no way to close stdin so something as simple as "cat" and relying on EOF to move the process forward doesn't work
1 parent ad219fe commit f254f48

File tree

12 files changed

+366
-0
lines changed

12 files changed

+366
-0
lines changed

Sources/Containerization/Agent/Vminitd.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ extension Vminitd: VirtualMachineAgent {
198198
_ = try await client.deleteProcess(request)
199199
}
200200

201+
public func closeProcessStdin(id: String, containerID: String?) async throws {
202+
let request = Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest.with {
203+
$0.id = id
204+
if let containerID {
205+
$0.containerID = containerID
206+
}
207+
}
208+
_ = try await client.closeProcessStdin(request)
209+
}
210+
201211
public func up(name: String, mtu: UInt32? = nil) async throws {
202212
let request = Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest.with {
203213
$0.interface = name

Sources/Containerization/LinuxContainer.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,13 @@ extension LinuxContainer {
715715
return try await state.vm.dial(port)
716716
}
717717

718+
/// Close the containers standard input to signal no more input is
719+
/// arriving.
720+
public func closeStdin() async throws {
721+
let state = try self.state.startedState("closeStdin")
722+
return try await state.process.closeStdin()
723+
}
724+
718725
/// Relay a unix socket from in the container to the host, or from the host
719726
/// to inside the container.
720727
public func relayUnixSocket(socket: UnixSocketConfiguration) async throws {

Sources/Containerization/LinuxProcess.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,30 @@ extension LinuxProcess {
211211
try handle.write(contentsOf: data)
212212
} catch {
213213
self.logger?.error("failed to write to stdin: \(error)")
214+
break
215+
}
216+
}
217+
218+
do {
219+
self.logger?.debug("stdin relay finished, closing")
220+
221+
// There's two ways we can wind up here:
222+
//
223+
// 1. The stream finished on its own (e.g. we wrote all the
224+
// data) and we will close the underlying stdin in the guest below.
225+
//
226+
// 2. The client explicitly called closeStdin() themselves
227+
// which will cancel this relay task AFTER actually closing
228+
// the fds. If the client did that, then this task will be
229+
// cancelled, and the fds are already gone so there's nothing
230+
// for us to do.
231+
if Task.isCancelled {
214232
return
215233
}
234+
235+
try await self._closeStdin()
236+
} catch {
237+
self.logger?.error("failed to close stdin: \(error)")
216238
}
217239
}
218240
}
@@ -361,6 +383,28 @@ extension LinuxProcess {
361383
}
362384
}
363385

386+
public func closeStdin() async throws {
387+
do {
388+
try await self._closeStdin()
389+
self.state.withLock {
390+
$0.stdinRelay?.cancel()
391+
}
392+
} catch {
393+
throw ContainerizationError(
394+
.internalError,
395+
message: "failed to close stdin",
396+
cause: error,
397+
)
398+
}
399+
}
400+
401+
func _closeStdin() async throws {
402+
try await self.agent.closeProcessStdin(
403+
id: self.id,
404+
containerID: self.owningContainer
405+
)
406+
}
407+
364408
/// Wait on the process to exit with an optional timeout. Returns the exit code of the process.
365409
@discardableResult
366410
public func wait(timeoutInSeconds: Int64? = nil) async throws -> Int32 {

Sources/Containerization/SandboxContext/SandboxContext.grpc.swift

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtoc
104104
callOptions: CallOptions?
105105
) -> UnaryCall<Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest, Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse>
106106

107+
func closeProcessStdin(
108+
_ request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest,
109+
callOptions: CallOptions?
110+
) -> UnaryCall<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>
111+
107112
func proxyVsock(
108113
_ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest,
109114
callOptions: CallOptions?
@@ -408,6 +413,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtocol {
408413
)
409414
}
410415

416+
/// Close IO for a given process.
417+
///
418+
/// - Parameters:
419+
/// - request: Request to send to CloseProcessStdin.
420+
/// - callOptions: Call options.
421+
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
422+
public func closeProcessStdin(
423+
_ request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest,
424+
callOptions: CallOptions? = nil
425+
) -> UnaryCall<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse> {
426+
return self.makeUnaryCall(
427+
path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.closeProcessStdin.path,
428+
request: request,
429+
callOptions: callOptions ?? self.defaultCallOptions,
430+
interceptors: self.interceptors?.makeCloseProcessStdinInterceptors() ?? []
431+
)
432+
}
433+
411434
/// Proxy a vsock port to a unix domain socket in the guest, or vice versa.
412435
///
413436
/// - Parameters:
@@ -704,6 +727,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientP
704727
callOptions: CallOptions?
705728
) -> GRPCAsyncUnaryCall<Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest, Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse>
706729

730+
func makeCloseProcessStdinCall(
731+
_ request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest,
732+
callOptions: CallOptions?
733+
) -> GRPCAsyncUnaryCall<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>
734+
707735
func makeProxyVsockCall(
708736
_ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest,
709737
callOptions: CallOptions?
@@ -928,6 +956,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco
928956
)
929957
}
930958

959+
public func makeCloseProcessStdinCall(
960+
_ request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest,
961+
callOptions: CallOptions? = nil
962+
) -> GRPCAsyncUnaryCall<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse> {
963+
return self.makeAsyncUnaryCall(
964+
path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.closeProcessStdin.path,
965+
request: request,
966+
callOptions: callOptions ?? self.defaultCallOptions,
967+
interceptors: self.interceptors?.makeCloseProcessStdinInterceptors() ?? []
968+
)
969+
}
970+
931971
public func makeProxyVsockCall(
932972
_ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest,
933973
callOptions: CallOptions? = nil
@@ -1207,6 +1247,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco
12071247
)
12081248
}
12091249

1250+
public func closeProcessStdin(
1251+
_ request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest,
1252+
callOptions: CallOptions? = nil
1253+
) async throws -> Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse {
1254+
return try await self.performAsyncUnaryCall(
1255+
path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.closeProcessStdin.path,
1256+
request: request,
1257+
callOptions: callOptions ?? self.defaultCallOptions,
1258+
interceptors: self.interceptors?.makeCloseProcessStdinInterceptors() ?? []
1259+
)
1260+
}
1261+
12101262
public func proxyVsock(
12111263
_ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest,
12121264
callOptions: CallOptions? = nil
@@ -1377,6 +1429,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientInterc
13771429
/// - Returns: Interceptors to use when invoking 'resizeProcess'.
13781430
func makeResizeProcessInterceptors() -> [ClientInterceptor<Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest, Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse>]
13791431

1432+
/// - Returns: Interceptors to use when invoking 'closeProcessStdin'.
1433+
func makeCloseProcessStdinInterceptors() -> [ClientInterceptor<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>]
1434+
13801435
/// - Returns: Interceptors to use when invoking 'proxyVsock'.
13811436
func makeProxyVsockInterceptors() -> [ClientInterceptor<Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, Com_Apple_Containerization_Sandbox_V3_ProxyVsockResponse>]
13821437

@@ -1424,6 +1479,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata {
14241479
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.killProcess,
14251480
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.waitProcess,
14261481
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.resizeProcess,
1482+
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.closeProcessStdin,
14271483
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.proxyVsock,
14281484
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.stopVsockProxy,
14291485
Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.ipLinkSet,
@@ -1521,6 +1577,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata {
15211577
type: GRPCCallType.unary
15221578
)
15231579

1580+
public static let closeProcessStdin = GRPCMethodDescriptor(
1581+
name: "CloseProcessStdin",
1582+
path: "/com.apple.containerization.sandbox.v3.SandboxContext/CloseProcessStdin",
1583+
type: GRPCCallType.unary
1584+
)
1585+
15241586
public static let proxyVsock = GRPCMethodDescriptor(
15251587
name: "ProxyVsock",
15261588
path: "/com.apple.containerization.sandbox.v3.SandboxContext/ProxyVsock",
@@ -1626,6 +1688,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider: Ca
16261688
/// not have a pty allocated.
16271689
func resizeProcess(request: Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse>
16281690

1691+
/// Close IO for a given process.
1692+
func closeProcessStdin(request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>
1693+
16291694
/// Proxy a vsock port to a unix domain socket in the guest, or vice versa.
16301695
func proxyVsock(request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Containerization_Sandbox_V3_ProxyVsockResponse>
16311696

@@ -1792,6 +1857,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider {
17921857
userFunction: self.resizeProcess(request:context:)
17931858
)
17941859

1860+
case "CloseProcessStdin":
1861+
return UnaryServerHandler(
1862+
context: context,
1863+
requestDeserializer: ProtobufDeserializer<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest>(),
1864+
responseSerializer: ProtobufSerializer<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>(),
1865+
interceptors: self.interceptors?.makeCloseProcessStdinInterceptors() ?? [],
1866+
userFunction: self.closeProcessStdin(request:context:)
1867+
)
1868+
17951869
case "ProxyVsock":
17961870
return UnaryServerHandler(
17971871
context: context,
@@ -1972,6 +2046,12 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvide
19722046
context: GRPCAsyncServerCallContext
19732047
) async throws -> Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse
19742048

2049+
/// Close IO for a given process.
2050+
func closeProcessStdin(
2051+
request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest,
2052+
context: GRPCAsyncServerCallContext
2053+
) async throws -> Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse
2054+
19752055
/// Proxy a vsock port to a unix domain socket in the guest, or vice versa.
19762056
func proxyVsock(
19772057
request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest,
@@ -2172,6 +2252,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvider {
21722252
wrapping: { try await self.resizeProcess(request: $0, context: $1) }
21732253
)
21742254

2255+
case "CloseProcessStdin":
2256+
return GRPCAsyncServerHandler(
2257+
context: context,
2258+
requestDeserializer: ProtobufDeserializer<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest>(),
2259+
responseSerializer: ProtobufSerializer<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>(),
2260+
interceptors: self.interceptors?.makeCloseProcessStdinInterceptors() ?? [],
2261+
wrapping: { try await self.closeProcessStdin(request: $0, context: $1) }
2262+
)
2263+
21752264
case "ProxyVsock":
21762265
return GRPCAsyncServerHandler(
21772266
context: context,
@@ -2317,6 +2406,10 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextServerInterc
23172406
/// Defaults to calling `self.makeInterceptors()`.
23182407
func makeResizeProcessInterceptors() -> [ServerInterceptor<Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest, Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse>]
23192408

2409+
/// - Returns: Interceptors to use when handling 'closeProcessStdin'.
2410+
/// Defaults to calling `self.makeInterceptors()`.
2411+
func makeCloseProcessStdinInterceptors() -> [ServerInterceptor<Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse>]
2412+
23202413
/// - Returns: Interceptors to use when handling 'proxyVsock'.
23212414
/// Defaults to calling `self.makeInterceptors()`.
23222415
func makeProxyVsockInterceptors() -> [ServerInterceptor<Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, Com_Apple_Containerization_Sandbox_V3_ProxyVsockResponse>]
@@ -2373,6 +2466,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata {
23732466
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.killProcess,
23742467
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.waitProcess,
23752468
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.resizeProcess,
2469+
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.closeProcessStdin,
23762470
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.proxyVsock,
23772471
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.stopVsockProxy,
23782472
Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.ipLinkSet,
@@ -2470,6 +2564,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata {
24702564
type: GRPCCallType.unary
24712565
)
24722566

2567+
public static let closeProcessStdin = GRPCMethodDescriptor(
2568+
name: "CloseProcessStdin",
2569+
path: "/com.apple.containerization.sandbox.v3.SandboxContext/CloseProcessStdin",
2570+
type: GRPCCallType.unary
2571+
)
2572+
24732573
public static let proxyVsock = GRPCMethodDescriptor(
24742574
name: "ProxyVsock",
24752575
path: "/com.apple.containerization.sandbox.v3.SandboxContext/ProxyVsock",

0 commit comments

Comments
 (0)