diff --git a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift index 785296f3..7018897b 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 "Stat" metadata. + public enum Stat: Sendable { + /// Request type for "Stat". + public typealias Input = Com_Apple_Containerization_Sandbox_V3_StatRequest + /// Response type for "Stat". + public typealias Output = Com_Apple_Containerization_Sandbox_V3_StatResponse + /// Descriptor for "Stat". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "com.apple.containerization.sandbox.v3.SandboxContext"), + method: "Stat", + 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, + Stat.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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: A streaming request of `Com_Apple_Containerization_Sandbox_V3_StatRequest` 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_StatResponse` messages. + func stat( + 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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_StatRequest` 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_StatResponse` message. + func stat( + 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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: A `Com_Apple_Containerization_Sandbox_V3_StatRequest` 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_StatResponse` to respond with. + func stat( + request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + context: GRPCCore.ServerContext + ) async throws -> Com_Apple_Containerization_Sandbox_V3_StatResponse + /// 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.Stat.descriptor, + deserializer: GRPCProtobuf.ProtobufDeserializer(), + serializer: GRPCProtobuf.ProtobufSerializer(), + handler: { request, context in + try await self.stat( + 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 stat( + request: GRPCCore.StreamingServerRequest, + context: GRPCCore.ServerContext + ) async throws -> GRPCCore.StreamingServerResponse { + let response = try await self.stat( + 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 stat( + request: GRPCCore.ServerRequest, + context: GRPCCore.ServerContext + ) async throws -> GRPCCore.ServerResponse { + return GRPCCore.ServerResponse( + message: try await self.stat( + 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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_StatRequest` message. + /// - serializer: A serializer for `Com_Apple_Containerization_Sandbox_V3_StatRequest` messages. + /// - deserializer: A deserializer for `Com_Apple_Containerization_Sandbox_V3_StatResponse` 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 stat( + 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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_StatRequest` message. + /// - serializer: A serializer for `Com_Apple_Containerization_Sandbox_V3_StatRequest` messages. + /// - deserializer: A deserializer for `Com_Apple_Containerization_Sandbox_V3_StatResponse` 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 stat( + 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.Stat.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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: A request containing a single `Com_Apple_Containerization_Sandbox_V3_StatRequest` 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 stat( + 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.stat( + 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 "Stat" method. + /// + /// > Source IDL Documentation: + /// > + /// > Stat a path in the guest 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 stat( + _ message: Com_Apple_Containerization_Sandbox_V3_StatRequest, + 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.stat( + 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..e68f70b9 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift @@ -941,6 +941,119 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyResponse: Sendable { public init() {} } +public struct Com_Apple_Containerization_Sandbox_V3_StatRequest: 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 unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_Stat: 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. + + /// st_dev: ID of device containing file + public var dev: UInt64 = 0 + + /// st_ino: inode number + public var ino: UInt64 = 0 + + /// st_mode: file type and mode (permissions) + public var mode: UInt32 = 0 + + /// st_nlink: number of hard links + public var nlink: UInt64 = 0 + + /// st_uid: user ID of owner + public var uid: UInt32 = 0 + + /// st_gid: group ID of owner + public var gid: UInt32 = 0 + + /// st_rdev: device ID (if special file) + public var rdev: UInt64 = 0 + + /// st_size: total size in bytes + public var size: Int64 = 0 + + /// st_blksize: preferred block size for filesystem I/O + public var blksize: Int64 = 0 + + /// st_blocks: number of 512-byte blocks allocated + public var blocks: Int64 = 0 + + /// st_atim: time of last access + public var atime: SwiftProtobuf.Google_Protobuf_Timestamp { + get {_atime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_atime = newValue} + } + /// Returns true if `atime` has been explicitly set. + public var hasAtime: Bool {self._atime != nil} + /// Clears the value of `atime`. Subsequent reads from it will return its default value. + public mutating func clearAtime() {self._atime = nil} + + /// st_mtim: time of last modification + public var mtime: SwiftProtobuf.Google_Protobuf_Timestamp { + get {_mtime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_mtime = newValue} + } + /// Returns true if `mtime` has been explicitly set. + public var hasMtime: Bool {self._mtime != nil} + /// Clears the value of `mtime`. Subsequent reads from it will return its default value. + public mutating func clearMtime() {self._mtime = nil} + + /// st_ctim: time of last status change + public var ctime: SwiftProtobuf.Google_Protobuf_Timestamp { + get {_ctime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_ctime = newValue} + } + /// Returns true if `ctime` has been explicitly set. + public var hasCtime: Bool {self._ctime != nil} + /// Clears the value of `ctime`. Subsequent reads from it will return its default value. + public mutating func clearCtime() {self._ctime = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _atime: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _mtime: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _ctime: SwiftProtobuf.Google_Protobuf_Timestamp? = nil +} + +public struct Com_Apple_Containerization_Sandbox_V3_StatResponse: @unchecked 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 stat: Com_Apple_Containerization_Sandbox_V3_Stat { + get {_storage._stat ?? Com_Apple_Containerization_Sandbox_V3_Stat()} + set {_uniqueStorage()._stat = newValue} + } + /// Returns true if `stat` has been explicitly set. + public var hasStat: Bool {_storage._stat != nil} + /// Clears the value of `stat`. Subsequent reads from it will return its default value. + public mutating func clearStat() {_uniqueStorage()._stat = nil} + + /// Non-empty if stat failed. + public var error: String { + get {_storage._error} + set {_uniqueStorage()._error = newValue} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + 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 +2927,207 @@ 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_StatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".StatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\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) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.path.isEmpty { + try visitor.visitSingularStringField(value: self.path, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_StatRequest, rhs: Com_Apple_Containerization_Sandbox_V3_StatRequest) -> Bool { + if lhs.path != rhs.path {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_Stat: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Stat" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}dev\0\u{1}ino\0\u{1}mode\0\u{1}nlink\0\u{1}uid\0\u{1}gid\0\u{1}rdev\0\u{1}size\0\u{1}blksize\0\u{1}blocks\0\u{1}atime\0\u{1}mtime\0\u{1}ctime\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.decodeSingularUInt64Field(value: &self.dev) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.ino) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.mode) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self.nlink) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.uid) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.gid) }() + case 7: try { try decoder.decodeSingularUInt64Field(value: &self.rdev) }() + case 8: try { try decoder.decodeSingularInt64Field(value: &self.size) }() + case 9: try { try decoder.decodeSingularInt64Field(value: &self.blksize) }() + case 10: try { try decoder.decodeSingularInt64Field(value: &self.blocks) }() + case 11: try { try decoder.decodeSingularMessageField(value: &self._atime) }() + case 12: try { try decoder.decodeSingularMessageField(value: &self._mtime) }() + case 13: try { try decoder.decodeSingularMessageField(value: &self._ctime) }() + 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.dev != 0 { + try visitor.visitSingularUInt64Field(value: self.dev, fieldNumber: 1) + } + if self.ino != 0 { + try visitor.visitSingularUInt64Field(value: self.ino, fieldNumber: 2) + } + if self.mode != 0 { + try visitor.visitSingularUInt32Field(value: self.mode, fieldNumber: 3) + } + if self.nlink != 0 { + try visitor.visitSingularUInt64Field(value: self.nlink, fieldNumber: 4) + } + if self.uid != 0 { + try visitor.visitSingularUInt32Field(value: self.uid, fieldNumber: 5) + } + if self.gid != 0 { + try visitor.visitSingularUInt32Field(value: self.gid, fieldNumber: 6) + } + if self.rdev != 0 { + try visitor.visitSingularUInt64Field(value: self.rdev, fieldNumber: 7) + } + if self.size != 0 { + try visitor.visitSingularInt64Field(value: self.size, fieldNumber: 8) + } + if self.blksize != 0 { + try visitor.visitSingularInt64Field(value: self.blksize, fieldNumber: 9) + } + if self.blocks != 0 { + try visitor.visitSingularInt64Field(value: self.blocks, fieldNumber: 10) + } + try { if let v = self._atime { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() + try { if let v = self._mtime { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + try { if let v = self._ctime { + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_Stat, rhs: Com_Apple_Containerization_Sandbox_V3_Stat) -> Bool { + if lhs.dev != rhs.dev {return false} + if lhs.ino != rhs.ino {return false} + if lhs.mode != rhs.mode {return false} + if lhs.nlink != rhs.nlink {return false} + if lhs.uid != rhs.uid {return false} + if lhs.gid != rhs.gid {return false} + if lhs.rdev != rhs.rdev {return false} + if lhs.size != rhs.size {return false} + if lhs.blksize != rhs.blksize {return false} + if lhs.blocks != rhs.blocks {return false} + if lhs._atime != rhs._atime {return false} + if lhs._mtime != rhs._mtime {return false} + if lhs._ctime != rhs._ctime {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_StatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".StatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}stat\0\u{1}error\0") + + fileprivate class _StorageClass { + var _stat: Com_Apple_Containerization_Sandbox_V3_Stat? = nil + var _error: String = String() + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _stat = source._stat + _error = source._error + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + 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.decodeSingularMessageField(value: &_storage._stat) }() + case 2: try { try decoder.decodeSingularStringField(value: &_storage._error) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // 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 + try { if let v = _storage._stat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !_storage._error.isEmpty { + try visitor.visitSingularStringField(value: _storage._error, fieldNumber: 2) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_StatResponse, rhs: Com_Apple_Containerization_Sandbox_V3_StatResponse) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._stat != rhs_storage._stat {return false} + if _storage._error != rhs_storage._error {return false} + return true + } + if !storagesAreEqual {return false} + } + 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..e30edbe9 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); + // Stat a path in the guest filesystem. + rpc Stat(StatRequest) returns (StatResponse); // Create a new process inside the container. rpc CreateProcess(CreateProcessRequest) returns (CreateProcessResponse); @@ -267,6 +269,29 @@ message CopyResponse { string error = 4; } +message StatRequest { string path = 1; } + +message Stat { + uint64 dev = 1; // st_dev: ID of device containing file + uint64 ino = 2; // st_ino: inode number + uint32 mode = 3; // st_mode: file type and mode (permissions) + uint64 nlink = 4; // st_nlink: number of hard links + uint32 uid = 5; // st_uid: user ID of owner + uint32 gid = 6; // st_gid: group ID of owner + uint64 rdev = 7; // st_rdev: device ID (if special file) + int64 size = 8; // st_size: total size in bytes + int64 blksize = 9; // st_blksize: preferred block size for filesystem I/O + int64 blocks = 10; // st_blocks: number of 512-byte blocks allocated + google.protobuf.Timestamp atime = 11; // st_atim: time of last access + google.protobuf.Timestamp mtime = 12; // st_mtim: time of last modification + google.protobuf.Timestamp ctime = 13; // st_ctim: time of last status change +} + +message StatResponse { + Stat stat = 1; + string error = 2; // Non-empty if stat failed. +} + message IpLinkSetRequest { string interface = 1; bool up = 2; diff --git a/Sources/Containerization/Vminitd.swift b/Sources/Containerization/Vminitd.swift index 04e08812..43053381 100644 --- a/Sources/Containerization/Vminitd.swift +++ b/Sources/Containerization/Vminitd.swift @@ -464,6 +464,37 @@ extension Vminitd { public let totalSize: UInt64 } + /// Stat a path in the guest filesystem and return its metadata. + public func stat( + path: URL + ) async throws -> ContainerizationOS.Stat { + let request = Com_Apple_Containerization_Sandbox_V3_StatRequest.with { + $0.path = path.path + } + + let response = try await client.stat(request) + guard response.error.isEmpty else { + throw ContainerizationError(.internalError, message: "stat: \(response.error)") + } + + let s = response.stat + return ContainerizationOS.Stat( + dev: s.dev, + ino: s.ino, + mode: s.mode, + nlink: s.nlink, + uid: s.uid, + gid: s.gid, + rdev: s.rdev, + size: s.size, + blksize: s.blksize, + blocks: s.blocks, + atime: TimeSpec(seconds: s.atime.seconds, nanoseconds: s.atime.nanos), + mtime: TimeSpec(seconds: s.mtime.seconds, nanoseconds: s.mtime.nanos), + ctime: TimeSpec(seconds: s.ctime.seconds, nanoseconds: s.ctime.nanos) + ) + } + /// Unified copy control plane. Sends a CopyRequest over gRPC and processes /// the response stream. Data transfer happens over a separate vsock connection /// managed by the caller. diff --git a/Sources/ContainerizationOS/Stat.swift b/Sources/ContainerizationOS/Stat.swift new file mode 100644 index 00000000..df9590a5 --- /dev/null +++ b/Sources/ContainerizationOS/Stat.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +/// A timestamp with second and nanosecond precision. +public struct TimeSpec: Sendable, Hashable { + /// Seconds since the Unix epoch. + public var seconds: Int64 + /// Nanoseconds past the second. + public var nanoseconds: Int32 + + public init(seconds: Int64, nanoseconds: Int32) { + self.seconds = seconds + self.nanoseconds = nanoseconds + } +} + +/// File metadata returned by a `stat` call. +public struct Stat: Sendable, Hashable { + /// ID of device containing file (`st_dev`). + public var dev: UInt64 + /// Inode number (`st_ino`). + public var ino: UInt64 + /// File type and mode (`st_mode`). + public var mode: UInt32 + /// Number of hard links (`st_nlink`). + public var nlink: UInt64 + /// User ID of owner (`st_uid`). + public var uid: UInt32 + /// Group ID of owner (`st_gid`). + public var gid: UInt32 + /// Device ID, if special file (`st_rdev`). + public var rdev: UInt64 + /// Total size in bytes (`st_size`). + public var size: Int64 + /// Preferred I/O block size (`st_blksize`). + public var blksize: Int64 + /// Number of 512-byte blocks allocated (`st_blocks`). + public var blocks: Int64 + /// Time of last access (`st_atim`). + public var atime: TimeSpec + /// Time of last modification (`st_mtim`). + public var mtime: TimeSpec + /// Time of last status change (`st_ctim`). + public var ctime: TimeSpec + + public init( + dev: UInt64, + ino: UInt64, + mode: UInt32, + nlink: UInt64, + uid: UInt32, + gid: UInt32, + rdev: UInt64, + size: Int64, + blksize: Int64, + blocks: Int64, + atime: TimeSpec, + mtime: TimeSpec, + ctime: TimeSpec + ) { + self.dev = dev + self.ino = ino + self.mode = mode + self.nlink = nlink + self.uid = uid + self.gid = gid + self.rdev = rdev + self.size = size + self.blksize = blksize + self.blocks = blocks + self.atime = atime + self.mtime = mtime + self.ctime = ctime + } +} diff --git a/Sources/Integration/ContainerTests.swift b/Sources/Integration/ContainerTests.swift index 3093d485..f970981e 100644 --- a/Sources/Integration/ContainerTests.swift +++ b/Sources/Integration/ContainerTests.swift @@ -1635,6 +1635,96 @@ extension IntegrationSuite { } } + func testStat() async throws { + let id = "test-stat" + + let bs = try await bootstrap(id) + + let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in + config.process.arguments = ["sleep", "100"] + config.bootLog = bs.bootLog + } + + func assertExec(_ container: LinuxContainer, id: String, cmd: String) async throws { + let exec = try await container.exec(id) { config in + config.arguments = ["sh", "-c", cmd] + } + try await exec.start() + let status = try await exec.wait() + try await exec.delete() + guard status.exitCode == 0 else { + throw IntegrationError.assert(msg: "\(id) failed with exit code \(status.exitCode)") + } + } + + do { + try await container.create() + try await container.start() + + // regular file: "regular file" is exactly 12 bytes + try await assertExec(container, id: "create-regular-file", cmd: "echo -n 'regular file' > /tmp/regular-file.txt") + // directory + try await assertExec(container, id: "create-dir", cmd: "mkdir /tmp/test-dir") + // relative symlink so stat() resolves the target within the same directory + try await assertExec(container, id: "create-symlink", cmd: "ln -s regular-file.txt /tmp/test-link") + // FIFO + try await assertExec(container, id: "create-fifo", cmd: "mkfifo /tmp/test-fifo") + + let vsock = try await container.dialVsock(port: 1024) + let vminitd = try Vminitd(connection: vsock, group: Self.eventLoop) + + let root = URL(filePath: container.root) + + // --- regular file --- + let regularStat = try await vminitd.stat(path: root.appending(path: "tmp/regular-file.txt")) + guard (regularStat.mode & UInt32(S_IFMT)) == S_IFREG else { + throw IntegrationError.assert(msg: "regular file: expected S_IFREG, got mode 0x\(String(regularStat.mode, radix: 16))") + } + guard regularStat.size == 12 else { + throw IntegrationError.assert(msg: "regular file: expected size 12, got \(regularStat.size)") + } + guard regularStat.ino > 0 else { + throw IntegrationError.assert(msg: "regular file: expected non-zero inode, got \(regularStat.ino)") + } + guard regularStat.nlink >= 1 else { + throw IntegrationError.assert(msg: "regular file: expected nlink >= 1, got \(regularStat.nlink)") + } + + // --- directory --- + let dirStat = try await vminitd.stat(path: root.appending(path: "tmp/test-dir")) + guard (dirStat.mode & UInt32(S_IFMT)) == S_IFDIR else { + throw IntegrationError.assert(msg: "directory: expected S_IFDIR, got mode 0x\(String(dirStat.mode, radix: 16))") + } + // A directory always has at least 2 hard links (. and its entry in the parent) + guard dirStat.nlink >= 2 else { + throw IntegrationError.assert(msg: "directory: expected nlink >= 2, got \(dirStat.nlink)") + } + + // --- symlink --- + // stat(2) follows symlinks, so the result reflects the target regular file + let symlinkStat = try await vminitd.stat(path: root.appending(path: "tmp/test-link")) + guard (symlinkStat.mode & UInt32(S_IFMT)) == S_IFREG else { + throw IntegrationError.assert(msg: "symlink (followed): expected S_IFREG, got mode 0x\(String(symlinkStat.mode, radix: 16))") + } + guard symlinkStat.size == regularStat.size else { + throw IntegrationError.assert(msg: "symlink (followed): expected size \(regularStat.size), got \(symlinkStat.size)") + } + + // --- FIFO --- + let fifoStat = try await vminitd.stat(path: root.appending(path: "tmp/test-fifo")) + guard (fifoStat.mode & UInt32(S_IFMT)) == S_IFIFO else { + throw IntegrationError.assert(msg: "FIFO: expected S_IFIFO, got mode 0x\(String(fifoStat.mode, radix: 16))") + } + + try await container.kill(SIGKILL) + try await container.wait() + try await container.stop() + } catch { + try? await container.stop() + throw error + } + } + func testCopyIn() async throws { let id = "test-copy-in" diff --git a/Sources/Integration/Suite.swift b/Sources/Integration/Suite.swift index 4352d2aa..08583a54 100644 --- a/Sources/Integration/Suite.swift +++ b/Sources/Integration/Suite.swift @@ -324,6 +324,7 @@ struct IntegrationSuite: AsyncParsableCommand { Test("container capabilities OCI default", testCapabilitiesOCIDefault), Test("container capabilities all capabilities", testCapabilitiesAllCapabilities), Test("container capabilities file ownership", testCapabilitiesFileOwnership), + Test("container stat", testStat), Test("container copy in", testCopyIn), Test("container copy out", testCopyOut), Test("container copy large file", testCopyLargeFile), diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index f43b2d34..b08e9dea 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -38,12 +38,14 @@ private let _mount = Musl.mount private let _umount = Musl.umount2 private let _kill = Musl.kill private let _sync = Musl.sync +private let _stat: @Sendable (UnsafePointer, UnsafeMutablePointer) -> Int32 = stat #elseif canImport(Glibc) import Glibc private let _mount = Glibc.mount private let _umount = Glibc.umount2 private let _kill = Glibc.kill private let _sync = Glibc.sync +private let _stat: @Sendable (UnsafePointer, UnsafeMutablePointer) -> Int32 = stat #endif extension ContainerizationError { @@ -362,6 +364,59 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ return .init() } + func stat( + request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + context: GRPCCore.ServerContext, + ) async throws -> Com_Apple_Containerization_Sandbox_V3_StatResponse { + log.debug( + "stat", + metadata: [ + "path": "\(request.path)" + ] + ) + + #if os(Linux) + #if canImport(Musl) + var s = Musl.stat() + #elseif canImport(Glibc) + var s = Glibc.stat() + #endif + let result = _stat(request.path, &s) + if result == -1 { + let error = swiftErrno("stat") + return .with { $0.error = "\(error)" } + } + return .with { + $0.stat = .with { + $0.dev = UInt64(s.st_dev) + $0.ino = UInt64(s.st_ino) + $0.mode = s.st_mode + $0.nlink = UInt64(s.st_nlink) + $0.uid = s.st_uid + $0.gid = s.st_gid + $0.rdev = UInt64(s.st_rdev) + $0.size = Int64(s.st_size) + $0.blksize = Int64(s.st_blksize) + $0.blocks = Int64(s.st_blocks) + $0.atime = .with { + $0.seconds = Int64(s.st_atim.tv_sec) + $0.nanos = Int32(s.st_atim.tv_nsec) + } + $0.mtime = .with { + $0.seconds = Int64(s.st_mtim.tv_sec) + $0.nanos = Int32(s.st_mtim.tv_nsec) + } + $0.ctime = .with { + $0.seconds = Int64(s.st_ctim.tv_sec) + $0.nanos = Int32(s.st_ctim.tv_nsec) + } + } + } + #else + fatalError("stat not supported on platform") + #endif + } + // Chunk size for streaming file transfers (1MB). private static let copyChunkSize = 1024 * 1024