mirror of
https://github.com/apple/container.git
synced 2026-07-12 20:47:04 +00:00
d6f052d206
## Motivation and Context
Now that we're in 2026, we need to update the license headers on all the
files. Unfortunately, Hawkeye doesn't have an attribute for the current
year to help us avoid this in the future. Instead, I had to work around
this by doing the following:
1. Update licenserc.toml with:
```
[properties]
... (other properties)
currentYear = "2026"
```
2. Update scripts/license-header.txt with
```
Copyright ©{{ " " }}{%- set created = attrs.git_file_created_year or
attrs.disk_file_created_year -%}{%- set modified = props["currentYear"]
-%}{%- if created != modified -%} {{created}}-{{modified}}{%- else
-%}{{created}}{%- endif -%}{{ " " }}{{ props["copyrightOwner"] }}.
```
Then I removed these two changes before committing. After this PR is
merged, all files will have recently had git updates, so the existing
code for setting the modified year should work as intended.
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
286 lines
8.5 KiB
Swift
286 lines
8.5 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 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
|