Skip to content

Commit 22a4223

Browse files
committed
ephemeral: Fix boot for images without virtiofs dracut module
Some images don't have the dracut virtiofs module enabled. It's tiny, and they probably *should*, but...on the other hand since we already inject other content into the initramfs here, we can just change things so instead of injecting kargs, we just inject a trivial unit into the initramfs - no dracut involved or needed. Assisted-by: OpenCode (Claude Sonnet 4.6) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent c1adfa9 commit 22a4223

3 files changed

Lines changed: 100 additions & 26 deletions

File tree

crates/kit/src/cpio.rs

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn write_file(writer: &mut impl Write, path: &str, content: &[u8]) -> io::Result
2929
Ok(())
3030
}
3131

32-
/// CPIO entry: either a directory or a file with content.
32+
/// CPIO entry: either a directory or a regular file (0644).
3333
enum Entry {
3434
Dir(&'static str),
3535
File(&'static str, &'static [u8]),
@@ -41,6 +41,7 @@ pub fn create_initramfs_units_cpio() -> io::Result<Vec<u8>> {
4141

4242
const UNIT_DIR: &str = "usr/lib/systemd/system";
4343
const DROPIN_DIR: &str = "usr/lib/systemd/system/initrd-fs.target.d";
44+
const ROOT_FS_DROPIN_DIR: &str = "usr/lib/systemd/system/initrd-root-fs.target.d";
4445

4546
let entries: &[Entry] = &[
4647
// Directory hierarchy
@@ -49,6 +50,17 @@ pub fn create_initramfs_units_cpio() -> io::Result<Vec<u8>> {
4950
Dir("usr/lib/systemd"),
5051
Dir(UNIT_DIR),
5152
Dir(DROPIN_DIR),
53+
Dir(ROOT_FS_DROPIN_DIR),
54+
// sysroot.mount — mounts the virtiofs "rootfs" tag read-only at
55+
// /sysroot. bcvk does not set root= on the kernel cmdline, so
56+
// systemd-fstab-generator never generates a competing sysroot.mount,
57+
// and dracut sets rootok=1 via its UNSET branch (no root= arg → trust
58+
// systemd generators). The bcvk-sysroot.conf drop-in below wires
59+
// this unit into initrd-root-fs.target.
60+
File(
61+
"usr/lib/systemd/system/sysroot.mount",
62+
include_bytes!("units/sysroot.mount"),
63+
),
5264
// Service units
5365
File(
5466
"usr/lib/systemd/system/bcvk-etc-overlay.service",
@@ -66,6 +78,15 @@ pub fn create_initramfs_units_cpio() -> io::Result<Vec<u8>> {
6678
"usr/lib/systemd/system/bcvk-journal-stream.service",
6779
include_bytes!("units/bcvk-journal-stream.service"),
6880
),
81+
// Drop-in to pull sysroot.mount into initrd-root-fs.target. Without
82+
// this, nothing in the dependency graph actually requests the mount;
83+
// dracut-rootfs-generator normally creates an
84+
// initrd-root-fs.target.requires/sysroot.mount symlink for block-device
85+
// roots, but for virtiofs (not a block device) it skips that step.
86+
File(
87+
"usr/lib/systemd/system/initrd-root-fs.target.d/bcvk-sysroot.conf",
88+
b"[Unit]\nRequires=sysroot.mount\nAfter=sysroot.mount\n",
89+
),
6990
// Drop-in configs to pull units into initrd-fs.target
7091
File(
7192
"usr/lib/systemd/system/initrd-fs.target.d/bcvk-etc-overlay.conf",
@@ -104,6 +125,7 @@ mod tests {
104125

105126
let mut entries = Vec::new();
106127
let mut etc_overlay_content = None;
128+
let mut sysroot_mount_content = None;
107129

108130
loop {
109131
let mut reader = cpio::NewcReader::new(cursor).expect("failed to read CPIO entry");
@@ -115,13 +137,20 @@ mod tests {
115137
let size = reader.entry().file_size() as usize;
116138
let mode = reader.entry().mode();
117139

118-
// Read file content for verification
119-
if name == "usr/lib/systemd/system/bcvk-etc-overlay.service" {
120-
let mut content = vec![0u8; size];
121-
reader
122-
.read_exact(&mut content)
123-
.expect("failed to read file content");
124-
etc_overlay_content = Some(String::from_utf8(content).expect("invalid UTF-8"));
140+
let mut content_buf = vec![0u8; size];
141+
reader
142+
.read_exact(&mut content_buf)
143+
.expect("failed to read file content");
144+
let content_str = String::from_utf8(content_buf).ok();
145+
146+
match name.as_str() {
147+
"usr/lib/systemd/system/bcvk-etc-overlay.service" => {
148+
etc_overlay_content = content_str.clone()
149+
}
150+
"usr/lib/systemd/system/sysroot.mount" => {
151+
sysroot_mount_content = content_str.clone()
152+
}
153+
_ => {}
125154
}
126155

127156
entries.push((name, size, mode));
@@ -130,37 +159,64 @@ mod tests {
130159

131160
let names: Vec<_> = entries.iter().map(|(n, _, _)| n.as_str()).collect();
132161

133-
// Verify directories
162+
// Verify directory hierarchy
134163
assert!(names.contains(&"usr"));
135164
assert!(names.contains(&"usr/lib"));
136165
assert!(names.contains(&"usr/lib/systemd"));
137166
assert!(names.contains(&"usr/lib/systemd/system"));
138167
assert!(names.contains(&"usr/lib/systemd/system/initrd-fs.target.d"));
168+
assert!(names.contains(&"usr/lib/systemd/system/initrd-root-fs.target.d"));
139169

140-
// Verify service files
170+
// sysroot.mount must be present and correct
171+
assert!(
172+
names.contains(&"usr/lib/systemd/system/sysroot.mount"),
173+
"sysroot.mount must be injected"
174+
);
175+
let sysroot = sysroot_mount_content.expect("sysroot.mount content missing");
176+
assert!(
177+
sysroot.contains("Type=virtiofs"),
178+
"sysroot.mount must use virtiofs"
179+
);
180+
assert!(
181+
sysroot.contains("What=rootfs"),
182+
"sysroot.mount must mount the 'rootfs' tag"
183+
);
184+
assert!(
185+
sysroot.contains("Where=/sysroot"),
186+
"sysroot.mount must target /sysroot"
187+
);
188+
assert!(
189+
sysroot.contains("Options=ro"),
190+
"sysroot.mount must be read-only"
191+
);
192+
193+
// Service units
141194
assert!(names.contains(&"usr/lib/systemd/system/bcvk-etc-overlay.service"));
142195
assert!(names.contains(&"usr/lib/systemd/system/bcvk-var-ephemeral.service"));
143196
assert!(names.contains(&"usr/lib/systemd/system/bcvk-copy-units.service"));
144197
assert!(names.contains(&"usr/lib/systemd/system/bcvk-journal-stream.service"));
145198

146-
// Verify drop-in configs
199+
// initrd-root-fs.target drop-in
200+
assert!(names.contains(&"usr/lib/systemd/system/initrd-root-fs.target.d/bcvk-sysroot.conf"));
201+
202+
// Drop-in configs
147203
assert!(names.contains(&"usr/lib/systemd/system/initrd-fs.target.d/bcvk-etc-overlay.conf"));
148204
assert!(
149205
names.contains(&"usr/lib/systemd/system/initrd-fs.target.d/bcvk-var-ephemeral.conf")
150206
);
151207
assert!(names.contains(&"usr/lib/systemd/system/initrd-fs.target.d/bcvk-copy-units.conf"));
152208

153-
// Verify file modes
209+
// Verify file modes: all entries are either regular files (0644) or directories
154210
for (name, _size, mode) in &entries {
155211
let file_type = *mode & 0o170000;
156-
if name.ends_with(".service") || name.ends_with(".conf") {
157-
assert_eq!(file_type, 0o100000, "{} should be regular file", name);
212+
if name.ends_with(".service") || name.ends_with(".conf") || name.ends_with(".mount") {
213+
assert_eq!(file_type, 0o100000, "{} should be a regular file", name);
158214
} else {
159-
assert_eq!(file_type, 0o040000, "{} should be directory", name);
215+
assert_eq!(file_type, 0o040000, "{} should be a directory", name);
160216
}
161217
}
162218

163-
// Verify file content is valid systemd unit
219+
// bcvk-etc-overlay.service must be a valid systemd unit
164220
let content = etc_overlay_content.expect("bcvk-etc-overlay.service not found");
165221
assert!(content.contains("[Unit]"));
166222
assert!(content.contains("[Service]"));

crates/kit/src/run_ephemeral.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -880,8 +880,6 @@ fn parse_service_exit_code(status_content: &str) -> Result<i32> {
880880
Ok(0)
881881
}
882882

883-
/// Check for required binaries in the privileged container
884-
///
885883
/// These binaries must be present in the privileged container that runs bcvk,
886884
/// not the guest bootc image that gets booted inside the VM.
887885
fn check_required_container_binaries() -> Result<()> {
@@ -1365,15 +1363,16 @@ StandardOutput=file:/dev/virtio-ports/executestatus
13651363
qemu_config.add_smbios_credential(credential);
13661364
}
13671365

1368-
// Build kernel command line for direct boot
1366+
// Build kernel command line for direct boot.
1367+
//
1368+
// We deliberately omit root=, rootfstype=, and rootflags= from the
1369+
// cmdline. When root= is absent dracut sets rootok=1 via its UNSET
1370+
// branch and defers entirely to systemd generators. systemd-fstab-
1371+
// generator likewise produces nothing without a root= arg. The
1372+
// virtiofs mount is handled solely by the sysroot.mount unit bcvk
1373+
// injects into every initramfs via the CPIO append, together with the
1374+
// initrd-root-fs.target.d/bcvk-sysroot.conf drop-in that wires it in.
13691375
let mut kernel_cmdline = [
1370-
// At the core we boot from the mounted container's root,
1371-
"rootfstype=virtiofs",
1372-
"root=rootfs",
1373-
// But read-only. We set up /etc overlay and /var copyup via
1374-
// systemd credentials rather than systemd.volatile=overlay
1375-
// to have more control over individual directories.
1376-
"rootflags=ro",
13771376
// This avoids having journald interact with the rootfs
13781377
// at all, which lessens the I/O traffic for virtiofs
13791378
"systemd.journald.storage=volatile",

crates/kit/src/units/sysroot.mount

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Unit]
2+
Description=bcvk ephemeral virtiofs root mount
3+
Documentation=https://github.com/bootc-dev/bcvk
4+
DefaultDependencies=no
5+
# bcvk does not put root= on the kernel cmdline, so neither dracut nor
6+
# systemd-fstab-generator will generate a competing sysroot.mount unit.
7+
# This unit is the sole handler for the virtiofs root mount in all
8+
# initramfs types (dracut, bootc composefs initramfs, mkosi-initrd, …).
9+
#
10+
# The initrd-root-fs.target.d/bcvk-sysroot.conf drop-in wires this unit
11+
# into the dependency graph so initrd-root-fs.target waits for it.
12+
ConditionPathExists=/etc/initrd-release
13+
Before=initrd-root-fs.target
14+
15+
[Mount]
16+
What=rootfs
17+
Where=/sysroot
18+
Type=virtiofs
19+
Options=ro

0 commit comments

Comments
 (0)