Files
containerization/vminitd/Sources/vminitd/Application.swift
T
NucleicandClaude Opus 4.8 11b9825e09 Vendor apple/containerization with a VM-extensions forwarding patch
Switch the containerization dependency from the github URL to a vendored copy
(third_party/containerization, upstream commit 6b7b42ca) referenced by path, so
we can carry a small local patch that upstream lacks: LinuxContainer.Configuration
gains a `vmExtensions` field forwarded into VMConfiguration.extensions. Upstream
already supports VMConfiguration.extensions + the VZInstanceExtension hook, but
LinuxContainer — our only entry point — never forwarded them, so there was no way
to attach a device (e.g. a memory balloon) to a container's VM.

Tests/, docs/, examples/, images/ and the corresponding test targets are trimmed
for footprint (we never build the dependency's tests). See PATCHES.md for the full
diff vs. upstream and the re-vendoring procedure. Also adds the ContainerizationExtras
product to NucleicCore (AddressAllocator, named in the configureVZ signature).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 20:22:21 -07:00

119 lines
4.1 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the Containerization 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.
//===----------------------------------------------------------------------===//
import ArgumentParser
import CVersion
import ContainerizationOS
import Foundation
import Logging
import VminitdCore
@main
struct Application: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "vminitd",
abstract: "Virtual machine init daemon",
version: "0.1.0",
subcommands: [
AgentCommand.self,
InitCommand.self,
PauseCommand.self,
],
defaultSubcommand: AgentCommand.self
)
static func main() async throws {
setVersionMetadata(Self.versionMetadata())
// Busybox-style: if invoked as .cz-init, run init mode directly.
let invoked = CommandLine.arguments.first?.split(separator: "/").last.map(String.init) ?? ""
if invoked == ".cz-init" {
let args = Array(CommandLine.arguments.dropFirst())
var command = try InitCommand.parse(args)
try command.run()
return
}
// Swift has issues spawning threads if /proc isn't mounted,
// so we do this synchronously before any async code runs.
try mountProc()
// When running as PID 1 with a Musl-static build, Swift's runtime
// captures argc/argv as empty. Recover argv from /proc/self/cmdline.
var command = try parseAsRoot(Self.procSelfArgv())
if let asyncCommand = command as? AsyncParsableCommand {
nonisolated(unsafe) var unsafeCommand = asyncCommand
try await unsafeCommand.run()
} else {
try command.run()
}
}
private static func versionMetadata() -> Logger.Metadata {
let gitCommit = String(cString: CZ_get_git_commit())
let gitTag = String(cString: CZ_get_git_tag())
let buildTime = String(cString: CZ_get_build_time())
var metadata: Logger.Metadata = ["commit": "\(gitCommit)", "built": "\(buildTime)"]
if !gitTag.isEmpty {
metadata["tag"] = "\(gitTag)"
}
return metadata
}
private static func mountProc() throws {
if isProcMounted() {
return
}
let mnt = ContainerizationOS.Mount(
type: "proc",
source: "proc",
target: "/proc",
options: []
)
try mnt.mount(createWithPerms: 0o755)
}
// /proc/self/cmdline holds argv as NUL-separated bytes. Read it after
// mountProc(). Returns argv minus argv[0], suitable for parseAsRoot(_:).
private static func procSelfArgv() -> [String] {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: "/proc/self/cmdline")) else {
return []
}
let parts = data.split(separator: 0, omittingEmptySubsequences: true)
.map { String(decoding: $0, as: UTF8.self) }
return Array(parts.dropFirst())
}
private static func isProcMounted() -> Bool {
guard let data = try? String(contentsOfFile: "/proc/mounts", encoding: .utf8) else {
return false
}
for line in data.split(separator: "\n") {
let fields = line.split(separator: " ")
if fields.count >= 2 {
let mountPoint = String(fields[1])
if mountPoint == "/proc" {
return true
}
}
}
return false
}
}