mirror of
https://github.com/apple/container.git
synced 2026-07-12 12:37:02 +00:00
5385a5c466
## Type of Change - [x] New feature - [x] Breaking change ## Motivation and Context We want to be able to support using multiple network plugins during `container`'s lifetime. This additionally means needing to pick an interface strategy to interpret a network attachment based on what network plugin was used to create that attachment. This PR will potentially replace https://github.com/apple/container/pull/1081. Followups: - doc updates to include the ability to specify plugin in the network creation cli ## Testing - [x] Tested locally - [x] Added/updated tests
292 lines
8.6 KiB
Swift
292 lines
8.6 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025-2026 Apple Inc. and the container 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.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if os(macOS)
|
|
import ContainerizationError
|
|
import Foundation
|
|
|
|
/// A message that can be pass across application boundaries via XPC.
|
|
public struct XPCMessage: Sendable {
|
|
/// Defined message key storing the route value.
|
|
public static let routeKey = "com.apple.container.xpc.route"
|
|
/// Defined message key storing the error value.
|
|
public static let errorKey = "com.apple.container.xpc.error"
|
|
|
|
// Access to `object` is protected by a lock
|
|
private nonisolated(unsafe) let object: xpc_object_t
|
|
private let lock = NSLock()
|
|
private let isErr: Bool
|
|
|
|
/// The underlying xpc object that the message wraps.
|
|
public var underlying: xpc_object_t {
|
|
lock.withLock {
|
|
object
|
|
}
|
|
}
|
|
public var isErrorType: Bool { isErr }
|
|
|
|
public init(object: xpc_object_t) {
|
|
self.object = object
|
|
self.isErr = xpc_get_type(self.object) == XPC_TYPE_ERROR
|
|
}
|
|
|
|
public init(route: String) {
|
|
self.object = xpc_dictionary_create_empty()
|
|
self.isErr = false
|
|
xpc_dictionary_set_string(self.object, Self.routeKey, route)
|
|
}
|
|
}
|
|
|
|
extension XPCMessage {
|
|
public static func == (lhs: XPCMessage, rhs: xpc_object_t) -> Bool {
|
|
xpc_equal(lhs.underlying, rhs)
|
|
}
|
|
|
|
public func reply() -> XPCMessage {
|
|
lock.withLock {
|
|
XPCMessage(object: xpc_dictionary_create_reply(object)!)
|
|
}
|
|
}
|
|
|
|
public func errorKeyDescription() -> String? {
|
|
guard self.isErr,
|
|
let xpcErr = lock.withLock({
|
|
xpc_dictionary_get_string(
|
|
self.object,
|
|
XPC_ERROR_KEY_DESCRIPTION
|
|
)
|
|
})
|
|
else {
|
|
return nil
|
|
}
|
|
return String(cString: xpcErr)
|
|
}
|
|
|
|
public func error() throws {
|
|
let data = data(key: Self.errorKey)
|
|
if let data {
|
|
let item = try? JSONDecoder().decode(ContainerXPCError.self, from: data)
|
|
precondition(item != nil, "expected to receive a ContainerXPCXPCError")
|
|
|
|
throw ContainerizationError(item!.code, message: item!.message)
|
|
}
|
|
}
|
|
|
|
public func set(error: ContainerizationError) {
|
|
var message = error.message
|
|
if let cause = error.cause {
|
|
message += " (cause: \"\(cause)\")"
|
|
}
|
|
let serializableError = ContainerXPCError(code: error.code.description, message: message)
|
|
let data = try? JSONEncoder().encode(serializableError)
|
|
precondition(data != nil)
|
|
|
|
set(key: Self.errorKey, value: data!)
|
|
}
|
|
}
|
|
|
|
struct ContainerXPCError: Codable {
|
|
let code: String
|
|
let message: String
|
|
}
|
|
|
|
extension XPCMessage {
|
|
public func data(key: String) -> Data? {
|
|
var length: Int = 0
|
|
let bytes = lock.withLock {
|
|
xpc_dictionary_get_data(self.object, key, &length)
|
|
}
|
|
|
|
guard let bytes else {
|
|
return nil
|
|
}
|
|
|
|
return Data(bytes: bytes, count: length)
|
|
}
|
|
|
|
/// dataNoCopy is similar to data, except the data is not copied
|
|
/// to a new buffer. What this means in practice is the second the
|
|
/// underlying xpc_object_t gets released by ARC the data will be
|
|
/// released as well. This variant should be used when you know the
|
|
/// data will be used before the object has no more references.
|
|
public func dataNoCopy(key: String) -> Data? {
|
|
var length: Int = 0
|
|
let bytes = lock.withLock {
|
|
xpc_dictionary_get_data(self.object, key, &length)
|
|
}
|
|
|
|
guard let bytes else {
|
|
return nil
|
|
}
|
|
|
|
return Data(
|
|
bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes),
|
|
count: length,
|
|
deallocator: .none
|
|
)
|
|
}
|
|
|
|
public func set(key: String, value: Data) {
|
|
value.withUnsafeBytes { ptr in
|
|
if let addr = ptr.baseAddress {
|
|
lock.withLock {
|
|
xpc_dictionary_set_data(self.object, key, addr, value.count)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func string(key: String) -> String? {
|
|
let _id = lock.withLock {
|
|
xpc_dictionary_get_string(self.object, key)
|
|
}
|
|
if let _id {
|
|
return String(cString: _id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func set(key: String, value: String) {
|
|
lock.withLock {
|
|
xpc_dictionary_set_string(self.object, key, value)
|
|
}
|
|
}
|
|
|
|
public func bool(key: String) -> Bool {
|
|
lock.withLock {
|
|
xpc_dictionary_get_bool(self.object, key)
|
|
}
|
|
}
|
|
|
|
public func set(key: String, value: Bool) {
|
|
lock.withLock {
|
|
xpc_dictionary_set_bool(self.object, key, value)
|
|
}
|
|
}
|
|
|
|
public func uint64(key: String) -> UInt64 {
|
|
lock.withLock {
|
|
xpc_dictionary_get_uint64(self.object, key)
|
|
}
|
|
}
|
|
|
|
public func set(key: String, value: UInt64) {
|
|
lock.withLock {
|
|
xpc_dictionary_set_uint64(self.object, key, value)
|
|
}
|
|
}
|
|
|
|
public func int64(key: String) -> Int64 {
|
|
lock.withLock {
|
|
xpc_dictionary_get_int64(self.object, key)
|
|
}
|
|
}
|
|
|
|
public func set(key: String, value: Int64) {
|
|
lock.withLock {
|
|
xpc_dictionary_set_int64(self.object, key, value)
|
|
}
|
|
}
|
|
|
|
public func date(key: String) -> Date {
|
|
lock.withLock {
|
|
let nsSinceEpoch = xpc_dictionary_get_date(self.object, key)
|
|
return Date(timeIntervalSince1970: TimeInterval(nsSinceEpoch) / 1_000_000_000)
|
|
}
|
|
}
|
|
|
|
public func set(key: String, value: Date) {
|
|
lock.withLock {
|
|
let nsSinceEpoch = Int64(value.timeIntervalSince1970 * 1_000_000_000)
|
|
xpc_dictionary_set_date(self.object, key, nsSinceEpoch)
|
|
}
|
|
}
|
|
|
|
public func fileHandle(key: String) -> FileHandle? {
|
|
let fd = lock.withLock {
|
|
xpc_dictionary_get_value(self.object, key)
|
|
}
|
|
if let fd {
|
|
let fd2 = xpc_fd_dup(fd)
|
|
return FileHandle(fileDescriptor: fd2, closeOnDealloc: false)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func set(key: String, value: FileHandle) {
|
|
let fd = xpc_fd_create(value.fileDescriptor)
|
|
close(value.fileDescriptor)
|
|
lock.withLock {
|
|
xpc_dictionary_set_value(self.object, key, fd)
|
|
}
|
|
}
|
|
|
|
public func fileHandles(key: String) -> [FileHandle]? {
|
|
let fds = lock.withLock {
|
|
xpc_dictionary_get_value(self.object, key)
|
|
}
|
|
if let fds {
|
|
let fd1 = xpc_array_dup_fd(fds, 0)
|
|
let fd2 = xpc_array_dup_fd(fds, 1)
|
|
if fd1 == -1 || fd2 == -1 {
|
|
return nil
|
|
}
|
|
return [
|
|
FileHandle(fileDescriptor: fd1, closeOnDealloc: false),
|
|
FileHandle(fileDescriptor: fd2, closeOnDealloc: false),
|
|
]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func set(key: String, value: [FileHandle]) throws {
|
|
let fdArray = xpc_array_create(nil, 0)
|
|
for fh in value {
|
|
guard let xpcFd = xpc_fd_create(fh.fileDescriptor) else {
|
|
throw ContainerizationError(
|
|
.internalError,
|
|
message: "failed to create xpc fd for \(fh.fileDescriptor)"
|
|
)
|
|
}
|
|
xpc_array_append_value(fdArray, xpcFd)
|
|
close(fh.fileDescriptor)
|
|
}
|
|
lock.withLock {
|
|
xpc_dictionary_set_value(self.object, key, fdArray)
|
|
}
|
|
}
|
|
|
|
public func set(key: String, xpcDictionary: xpc_object_t) {
|
|
lock.withLock {
|
|
xpc_dictionary_set_value(self.object, key, xpcDictionary)
|
|
}
|
|
}
|
|
|
|
public func endpoint(key: String) -> xpc_endpoint_t? {
|
|
lock.withLock {
|
|
xpc_dictionary_get_value(self.object, key)
|
|
}
|
|
}
|
|
|
|
public func set(key: String, value: xpc_endpoint_t) {
|
|
lock.withLock {
|
|
xpc_dictionary_set_value(self.object, key, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|