diff --git a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift index 1600090f..6a3bc13f 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift @@ -85,6 +85,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtoc handler: @escaping (Com_Apple_Containerization_Sandbox_V3_CopyResponse) -> Void ) -> ServerStreamingCall + func stat( + _ request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + callOptions: CallOptions? + ) -> UnaryCall + func createProcess( _ request: Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest, callOptions: CallOptions? @@ -366,6 +371,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtocol { ) } + /// Stat a path in the guest filesystem. + /// + /// - Parameters: + /// - request: Request to send to Stat. + /// - callOptions: Call options. + /// - Returns: A `UnaryCall` with futures for the metadata, status and response. + public func stat( + _ request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + callOptions: CallOptions? = nil + ) -> UnaryCall { + return self.makeUnaryCall( + path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.stat.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeStatInterceptors() ?? [] + ) + } + /// Create a new process inside the container. /// /// - Parameters: @@ -805,6 +828,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientP callOptions: CallOptions? ) -> GRPCAsyncServerStreamingCall + func makeStatCall( + _ request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + func makeCreateProcessCall( _ request: Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest, callOptions: CallOptions? @@ -1026,6 +1054,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco ) } + public func makeStatCall( + _ request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.stat.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeStatInterceptors() ?? [] + ) + } + public func makeCreateProcessCall( _ request: Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest, callOptions: CallOptions? = nil @@ -1365,6 +1405,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco ) } + public func stat( + _ request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + callOptions: CallOptions? = nil + ) async throws -> Com_Apple_Containerization_Sandbox_V3_StatResponse { + return try await self.performAsyncUnaryCall( + path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.stat.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeStatInterceptors() ?? [] + ) + } + public func createProcess( _ request: Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest, callOptions: CallOptions? = nil @@ -1631,6 +1683,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientInterc /// - Returns: Interceptors to use when invoking 'copy'. func makeCopyInterceptors() -> [ClientInterceptor] + /// - Returns: Interceptors to use when invoking 'stat'. + func makeStatInterceptors() -> [ClientInterceptor] + /// - Returns: Interceptors to use when invoking 'createProcess'. func makeCreateProcessInterceptors() -> [ClientInterceptor] @@ -1701,6 +1756,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata { Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.setupEmulator, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.writeFile, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.copy, + Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.stat, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.createProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.deleteProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.startProcess, @@ -1783,6 +1839,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata { type: GRPCCallType.serverStreaming ) + public static let stat = GRPCMethodDescriptor( + name: "Stat", + path: "/com.apple.containerization.sandbox.v3.SandboxContext/Stat", + type: GRPCCallType.unary + ) + public static let createProcess = GRPCMethodDescriptor( name: "CreateProcess", path: "/com.apple.containerization.sandbox.v3.SandboxContext/CreateProcess", @@ -1931,6 +1993,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider: Ca /// the gRPC stream is used only for control/metadata. func copy(request: Com_Apple_Containerization_Sandbox_V3_CopyRequest, context: StreamingResponseCallContext) -> EventLoopFuture + /// Stat a path in the guest filesystem. + func stat(request: Com_Apple_Containerization_Sandbox_V3_StatRequest, context: StatusOnlyCallContext) -> EventLoopFuture + /// Create a new process inside the container. func createProcess(request: Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest, context: StatusOnlyCallContext) -> EventLoopFuture @@ -2089,6 +2154,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider { userFunction: self.copy(request:context:) ) + case "Stat": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStatInterceptors() ?? [], + userFunction: self.stat(request:context:) + ) + case "CreateProcess": return UnaryServerHandler( context: context, @@ -2328,6 +2402,12 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvide context: GRPCAsyncServerCallContext ) async throws + /// Stat a path in the guest filesystem. + func stat( + request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + context: GRPCAsyncServerCallContext + ) async throws -> Com_Apple_Containerization_Sandbox_V3_StatResponse + /// Create a new process inside the container. func createProcess( request: Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest, @@ -2547,6 +2627,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvider { wrapping: { try await self.copy(request: $0, responseStream: $1, context: $2) } ) + case "Stat": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStatInterceptors() ?? [], + wrapping: { try await self.stat(request: $0, context: $1) } + ) + case "CreateProcess": return GRPCAsyncServerHandler( context: context, @@ -2757,6 +2846,10 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextServerInterc /// Defaults to calling `self.makeInterceptors()`. func makeCopyInterceptors() -> [ServerInterceptor] + /// - Returns: Interceptors to use when handling 'stat'. + /// Defaults to calling `self.makeInterceptors()`. + func makeStatInterceptors() -> [ServerInterceptor] + /// - Returns: Interceptors to use when handling 'createProcess'. /// Defaults to calling `self.makeInterceptors()`. func makeCreateProcessInterceptors() -> [ServerInterceptor] @@ -2845,6 +2938,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata { Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.setupEmulator, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.writeFile, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.copy, + Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.stat, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.createProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.deleteProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.startProcess, @@ -2927,6 +3021,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata { type: GRPCCallType.serverStreaming ) + public static let stat = GRPCMethodDescriptor( + name: "Stat", + path: "/com.apple.containerization.sandbox.v3.SandboxContext/Stat", + type: GRPCCallType.unary + ) + public static let createProcess = GRPCMethodDescriptor( name: "CreateProcess", path: "/com.apple.containerization.sandbox.v3.SandboxContext/CreateProcess", diff --git a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift index 87d22643..f10feb8a 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift @@ -937,6 +937,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 {return _atime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_atime = newValue} + } + /// Returns true if `atime` has been explicitly set. + public var hasAtime: Bool {return 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 {return _mtime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_mtime = newValue} + } + /// Returns true if `mtime` has been explicitly set. + public var hasMtime: Bool {return 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 {return _ctime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_ctime = newValue} + } + /// Returns true if `ctime` has been explicitly set. + public var hasCtime: Bool {return 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 {return _storage._stat ?? Com_Apple_Containerization_Sandbox_V3_Stat()} + set {_uniqueStorage()._stat = newValue} + } + /// Returns true if `stat` has been explicitly set. + public var hasStat: Bool {return _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 {return _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 @@ -2929,6 +3042,226 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyResponse.Status: SwiftProtob ] } +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 = [ + 1: .same(proto: "path"), + ] + + 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 = [ + 1: .same(proto: "dev"), + 2: .same(proto: "ino"), + 3: .same(proto: "mode"), + 4: .same(proto: "nlink"), + 5: .same(proto: "uid"), + 6: .same(proto: "gid"), + 7: .same(proto: "rdev"), + 8: .same(proto: "size"), + 9: .same(proto: "blksize"), + 10: .same(proto: "blocks"), + 11: .same(proto: "atime"), + 12: .same(proto: "mtime"), + 13: .same(proto: "ctime"), + ] + + 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 = [ + 1: .same(proto: "stat"), + 2: .same(proto: "error"), + ] + + 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 = [ 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 363a2131..7dcb2f52 100644 --- a/Sources/Containerization/Vminitd.swift +++ b/Sources/Containerization/Vminitd.swift @@ -448,6 +448,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..92dbeecd --- /dev/null +++ b/Sources/ContainerizationOS/Stat.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025-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/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index 405ae42b..40df37bb 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -37,12 +37,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 { @@ -355,6 +357,59 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid return .init() } + func stat( + request: Com_Apple_Containerization_Sandbox_V3_StatRequest, + context: GRPCAsyncServerCallContext, + ) 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