From e44b0c24ae5cb36b37b1640ad77286aa0e24f60f Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sat, 18 Jul 2026 22:31:41 -0700 Subject: [PATCH] feat(console): redirect output to a log file Signed-off-by: Changyuan Lyu --- alioth-cli/src/boot/boot.rs | 15 ++++++- alioth-cli/src/boot/boot_test.rs | 1 + alioth-cli/src/boot/config.rs | 2 + alioth/src/device/console.rs | 70 ++++++++++++++++++++++++++++++++ alioth/src/vm/vm.rs | 35 ++++++++++++---- 5 files changed, 112 insertions(+), 11 deletions(-) diff --git a/alioth-cli/src/boot/boot.rs b/alioth-cli/src/boot/boot.rs index e96c1d3f..9e10a2c8 100644 --- a/alioth-cli/src/boot/boot.rs +++ b/alioth-cli/src/boot/boot.rs @@ -19,6 +19,7 @@ use std::mem; use std::path::{Path, PathBuf}; use alioth::board::{BoardSpec, CpuSpec}; +use alioth::device::console::ConsoleSpec; #[cfg(target_arch = "x86_64")] use alioth::device::fw_cfg::FwCfgItemSpec; use alioth::errors::{DebugTrace, trace_error}; @@ -156,6 +157,11 @@ pub struct BootArgs { ))] vsock: Option, + #[arg(long, help( + help_text::("Configure guest console.") + ))] + console: Option>, + #[cfg(target_os = "linux")] #[arg(long, help(help_text::( "Assign a host PCI device to the guest using IOMMUFD API." @@ -322,6 +328,11 @@ fn parse_args(mut args: BootArgs, objects: HashMap<&str, &str>) -> Result(hypervisor: &H, spec: VmSpec) -> Result, ali let vm = Machine::new(hypervisor, spec.board)?; #[cfg(target_arch = "x86_64")] - vm.add_com1()?; + vm.add_com1(&spec.console)?; #[cfg(target_arch = "aarch64")] - vm.add_pl011()?; + vm.add_pl011(&spec.console)?; #[cfg(target_arch = "aarch64")] vm.add_pl031(); diff --git a/alioth-cli/src/boot/boot_test.rs b/alioth-cli/src/boot/boot_test.rs index 1931e006..1f950794 100644 --- a/alioth-cli/src/boot/boot_test.rs +++ b/alioth-cli/src/boot/boot_test.rs @@ -98,6 +98,7 @@ fn test_parse_args() { ]); let spec = parse_args(args, objects).unwrap(); let want = VmSpec { + console: Default::default(), board: BoardSpec { cpu: CpuSpec { count: 16, diff --git a/alioth-cli/src/boot/config.rs b/alioth-cli/src/boot/config.rs index c0f6d0af..1be32663 100644 --- a/alioth-cli/src/boot/config.rs +++ b/alioth-cli/src/boot/config.rs @@ -16,6 +16,7 @@ use std::path::Path; use alioth::board::BoardSpec; +use alioth::device::console::ConsoleSpec; #[cfg(target_arch = "x86_64")] use alioth::device::fw_cfg::FwCfgItemSpec; use alioth::loader::PayloadSpec; @@ -98,6 +99,7 @@ pub struct VmSpec { pub payload: PayloadSpec, + pub console: ConsoleSpec, pub net: Vec, pub blk: Vec, pub fs: Vec, diff --git a/alioth/src/device/console.rs b/alioth/src/device/console.rs index 4f02e809..a716791d 100644 --- a/alioth/src/device/console.rs +++ b/alioth/src/device/console.rs @@ -13,8 +13,10 @@ // limitations under the License. use std::fmt::Debug; +use std::fs::{File, OpenOptions}; use std::io::{self, ErrorKind, Read, Write}; use std::mem::MaybeUninit; +use std::path::Path; use std::sync::Arc; use std::thread::JoinHandle; @@ -24,6 +26,8 @@ use libc::{ }; use mio::unix::SourceFd; use mio::{Events, Interest, Poll, Registry, Token}; +use serde::Deserialize; +use serde_aco::Help; use crate::device::Result; use crate::ffi; @@ -35,6 +39,61 @@ pub trait Console: Debug + Send + Sync + 'static { fn deactivate(&self, registry: &Registry) -> io::Result<()>; } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Help)] +pub struct LogConsoleSpec { + /// Path to the log file. + pub path: Box, + /// Append logs to the file if it already exits. + #[serde(default)] + pub append: bool, +} + +#[derive(Debug)] +pub struct LogConsole { + file: File, +} + +impl LogConsole { + pub fn new(spec: &LogConsoleSpec) -> Result { + let file = OpenOptions::new() + .create(true) + .write(true) + .append(spec.append) + .open(&spec.path)?; + Ok(LogConsole { file }) + } +} + +impl Console for LogConsole { + const TOKEN_INPUT: Token = Token(0); + + fn activate(&self, _registry: &Registry) -> io::Result<()> { + Ok(()) + } + + fn deactivate(&self, _registry: &Registry) -> io::Result<()> { + Ok(()) + } +} + +impl Read for &LogConsole { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Write for &LogConsole { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut file = &self.file; + file.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + let mut file = &self.file; + file.flush() + } +} + #[derive(Debug)] struct StdinBackup { termios: Option, @@ -131,6 +190,17 @@ impl Console for StdioConsole { } } +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Help)] +pub enum ConsoleSpec { + /// Redirect guest console to host stdio (requires TTY). + #[default] + #[serde(alias = "stdio")] + Stdio, + /// Redirect guest console to a log file. + #[serde(alias = "log")] + Log(LogConsoleSpec), +} + pub trait UartRecv: Send + 'static { fn receive(&self, bytes: &[u8]); } diff --git a/alioth/src/vm/vm.rs b/alioth/src/vm/vm.rs index dcffb564..30397715 100644 --- a/alioth/src/vm/vm.rs +++ b/alioth/src/vm/vm.rs @@ -31,10 +31,11 @@ use crate::arch::layout::{PL011_START, PL031_START}; use crate::arch::layout::{PORT_CMOS_REG, PORT_COM1, PORT_FW_CFG_SELECTOR, PORT_FWDBG}; use crate::board::{Board, BoardSpec}; use crate::cpu::{Context, State, VcpuHandle, stop_vcpus, vcpu_thread}; +use crate::device::MmioDev; use crate::device::clock::SystemClock; #[cfg(target_arch = "x86_64")] use crate::device::cmos::Cmos; -use crate::device::console::StdioConsole; +use crate::device::console::{ConsoleSpec, LogConsole, StdioConsole}; #[cfg(target_arch = "x86_64")] use crate::device::fw_cfg::{FwCfg, FwCfgItemSpec}; #[cfg(target_arch = "x86_64")] @@ -174,12 +175,20 @@ where } #[cfg(target_arch = "x86_64")] - pub fn add_com1(&self) -> Result<(), Error> { + pub fn add_com1(&self, spec: &ConsoleSpec) -> Result<(), Error> { let io_apic = self.ctx.board.arch.io_apic.clone(); - let console = StdioConsole::new().context(error::CreateConsole)?; - let com1 = Serial::new(PORT_COM1, io_apic, 4, console).context(error::CreateConsole)?; + let com1: Arc = match spec { + ConsoleSpec::Stdio => { + let console = StdioConsole::new().context(error::CreateConsole)?; + Arc::new(Serial::new(PORT_COM1, io_apic, 4, console).context(error::CreateConsole)?) + } + ConsoleSpec::Log(spec) => { + let console = LogConsole::new(spec).context(error::CreateConsole)?; + Arc::new(Serial::new(PORT_COM1, io_apic, 4, console).context(error::CreateConsole)?) + } + }; let mut io_devs = self.ctx.board.io_devs.write(); - io_devs.push((PORT_COM1, Arc::new(com1))); + io_devs.push((PORT_COM1, com1)); Ok(()) } @@ -198,12 +207,20 @@ where } #[cfg(target_arch = "aarch64")] - pub fn add_pl011(&self) -> Result<(), Error> { + pub fn add_pl011(&self, spec: &ConsoleSpec) -> Result<(), Error> { let irq_line = self.ctx.board.vm.create_irq_sender(1)?; - let console = StdioConsole::new().context(error::CreateConsole)?; - let pl011_dev = Pl011::new(PL011_START, irq_line, console).context(error::CreateConsole)?; + let pl011_dev: Arc = match spec { + ConsoleSpec::Stdio => { + let console = StdioConsole::new().context(error::CreateConsole)?; + Arc::new(Pl011::new(PL011_START, irq_line, console).context(error::CreateConsole)?) + } + ConsoleSpec::Log(spec) => { + let console = LogConsole::new(spec).context(error::CreateConsole)?; + Arc::new(Pl011::new(PL011_START, irq_line, console).context(error::CreateConsole)?) + } + }; let mut mmio_devs = self.ctx.board.mmio_devs.write(); - mmio_devs.push((PL011_START, Arc::new(pl011_dev))); + mmio_devs.push((PL011_START, pl011_dev)); Ok(()) }