containerization-framework 0.8.0

Rust bindings for Apple's Containerization framework: Linux containers in lightweight VMs, in-process and without a daemon.
//===----------------------------------------------------------------------===//
// `Vminitd`, from Containerization: the client of the agent running in the
// guest, which `VZVirtualMachineInstance.dialAgent()` returns.
//===----------------------------------------------------------------------===//

import Containerization
import ContainerizationExtras
import ContainerizationOCI
import ContainerizationOS
import Foundation

final class CzVminitd: Sendable {
  let agent: Vminitd

  init(_ agent: Vminitd) {
    self.agent = agent
  }

  func standardSetup() -> CzOutcome {
    let agent = agent

    return CzOutcome { try blocking { try await agent.standardSetup() } }
  }

  func close() -> CzOutcome {
    let agent = agent

    return CzOutcome { try blocking { try await agent.close() } }
  }

  func filesystemOperation(operation: FilesystemOperationKind, path: RustStr, containerID: RustString?) -> CzOutcome {
    let agent = agent
    let path = path.toString()
    let containerID = containerID?.toString()
    let operation: FilesystemOperation =
      switch operation {
      case .Freeze: .freeze
      case .Thaw: .thaw
      case .Trim: .trim
      }

    return CzOutcome {
      try blocking { try await agent.filesystemOperation(operation: operation, path: path, containerID: containerID) }
    }
  }

  func getenv(key: RustStr) -> CzOutcome {
    let agent = agent
    let key = key.toString()

    return CzOutcome { try blocking { try await agent.getenv(key: key) } }
  }

  func setenv(key: RustStr, value: RustStr) -> CzOutcome {
    let agent = agent
    let key = key.toString()
    let value = value.toString()

    return CzOutcome { try blocking { try await agent.setenv(key: key, value: value) } }
  }

  func mount(mount: RustOciMount) -> CzOutcome {
    let agent = agent
    let mount = ContainerizationOCI.Mount(mount)

    return CzOutcome { try blocking { try await agent.mount(mount) } }
  }

  func umount(path: RustStr, flags: Int32) -> CzOutcome {
    let agent = agent
    let path = path.toString()

    return CzOutcome { try blocking { try await agent.umount(path: path, flags: flags) } }
  }

  func mkdir(path: RustStr, all: Bool, perms: UInt32) -> CzOutcome {
    let agent = agent
    let path = path.toString()

    return CzOutcome { try blocking { try await agent.mkdir(path: path, all: all, perms: perms) } }
  }

  func kill(pid: Int32, signal: Int32) -> CzOutcome {
    let agent = agent

    return CzOutcome { try blocking { try await agent.kill(pid: pid, signal: signal) } }
  }

  func sync() -> CzOutcome {
    let agent = agent

    return CzOutcome { try blocking { try await agent.sync() } }
  }

  /// `options` stands for `nil` when `hasOptions` is false.
  func createProcess(
    id: RustStr,
    containerID: RustString?,
    stdinPort: UInt32?,
    stdoutPort: UInt32?,
    stderrPort: UInt32?,
    ociRuntimePath: RustString?,
    configuration: RustSpec,
    hasOptions: Bool,
    options: RustVec<UInt8>
  ) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()
    let ociRuntimePath = ociRuntimePath?.toString()
    let configuration = ContainerizationOCI.Spec(configuration)
    let options = hasOptions ? Data(options) : nil

    return CzOutcome {
      try blocking {
        try await agent.createProcess(
          id: id,
          containerID: containerID,
          stdinPort: stdinPort,
          stdoutPort: stdoutPort,
          stderrPort: stderrPort,
          ociRuntimePath: ociRuntimePath,
          configuration: configuration,
          options: options
        )
      }
    }
  }

  func startProcess(id: RustStr, containerID: RustString?) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()

    return CzOutcome { try blocking { try await agent.startProcess(id: id, containerID: containerID) } }
  }

  func signalProcess(id: RustStr, containerID: RustString?, signal: Int32) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()

    return CzOutcome {
      try blocking { try await agent.signalProcess(id: id, containerID: containerID, signal: signal) }
    }
  }

  func resizeProcess(id: RustStr, containerID: RustString?, columns: UInt32, rows: UInt32) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()

    return CzOutcome {
      try blocking { try await agent.resizeProcess(id: id, containerID: containerID, columns: columns, rows: rows) }
    }
  }

  func waitProcess(id: RustStr, containerID: RustString?, timeoutInSeconds: Int64?) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()

    return CzOutcome {
      try blocking { try await agent.waitProcess(id: id, containerID: containerID, timeoutInSeconds: timeoutInSeconds) }
    }
  }

  func deleteProcess(id: RustStr, containerID: RustString?) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()

    return CzOutcome { try blocking { try await agent.deleteProcess(id: id, containerID: containerID) } }
  }

  func closeProcessStdin(id: RustStr, containerID: RustString?) -> CzOutcome {
    let agent = agent
    let id = id.toString()
    let containerID = containerID?.toString()

    return CzOutcome { try blocking { try await agent.closeProcessStdin(id: id, containerID: containerID) } }
  }

  func up(name: RustStr, mtu: UInt32?) -> CzOutcome {
    let agent = agent
    let name = name.toString()

    return CzOutcome { try blocking { try await agent.up(name: name, mtu: mtu) } }
  }

  func down(name: RustStr) -> CzOutcome {
    let agent = agent
    let name = name.toString()

    return CzOutcome { try blocking { try await agent.down(name: name) } }
  }

  func addressAdd(name: RustStr, address: RustInterfaceAddress) -> CzOutcome {
    let agent = agent
    let name = name.toString()

    return CzOutcome {
      let address = try InterfaceAddress(address)

      return try blocking { try await agent.addressAdd(name: name, address: address) }
    }
  }

  func routeAddLink(name: RustStr, route: RustLinkRoute) -> CzOutcome {
    let agent = agent
    let name = name.toString()
    let route = LinkRoute(route)

    return CzOutcome { try blocking { try await agent.routeAddLink(name: name, route: route) } }
  }

  func routeAddDefault(name: RustStr, route: RustDefaultRoute) -> CzOutcome {
    let agent = agent
    let name = name.toString()
    let route = DefaultRoute(route)

    return CzOutcome { try blocking { try await agent.routeAddDefault(name: name, route: route) } }
  }

  func configureDNS(config: RustDns, location: RustStr) -> CzOutcome {
    let agent = agent
    let config = DNS(config)
    let location = location.toString()

    return CzOutcome { try blocking { try await agent.configureDNS(config: config, location: location) } }
  }

  func configureHosts(config: RustHosts, location: RustStr) -> CzOutcome {
    let agent = agent
    let config = Hosts(config)
    let location = location.toString()

    return CzOutcome { try blocking { try await agent.configureHosts(config: config, location: location) } }
  }

  func containerStatistics(containerIDs: RustVec<RustString>, categories: Int64) -> CzOutcome {
    let agent = agent
    let containerIDs = strings(containerIDs)
    let categories = StatCategory(rawValue: Int(categories))

    return CzOutcome {
      try blocking { try await agent.containerStatistics(containerIDs: containerIDs, categories: categories) }
    }
  }

  func setupEmulator(binaryPath: RustStr, configuration: RustBinfmtEntry) -> CzOutcome {
    let agent = agent
    let binaryPath = binaryPath.toString()
    // `Binfmt.Entry` isn't `Sendable`, though it holds only `String`s.
    nonisolated(unsafe) let configuration = Binfmt.Entry(configuration)

    return CzOutcome {
      try blocking { try await agent.setupEmulator(binaryPath: binaryPath, configuration: configuration) }
    }
  }

  func setTime(sec: Int64, usec: Int32) -> CzOutcome {
    let agent = agent

    return CzOutcome { try blocking { try await agent.setTime(sec: sec, usec: usec) } }
  }

  func sysctl(keys: RustVec<RustString>, values: RustVec<RustString>) -> CzOutcome {
    let agent = agent
    let settings = dictionary(keys, values)

    return CzOutcome { try blocking { try await agent.sysctl(settings: settings) } }
  }

  func stat(root: RustStr, path: RustStr) -> CzOutcome {
    let agent = agent
    let root = root.toString()
    let path = path.toString()

    return CzOutcome { try blocking { try await agent.stat(root: root, path: path) } }
  }

  func enableRosetta() -> CzOutcome {
    let agent = agent

    return CzOutcome { try blocking { try await agent.enableRosetta() } }
  }

  func relaySocket(port: UInt32, configuration: CzUnixSocketConfiguration) -> CzOutcome {
    let agent = agent
    let configuration = configuration.configuration

    return CzOutcome { try blocking { try await agent.relaySocket(port: port, configuration: configuration) } }
  }

  func stopSocketRelay(configuration: CzUnixSocketConfiguration) -> CzOutcome {
    let agent = agent
    let configuration = configuration.configuration

    return CzOutcome { try blocking { try await agent.stopSocketRelay(configuration: configuration) } }
  }
}

extension CzVirtualMachineInstance {
  func dialAgent() -> CzOutcome {
    let instance = instance

    return CzOutcome { CzVminitd(try blocking { try await instance.dialAgent() }) }
  }
}

// MARK: Rust to Swift

extension InterfaceAddress {
  init(_ address: RustInterfaceAddressRef) throws {
    self.init(
      ipv4Address: try CIDRv4(bridged: address.ipv4AddressValue(), prefix: address.ipv4AddressPrefix()),
      ipv6Address: address.hasIpv6Address()
        ? try CIDRv6(bridged: address.ipv6AddressAddress(), prefix: address.ipv6AddressPrefix())
        : nil
    )
  }
}

extension LinkRoute {
  init(_ route: RustLinkRouteRef) {
    self.init(
      ipv4Destination: route.ipv4Destination().map { IPv4Address($0) },
      ipv4Source: route.ipv4Source().map { IPv4Address($0) },
      ipv6Destination: route.hasIpv6Destination() ? IPv6Address(route.ipv6Destination()) : nil,
      ipv6Source: route.hasIpv6Source() ? IPv6Address(route.ipv6Source()) : nil
    )
  }
}

extension DefaultRoute {
  init(_ route: RustDefaultRouteRef) {
    self.init(
      ipv4Gateway: route.ipv4Gateway().map { IPv4Address($0) },
      ipv6Gateway: route.hasIpv6Gateway() ? IPv6Address(route.ipv6Gateway()) : nil
    )
  }
}

extension Binfmt.Entry {
  init(_ entry: RustBinfmtEntryRef) {
    self.init(
      name: entry.name().toString(),
      type: entry.entryType().toString(),
      offset: entry.offset().toString(),
      magic: entry.magic().toString(),
      mask: entry.mask().toString(),
      flags: entry.flags().toString()
    )
  }
}

// MARK: Swift to Rust

extension CzOutcome {
  func vminitd() -> CzVminitd { taken() }

  /// A `Stat`'s unsigned fields, in Swift's order.
  func statUnsigned() -> RustVec<UInt64> {
    let stat: ContainerizationOS.Stat = taken()
    let vec = RustVec<UInt64>()
    for value in [
      stat.dev, stat.ino, UInt64(stat.mode), stat.nlink, UInt64(stat.uid), UInt64(stat.gid), stat.rdev,
    ] {
      vec.push(value: value)
    }
    return vec
  }

  /// A `Stat`'s signed fields, in Swift's order, with each `TimeSpec` as its
  /// seconds and nanoseconds.
  func statSigned() -> RustVec<Int64> {
    let stat: ContainerizationOS.Stat = taken()
    let vec = RustVec<Int64>()
    for value in [
      stat.size, stat.blksize, stat.blocks,
      stat.atime.seconds, Int64(stat.atime.nanoseconds),
      stat.mtime.seconds, Int64(stat.mtime.nanoseconds),
      stat.ctime.seconds, Int64(stat.ctime.nanoseconds),
    ] {
      vec.push(value: value)
    }
    return vec
  }
}

/// For a unit test: `Binfmt.path`.
func binfmtPath() -> String {
  Binfmt.path
}

/// For a unit test: `Binfmt.Entry.amd64()`'s fields, in Swift's order.
func binfmtEntryAmd64() -> RustVec<RustString> {
  let entry = Binfmt.Entry.amd64()

  return rustStrings([entry.name, entry.type, entry.offset, entry.magic, entry.mask, entry.flags])
}