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]>
119 lines
4.1 KiB
Swift
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
|
|
}
|
|
}
|