add ability to export initfs as block (#268)

This refactors the rootfs command to produce an image and have the
ability to create an ext4 formatted block of the init filesystem
directly from the command.

closes #220

Signed-off-by: crosbymichael <michael_crosby@apple.com>
This commit is contained in:
Michael Crosby
2025-08-15 00:12:14 -07:00
committed by GitHub
parent e08b0d4fb4
commit 54980872d9
3 changed files with 104 additions and 18 deletions
+6 -1
View File
@@ -57,7 +57,12 @@ containerization:
init: containerization vminitd
@echo Creating init.ext4...
@rm -f bin/init.rootfs.tar.gz bin/init.block
@./bin/cctl rootfs create --vminitd vminitd/bin/vminitd --labels org.opencontainers.image.source=https://github.com/apple/containerization --vmexec vminitd/bin/vmexec bin/init.rootfs.tar.gz vminit:latest
@./bin/cctl rootfs create \
--vminitd vminitd/bin/vminitd \
--labels org.opencontainers.image.source=https://github.com/apple/containerization \
--vmexec vminitd/bin/vmexec \
--image vminit:latest \
bin/init.rootfs.tar.gz
.PHONY: cross-prep
cross-prep:
@@ -32,13 +32,56 @@ public struct EXT4Unpacker: Unpacker {
self.blockSizeInBytes = blockSizeInBytes
}
public func unpack(_ image: Image, for platform: Platform, at path: URL, progress: ProgressHandler? = nil) async throws -> Mount {
#if os(macOS)
/// Performs the unpacking of a tar archive into a filesystem.
/// - Parameters:
/// - archive: The archive to unpack.
/// - compression: The compression to use when unpacking the image.
/// - path: The path to the filesystem that will be created.
public func unpack(
archive: URL,
compression: ContainerizationArchive.Filter,
at path: URL
) throws {
let cleanedPath = try prepareUnpackPath(path: path)
let filesystem = try EXT4.Formatter(
FilePath(cleanedPath),
minDiskSize: blockSizeInBytes
)
defer { try? filesystem.close() }
try filesystem.unpack(
source: archive,
format: .paxRestricted,
compression: compression,
progress: nil
)
}
#endif
/// Returns a `Mount` point after unpacking the image into a filesystem.
/// - Parameters:
/// - image: The image to unpack.
/// - platform: The platform content to unpack.
/// - path: The path to the directory where the filesystem will be created.
/// - progress: The progress handler to invoke as the unpacking progresses.
public func unpack(
_ image: Image,
for platform: Platform,
at path: URL,
progress: ProgressHandler? = nil
) async throws -> Mount {
#if !os(macOS)
throw ContainerizationError(.unsupported, message: "Cannot unpack an image on current platform")
#else
let blockPath = try prepareUnpackPath(path: path)
let cleanedPath = try prepareUnpackPath(path: path)
let manifest = try await image.manifest(for: platform)
let filesystem = try EXT4.Formatter(FilePath(path), minDiskSize: blockSizeInBytes)
let filesystem = try EXT4.Formatter(
FilePath(
cleanedPath
),
minDiskSize: blockSizeInBytes
)
defer { try? filesystem.close() }
for layer in manifest.layers {
@@ -64,7 +107,7 @@ public struct EXT4Unpacker: Unpacker {
return .block(
format: "ext4",
source: blockPath,
source: cleanedPath,
destination: "/",
options: []
)
+51 -13
View File
@@ -46,9 +46,14 @@ extension Application {
@Option(name: .long, help: "Labels to add to the built image of the form <key1>=<value1>, [<key2>=<value2>,...]")
var labels: [String] = []
@Argument var rootfsPath: String
@Option(name: .customLong("image"), help: "The name of the image to produce.")
var imageName: String?
@Argument var tag: String
@Option(name: .customLong("ext4"), help: "The path to an ext4 image to create.")
var ext4File: String?
// The path where the intermediate tar archive is created.
@Argument var tarPath: String
private static let directories = [
"bin",
@@ -63,19 +68,51 @@ extension Application {
]
func run() async throws {
try await writeArchive()
let p = try Platform(from: platformString)
let rootfs = URL(filePath: rootfsPath)
let labels = Application.parseKeyValuePairs(from: labels)
_ = try await InitImage.create(
reference: tag, rootfs: rootfs,
platform: p, labels: labels,
imageStore: Application.imageStore,
contentStore: Application.contentStore)
let path = URL(filePath: self.tarPath)
try await writeArchive(path: path)
if let image = self.imageName {
print("creating initfs image \(image)...")
try await outputImage(
path: path,
reference: image
)
}
if let ext4Path = self.ext4File {
print("creating initfs ext4 image at \(ext4Path)...")
try await outputExt4(
archive: path,
to: URL(filePath: ext4Path)
)
}
}
private func writeArchive() async throws {
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: URL(filePath: rootfsPath))
private func outputExt4(archive: URL, to path: URL) async throws {
let unpacker = EXT4Unpacker(blockSizeInBytes: 256.mib())
try unpacker.unpack(archive: archive, compression: .gzip, at: path)
}
private func outputImage(path: URL, reference: String) async throws {
let p = try Platform(from: platformString)
let labels = Application.parseKeyValuePairs(from: labels)
_ = try await InitImage.create(
reference: reference,
rootfs: path,
platform: p,
labels: labels,
imageStore: Application.imageStore,
contentStore: Application.contentStore
)
}
private func writeArchive(path: URL) async throws {
let writer = try ArchiveWriter(
format: .pax,
filter: .gzip,
file: path,
)
let ts = Date()
let entry = WriteEntry()
entry.permissions = 0o755
@@ -84,6 +121,7 @@ extension Application {
entry.group = 0
entry.owner = 0
entry.fileType = .directory
// create the initial directory structure.
for dir in Self.directories {
entry.path = dir