fix: enable job control for shell with --kernel

This commit is contained in:
2026-06-16 23:38:09 +02:00
parent 8875bcc92a
commit a81e699619
2 changed files with 48 additions and 10 deletions
+2 -1
View File
@@ -205,7 +205,8 @@ ecr --kernel /boot/vmlinuz debian -- /bin/sh -c "echo hello"
4. Launch QEMU with: 4. Launch QEMU with:
- `-kernel <path>` - provided kernel - `-kernel <path>` - provided kernel
- `-initrd initramfs.cpio.gz` - rootfs as initramfs - `-initrd initramfs.cpio.gz` - rootfs as initramfs
- `-append "console=ttyS0 quiet rdinit=/bin/sh"` - kernel command line - `-append "console=ttyS0 quiet rdinit=/bin/sh -- -c \"setsid sh -c 'exec sh </dev/ttyS0 >/dev/ttyS0 2>&1'\""` - kernel command line
7. Essential device nodes (/dev/ttyS0, /dev/null, /dev/tty) are added to initramfs for proper console support
- `-m <memory>` - memory size (default 2G) - `-m <memory>` - memory size (default 2G)
- `-display none -serial mon:stdio` - console on stdio - `-display none -serial mon:stdio` - console on stdio
- `-netdev user,id=net0 -device virtio-net-pci,netdev=net0` - network - `-netdev user,id=net0 -device virtio-net-pci,netdev=net0` - network
+46 -9
View File
@@ -57,15 +57,17 @@ pub fn launch_qemu(config: QemuConfig) -> Result<()> {
// For initramfs boot, use rdinit= instead of init= // For initramfs boot, use rdinit= instead of init=
// No root= needed as initramfs becomes the rootfs // No root= needed as initramfs becomes the rootfs
// 'quiet' suppresses kernel log messages for a cleaner console // 'quiet' suppresses kernel log messages for a cleaner console
// Use setsid with redirected stdio to get proper job control
// /dev/ttyS0 is created in the initramfs for serial console
let kernel_append = if let Some(ref cmd) = config.command { let kernel_append = if let Some(ref cmd) = config.command {
let cmd_str = cmd.join(" "); let cmd_str = cmd.join(" ");
format!( format!(
"console=ttyS0 quiet rdinit=/bin/sh -- -c \"{}\"", "console=ttyS0 quiet rdinit=/bin/sh -- -c \"setsid sh -c 'exec sh </dev/ttyS0 >/dev/ttyS0 2>&1 -c {}'\"",
cmd_str cmd_str
) )
} else { } else {
// Default to interactive shell // Default to interactive shell with proper job control
"console=ttyS0 quiet rdinit=/bin/sh".to_string() "console=ttyS0 quiet rdinit=/bin/sh -- -c \"setsid sh -c 'exec sh </dev/ttyS0 >/dev/ttyS0 2>&1'\"".to_string()
}; };
veprintln!("Launching QEMU: {}", qemu_bin); veprintln!("Launching QEMU: {}", qemu_bin);
@@ -175,22 +177,25 @@ fn create_cpio_archive(rootfs: &Path) -> Result<Vec<u8>> {
// Collect all entries with their data // Collect all entries with their data
let entries = collect_entries(rootfs, rootfs)?; let entries = collect_entries(rootfs, rootfs)?;
// Collect entry names for checking existence later
let entry_names: Vec<&str> = entries.iter().map(|(n, _, _, _, _)| n.as_str()).collect();
// Write each entry using the cpio crate // Write each entry using the cpio crate
for (name, mode, mtime, nlink, data) in entries { for (name, mode, mtime, nlink, data) in &entries {
let file_size = data.len() as u32; let file_size = data.len() as u32;
let builder = NewcBuilder::new(&name) let builder = NewcBuilder::new(name)
.mode(mode) .mode(*mode)
.uid(0) .uid(0)
.gid(0) .gid(0)
.nlink(nlink) .nlink(*nlink)
.mtime(mtime); .mtime(*mtime);
// Write header and get a writer // Write header and get a writer
let mut writer = builder.write(&mut archive, file_size); let mut writer = builder.write(&mut archive, file_size);
// Write the file content // Write the file content
writer.write_all(&data) writer.write_all(data)
.context("Failed to write file content to cpio archive")?; .context("Failed to write file content to cpio archive")?;
// Finish this entry (returns the underlying writer) // Finish this entry (returns the underlying writer)
@@ -198,6 +203,38 @@ fn create_cpio_archive(rootfs: &Path) -> Result<Vec<u8>> {
.context("Failed to finish cpio entry")?; .context("Failed to finish cpio entry")?;
} }
// Add essential device nodes for serial console
// These are character devices (mode 0o020xxx)
let device_nodes = [
// /dev/ttyS0 - serial console (major 4, minor 64)
("dev/ttyS0", 0o020644, 4, 64),
// /dev/null (major 1, minor 3)
("dev/null", 0o020644, 1, 3),
// /dev/tty - controlling terminal (major 5, minor 0)
("dev/tty", 0o020666, 5, 0),
];
for (name, mode, major, minor) in device_nodes {
// Check if this device node already exists in the archive
if entry_names.contains(&name) {
continue;
}
let builder = NewcBuilder::new(name)
.mode(mode)
.uid(0)
.gid(0)
.nlink(1)
.mtime(0)
.rdev_major(major)
.rdev_minor(minor);
// Device nodes have zero size
let writer = builder.write(&mut archive, 0);
writer.finish()
.context("Failed to finish device node entry")?;
}
// Write the trailer (takes ownership and returns the writer) // Write the trailer (takes ownership and returns the writer)
archive = newc::trailer(archive) archive = newc::trailer(archive)
.context("Failed to write cpio trailer")?; .context("Failed to write cpio trailer")?;