From a81e6996193e0e0afa537736bdfc3d4e912f865d Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Tue, 16 Jun 2026 23:38:09 +0200 Subject: [PATCH] fix: enable job control for shell with --kernel --- SPEC.md | 3 ++- src/qemu_vm.rs | 55 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/SPEC.md b/SPEC.md index 4d9d067..5009c2c 100644 --- a/SPEC.md +++ b/SPEC.md @@ -205,7 +205,8 @@ ecr --kernel /boot/vmlinuz debian -- /bin/sh -c "echo hello" 4. Launch QEMU with: - `-kernel ` - provided kernel - `-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 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 size (default 2G) - `-display none -serial mon:stdio` - console on stdio - `-netdev user,id=net0 -device virtio-net-pci,netdev=net0` - network diff --git a/src/qemu_vm.rs b/src/qemu_vm.rs index f384ee9..8c9ac7b 100644 --- a/src/qemu_vm.rs +++ b/src/qemu_vm.rs @@ -57,15 +57,17 @@ pub fn launch_qemu(config: QemuConfig) -> Result<()> { // For initramfs boot, use rdinit= instead of init= // No root= needed as initramfs becomes the rootfs // '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 cmd_str = cmd.join(" "); format!( - "console=ttyS0 quiet rdinit=/bin/sh -- -c \"{}\"", + "console=ttyS0 quiet rdinit=/bin/sh -- -c \"setsid sh -c 'exec sh /dev/ttyS0 2>&1 -c {}'\"", cmd_str ) } else { - // Default to interactive shell - "console=ttyS0 quiet rdinit=/bin/sh".to_string() + // Default to interactive shell with proper job control + "console=ttyS0 quiet rdinit=/bin/sh -- -c \"setsid sh -c 'exec sh /dev/ttyS0 2>&1'\"".to_string() }; veprintln!("Launching QEMU: {}", qemu_bin); @@ -175,22 +177,25 @@ fn create_cpio_archive(rootfs: &Path) -> Result> { // Collect all entries with their data 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 - for (name, mode, mtime, nlink, data) in entries { + for (name, mode, mtime, nlink, data) in &entries { let file_size = data.len() as u32; - let builder = NewcBuilder::new(&name) - .mode(mode) + let builder = NewcBuilder::new(name) + .mode(*mode) .uid(0) .gid(0) - .nlink(nlink) - .mtime(mtime); + .nlink(*nlink) + .mtime(*mtime); // Write header and get a writer let mut writer = builder.write(&mut archive, file_size); // Write the file content - writer.write_all(&data) + writer.write_all(data) .context("Failed to write file content to cpio archive")?; // Finish this entry (returns the underlying writer) @@ -198,6 +203,38 @@ fn create_cpio_archive(rootfs: &Path) -> Result> { .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) archive = newc::trailer(archive) .context("Failed to write cpio trailer")?;