Merge nucleic/fuzzy-yarn-otter-2pji into dev
This commit is contained in:
@@ -33,8 +33,13 @@ public struct Vminitd: Sendable {
|
||||
let client: Com_Apple_Containerization_Sandbox_V3_SandboxContext.Client<HTTP2ClientTransport.WrappedChannel>
|
||||
public let grpcClient: GRPCClient<HTTP2ClientTransport.WrappedChannel>
|
||||
private let connectionTask: Task<Void, Error>
|
||||
private let deadlinePolicy: DeadlinePolicy
|
||||
|
||||
public init(connection: FileHandle, group: any EventLoopGroup) async throws {
|
||||
public init(
|
||||
connection: FileHandle,
|
||||
group: any EventLoopGroup,
|
||||
deadlinePolicy: DeadlinePolicy = .standard
|
||||
) async throws {
|
||||
// Configure the gRPC pipeline from inside the channel initializer — before the channel
|
||||
// becomes active — so no early server frames (e.g. SETTINGS) are dropped. `configure` is
|
||||
// supplied by `wrapping(config:serviceConfig:makeChannel:)` and must be called exactly once.
|
||||
@@ -54,6 +59,7 @@ public struct Vminitd: Sendable {
|
||||
let grpcClient = GRPCClient(transport: transport)
|
||||
self.grpcClient = grpcClient
|
||||
self.client = Com_Apple_Containerization_Sandbox_V3_SandboxContext.Client(wrapping: self.grpcClient)
|
||||
self.deadlinePolicy = deadlinePolicy
|
||||
// Not very structured concurrency friendly, but we'd need to expose a way on the protocol to "run" the
|
||||
// agent otherwise, which some agents might not even need.
|
||||
self.connectionTask = Task {
|
||||
@@ -64,7 +70,41 @@ public struct Vminitd: Sendable {
|
||||
/// Close the connection to the guest agent.
|
||||
public func close() async throws {
|
||||
self.grpcClient.beginGracefulShutdown()
|
||||
try await self.connectionTask.value
|
||||
let deadline = deadlinePolicy.deadline(for: .agentClose)
|
||||
do {
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
group.addTask {
|
||||
try await self.connectionTask.value
|
||||
}
|
||||
group.addTask {
|
||||
try await ContinuousClock().sleep(until: deadline)
|
||||
throw self.deadlinePolicy.timeoutError(for: .agentClose)
|
||||
}
|
||||
|
||||
do {
|
||||
_ = try await group.next()
|
||||
group.cancelAll()
|
||||
} catch {
|
||||
// Cancelling the task executing runConnections() is GRPCCore's force-shutdown
|
||||
// API. It cancels in-flight RPCs and closes the underlying transport channel.
|
||||
self.connectionTask.cancel()
|
||||
group.cancelAll()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
} catch let error as ContainerizationError where error.code == .timeout {
|
||||
// The transport has already been force-shut down above. A bounded close has fulfilled
|
||||
// its cleanup contract even though graceful shutdown did not finish in time.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func rpc<Result: Sendable>(
|
||||
operation: DeadlinePolicy.Operation,
|
||||
deadline: DeadlinePolicy.Deadline,
|
||||
_ call: @Sendable (GRPCCore.CallOptions) async throws -> Result
|
||||
) async throws -> Result {
|
||||
try await deadlinePolicy.performGRPC(operation: operation, deadline: deadline, call)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +127,7 @@ extension Vminitd: VirtualMachineAgent {
|
||||
}
|
||||
|
||||
public func writeFile(path: String, data: Data, flags: WriteFileFlags, mode: UInt32) async throws {
|
||||
_ = try await client.writeFile(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_WriteFileRequest.with {
|
||||
$0.path = path
|
||||
$0.mode = mode
|
||||
$0.data = data
|
||||
@@ -97,17 +136,24 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.createIfMissing = flags.create
|
||||
$0.createParentDirs = flags.createParentDirectories
|
||||
}
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.writeFile(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get statistics for containers. If `containerIDs` is empty returns stats for all containers
|
||||
/// in the guest. If `categories` is empty, all categories are returned.
|
||||
public func containerStatistics(containerIDs: [String], categories: StatCategory) async throws -> [ContainerStatistics] {
|
||||
let response = try await client.containerStatistics(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest.with {
|
||||
$0.containerIds = containerIDs
|
||||
$0.categories = categories.toProtoCategories()
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.statistics
|
||||
let response = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.containerStatistics(request, options: options)
|
||||
}
|
||||
|
||||
return response.containers.map { protoStats in
|
||||
ContainerStatistics(
|
||||
@@ -184,41 +230,56 @@ extension Vminitd: VirtualMachineAgent {
|
||||
|
||||
/// Mount a filesystem in the sandbox's environment.
|
||||
public func mount(_ mount: ContainerizationOCI.Mount) async throws {
|
||||
_ = try await client.mount(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_MountRequest.with {
|
||||
$0.type = mount.type
|
||||
$0.source = mount.source
|
||||
$0.destination = mount.destination
|
||||
$0.options = mount.options
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.mount(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmount a filesystem in the sandbox's environment.
|
||||
public func umount(path: String, flags: Int32) async throws {
|
||||
_ = try await client.umount(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_UmountRequest.with {
|
||||
$0.path = path
|
||||
$0.flags = flags
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.umount(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a directory inside the sandbox's environment.
|
||||
public func mkdir(path: String, all: Bool, perms: UInt32) async throws {
|
||||
_ = try await client.mkdir(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_MkdirRequest.with {
|
||||
$0.path = path
|
||||
$0.all = all
|
||||
$0.perms = perms
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.mkdir(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a filesystem operation on a path inside the sandbox's environment.
|
||||
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws {
|
||||
_ = try await client.filesystemOperation(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest.with {
|
||||
$0.operation = operation.toProtoOperation()
|
||||
$0.path = path
|
||||
})
|
||||
}
|
||||
let operationClass = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(
|
||||
operation: operationClass,
|
||||
deadline: deadlinePolicy.deadline(for: operationClass)
|
||||
) { options in
|
||||
try await client.filesystemOperation(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
public func createProcess(
|
||||
@@ -232,26 +293,34 @@ extension Vminitd: VirtualMachineAgent {
|
||||
options: Data?
|
||||
) async throws {
|
||||
let enc = JSONEncoder()
|
||||
_ = try await client.createProcess(
|
||||
.with {
|
||||
$0.id = id
|
||||
if let stdinPort {
|
||||
$0.stdin = stdinPort
|
||||
}
|
||||
if let stdoutPort {
|
||||
$0.stdout = stdoutPort
|
||||
}
|
||||
if let stderrPort {
|
||||
$0.stderr = stderrPort
|
||||
}
|
||||
if let containerID {
|
||||
$0.containerID = containerID
|
||||
}
|
||||
if let ociRuntimePath {
|
||||
$0.ociRuntimePath = ociRuntimePath
|
||||
}
|
||||
$0.configuration = try enc.encode(configuration)
|
||||
})
|
||||
let request = try Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest.with {
|
||||
$0.id = id
|
||||
if let stdinPort {
|
||||
$0.stdin = stdinPort
|
||||
}
|
||||
if let stdoutPort {
|
||||
$0.stdout = stdoutPort
|
||||
}
|
||||
if let stderrPort {
|
||||
$0.stderr = stderrPort
|
||||
}
|
||||
if let containerID {
|
||||
$0.containerID = containerID
|
||||
}
|
||||
if let ociRuntimePath {
|
||||
$0.ociRuntimePath = ociRuntimePath
|
||||
}
|
||||
$0.configuration = try enc.encode(configuration)
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.createProcess
|
||||
do {
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.createProcess(request, options: options)
|
||||
}
|
||||
} catch let error as ContainerizationError where error.code == .timeout {
|
||||
cleanupTimedOutProcess(id: id, containerID: containerID, signalFirst: false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -262,7 +331,16 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.containerID = containerID
|
||||
}
|
||||
}
|
||||
let resp = try await client.startProcess(request)
|
||||
let operation = DeadlinePolicy.Operation.startProcess
|
||||
let resp: Com_Apple_Containerization_Sandbox_V3_StartProcessResponse
|
||||
do {
|
||||
resp = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.startProcess(request, options: options)
|
||||
}
|
||||
} catch let error as ContainerizationError where error.code == .timeout {
|
||||
cleanupTimedOutProcess(id: id, containerID: containerID, signalFirst: true)
|
||||
throw error
|
||||
}
|
||||
return resp.pid
|
||||
}
|
||||
|
||||
@@ -274,7 +352,10 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.containerID = containerID
|
||||
}
|
||||
}
|
||||
_ = try await client.killProcess(request)
|
||||
let operation = DeadlinePolicy.Operation.processControl
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.killProcess(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
public func resizeProcess(id: String, containerID: String?, columns: UInt32, rows: UInt32) async throws {
|
||||
@@ -286,7 +367,10 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.columns = columns
|
||||
$0.rows = rows
|
||||
}
|
||||
_ = try await client.resizeProcess(request)
|
||||
let operation = DeadlinePolicy.Operation.processControl
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.resizeProcess(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
public func waitProcess(
|
||||
@@ -301,24 +385,29 @@ extension Vminitd: VirtualMachineAgent {
|
||||
}
|
||||
}
|
||||
|
||||
var callOpts = GRPCCore.CallOptions.defaults
|
||||
if let timeoutInSeconds {
|
||||
callOpts.timeout = .seconds(timeoutInSeconds)
|
||||
}
|
||||
|
||||
do {
|
||||
let resp = try await client.waitProcess(request, options: callOpts)
|
||||
return ExitStatus(exitCode: resp.exitCode, exitedAt: resp.exitedAt.date)
|
||||
} catch {
|
||||
if let err = error as? RPCError, err.code == .deadlineExceeded {
|
||||
let operation = DeadlinePolicy.Operation.waitProcess
|
||||
let deadline = ContinuousClock().now.advanced(by: .seconds(timeoutInSeconds))
|
||||
do {
|
||||
let resp = try await rpc(operation: operation, deadline: deadline) { options in
|
||||
try await client.waitProcess(request, options: options)
|
||||
}
|
||||
return ExitStatus(exitCode: resp.exitCode, exitedAt: resp.exitedAt.date)
|
||||
} catch let error as ContainerizationError where error.code == .timeout {
|
||||
throw ContainerizationError(
|
||||
.timeout,
|
||||
message: "failed to wait for process exit within timeout of \(timeoutInSeconds!) seconds",
|
||||
cause: err
|
||||
message: "failed to wait for process exit within timeout of \(timeoutInSeconds) seconds",
|
||||
cause: error.cause
|
||||
)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
// Phase-2 exception, intentionally greppable: the guest's ManagedProcess.wait() does not
|
||||
// remove a stored continuation when an RPC is cancelled. Renewing bounded RPCs here would
|
||||
// leak one guest waiter per lease. Preserve the existing unbounded API until the guest gains
|
||||
// cancellation-aware waiter removal; explicit caller timeouts above are deadline-routed.
|
||||
let resp = try await client.waitProcess(request)
|
||||
return ExitStatus(exitCode: resp.exitCode, exitedAt: resp.exitedAt.date)
|
||||
}
|
||||
|
||||
public func deleteProcess(id: String, containerID: String?) async throws {
|
||||
@@ -328,16 +417,10 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.containerID = containerID
|
||||
}
|
||||
}
|
||||
// [Nucleic vendored patch] Bound the teardown RPC so a wedged agent channel can't hang an
|
||||
// exec's cleanup forever. Nucleic fires `LinuxProcess.delete()` after every turn to reclaim
|
||||
// the per-exec connection; if `deleteProcess` never returned, that reclaim task would leak
|
||||
// and the connection would stay open — reintroducing the very accumulation the delete exists
|
||||
// to prevent. Generous: a healthy delete returns in milliseconds, so this only trips a
|
||||
// genuinely stuck channel, and `LinuxProcess.performDeletion` still closes the agent
|
||||
// connection on the thrown deadline.
|
||||
var callOpts = GRPCCore.CallOptions.defaults
|
||||
callOpts.timeout = .seconds(30)
|
||||
_ = try await client.deleteProcess(request, options: callOpts)
|
||||
let operation = DeadlinePolicy.Operation.deleteProcess
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.deleteProcess(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
public func closeProcessStdin(id: String, containerID: String?) async throws {
|
||||
@@ -347,7 +430,23 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.containerID = containerID
|
||||
}
|
||||
}
|
||||
_ = try await client.closeProcessStdin(request)
|
||||
let operation = DeadlinePolicy.Operation.processControl
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.closeProcessStdin(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// The timed-out RPC has already been cancelled by gRPC. Reclaim any guest record which was
|
||||
/// committed immediately before cancellation won the race. Cleanup has its own bounded RPCs so
|
||||
/// the phase timeout can settle its caller without retaining an immortal cleanup task.
|
||||
private func cleanupTimedOutProcess(id: String, containerID: String?, signalFirst: Bool) {
|
||||
Task {
|
||||
if signalFirst {
|
||||
try? await self.signalProcess(id: id, containerID: containerID, signal: SIGKILL)
|
||||
}
|
||||
try? await self.deleteProcess(id: id, containerID: containerID)
|
||||
try? await self.close()
|
||||
}
|
||||
}
|
||||
|
||||
public func up(name: String, mtu: UInt32? = nil) async throws {
|
||||
@@ -356,7 +455,10 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.up = true
|
||||
if let mtu { $0.mtu = mtu }
|
||||
}
|
||||
_ = try await client.ipLinkSet(request)
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.ipLinkSet(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
public func down(name: String) async throws {
|
||||
@@ -364,25 +466,32 @@ extension Vminitd: VirtualMachineAgent {
|
||||
$0.interface = name
|
||||
$0.up = false
|
||||
}
|
||||
_ = try await client.ipLinkSet(request)
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.ipLinkSet(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an environment variable from the sandbox's environment.
|
||||
public func getenv(key: String) async throws -> String {
|
||||
let response = try await client.getenv(
|
||||
.with {
|
||||
$0.key = key
|
||||
})
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_GetenvRequest.with { $0.key = key }
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
let response = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.getenv(request, options: options)
|
||||
}
|
||||
return response.value
|
||||
}
|
||||
|
||||
/// Set an environment variable in the sandbox's environment.
|
||||
public func setenv(key: String, value: String) async throws {
|
||||
_ = try await client.setenv(
|
||||
.with {
|
||||
$0.key = key
|
||||
$0.value = value
|
||||
})
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_SetenvRequest.with {
|
||||
$0.key = key
|
||||
$0.value = value
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.setenv(request, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +508,10 @@ extension Vminitd {
|
||||
$0.mask = configuration.mask
|
||||
$0.flags = configuration.flags
|
||||
}
|
||||
_ = try await client.setupEmulator(request)
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.setupEmulator(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the guest time.
|
||||
@@ -408,7 +520,10 @@ extension Vminitd {
|
||||
$0.sec = sec
|
||||
$0.usec = usec
|
||||
}
|
||||
_ = try await client.setTime(request)
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.setTime(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the provided sysctls inside the Sandbox's environment.
|
||||
@@ -416,19 +531,25 @@ extension Vminitd {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_SysctlRequest.with {
|
||||
$0.settings = settings
|
||||
}
|
||||
_ = try await client.sysctl(request)
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.sysctl(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an IP address to the sandbox's network interfaces.
|
||||
public func addressAdd(name: String, address: InterfaceAddress) async throws {
|
||||
_ = try await client.ipAddrAdd(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_IpAddrAddRequest.with {
|
||||
$0.interface = name
|
||||
$0.ipv4Address = address.ipv4Address.description
|
||||
if let ipv6Address = address.ipv6Address {
|
||||
$0.ipv6Address = ipv6Address.description
|
||||
}
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.ipAddrAdd(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a link-scoped route in the sandbox's environment, used to install an
|
||||
@@ -437,8 +558,7 @@ extension Vminitd {
|
||||
/// `route.ipv4Destination`/`route.ipv6Destination` carry the
|
||||
/// gateway address; the wire format is a CIDR string with the per-family host prefix appended.
|
||||
public func routeAddLink(name: String, route: LinkRoute) async throws {
|
||||
_ = try await client.ipRouteAddLink(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_IpRouteAddLinkRequest.with {
|
||||
$0.interface = name
|
||||
if let ipv4Destination = route.ipv4Destination {
|
||||
$0.dstIpv4Addr = "\(ipv4Destination.description)/32"
|
||||
@@ -452,26 +572,32 @@ extension Vminitd {
|
||||
if let ipv6Source = route.ipv6Source {
|
||||
$0.srcIpv6Addr = ipv6Source.description
|
||||
}
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.ipRouteAddLink(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the default route in the sandbox's environment.
|
||||
public func routeAddDefault(name: String, route: DefaultRoute) async throws {
|
||||
_ = try await client.ipRouteAddDefault(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_IpRouteAddDefaultRequest.with {
|
||||
$0.interface = name
|
||||
$0.ipv4Gateway = route.ipv4Gateway?.description ?? ""
|
||||
if let ipv6Gateway = route.ipv6Gateway {
|
||||
$0.ipv6Gateway = ipv6Gateway.description
|
||||
}
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.ipRouteAddDefault(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure DNS within the sandbox's environment.
|
||||
public func configureDNS(config: DNS, location: String) async throws {
|
||||
try config.validate()
|
||||
_ = try await client.configureDns(
|
||||
.with {
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest.with {
|
||||
$0.location = location
|
||||
$0.nameservers = config.nameservers
|
||||
if let domain = config.domain {
|
||||
@@ -479,25 +605,39 @@ extension Vminitd {
|
||||
}
|
||||
$0.searchDomains = config.searchDomains
|
||||
$0.options = config.options
|
||||
})
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.configureDns(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure /etc/hosts within the sandbox's environment.
|
||||
public func configureHosts(config: Hosts, location: String) async throws {
|
||||
_ = try await client.configureHosts(config.toAgentHostsRequest(location: location))
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
let request = config.toAgentHostsRequest(location: location)
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.configureHosts(request, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a sync call.
|
||||
public func sync() async throws {
|
||||
_ = try await client.sync(.init())
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
_ = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.sync(.init(), options: options)
|
||||
}
|
||||
}
|
||||
|
||||
public func kill(pid: Int32, signal: Int32) async throws -> Int32 {
|
||||
let response = try await client.kill(
|
||||
.with {
|
||||
$0.pid = pid
|
||||
$0.signal = signal
|
||||
})
|
||||
let request = Com_Apple_Containerization_Sandbox_V3_KillRequest.with {
|
||||
$0.pid = pid
|
||||
$0.signal = signal
|
||||
}
|
||||
let operation = DeadlinePolicy.Operation.processControl
|
||||
let response = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.kill(request, options: options)
|
||||
}
|
||||
return response.result
|
||||
}
|
||||
|
||||
@@ -519,7 +659,10 @@ extension Vminitd {
|
||||
|
||||
let response: Com_Apple_Containerization_Sandbox_V3_StatResponse
|
||||
do {
|
||||
response = try await client.stat(request)
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
response = try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.stat(request, options: options)
|
||||
}
|
||||
} catch let error as RPCError where error.code == .notFound {
|
||||
throw ContainerizationError(.notFound, message: "stat: path not found '\(path.path)'", cause: error)
|
||||
}
|
||||
@@ -570,9 +713,12 @@ extension Vminitd {
|
||||
$0.isArchive = isArchive
|
||||
}
|
||||
|
||||
try await client.copy(
|
||||
request,
|
||||
onResponse: { stream in
|
||||
let operation = DeadlinePolicy.Operation.filesystem
|
||||
try await rpc(operation: operation, deadline: deadlinePolicy.deadline(for: operation)) { options in
|
||||
try await client.copy(
|
||||
request,
|
||||
options: options,
|
||||
onResponse: { stream in
|
||||
for try await response in stream.messages {
|
||||
if !response.error.isEmpty {
|
||||
throw ContainerizationError(.internalError, message: "copy: \(response.error)")
|
||||
@@ -587,6 +733,7 @@ extension Vminitd {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user