From 5aba28a2a652eabf3162ab2d6010cf7e209d13b2 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 8 Jan 2026 11:05:44 +0100 Subject: [PATCH 1/4] libkrun: Introduce RUNNING_CTX_MAP to track running VM instances Introduce a static map to store references to running Vmm instances, enabling operations on VMs after they have been started. Signed-off-by: Matej Hrica --- src/libkrun/src/lib.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 13f0fb5f7..6272727a0 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -34,8 +34,8 @@ use std::os::fd::{BorrowedFd, FromRawFd, RawFd}; use std::path::PathBuf; use std::slice; use std::sync::atomic::{AtomicI32, Ordering}; -use std::sync::LazyLock; use std::sync::Mutex; +use std::sync::{Arc, LazyLock}; use utils::eventfd::EventFd; use vmm::resources::{ DefaultVirtioConsoleConfig, PortConfig, SerialConsoleConfig, TsiFlags, VirtioConsoleConfigMode, @@ -57,6 +57,7 @@ use vmm::vmm_config::machine_config::VmConfig; #[cfg(feature = "net")] use vmm::vmm_config::net::NetworkInterfaceConfig; use vmm::vmm_config::vsock::VsockDeviceConfig; +use vmm::Vmm; #[cfg(feature = "nitro")] use nitro::enclaves::NitroEnclave; @@ -428,7 +429,13 @@ fn with_cfg(ctx_id: u32, f: impl FnOnce(&mut ContextConfig) -> i32) -> i32 { } } +// Vmm configuration(s) to be started static CTX_MAP: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + +// Running vmm instances +static RUNNING_CTX_MAP: Lazy>>>> = + Lazy::new(|| Mutex::new(HashMap::new())); + static CTX_IDS: AtomicI32 = AtomicI32::new(0); fn log_level_to_filter_str(level: u32) -> &'static str { @@ -2654,7 +2661,7 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { let (sender, _receiver) = unbounded(); - let _vmm = match vmm::builder::build_microvm( + let vmm = match vmm::builder::build_microvm( &ctx_cfg.vmr, &mut event_manager, ctx_cfg.shutdown_efd, @@ -2667,18 +2674,20 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { } }; + RUNNING_CTX_MAP.lock().unwrap().insert(ctx_id, vmm.clone()); + #[cfg(target_os = "macos")] if ctx_cfg.gpu_virgl_flags.is_some() { - vmm::worker::start_worker_thread(_vmm.clone(), _receiver).unwrap(); + vmm::worker::start_worker_thread(vmm.clone(), _receiver).unwrap(); } #[cfg(target_arch = "x86_64")] if ctx_cfg.vmr.split_irqchip { - vmm::worker::start_worker_thread(_vmm.clone(), _receiver.clone()).unwrap(); + vmm::worker::start_worker_thread(vmm.clone(), _receiver.clone()).unwrap(); } #[cfg(any(feature = "amd-sev", feature = "tdx"))] - vmm::worker::start_worker_thread(_vmm.clone(), _receiver.clone()).unwrap(); + vmm::worker::start_worker_thread(vmm.clone(), _receiver.clone()).unwrap(); loop { match event_manager.run() { From 23969f19bca219e3b8ff3f2196df4852b6ff6945 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 8 Jan 2026 11:13:07 +0100 Subject: [PATCH 2/4] devices/console, lib: Add API to wait for console being ready Introduce krun_get_console_ready_fd() that returns an EventFd. The event is signaled when the console device is ready according to the guest. This allows applications to know when it's possible to start adding ports at runtime. Signed-off-by: Matej Hrica --- include/libkrun.h | 19 ++++++++ src/devices/src/virtio/console/device.rs | 10 +++- src/libkrun/src/lib.rs | 62 ++++++++++++++++++------ src/vmm/src/builder.rs | 22 +++++++-- src/vmm/src/resources.rs | 11 ++++- 5 files changed, 102 insertions(+), 22 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index b0d89ee6e..d232d6a66 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -1109,6 +1109,25 @@ int32_t krun_add_serial_console_default(uint32_t ctx_id, */ int32_t krun_add_virtio_console_multiport(uint32_t ctx_id); +/* + * Returns an eventfd that becomes readable when the virtio-console device is ready + * to accept dynamically added ports. + * + * This function must be called after krun_start_enter() has been invoked (typically from + * another thread). The returned fd can be polled; when readable, read an 8-byte value + * from it to consume the event, then call krun_add_console_port_tty() or + * krun_add_console_port_inout() to add ports dynamically. + * + * Arguments: + * "ctx_id" - the configuration context ID. + * "console_id" - the console ID returned by krun_add_virtio_console_multiport(). + * + * Returns: + * The eventfd file descriptor (>= 0) on success, or a negative error number on failure. + * Returns -EAGAIN if the VM has not been started yet. + */ +int32_t krun_get_console_ready_fd(uint32_t ctx_id, uint32_t console_id); + /* * Adds a TTY port to a multi-port virtio-console device. * diff --git a/src/devices/src/virtio/console/device.rs b/src/devices/src/virtio/console/device.rs index 2e4896e9c..9032da3c3 100644 --- a/src/devices/src/virtio/console/device.rs +++ b/src/devices/src/virtio/console/device.rs @@ -67,10 +67,14 @@ pub struct Console { pub(crate) sigwinch_evt: EventFd, config: VirtioConsoleConfig, + console_ready_evt: Arc, } impl Console { - pub fn new(ports: Vec) -> super::Result { + pub fn new( + ports: Vec, + console_ready_evt: Arc, + ) -> super::Result { assert!(!ports.is_empty(), "Expected at least 1 port"); let num_queues = num_queues(ports.len()); @@ -105,6 +109,7 @@ impl Console { .map_err(ConsoleError::EventFd)?, device_state: DeviceState::Inactive, config, + console_ready_evt, }) } @@ -195,6 +200,9 @@ impl Console { for port_id in 0..self.ports.len() { self.control.port_add(port_id as u32); } + if let Err(e) = self.console_ready_evt.write(1) { + warn!("Failed to trigger console_ready_evt: {e:?}"); + } } control_event::VIRTIO_CONSOLE_PORT_READY => { if cmd.value != 1 { diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 6272727a0..ad23db7ba 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -38,8 +38,8 @@ use std::sync::Mutex; use std::sync::{Arc, LazyLock}; use utils::eventfd::EventFd; use vmm::resources::{ - DefaultVirtioConsoleConfig, PortConfig, SerialConsoleConfig, TsiFlags, VirtioConsoleConfigMode, - VmResources, VsockConfig, + DefaultVirtioConsoleConfig, PortConfig, SerialConsoleConfig, TsiFlags, VirtioConsoleConfig, + VirtioConsoleConfigMode, VmResources, VsockConfig }; #[cfg(feature = "blk")] use vmm::vmm_config::block::{BlockDeviceConfig, BlockRootConfig}; @@ -2333,15 +2333,18 @@ pub unsafe extern "C" fn krun_add_virtio_console_default( Entry::Occupied(mut ctx_cfg) => { let cfg = ctx_cfg.get_mut(); - cfg.vmr - .virtio_consoles - .push(VirtioConsoleConfigMode::Autoconfigure( - DefaultVirtioConsoleConfig { - input_fd, - output_fd, - err_fd, - }, - )); + let console_ready_evt = Arc::new( + EventFd::new(utils::eventfd::EFD_NONBLOCK) + .expect("Failed to create console_ready_evt"), + ); + cfg.vmr.virtio_consoles.push(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Autoconfigure(DefaultVirtioConsoleConfig { + input_fd, + output_fd, + err_fd, + }), + console_ready_evt, + }); } Entry::Vacant(_) => return -libc::ENOENT, } @@ -2357,9 +2360,14 @@ pub unsafe extern "C" fn krun_add_virtio_console_multiport(ctx_id: u32) -> i32 { let cfg = ctx_cfg.get_mut(); let console_id = cfg.vmr.virtio_consoles.len() as i32; - cfg.vmr - .virtio_consoles - .push(VirtioConsoleConfigMode::Explicit(Vec::new())); + let console_ready_evt = Arc::new( + EventFd::new(utils::eventfd::EFD_NONBLOCK) + .expect("Failed to create console_ready_evt"), + ); + cfg.vmr.virtio_consoles.push(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Explicit(Vec::new()), + console_ready_evt, + }); console_id } @@ -2367,6 +2375,22 @@ pub unsafe extern "C" fn krun_add_virtio_console_multiport(ctx_id: u32) -> i32 { } } +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn krun_get_console_ready_fd(ctx_id: u32, console_id: u32) -> i32 { + // VM not running yet, get the pre-created eventfd from CTX_MAP + match CTX_MAP.lock().unwrap().get(&ctx_id) { + Some(ctx_cfg) => match ctx_cfg.vmr.virtio_consoles.get(console_id as usize) { + Some(console_cfg) => console_cfg.console_ready_evt.as_raw_fd(), + None => { + error!("krun_get_console_ready_fd: Invalid console id={console_id}"); + -libc::ENOENT + } + }, + None => -libc::ENOENT, + } +} + #[allow(clippy::missing_safety_doc)] #[no_mangle] pub unsafe extern "C" fn krun_add_console_port_tty( @@ -2397,7 +2421,10 @@ pub unsafe extern "C" fn krun_add_console_port_tty( let cfg = ctx_cfg.get_mut(); match cfg.vmr.virtio_consoles.get_mut(console_id as usize) { - Some(VirtioConsoleConfigMode::Explicit(ports)) => { + Some(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Explicit(ports), + .. + }) => { ports.push(PortConfig::Tty { name: name_str, tty_fd, @@ -2434,7 +2461,10 @@ pub unsafe extern "C" fn krun_add_console_port_inout( let cfg = ctx_cfg.get_mut(); match cfg.vmr.virtio_consoles.get_mut(console_id as usize) { - Some(VirtioConsoleConfigMode::Explicit(ports)) => { + Some(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Explicit(ports), + .. + }) => { ports.push(PortConfig::InOut { name: name_str, input_fd, diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 92ac87079..7583935b5 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -25,6 +25,7 @@ use crate::device_manager::legacy::PortIODeviceManager; use crate::device_manager::mmio::MMIODeviceManager; use crate::resources::{ DefaultVirtioConsoleConfig, PortConfig, TsiFlags, VirtioConsoleConfigMode, VmResources, + VirtioConsoleConfig }; use crate::vmm_config::external_kernel::{ExternalKernel, KernelFormat}; #[cfg(feature = "net")] @@ -2113,25 +2114,38 @@ fn attach_console_devices( event_manager: &mut EventManager, intc: IrqChip, vm_resources: &VmResources, - cfg: Option<&VirtioConsoleConfigMode>, + cfg: Option<&VirtioConsoleConfig>, id_number: u32, ) -> std::result::Result<(), StartMicrovmError> { use self::StartMicrovmError::*; let creating_implicit_console = cfg.is_none(); + let console_ready_evt = match cfg { + Some(VirtioConsoleConfig { console_ready_evt, .. }) => console_ready_evt.clone(), + None => Arc::new(EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap()), + }; + let ports = match cfg { None => autoconfigure_console_ports(vmm, vm_resources, None, creating_implicit_console)?, - Some(VirtioConsoleConfigMode::Autoconfigure(autocfg)) => autoconfigure_console_ports( + Some(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Autoconfigure(autocfg), + .. + }) => autoconfigure_console_ports( vmm, vm_resources, Some(autocfg), creating_implicit_console, )?, - Some(VirtioConsoleConfigMode::Explicit(ports)) => create_explicit_ports(vmm, ports)?, + Some(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Explicit(ports), + .. + }) => create_explicit_ports(vmm, ports)?, }; - let console = Arc::new(Mutex::new(devices::virtio::Console::new(ports).unwrap())); + let console = Arc::new(Mutex::new( + devices::virtio::Console::new(ports, console_ready_evt).unwrap(), + )); vmm.exit_observers.push(console.clone()); diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index d8d0fff24..dcba292cd 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -9,6 +9,9 @@ use std::fs::File; use std::io::BufReader; use std::os::fd::RawFd; use std::path::PathBuf; +use std::sync::Arc; + +use utils::eventfd::EventFd; #[cfg(feature = "tee")] use serde::{Deserialize, Serialize}; @@ -100,6 +103,12 @@ pub enum VirtioConsoleConfigMode { Explicit(Vec), } +pub struct VirtioConsoleConfig { + pub mode: VirtioConsoleConfigMode, + /// EventFd that will be signaled when the console device is ready + pub console_ready_evt: Arc, +} + pub enum PortConfig { Tty { name: String, @@ -188,7 +197,7 @@ pub struct VmResources { /// Serial consoles to attach to the guest pub serial_consoles: Vec, /// Virtio consoles to attach to the guest - pub virtio_consoles: Vec, + pub virtio_consoles: Vec, } impl VmResources { From 7b08b6860a6272236f74c915f03e209b1336c887 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 8 Jan 2026 12:45:15 +0100 Subject: [PATCH 3/4] devices/console, libkrun: Support dynamically adding more ports Add ConsoleController, an external handle that allows adding ports at runtime after the VM has started. It uses a channel to send new port descriptions to the running Console device. Add Console::with_controller factory method that returns both the Console and a ConsoleController. Update krun_add_console_port_tty and krun_add_console_port_inout to check RUNNING_CTX_MAP first, allowing ports to be added to a running VM. Introduce krun_console_reserve_ports() which pre-allocate virtqueues in the device for ports that will be added later at runtime. Signed-off-by: Matej Hrica --- include/libkrun.h | 17 +++ src/devices/Cargo.toml | 6 +- src/devices/src/virtio/console/device.rs | 140 ++++++++++++++++++----- src/devices/src/virtio/console/mod.rs | 2 +- src/libkrun/src/lib.rs | 92 ++++++++++++++- src/vmm/src/builder.rs | 89 +++++++------- src/vmm/src/lib.rs | 34 +++++- src/vmm/src/resources.rs | 6 +- 8 files changed, 305 insertions(+), 81 deletions(-) diff --git a/include/libkrun.h b/include/libkrun.h index d232d6a66..23a30be53 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -1109,6 +1109,23 @@ int32_t krun_add_serial_console_default(uint32_t ctx_id, */ int32_t krun_add_virtio_console_multiport(uint32_t ctx_id); +/* + * Reserves additional port slots on a multi-port virtio-console device for dynamic addition + * after the VM has started. + * + * This function must be called before krun_start_enter(). The reserved ports can be populated + * later by calling krun_add_console_port_tty() or krun_add_console_port_inout() on a running VM. + * + * Arguments: + * "ctx_id" - the configuration context ID. + * "console_id" - the console ID returned by krun_add_virtio_console_multiport(). + * "num_ports" - the number of additional port slots to reserve. + * + * Returns: + * Zero on success or a negative error number on failure. + */ +int32_t krun_console_reserve_ports(uint32_t ctx_id, uint32_t console_id, uint32_t num_ports); + /* * Returns an eventfd that becomes readable when the virtio-console device is ready * to accept dynamically added ports. diff --git a/src/devices/Cargo.toml b/src/devices/Cargo.toml index 9ec04c141..8597c669d 100644 --- a/src/devices/Cargo.toml +++ b/src/devices/Cargo.toml @@ -11,8 +11,8 @@ tdx = ["blk", "tee"] net = [] blk = [] efi = ["blk", "net"] -gpu = ["rutabaga_gfx", "thiserror", "zerocopy", "krun_display"] -snd = ["pw", "thiserror"] +gpu = ["rutabaga_gfx", "zerocopy", "krun_display"] +snd = ["pw"] input = ["zerocopy", "krun_input"] virgl_resource_map2 = [] nitro = [] @@ -27,7 +27,7 @@ log = "0.4.0" nix = { version = "0.30.1", features = ["ioctl", "net", "poll", "socket", "fs"] } pw = { package = "pipewire", version = "0.8.0", optional = true } rand = "0.9.2" -thiserror = { version = "2.0", optional = true } +thiserror = { version = "2.0" } virtio-bindings = "0.2.0" vm-memory = { version = ">=0.13", features = ["backend-mmap"] } zerocopy = { version = "0.8.26", optional = true, features = ["derive"] } diff --git a/src/devices/src/virtio/console/device.rs b/src/devices/src/virtio/console/device.rs index 9032da3c3..33eb31523 100644 --- a/src/devices/src/virtio/console/device.rs +++ b/src/devices/src/virtio/console/device.rs @@ -1,13 +1,3 @@ -use std::cmp; -use std::io::Write; -use std::iter::zip; -use std::mem::{size_of, size_of_val}; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::Arc; - -use utils::eventfd::EventFd; -use vm_memory::{ByteValued, Bytes, GuestMemoryMmap}; - use super::super::{ ActivateError, ActivateResult, ConsoleError, DeviceState, Queue as VirtQueue, VirtioDevice, }; @@ -21,6 +11,16 @@ use crate::virtio::console::port_queue_mapping::{ num_queues, port_id_to_queue_idx, QueueDirection, }; use crate::virtio::{InterruptTransport, PortDescription, VmmExitObserver}; +use std::cmp; +use std::io::Write; +use std::iter::zip; +use std::mem::{size_of, size_of_val}; +use std::os::fd::BorrowedFd; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::{Arc, Mutex}; +use thiserror::Error; +use utils::eventfd::EventFd; +use vm_memory::{ByteValued, Bytes, GuestMemoryMmap}; pub(crate) const CONTROL_RXQ_INDEX: usize = 2; pub(crate) const CONTROL_TXQ_INDEX: usize = 3; @@ -67,17 +67,75 @@ pub struct Console { pub(crate) sigwinch_evt: EventFd, config: VirtioConsoleConfig, + ready: bool, console_ready_evt: Arc, } +#[derive(Error, Debug)] +#[repr(i32)] +pub enum ConsoleControllerError { + #[error("Console device is not ready to ")] + NotReady, + + #[error("Backend implementation error")] + OutOfPorts, +} + +#[derive(Clone)] +pub struct ConsoleController { + console: Arc>, +} + +impl ConsoleController { + pub fn console_ready_fd(&self) -> BorrowedFd<'_> { + unsafe { + BorrowedFd::borrow_raw(self.console.lock().unwrap().console_ready_evt.as_raw_fd()) + } + } + + pub fn is_ready(&self) -> bool { + self.console.lock().unwrap().ready + } + + pub fn add_port( + &self, + port_description: PortDescription, + ) -> Result<(), ConsoleControllerError> { + let mut console = self.console.lock().unwrap(); + if !console.ready { + return Err(ConsoleControllerError::NotReady); + } + + if console.ports.len() as u32 >= console.config.max_nr_ports { + return Err(ConsoleControllerError::OutOfPorts); + } + + let port_id = console.ports.len() as u32; + console.ports.push(Port::new(port_id, port_description)); + + // Notify the guest we have added a port + // From this point forward, we will automatically set up the port as open and start worker + // threads once we receive the VIRTIO_CONSOLE_PORT_READY + console.control.port_add(port_id); + Ok(()) + } +} + impl Console { pub fn new( ports: Vec, + reserved_port_count: u32, console_ready_evt: Arc, ) -> super::Result { - assert!(!ports.is_empty(), "Expected at least 1 port"); + assert!( + !ports.is_empty() || reserved_port_count > 0, + "Expected at least 1 port or reserved port" + ); + + let max_ports = ports.len() as u32 + reserved_port_count; - let num_queues = num_queues(ports.len()); + // Pre-allocate queues for all potential ports + let num_queues = num_queues(max_ports as usize); let queues = vec![VirtQueue::new(QUEUE_SIZE); num_queues]; let mut queue_events = Vec::new(); @@ -90,11 +148,13 @@ impl Console { .map(|(port_id, description)| Port::new(port_id, description)) .collect(); - let (cols, rows) = ports[0] - .terminal() + let (cols, rows) = ports + .first() + .and_then(|p| p.terminal()) .map(|t| t.get_win_size()) .unwrap_or((0, 0)); - let config = VirtioConsoleConfig::new(cols, rows, ports.len() as u32); + + let config = VirtioConsoleConfig::new(cols, rows, max_ports); Ok(Console { control: ConsoleControl::new(), @@ -109,10 +169,24 @@ impl Console { .map_err(ConsoleError::EventFd)?, device_state: DeviceState::Inactive, config, + ready: false, console_ready_evt, }) } + pub fn with_controller( + ports: Vec, + reserved_port_count: u32, + console_ready_evt: Arc, + ) -> super::Result<(Arc>, ConsoleController)> { + let console = Arc::new(Mutex::new(Self::new( + ports, + reserved_port_count, + console_ready_evt, + )?)); + Ok((console.clone(), ConsoleController { console })) + } + pub fn id(&self) -> &str { defs::CONSOLE_DEV_ID } @@ -203,6 +277,8 @@ impl Console { if let Err(e) = self.console_ready_evt.write(1) { warn!("Failed to trigger console_ready_evt: {e:?}"); } + self.console_ready_evt.write(1).unwrap(); + self.ready = true; } control_event::VIRTIO_CONSOLE_PORT_READY => { if cmd.value != 1 { @@ -242,12 +318,19 @@ impl Console { } }; - if !opened { + if opened { + ports_to_start.push(cmd.id); + } else if let Some(port) = self.ports.get_mut(cmd.id as usize) { log::debug!("Guest closed port {}", cmd.id); - continue; + port.shutdown(); + // TODO: close the underlying file descriptors for the port + // (note that it requires an API for being able to reopen the ports!) + } else { + warn!( + "Guest tried to close port {} but we don't have such port!", + cmd.id + ); } - - ports_to_start.push(cmd.id as usize); } _ => log::warn!("Unknown console control event {:x}", cmd.event), } @@ -255,13 +338,15 @@ impl Console { for port_id in ports_to_start { log::trace!("Starting port io for port {port_id}"); - self.ports[port_id].start( - mem.clone(), - self.queues[port_id_to_queue_idx(QueueDirection::Rx, port_id)].clone(), - self.queues[port_id_to_queue_idx(QueueDirection::Tx, port_id)].clone(), - interrupt.clone(), - self.control.clone(), - ); + if let Some(port) = self.ports.get_mut(port_id as usize) { + port.start( + mem.clone(), + self.queues[port_id_to_queue_idx(QueueDirection::Rx, port_id as usize)].clone(), + self.queues[port_id_to_queue_idx(QueueDirection::Tx, port_id as usize)].clone(), + interrupt.clone(), + self.control.clone(), + ); + } } raise_irq @@ -343,9 +428,10 @@ impl VirtioDevice for Console { // the device, but we don't support any scenario in which // neither GuestMemory nor the queue events would change, // so let's avoid doing any unnecessary work. - for port in &mut self.ports { + for port in self.ports.iter_mut() { port.shutdown(); } + self.ready = false; true } } diff --git a/src/devices/src/virtio/console/mod.rs b/src/devices/src/virtio/console/mod.rs index 33e47994c..4c785a277 100644 --- a/src/devices/src/virtio/console/mod.rs +++ b/src/devices/src/virtio/console/mod.rs @@ -8,7 +8,7 @@ mod process_rx; mod process_tx; pub use self::defs::uapi::VIRTIO_ID_CONSOLE as TYPE_CONSOLE; -pub use self::device::Console; +pub use self::device::{Console, ConsoleController, ConsoleControllerError}; pub use self::port::PortDescription; mod defs { diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index ad23db7ba..57253f7fd 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -28,7 +28,6 @@ use std::ffi::CString; use std::ffi::{c_void, CStr}; use std::fs::File; use std::io::IsTerminal; -#[cfg(target_os = "linux")] use std::os::fd::AsRawFd; use std::os::fd::{BorrowedFd, FromRawFd, RawFd}; use std::path::PathBuf; @@ -39,7 +38,7 @@ use std::sync::{Arc, LazyLock}; use utils::eventfd::EventFd; use vmm::resources::{ DefaultVirtioConsoleConfig, PortConfig, SerialConsoleConfig, TsiFlags, VirtioConsoleConfig, - VirtioConsoleConfigMode, VmResources, VsockConfig + VirtioConsoleConfigMode, VmResources, VsockConfig, }; #[cfg(feature = "blk")] use vmm::vmm_config::block::{BlockDeviceConfig, BlockRootConfig}; @@ -64,6 +63,7 @@ use nitro::enclaves::NitroEnclave; #[cfg(feature = "gpu")] use devices::virtio::display::{DisplayInfoEdid, PhysicalSize, MAX_DISPLAYS}; +use devices::virtio::{port_io, ConsoleControllerError, PortDescription}; #[cfg(feature = "input")] use krun_input::{InputConfigBackend, InputEventProviderBackend}; @@ -2365,7 +2365,10 @@ pub unsafe extern "C" fn krun_add_virtio_console_multiport(ctx_id: u32) -> i32 { .expect("Failed to create console_ready_evt"), ); cfg.vmr.virtio_consoles.push(VirtioConsoleConfig { - mode: VirtioConsoleConfigMode::Explicit(Vec::new()), + mode: VirtioConsoleConfigMode::Explicit { + ports: Vec::new(), + reserved_count: 0, + }, console_ready_evt, }); @@ -2375,9 +2378,48 @@ pub unsafe extern "C" fn krun_add_virtio_console_multiport(ctx_id: u32) -> i32 { } } +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn krun_console_reserve_ports( + ctx_id: u32, + console_id: u32, + num_ports: u32, +) -> i32 { + match CTX_MAP.lock().unwrap().entry(ctx_id) { + Entry::Occupied(mut ctx_cfg) => { + match ctx_cfg + .get_mut() + .vmr + .virtio_consoles + .get_mut(console_id as usize) + { + Some(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Explicit { reserved_count, .. }, + .. + }) => { + *reserved_count += num_ports; + KRUN_SUCCESS + } + _ => -libc::EINVAL, + } + } + Entry::Vacant(_) => -libc::ENOENT, + } +} + #[allow(clippy::missing_safety_doc)] #[no_mangle] pub unsafe extern "C" fn krun_get_console_ready_fd(ctx_id: u32, console_id: u32) -> i32 { + // Check if VM is running + if let Some(vmm) = RUNNING_CTX_MAP.lock().unwrap().get(&ctx_id).cloned() { + let vmm = vmm.lock().unwrap(); + let Some(console) = vmm.console_controller(console_id) else { + error!("krun_get_console_ready_fd: Invalid console id={console_id}"); + return -libc::ENOENT; + }; + return console.console_ready_fd().as_raw_fd(); + } + // VM not running yet, get the pre-created eventfd from CTX_MAP match CTX_MAP.lock().unwrap().get(&ctx_id) { Some(ctx_cfg) => match ctx_cfg.vmr.virtio_consoles.get(console_id as usize) { @@ -2416,13 +2458,34 @@ pub unsafe extern "C" fn krun_add_console_port_tty( return -libc::ENOTTY; } + if let Some(vmm) = RUNNING_CTX_MAP.lock().unwrap().get(&ctx_id) { + let mut vmm = vmm.lock().unwrap(); + vmm.setup_terminal_raw_mode(unsafe { BorrowedFd::borrow_raw(tty_fd) }, false); + + let Some(console) = vmm.console_controller(console_id) else { + error!("krun_add_console_port_tty: Invalid console id={console_id}"); + return -libc::ENOENT; + }; + + return match console.add_port(PortDescription { + name: name_str.into(), + input: Some(port_io::input_to_raw_fd_dup(tty_fd).unwrap()), + output: Some(port_io::output_to_raw_fd_dup(tty_fd).unwrap()), + terminal: Some(port_io::term_fd(tty_fd).unwrap()), + }) { + Ok(()) => KRUN_SUCCESS, + Err(ConsoleControllerError::NotReady) => -libc::EAGAIN, + Err(ConsoleControllerError::OutOfPorts) => -libc::ENOMEM, + }; + } + match CTX_MAP.lock().unwrap().entry(ctx_id) { Entry::Occupied(mut ctx_cfg) => { let cfg = ctx_cfg.get_mut(); match cfg.vmr.virtio_consoles.get_mut(console_id as usize) { Some(VirtioConsoleConfig { - mode: VirtioConsoleConfigMode::Explicit(ports), + mode: VirtioConsoleConfigMode::Explicit { ports, .. }, .. }) => { ports.push(PortConfig::Tty { @@ -2456,13 +2519,32 @@ pub unsafe extern "C" fn krun_add_console_port_inout( } }; + if let Some(vmm) = RUNNING_CTX_MAP.lock().unwrap().get(&ctx_id) { + let vmm = vmm.lock().unwrap(); + let Some(console) = vmm.console_controller(console_id) else { + error!("krun_add_console_port_tty: Invalid console id={console_id}"); + return -libc::ENOENT; + }; + + return match console.add_port(PortDescription { + name: name_str.into(), + input: Some(port_io::input_to_raw_fd_dup(input_fd).unwrap()), + output: Some(port_io::output_to_raw_fd_dup(output_fd).unwrap()), + terminal: None, + }) { + Ok(()) => KRUN_SUCCESS, + Err(ConsoleControllerError::NotReady) => -libc::EAGAIN, + Err(ConsoleControllerError::OutOfPorts) => -libc::ENOMEM, + }; + } + match CTX_MAP.lock().unwrap().entry(ctx_id) { Entry::Occupied(mut ctx_cfg) => { let cfg = ctx_cfg.get_mut(); match cfg.vmr.virtio_consoles.get_mut(console_id as usize) { Some(VirtioConsoleConfig { - mode: VirtioConsoleConfigMode::Explicit(ports), + mode: VirtioConsoleConfigMode::Explicit { ports, .. }, .. }) => { ports.push(PortConfig::InOut { diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 7583935b5..3c0fd35ec 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -24,8 +24,8 @@ use super::{Error, Vmm}; use crate::device_manager::legacy::PortIODeviceManager; use crate::device_manager::mmio::MMIODeviceManager; use crate::resources::{ - DefaultVirtioConsoleConfig, PortConfig, TsiFlags, VirtioConsoleConfigMode, VmResources, - VirtioConsoleConfig + DefaultVirtioConsoleConfig, PortConfig, TsiFlags, VirtioConsoleConfig, VirtioConsoleConfigMode, + VmResources, }; use crate::vmm_config::external_kernel::{ExternalKernel, KernelFormat}; #[cfg(feature = "net")] @@ -46,7 +46,9 @@ use devices::legacy::{IoApic, IrqChipT}; use devices::legacy::{IrqChip, IrqChipDevice}; #[cfg(all(target_os = "linux", target_arch = "aarch64"))] use devices::legacy::{KvmGicV2, KvmGicV3}; -use devices::virtio::{port_io, MmioTransport, PortDescription, VirtioDevice, Vsock}; +use devices::virtio::{ + port_io, Console, ConsoleController, MmioTransport, PortDescription, VirtioDevice, Vsock, +}; #[cfg(feature = "tee")] use kbs_types::Tee; @@ -56,7 +58,6 @@ use crate::device_manager; use crate::signal_handler::register_sigint_handler; #[cfg(target_os = "linux")] use crate::signal_handler::register_sigwinch_handler; -use crate::terminal::{term_restore_mode, term_set_raw_mode}; #[cfg(feature = "blk")] use crate::vmm_config::block::BlockBuilder; #[cfg(not(any(feature = "tee", feature = "nitro")))] @@ -197,6 +198,8 @@ pub enum StartMicrovmError { RegisterFsDevice(device_manager::mmio::Error), // Cannot initialize a MMIO Fs Device or add ad device to the MMIO Bus. RegisterConsoleDevice(device_manager::mmio::Error), + /// Cannot create console channel for dynamic port addition. + CreateConsoleChannel(io::Error), /// Cannot register SIGWINCH event file descriptor. #[cfg(target_os = "linux")] RegisterFsSigwinch(kvm_ioctls::Error), @@ -411,6 +414,12 @@ impl Display for StartMicrovmError { "Cannot initialize a MMIO Console Device or add a device to the MMIO Bus. {err_msg}" ) } + CreateConsoleChannel(ref err) => { + write!( + f, + "Cannot create console channel for dynamic port addition. {err}" + ) + } #[cfg(target_os = "linux")] RegisterFsSigwinch(ref err) => { let mut err_msg = format!("{err}"); @@ -963,20 +972,26 @@ pub fn build_microvm( mmio_device_manager, #[cfg(target_arch = "x86_64")] pio_device_manager, + console_controllers: Vec::new(), }; // Set raw mode for FDs that are connected to legacy serial devices. for serial_tty in serial_ttys { - setup_terminal_raw_mode(&mut vmm, Some(serial_tty), false); + vmm.setup_terminal_raw_mode(serial_tty, false); } #[cfg(not(feature = "tee"))] attach_balloon_device(&mut vmm, event_manager, intc.clone())?; #[cfg(not(feature = "tee"))] attach_rng_device(&mut vmm, event_manager, intc.clone())?; + + let mut console_controllers = Vec::with_capacity( + vm_resources.virtio_consoles.len() + !vm_resources.disable_implicit_console as usize, + ); + let mut console_id = 0; if !vm_resources.disable_implicit_console { - attach_console_devices( + let controller = attach_console_devices( &mut vmm, event_manager, intc.clone(), @@ -984,11 +999,12 @@ pub fn build_microvm( None, console_id, )?; + console_controllers.push(controller); console_id += 1; } for console_cfg in vm_resources.virtio_consoles.iter() { - attach_console_devices( + let controller = attach_console_devices( &mut vmm, event_manager, intc.clone(), @@ -996,9 +1012,12 @@ pub fn build_microvm( Some(console_cfg), console_id, )?; + console_controllers.push(controller); console_id += 1; } + vmm.console_controllers = console_controllers; + #[cfg(not(any(feature = "tee", feature = "nitro")))] let export_table: Option = if cfg!(feature = "gpu") { Some(Default::default()) @@ -2006,7 +2025,9 @@ fn autoconfigure_console_ports( .map(|fd| port_io::term_fd(fd.as_raw_fd()).unwrap()) .unwrap_or_else(|| port_io::term_fixed_size(0, 0)); - setup_terminal_raw_mode(vmm, term_fd, forwarding_sigint); + if let Some(fd) = term_fd { + vmm.setup_terminal_raw_mode(fd, forwarding_sigint); + } let mut ports = vec![PortDescription::console( console_input, @@ -2039,30 +2060,6 @@ fn autoconfigure_console_ports( } } -fn setup_terminal_raw_mode( - vmm: &mut Vmm, - term_fd: Option>, - handle_signals_by_terminal: bool, -) { - if let Some(term_fd) = term_fd { - match term_set_raw_mode(term_fd, handle_signals_by_terminal) { - Ok(old_mode) => { - let raw_fd = term_fd.as_raw_fd(); - vmm.exit_observers.push(Arc::new(Mutex::new(move || { - if let Err(e) = - term_restore_mode(unsafe { BorrowedFd::borrow_raw(raw_fd) }, &old_mode) - { - log::error!("Failed to restore terminal mode: {e}") - } - }))); - } - Err(e) => { - log::error!("Failed to set terminal to raw mode: {e}") - } - }; - } -} - fn create_explicit_ports( vmm: &mut Vmm, port_configs: &[PortConfig], @@ -2074,7 +2071,7 @@ fn create_explicit_ports( PortConfig::Tty { name, tty_fd } => { assert!(*tty_fd > 0, "PortConfig::Tty must have a valid tty_fd"); let term_fd = unsafe { BorrowedFd::borrow_raw(*tty_fd) }; - setup_terminal_raw_mode(vmm, Some(term_fd), false); + vmm.setup_terminal_raw_mode(term_fd, false); PortDescription { name: name.clone().into(), @@ -2116,14 +2113,23 @@ fn attach_console_devices( vm_resources: &VmResources, cfg: Option<&VirtioConsoleConfig>, id_number: u32, -) -> std::result::Result<(), StartMicrovmError> { +) -> std::result::Result { use self::StartMicrovmError::*; let creating_implicit_console = cfg.is_none(); - let console_ready_evt = match cfg { - Some(VirtioConsoleConfig { console_ready_evt, .. }) => console_ready_evt.clone(), - None => Arc::new(EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap()), + let (reserved_count, console_ready_evt) = match cfg { + Some(VirtioConsoleConfig { + mode: VirtioConsoleConfigMode::Explicit { reserved_count, .. }, + console_ready_evt, + }) => (*reserved_count, console_ready_evt.clone()), + Some(VirtioConsoleConfig { + console_ready_evt, .. + }) => (0, console_ready_evt.clone()), + None => ( + 0, + Arc::new(EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap()), + ), }; let ports = match cfg { @@ -2138,14 +2144,13 @@ fn attach_console_devices( creating_implicit_console, )?, Some(VirtioConsoleConfig { - mode: VirtioConsoleConfigMode::Explicit(ports), + mode: VirtioConsoleConfigMode::Explicit { ports, .. }, .. }) => create_explicit_ports(vmm, ports)?, }; - let console = Arc::new(Mutex::new( - devices::virtio::Console::new(ports, console_ready_evt).unwrap(), - )); + let (console, console_controller) = + Console::with_controller(ports, reserved_count, console_ready_evt).unwrap(); vmm.exit_observers.push(console.clone()); @@ -2161,7 +2166,7 @@ fn attach_console_devices( attach_mmio_device(vmm, format!("hvc{id_number}"), intc, console) .map_err(RegisterConsoleDevice)?; - Ok(()) + Ok(console_controller) } #[cfg(feature = "net")] diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index d029ce718..c074f2588 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -30,7 +30,7 @@ mod linux; use crate::linux::vstate; #[cfg(target_os = "macos")] mod macos; -mod terminal; +pub mod terminal; pub mod worker; #[cfg(target_os = "macos")] @@ -38,6 +38,7 @@ use macos::vstate; use std::fmt::{Display, Formatter}; use std::io; +use std::os::fd::BorrowedFd; use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::{Arc, Mutex}; @@ -57,7 +58,7 @@ use crossbeam_channel::Sender; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use devices::fdt; use devices::legacy::IrqChip; -use devices::virtio::VmmExitObserver; +use devices::virtio::{ConsoleController, VmmExitObserver}; use devices::{BusDevice, DeviceType}; use kernel::cmdline::Cmdline as KernelCmdline; use polly::event_manager::{self, EventManager, Subscriber}; @@ -207,6 +208,7 @@ pub struct Vmm { mmio_device_manager: MMIODeviceManager, #[cfg(target_arch = "x86_64")] pio_device_manager: PortIODeviceManager, + console_controllers: Vec, } impl Vmm { @@ -392,6 +394,34 @@ impl Vmm { pub fn remove_mapping(&self, reply_sender: Sender, guest_addr: u64, len: u64) { self.vm.remove_mapping(reply_sender, guest_addr, len); } + + pub fn console_controller(&self, console_id: u32) -> Option<&ConsoleController> { + self.console_controllers.get(console_id as usize) + } + + /// Set up raw mode for a terminal fd and register cleanup on exit. + pub fn setup_terminal_raw_mode( + &mut self, + term_fd: BorrowedFd<'_>, + handle_signals_by_terminal: bool, + ) { + match terminal::term_set_raw_mode(term_fd, handle_signals_by_terminal) { + Ok(old_mode) => { + let raw_fd = term_fd.as_raw_fd(); + self.exit_observers.push(Arc::new(Mutex::new(move || { + if let Err(e) = terminal::term_restore_mode( + unsafe { BorrowedFd::borrow_raw(raw_fd) }, + &old_mode, + ) { + log::error!("Failed to restore terminal mode: {e}") + } + }))); + } + Err(e) => { + log::error!("Failed to set terminal to raw mode: {e}") + } + } + } } impl Subscriber for Vmm { diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index dcba292cd..22bbb1622 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -100,7 +100,11 @@ pub struct DefaultVirtioConsoleConfig { pub enum VirtioConsoleConfigMode { Autoconfigure(DefaultVirtioConsoleConfig), - Explicit(Vec), + Explicit { + ports: Vec, + /// Number of additional reserved port slots for dynamic addition after VM start + reserved_count: u32, + }, } pub struct VirtioConsoleConfig { From b2d461767dda779bbca4f3a8d8da48bd8c18bbfb Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Thu, 8 Jan 2026 12:46:42 +0100 Subject: [PATCH 4/4] examples/consoles: Support attaching consoles and pipes at runtime Add dynamic console management that waits for the console device to become ready, then allows interactive addition of new console ports and pipe ports while the VM is running. Signed-off-by: Matej Hrica --- examples/consoles.c | 222 ++++++++++++++++++++++++++++---------------- 1 file changed, 144 insertions(+), 78 deletions(-) diff --git a/examples/consoles.c b/examples/consoles.c index 30a17a492..7e738e28e 100644 --- a/examples/consoles.c +++ b/examples/consoles.c @@ -5,11 +5,15 @@ #include #include #include +#include +#include #include #include #include +#define NUM_RESERVED_PORTS 64 + static int cmd_output(char *output, size_t output_size, const char *prog, ...) { va_list args; @@ -63,12 +67,11 @@ static int create_tmux_tty(const char *session_name) { char tty_path[256]; char wait_cmd[128]; - + snprintf(wait_cmd, sizeof(wait_cmd), "waitpid %d", (int)getpid()); if (cmd("tmux", "new-session", "-d", "-s", session_name, "sh", "-c", wait_cmd, NULL) != 0) return -1; - // Hook up tmux to send us SIGWINCH signal on resize char hook_cmd[128]; snprintf(hook_cmd, sizeof(hook_cmd), "run-shell 'kill -WINCH %d'", (int)getpid()); cmd("tmux", "set-hook", "-g", "client-resized", hook_cmd, NULL); @@ -90,23 +93,120 @@ static int mkfifo_if_needed(const char *path) return 0; } - static int create_fifo_inout(const char *fifo_in, const char *fifo_out, int *input_fd, int *output_fd) { if (mkfifo_if_needed(fifo_in) < 0) return -1; if (mkfifo_if_needed(fifo_out) < 0) return -1; - int in_fd = open(fifo_in, O_RDONLY | O_NONBLOCK); - if (in_fd < 0) return -1; + *input_fd = open(fifo_in, O_RDWR | O_NONBLOCK); + if (*input_fd < 0) return -1; - int out_fd = open(fifo_out, O_RDWR | O_NONBLOCK); - if (out_fd < 0) { close(in_fd); return -1; } + *output_fd = open(fifo_out, O_RDWR | O_NONBLOCK); + if (*output_fd < 0) { + close(*input_fd); + return -1; + } - *input_fd = in_fd; - *output_fd = out_fd; return 0; } +struct console_state { + uint32_t ctx_id; + uint32_t console_id; + int ready_fd; +}; + +static void *dynamic_console_thread(void *arg) +{ + struct console_state *state = arg; + int ready_fd = state->ready_fd; + + struct pollfd pfd = { .fd = ready_fd, .events = POLLIN }; + fprintf(stderr, "Waiting for console device...\n"); + if (poll(&pfd, 1, -1) < 0) { + perror("poll"); + return NULL; + } + + uint64_t val; + if (read(ready_fd, &val, sizeof(val)) != sizeof(val)) { + perror("read eventfd"); + return NULL; + } + + fprintf(stderr, "\n"); + fprintf(stderr, "=== VM Started ===\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "*** To interact with the VM (hvc0), run in another terminal: ***\n"); + fprintf(stderr, " tmux attach -t krun-console-1\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Commands: 'c' = add console\n"); + fprintf(stderr, " 'p' = add pipe\n"); + fprintf(stderr, "\n"); + + int console_count = 1; /* console-1 already exists (hvc0) */ + int pipe_count = 0; + char line[16]; + while (1) { + fprintf(stderr, "> "); + if (fgets(line, sizeof(line), stdin) == NULL) break; + + if (line[0] == 'c' || line[0] == 'C') { + console_count++; + char sess[64], port[64]; + snprintf(sess, sizeof(sess), "krun-console-%d", console_count); + snprintf(port, sizeof(port), "console-%d", console_count); + + int fd = create_tmux_tty(sess); + if (fd < 0) { fprintf(stderr, "tmux: failed to create session '%s'\n", sess); continue; } + + int err = krun_add_console_port_tty(state->ctx_id, state->console_id, port, fd); + if (err) { fprintf(stderr, "add port: %s\n", strerror(-err)); close(fd); continue; } + + fprintf(stderr, "Created console '%s' (port %d, /dev/hvc%d):\n", port, console_count + pipe_count - 1, console_count - 1); + fprintf(stderr, " On host: tmux attach -t %s\n", sess); + fprintf(stderr, " In guest: setsid /sbin/agetty -a $(whoami) -L hvc%d xterm-256color\n", console_count - 1); + if (console_count + pipe_count > NUM_RESERVED_PORTS) { + fprintf(stderr, "Reached max reserved ports (%d)\n", NUM_RESERVED_PORTS); + break; + } + } + + if (line[0] == 'p' || line[0] == 'P') { + pipe_count++; + char port[64], fifo_in[128], fifo_out[128]; + snprintf(port, sizeof(port), "pipe-%d", pipe_count); + snprintf(fifo_in, sizeof(fifo_in), "/tmp/krun_pipe%d_in", pipe_count); + snprintf(fifo_out, sizeof(fifo_out), "/tmp/krun_pipe%d_out", pipe_count); + + int in_fd, out_fd; + if (create_fifo_inout(fifo_in, fifo_out, &in_fd, &out_fd) < 0) { + perror("create_fifo_inout"); continue; + } + + int err = krun_add_console_port_inout(state->ctx_id, state->console_id, port, in_fd, out_fd); + if (err) { + fprintf(stderr, "add port: %s\n", strerror(-err)); + close(in_fd); + close(out_fd); + continue; + } + + fprintf(stderr, "Created pipe '%s' (port %d):\n", port, console_count + pipe_count - 1); + fprintf(stderr, " In guest: DEV=/dev/$(grep -l %s /sys/class/virtio-ports/*/name | cut -d/ -f5)\n", port); + fprintf(stderr, " cat $DEV OR echo data > $DEV\n"); + fprintf(stderr, " On host: echo 'data' > %s # send to guest\n", fifo_in); + fprintf(stderr, " cat %s # receive from guest\n", fifo_out); + if (console_count + pipe_count > NUM_RESERVED_PORTS) { + fprintf(stderr, "Reached max reserved ports (%d)\n", NUM_RESERVED_PORTS); + break; + } + } + } + + return NULL; +} + int main(int argc, char *const argv[]) { if (argc < 3) { @@ -119,100 +219,66 @@ int main(int argc, char *const argv[]) const char *const *command_args = (argc > 3) ? (const char *const *)&argv[3] : NULL; const char *const envp[] = { 0 }; - krun_set_log_level(KRUN_LOG_LEVEL_WARN); + krun_set_log_level(KRUN_LOG_LEVEL_DEBUG); int err; int ctx_id = krun_create_ctx(); if (ctx_id < 0) { errno = -ctx_id; perror("krun_create_ctx"); return 1; } if ((err = krun_disable_implicit_console(ctx_id))) { - errno = -err; - perror("krun_disable_implicit_console"); - return 1; + errno = -err; perror("krun_disable_implicit_console"); return 1; } int console_id = krun_add_virtio_console_multiport(ctx_id); if (console_id < 0) { - errno = -console_id; - perror("krun_add_virtio_console_multiport"); - return 1; + errno = -console_id; perror("krun_add_virtio_console_multiport"); return 1; } - /* Configure console ports - edit this section to add/remove ports */ + /* Create 1 initial console BEFORE VM starts - this will run the command */ { - - // You could also use the controlling terminal of this process in the guest: - /* - if ((err = krun_add_console_port_tty(ctx_id, console_id, "host_tty", open("/dev/tty", O_RDWR)))) { - errno = -err; - perror("port host_tty"); - return 1; - } - */ - - int num_consoles = 3; - for (int i = 0; i < num_consoles; i++) { - char session_name[64]; - char port_name[64]; - snprintf(session_name, sizeof(session_name), "krun-console-%d", i + 1); - snprintf(port_name, sizeof(port_name), "console-%d", i + 1); - - int tmux_fd = create_tmux_tty(session_name); - if (tmux_fd < 0) { - perror("create_tmux_tty"); - return 1; - } - if ((err = krun_add_console_port_tty(ctx_id, console_id, port_name, tmux_fd))) { - errno = -err; - perror("krun_add_console_port_tty"); - return 1; - } - } - - int in_fd, out_fd; - if (create_fifo_inout("/tmp/consoles_example_in", "/tmp/consoles_example_out", &in_fd, &out_fd) < 0) { - perror("create_fifo_inout"); - return 1; - } - if ((err = krun_add_console_port_inout(ctx_id, console_id, "fifo_inout", in_fd, out_fd))) { - errno = -err; - perror("krun_add_console_port_inout"); - return 1; + int fd = create_tmux_tty("krun-console-1"); + if (fd < 0) { fprintf(stderr, "create_tmux_tty failed (session already exists?)\n"); return 1; } + if ((err = krun_add_console_port_tty(ctx_id, console_id, "console-1", fd))) { + errno = -err; perror("krun_add_console_port_tty"); return 1; } + } - fprintf(stderr, "\n=== Console ports configured ===\n"); - for (int i = 0; i < num_consoles; i++) { - fprintf(stderr, " console-%d: tmux attach -t krun-console-%d\n", i + 1, i + 1); - } - fprintf(stderr, " fifo_inout: /tmp/consoles_example_in (host->guest)\n"); - fprintf(stderr, " fifo_inout: /tmp/consoles_example_out (guest->host)\n"); - fprintf(stderr, "================================\n\n"); + /* Reserve ports for dynamic addition */ + if ((err = krun_console_reserve_ports(ctx_id, console_id, NUM_RESERVED_PORTS))) { + errno = -err; perror("krun_console_reserve_ports"); return 1; } if ((err = krun_set_vm_config(ctx_id, 4, 4096))) { - errno = -err; - perror("krun_set_vm_config"); - return 1; + errno = -err; perror("krun_set_vm_config"); return 1; } - if ((err = krun_set_root(ctx_id, root_dir))) { - errno = -err; - perror("krun_set_root"); - return 1; + errno = -err; perror("krun_set_root"); return 1; } - if ((err = krun_set_exec(ctx_id, command, command_args, envp))) { - errno = -err; - perror("krun_set_exec"); - return 1; + errno = -err; perror("krun_set_exec"); return 1; + } + + fprintf(stderr, "\nStarting VM...\n"); + + int ready_fd = krun_get_console_ready_fd(ctx_id, console_id); + if (ready_fd < 0) { + errno = -ready_fd; perror("krun_get_console_ready_fd"); return 1; } + struct console_state state = { + .ctx_id = ctx_id, + .console_id = console_id, + .ready_fd = ready_fd, + }; + + pthread_t dyn_thread; + pthread_create(&dyn_thread, NULL, dynamic_console_thread, &state); + pthread_detach(dyn_thread); + + /* Run VM in main thread - this blocks until VM exits, then calls _exit() */ if ((err = krun_start_enter(ctx_id))) { - errno = -err; - perror("krun_start_enter"); - return 1; + errno = -err; perror("krun_start_enter"); return 1; } + return 0; } - -