containerization-framework 0.2.0

Rust bindings for Apple's Containerization framework: Linux containers in lightweight VMs, in-process and without a daemon.
//===----------------------------------------------------------------------===//
// Containerization is async throughout; the bridge is not.
//===----------------------------------------------------------------------===//

import Foundation

/// Runs an async body in a detached task and blocks until it finishes.
///
/// Rust calls in on its own threads, never on Swift's cooperative pool, so
/// blocking here cannot deadlock the executor the body runs on.
func blocking<T>(_ body: @escaping @Sendable () async throws -> T) throws -> T {
    let semaphore = DispatchSemaphore(value: 0)
    nonisolated(unsafe) var outcome: Result<T, any Error>?

    Task.detached {
        do {
            outcome = .success(try await body())
        } catch {
            outcome = .failure(error)
        }
        semaphore.signal()
    }

    semaphore.wait()

    guard let outcome else {
        throw BridgeError.noOutcome
    }

    return try outcome.get()
}

enum BridgeError: Error, CustomStringConvertible {
    /// Cannot happen: the semaphore is only signalled after `outcome` is set.
    case noOutcome
    /// A value the bridge could not parse: a malformed wire string, or a
    /// setting that never made sense.
    case malformed(String, String)
    /// An `exec` for a session this process does not hold, e.g. a joining
    /// caller reaching the wrong process. The control socket prevents it.
    case notBooted(String)
    /// The binary is not signed for virtualization, usually a rebuild that was
    /// not re-signed.
    case unentitled
    /// A build step exited non-zero. Carries the step's label so the message
    /// names what failed.
    case stepFailed(String, Int32)
    /// The ingest body left no index descriptor: a bug in `Build.ingest`.
    case notIngested(String)
    /// The kernel download returned a non-success status.
    case kernelUnavailable(String, Int)
    /// The downloaded archive lacks the kernel at the expected path: the
    /// release layout has changed.
    case kernelMissing(String)
    /// A rootfs could not be copied to or from the build cache. Filesystems
    /// without clones fall back to a copy and never land here.
    case notCloned(String, String)

    var description: String {
        switch self {
        case .noOutcome:
            return "the bridged task signalled completion without an outcome"
        case .malformed(let what, let value):
            return "malformed \(what): \(value.debugDescription)"
        case .notBooted(let name):
            return "this process does not own a session named \(name)"
        case .stepFailed(let label, let code):
            return "build step failed with exit code \(code): \(label)"
        case .notIngested(let reference):
            return "nothing was ingested for \(reference)"
        case .kernelUnavailable(let url, let status):
            return "downloading a kernel from \(url) answered \(status)"
        case .kernelMissing(let path):
            return "the downloaded archive has no \(path)"
        case .notCloned(let name, let reason):
            return "could not copy \(name): \(reason)"
        case .unentitled:
            return "this build is not signed for virtualization!"
        }
    }
}