diff --git a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift index 785296f3..5e779800 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift @@ -166,6 +166,19 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContext: Sendable { type: .serverStreaming ) } + /// Namespace for "FilesystemOperation" metadata. + public enum FilesystemOperation: Sendable { + /// Request type for "FilesystemOperation". + public typealias Input = Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest + /// Response type for "FilesystemOperation". + public typealias Output = Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse + /// Descriptor for "FilesystemOperation". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "com.apple.containerization.sandbox.v3.SandboxContext"), + method: "FilesystemOperation", + type: .unary + ) + } /// Namespace for "CreateProcess" metadata. public enum CreateProcess: Sendable { /// Request type for "CreateProcess". @@ -412,6 +425,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContext: Sendable { SetupEmulator.descriptor, WriteFile.descriptor, Copy.descriptor, + FilesystemOperation.descriptor, CreateProcess.descriptor, DeleteProcess.descriptor, StartProcess.descriptor, @@ -641,6 +655,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext { context: GRPCCore.ServerContext ) async throws -> GRPCCore.StreamingServerResponse + /// Handle the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - request: A streaming request of `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` messages. + /// - context: Context providing information about the RPC. + /// - Throws: Any error which occurred during the processing of the request. Thrown errors + /// of type `RPCError` are mapped to appropriate statuses. All other errors are converted + /// to an internal error. + /// - Returns: A streaming response of `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse` messages. + func filesystemOperation( + request: GRPCCore.StreamingServerRequest, + context: GRPCCore.ServerContext + ) async throws -> GRPCCore.StreamingServerResponse + /// Handle the "CreateProcess" method. /// /// > Source IDL Documentation: @@ -1161,6 +1193,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext { context: GRPCCore.ServerContext ) async throws -> GRPCCore.StreamingServerResponse + /// Handle the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` message. + /// - context: Context providing information about the RPC. + /// - Throws: Any error which occurred during the processing of the request. Thrown errors + /// of type `RPCError` are mapped to appropriate statuses. All other errors are converted + /// to an internal error. + /// - Returns: A response containing a single `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse` message. + func filesystemOperation( + request: GRPCCore.ServerRequest, + context: GRPCCore.ServerContext + ) async throws -> GRPCCore.ServerResponse + /// Handle the "CreateProcess" method. /// /// > Source IDL Documentation: @@ -1680,6 +1730,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext { context: GRPCCore.ServerContext ) async throws + /// Handle the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - request: A `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` message. + /// - context: Context providing information about the RPC. + /// - Throws: Any error which occurred during the processing of the request. Thrown errors + /// of type `RPCError` are mapped to appropriate statuses. All other errors are converted + /// to an internal error. + /// - Returns: A `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse` to respond with. + func filesystemOperation( + request: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest, + context: GRPCCore.ServerContext + ) async throws -> Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse + /// Handle the "CreateProcess" method. /// /// > Source IDL Documentation: @@ -2121,6 +2189,17 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext.StreamingServiceP ) } ) + router.registerHandler( + forMethod: Com_Apple_Containerization_Sandbox_V3_SandboxContext.Method.FilesystemOperation.descriptor, + deserializer: GRPCProtobuf.ProtobufDeserializer(), + serializer: GRPCProtobuf.ProtobufSerializer(), + handler: { request, context in + try await self.filesystemOperation( + request: request, + context: context + ) + } + ) router.registerHandler( forMethod: Com_Apple_Containerization_Sandbox_V3_SandboxContext.Method.CreateProcess.descriptor, deserializer: GRPCProtobuf.ProtobufDeserializer(), @@ -2435,6 +2514,17 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext.ServiceProtocol { return response } + public func filesystemOperation( + request: GRPCCore.StreamingServerRequest, + context: GRPCCore.ServerContext + ) async throws -> GRPCCore.StreamingServerResponse { + let response = try await self.filesystemOperation( + request: GRPCCore.ServerRequest(stream: request), + context: context + ) + return GRPCCore.StreamingServerResponse(single: response) + } + public func createProcess( request: GRPCCore.StreamingServerRequest, context: GRPCCore.ServerContext @@ -2771,6 +2861,19 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServiceProt ) } + public func filesystemOperation( + request: GRPCCore.ServerRequest, + context: GRPCCore.ServerContext + ) async throws -> GRPCCore.ServerResponse { + return GRPCCore.ServerResponse( + message: try await self.filesystemOperation( + request: request.message, + context: context + ), + metadata: [:] + ) + } + public func createProcess( request: GRPCCore.ServerRequest, context: GRPCCore.ServerContext @@ -3251,6 +3354,29 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext { onResponse handleResponse: @Sendable @escaping (GRPCCore.StreamingClientResponse) async throws -> Result ) async throws -> Result where Result: Sendable + /// Call the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` message. + /// - serializer: A serializer for `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` messages. + /// - deserializer: A deserializer for `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func filesystemOperation( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + /// Call the "CreateProcess" method. /// /// > Source IDL Documentation: @@ -4027,6 +4153,40 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext { ) } + /// Call the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` message. + /// - serializer: A serializer for `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` messages. + /// - deserializer: A deserializer for `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func filesystemOperation( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Com_Apple_Containerization_Sandbox_V3_SandboxContext.Method.FilesystemOperation.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + /// Call the "CreateProcess" method. /// /// > Source IDL Documentation: @@ -4935,6 +5095,35 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext.ClientProtocol { ) } + /// Call the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func filesystemOperation( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.filesystemOperation( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + /// Call the "CreateProcess" method. /// /// > Source IDL Documentation: @@ -5792,6 +5981,39 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContext.ClientProtocol { ) } + /// Call the "FilesystemOperation" method. + /// + /// > Source IDL Documentation: + /// > + /// > Perform a filesystem operation on a mounted filesystem. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func filesystemOperation( + _ message: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.filesystemOperation( + request: request, + options: options, + onResponse: handleResponse + ) + } + /// Call the "CreateProcess" method. /// /// > Source IDL Documentation: diff --git a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift index e35e2487..068f7c4a 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift @@ -941,6 +941,72 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyResponse: Sendable { public init() {} } +public struct Com_Apple_Containerization_Sandbox_V3_FiFreezeParams: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_FiThawParams: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var path: String = String() + + public var operation: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest.OneOf_Operation? = nil + + public var freeze: Com_Apple_Containerization_Sandbox_V3_FiFreezeParams { + get { + if case .freeze(let v)? = operation {return v} + return Com_Apple_Containerization_Sandbox_V3_FiFreezeParams() + } + set {operation = .freeze(newValue)} + } + + public var thaw: Com_Apple_Containerization_Sandbox_V3_FiThawParams { + get { + if case .thaw(let v)? = operation {return v} + return Com_Apple_Containerization_Sandbox_V3_FiThawParams() + } + set {operation = .thaw(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Operation: Equatable, Sendable { + case freeze(Com_Apple_Containerization_Sandbox_V3_FiFreezeParams) + case thaw(Com_Apple_Containerization_Sandbox_V3_FiThawParams) + + } + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2814,6 +2880,135 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyResponse.Status: SwiftProtob public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0METADATA\0\u{1}COMPLETE\0") } +extension Com_Apple_Containerization_Sandbox_V3_FiFreezeParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".FiFreezeParams" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_FiFreezeParams, rhs: Com_Apple_Containerization_Sandbox_V3_FiFreezeParams) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_FiThawParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".FiThawParams" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_FiThawParams, rhs: Com_Apple_Containerization_Sandbox_V3_FiThawParams) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".FilesystemOperationRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}freeze\0\u{1}thaw\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.path) }() + case 2: try { + var v: Com_Apple_Containerization_Sandbox_V3_FiFreezeParams? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .freeze(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .freeze(v) + } + }() + case 3: try { + var v: Com_Apple_Containerization_Sandbox_V3_FiThawParams? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .thaw(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .thaw(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.path.isEmpty { + try visitor.visitSingularStringField(value: self.path, fieldNumber: 1) + } + switch self.operation { + case .freeze?: try { + guard case .freeze(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .thaw?: try { + guard case .thaw(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest, rhs: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest) -> Bool { + if lhs.path != rhs.path {return false} + if lhs.operation != rhs.operation {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".FilesystemOperationResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse, rhs: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".IpLinkSetRequest" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}up\0\u{1}mtu\0") diff --git a/Sources/Containerization/SandboxContext/SandboxContext.proto b/Sources/Containerization/SandboxContext/SandboxContext.proto index 5250ced7..9a4cce26 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.proto +++ b/Sources/Containerization/SandboxContext/SandboxContext.proto @@ -28,6 +28,8 @@ service SandboxContext { // Data transfer happens over a dedicated vsock connection; // the gRPC stream is used only for control/metadata. rpc Copy(CopyRequest) returns (stream CopyResponse); + // Perform a filesystem operation on a mounted filesystem. + rpc FilesystemOperation(FilesystemOperationRequest) returns (FilesystemOperationResponse); // Create a new process inside the container. rpc CreateProcess(CreateProcessRequest) returns (CreateProcessResponse); @@ -267,6 +269,19 @@ message CopyResponse { string error = 4; } +message FiFreezeParams {} +message FiThawParams {} + +message FilesystemOperationRequest { + string path = 1; + oneof operation { + FiFreezeParams freeze = 2; + FiThawParams thaw = 3; + } +} + +message FilesystemOperationResponse {} + message IpLinkSetRequest { string interface = 1; bool up = 2; diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index f43b2d34..a692b570 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -615,6 +615,47 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ } } + func filesystemOperation(request: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest, context: GRPCCore.ServerContext) + async throws -> Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse + { + log.debug( + "filesystemOperation", + metadata: [ + "operation": "\(request.operation)", + "path": "\(request.path)", + ]) + + // TODO: path validation + + do { + switch request.operation { + case .freeze: + try freezeFilesystem(path: request.path) + case .thaw: + try thawFilesystem(path: request.path) + case .none: + throw RPCError(code: .invalidArgument, message: "invalid operation") + } + } catch { + log.error( + "filesystemOperation", + metadata: [ + "error": "\(error)" + ]) + throw RPCError(code: .internalError, message: "filesystemOperation", cause: error) + } + + return .init() + } + + private func freezeFilesystem(path: String) throws { + // TODO: implement freeze + } + + private func thawFilesystem(path: String) throws { + // TODO: implement thaw + } + func umount(request: Com_Apple_Containerization_Sandbox_V3_UmountRequest, context: GRPCCore.ServerContext) async throws -> Com_Apple_Containerization_Sandbox_V3_UmountResponse {