containerization-framework 0.10.0

Rust bindings for Apple's Containerization framework: Linux containers in lightweight VMs, in-process and without a daemon.
//===----------------------------------------------------------------------===//
// ContainerizationOS: `Terminal`, `CapabilityName`, `CapabilitySet`,
// `KeychainQuery`, `Sysctl`, `ProcNetSnmp` and `File`.
//
// `CapabilityName` and `CapabilitySet` cross as their `description`s.
//===----------------------------------------------------------------------===//

import ContainerizationOS
import Foundation

/// For a unit test: `CapabilityName.allCases`' descriptions.
func capabilityNameDescriptions() -> RustVec<RustString> {
  rustStrings(CapabilityName.allCases.map(\.description))
}

/// For a unit test: `CapabilityName.allCases`' `capValue`s.
func capabilityNameCapValues() -> RustVec<UInt32> {
  rustVec(CapabilityName.allCases.map(\.capValue))
}

/// For a unit test: each `CapabilitySet`'s description, in the order of Rust's
/// `ALL`.
func capabilitySetDescriptions() -> RustVec<RustString> {
  rustStrings(
    [ContainerizationOS.CapabilitySet.bounding, .effective, .inheritable, .permitted, .ambient].map(\.description))
}

func parseCapabilityName(rawValue: RustStr) -> CzOutcome {
  let rawValue = rawValue.toString()

  return CzOutcome { try CapabilityName(rawValue: rawValue).description }
}

func parseCapabilitySet(rawValue: RustStr) -> CzOutcome {
  let rawValue = rawValue.toString()

  // Qualified: the bridge has a `CapabilitySet` of its own.
  return CzOutcome { try ContainerizationOS.CapabilitySet(rawValue: rawValue).description }
}

func newTerminal(descriptor: Int32, setInitState: Bool) -> CzOutcome {
  CzOutcome { CzTerminal(try Terminal(descriptor: descriptor, setInitState: setInitState)) }
}

func currentTerminal() -> CzOutcome {
  CzOutcome { CzTerminal(try Terminal.current) }
}

func createTerminal(hasInitialSize: Bool, width: UInt16, height: UInt16) -> CzOutcome {
  let initialSize = hasInitialSize ? Terminal.Size(width: width, height: height) : nil

  return CzOutcome { try Terminal.create(initialSize: initialSize) }
}

/// A `Terminal`.
final class CzTerminal: Sendable {
  let terminal: Terminal

  init(_ terminal: Terminal) {
    self.terminal = terminal
  }

  /// A copy of the terminal, for a call to take.
  func duplicate() -> CzTerminal {
    CzTerminal(terminal)
  }

  func handle() -> Int32 {
    terminal.handle.fileDescriptor
  }

  func write(data: RustVec<UInt8>) -> CzOutcome {
    let data = Data(data)

    return CzOutcome { try terminal.write(data) }
  }

  func size() -> CzOutcome {
    CzOutcome { try terminal.size }
  }

  func resizeFrom(pty: CzTerminal) -> CzOutcome {
    CzOutcome { try terminal.resize(from: pty.terminal) }
  }

  func resizeSize(width: UInt16, height: UInt16) -> CzOutcome {
    CzOutcome { try terminal.resize(size: Terminal.Size(width: width, height: height)) }
  }

  func resize(width: UInt16, height: UInt16) -> CzOutcome {
    CzOutcome { try terminal.resize(width: width, height: height) }
  }

  func setraw() -> CzOutcome {
    CzOutcome { try terminal.setraw() }
  }

  func enableEcho() -> CzOutcome {
    CzOutcome { try terminal.enableEcho() }
  }

  func disableEcho() -> CzOutcome {
    CzOutcome { try terminal.disableEcho() }
  }

  func close() -> CzOutcome {
    CzOutcome { try terminal.close() }
  }

  func reset() -> CzOutcome {
    CzOutcome { try terminal.reset() }
  }

  func tryReset() {
    terminal.tryReset()
  }
}

func keychainQuerySave(
  securityDomain: RustStr,
  accessGroup: RustString?,
  hostname: RustStr,
  username: RustStr,
  password: RustStr
) -> CzOutcome {
  let securityDomain = securityDomain.toString()
  let accessGroup = accessGroup?.toString()
  let hostname = hostname.toString()
  let username = username.toString()
  let password = password.toString()

  return CzOutcome {
    try KeychainQuery().save(
      securityDomain: securityDomain,
      accessGroup: accessGroup,
      hostname: hostname,
      username: username,
      password: password
    )
  }
}

func keychainQueryDelete(securityDomain: RustStr, accessGroup: RustString?, hostname: RustStr) -> CzOutcome {
  let securityDomain = securityDomain.toString()
  let accessGroup = accessGroup?.toString()
  let hostname = hostname.toString()

  return CzOutcome {
    try KeychainQuery().delete(securityDomain: securityDomain, accessGroup: accessGroup, hostname: hostname)
  }
}

func keychainQueryGet(securityDomain: RustStr, accessGroup: RustString?, hostname: RustStr) -> CzOutcome {
  let securityDomain = securityDomain.toString()
  let accessGroup = accessGroup?.toString()
  let hostname = hostname.toString()

  return CzOutcome {
    absent(try KeychainQuery().get(securityDomain: securityDomain, accessGroup: accessGroup, hostname: hostname))
  }
}

func keychainQueryList(securityDomain: RustStr, accessGroup: RustString?) -> CzOutcome {
  let securityDomain = securityDomain.toString()
  let accessGroup = accessGroup?.toString()

  return CzOutcome { try KeychainQuery().list(securityDomain: securityDomain, accessGroup: accessGroup) }
}

func keychainQueryExists(securityDomain: RustStr, accessGroup: RustString?, hostname: RustStr) -> CzOutcome {
  let securityDomain = securityDomain.toString()
  let accessGroup = accessGroup?.toString()
  let hostname = hostname.toString()

  return CzOutcome {
    try KeychainQuery().exists(securityDomain: securityDomain, accessGroup: accessGroup, hostname: hostname)
  }
}

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

  return CzOutcome { try Sysctl.byName(name) }
}

func procNetSnmp(snmp: RustStr, netstat: RustString?, snmp6: RustString?) -> CzOutcome {
  let snmp = snmp.toString()
  let netstat = netstat?.toString()
  let snmp6 = snmp6?.toString()

  return CzOutcome { ProcNetSnmp(snmp: snmp, netstat: netstat, snmp6: snmp6) }
}

func readProcNetSnmp(pid: Int32?) -> CzOutcome {
  CzOutcome { try ProcNetSnmp.read(pid: pid) }
}

func fileInfoAt(path: RustStr) -> CzOutcome {
  let path = path.toString()

  return CzOutcome { CzFileInfo(try File.info(path)) }
}

/// A `FileInfo`.
final class CzFileInfo: Sendable {
  let info: FileInfo

  init(_ info: FileInfo) {
    self.info = info
  }

  func mode() -> UInt16 { info.mode }
  func uid() -> Int64 { Int64(info.uid) }
  func gid() -> Int64 { Int64(info.gid) }
  func dev() -> Int64 { Int64(info.dev) }
  func ino() -> Int64 { Int64(info.ino) }
  func size() -> Int64 { Int64(info.size) }
  func path() -> String { info.path }
  func isDirectory() -> Bool { info.isDirectory }
  func isPipe() -> Bool { info.isPipe }
  func isSocket() -> Bool { info.isSocket }
  func isLink() -> Bool { info.isLink }
  func isRegularFile() -> Bool { info.isRegularFile }
  func isBlock() -> Bool { info.isBlock }
  func isChar() -> Bool { info.isChar }
}

extension CzOutcome {
  func terminal() -> CzTerminal { taken() }
  func fileInfo() -> CzFileInfo { taken() }

  func parentTerminal() -> CzTerminal {
    CzTerminal((taken() as (parent: Terminal, child: Terminal)).parent)
  }

  func childTerminal() -> CzTerminal {
    CzTerminal((taken() as (parent: Terminal, child: Terminal)).child)
  }

  func terminalSizeWidth() -> UInt16 {
    (taken() as Terminal.Size).width
  }

  func terminalSizeHeight() -> UInt16 {
    (taken() as Terminal.Size).height
  }

  // A `KeychainQueryResult`, field by field: Rust builds its own.

  func keychainQueryResultUsername() -> String {
    (taken() as KeychainQueryResult).username
  }

  func keychainQueryResultPassword() -> String {
    (taken() as KeychainQueryResult).password
  }

  func keychainQueryResultModifiedDate() -> Double {
    (taken() as KeychainQueryResult).modifiedDate.timeIntervalSince1970
  }

  func keychainQueryResultCreatedDate() -> Double {
    (taken() as KeychainQueryResult).createdDate.timeIntervalSince1970
  }

  /// The held `ProcNetSnmp`'s `tcp`, `udp`, `icmp`, `ip`, `udp6`, `icmp6` and
  /// `ip6` counters. The last three may be `Absent`.
  func procNetSnmpCounters() -> CzOutcome {
    let snmp: ProcNetSnmp = taken()

    return CzOutcome.holding([
      snmp.tcp, snmp.udp, snmp.icmp, snmp.ip, absent(snmp.udp6), absent(snmp.icmp6), absent(snmp.ip6),
    ])
  }

  /// The held `ProcNetSnmp.Counters`' `UInt64` fields, in Swift's order.
  func procNetSnmpCountersNumbers() -> RustVec<UInt64> {
    let counters: ProcNetSnmp.Counters = taken()

    return rustVec([counters.receivedPackets, counters.transmittedPackets])
  }

  /// The held `ProcNetSnmp.Counters`' `UInt64?` fields, in Swift's order,
  /// each a `UInt64` or `Absent`.
  func procNetSnmpCountersOptionalNumbers() -> CzOutcome {
    let counters: ProcNetSnmp.Counters = taken()
    let numbers = [
      counters.receivedBytes, counters.transmittedBytes, counters.receivedDrops, counters.transmittedDrops,
      counters.receivedErrors, counters.transmittedErrors,
    ]

    return CzOutcome.holding(numbers.map { absent($0) })
  }

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

  /// 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()
    return rustVec([
      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),
    ])
  }
}

/// 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])
}