Skip to content

Commit f00bc34

Browse files
committed
allow redirecting guest serial output to a file
Add a new field, `serial_out_path`, to the logger configuration (available both via API and CLI) which can be set to the path of a file into which the output of the guest's serial console should be dumped. Have the file behave identically to our log file (e.g. it must already exist, Firecracker does not create it). Signed-off-by: Patrick Roy <[email protected]>
1 parent d5e27a8 commit f00bc34

File tree

6 files changed

+40
-3
lines changed

6 files changed

+40
-3
lines changed

src/firecracker/src/api_server/request/logger.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ mod tests {
3636

3737
let expected_config = LoggerConfig {
3838
log_path: Some(PathBuf::from("log")),
39+
serial_out_path: None,
3940
level: Some(LevelFilter::Warn),
4041
show_level: Some(false),
4142
show_log_origin: Some(false),
@@ -55,6 +56,7 @@ mod tests {
5556

5657
let expected_config = LoggerConfig {
5758
log_path: Some(PathBuf::from("log")),
59+
serial_out_path: None,
5860
level: Some(LevelFilter::Debug),
5961
show_level: Some(false),
6062
show_log_origin: Some(false),

src/firecracker/src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ fn main_exec() -> Result<(), MainError> {
208208
.takes_value(true)
209209
.help("Path to a fifo or a file used for configuring the logger on startup."),
210210
)
211+
.arg(
212+
Argument::new("serial-out-path")
213+
.takes_value(true)
214+
.help("Path to a fifo or a file used for serial output."),
215+
)
211216
.arg(
212217
Argument::new("level")
213218
.takes_value(true)
@@ -295,6 +300,7 @@ fn main_exec() -> Result<(), MainError> {
295300
.set(String::from(instance_id))
296301
.unwrap();
297302
let log_path = arguments.single_value("log-path").map(PathBuf::from);
303+
let serial_out_path = arguments.single_value("serial-out-path").map(PathBuf::from);
298304
let level = arguments
299305
.single_value("level")
300306
.map(|s| vmm::logger::LevelFilter::from_str(s))
@@ -306,6 +312,7 @@ fn main_exec() -> Result<(), MainError> {
306312
LOGGER
307313
.update(LoggerConfig {
308314
log_path,
315+
serial_out_path,
309316
level,
310317
show_level,
311318
show_log_origin,

src/vmm/src/builder.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use std::fmt::Debug;
77
use std::io;
8+
use std::os::unix::fs::OpenOptionsExt;
89
#[cfg(feature = "gdb")]
910
use std::sync::mpsc;
1011
use std::sync::{Arc, Mutex};
@@ -51,7 +52,7 @@ use crate::devices::virtio::vsock::{Vsock, VsockUnixBackend};
5152
#[cfg(feature = "gdb")]
5253
use crate::gdb;
5354
use crate::initrd::{InitrdConfig, InitrdError};
54-
use crate::logger::{debug, error};
55+
use crate::logger::{LOGGER, debug, error};
5556
use crate::persist::{MicrovmState, MicrovmStateError};
5657
use crate::resources::VmResources;
5758
use crate::seccomp::BpfThreadMap;
@@ -551,7 +552,20 @@ pub fn build_microvm_from_snapshot(
551552
pub fn setup_serial_device(
552553
event_manager: &mut EventManager,
553554
) -> Result<Arc<Mutex<BusDevice>>, VmmError> {
554-
set_stdout_nonblocking();
555+
let serial_out = match LOGGER.0.lock().expect("logger poisoned").serial_out_path {
556+
Some(ref path) => std::fs::OpenOptions::new()
557+
.custom_flags(libc::O_NONBLOCK)
558+
.read(true)
559+
.write(true)
560+
.open(path)
561+
.map_err(VmmError::Serial)
562+
.map(SerialOut::File)?,
563+
None => {
564+
set_stdout_nonblocking();
565+
566+
SerialOut::Stdout(std::io::stdout())
567+
}
568+
};
555569

556570
let interrupt_evt = EventFdTrigger::new(EventFd::new(EFD_NONBLOCK).map_err(VmmError::EventFd)?);
557571
let kick_stdin_read_evt =
@@ -562,7 +576,7 @@ pub fn setup_serial_device(
562576
SerialEventsWrapper {
563577
buffer_ready_event_fd: Some(kick_stdin_read_evt),
564578
},
565-
SerialOut::Stdout(std::io::stdout()),
579+
serial_out,
566580
),
567581
input: Some(std::io::stdin()),
568582
})));

src/vmm/src/devices/legacy/serial.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
//! Implements a wrapper over an UART serial device.
99
use std::fmt::Debug;
10+
use std::fs::File;
1011
use std::io;
1112
use std::io::{Read, Write};
1213
use std::os::unix::io::{AsRawFd, RawFd};
@@ -127,18 +128,21 @@ impl SerialEvents for SerialEventsWrapper {
127128
pub enum SerialOut {
128129
Sink,
129130
Stdout(std::io::Stdout),
131+
File(File),
130132
}
131133
impl std::io::Write for SerialOut {
132134
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
133135
match self {
134136
Self::Sink => Ok(buf.len()),
135137
Self::Stdout(stdout) => stdout.write(buf),
138+
Self::File(file) => file.write(buf),
136139
}
137140
}
138141
fn flush(&mut self) -> std::io::Result<()> {
139142
match self {
140143
Self::Sink => Ok(()),
141144
Self::Stdout(stdout) => stdout.flush(),
145+
Self::File(file) => file.flush(),
142146
}
143147
}
144148
}

src/vmm/src/logger/logging.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub static INSTANCE_ID: OnceLock<String> = OnceLock::new();
2828
/// Default values matching the swagger specification (`src/firecracker/swagger/firecracker.yaml`).
2929
pub static LOGGER: Logger = Logger(Mutex::new(LoggerConfiguration {
3030
target: None,
31+
serial_out_path: None,
3132
filter: LogFilter { module: None },
3233
format: LogFormat {
3334
show_level: false,
@@ -72,6 +73,10 @@ impl Logger {
7273
guard.target = Some(file);
7374
};
7475

76+
if let Some(serial_out_path) = config.serial_out_path {
77+
guard.serial_out_path = Some(serial_out_path);
78+
}
79+
7580
if let Some(show_level) = config.show_level {
7681
guard.format.show_level = show_level;
7782
}
@@ -104,6 +109,7 @@ pub struct LogFormat {
104109
#[derive(Debug)]
105110
pub struct LoggerConfiguration {
106111
pub target: Option<std::fs::File>,
112+
pub serial_out_path: Option<PathBuf>,
107113
pub filter: LogFilter,
108114
pub format: LogFormat,
109115
}
@@ -186,6 +192,8 @@ impl Log for Logger {
186192
pub struct LoggerConfig {
187193
/// Named pipe or file used as output for logs.
188194
pub log_path: Option<PathBuf>,
195+
/// Named pipe of file used as output for guest serial console.
196+
pub serial_out_path: Option<PathBuf>,
189197
/// The level of the Logger.
190198
pub level: Option<LevelFilter>,
191199
/// Whether to show the log level in the log.
@@ -366,6 +374,7 @@ mod tests {
366374
// Create logger.
367375
let logger = Logger(Mutex::new(LoggerConfiguration {
368376
target: Some(target),
377+
serial_out_path: None,
369378
filter: LogFilter {
370379
module: Some(String::from("module")),
371380
},

src/vmm/src/rpc_interface.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,7 @@ mod tests {
11871187
)));
11881188
check_unsupported(runtime_request(VmmAction::ConfigureLogger(LoggerConfig {
11891189
log_path: Some(PathBuf::new()),
1190+
serial_out_path: None,
11901191
level: Some(crate::logger::LevelFilter::Debug),
11911192
show_level: Some(false),
11921193
show_log_origin: Some(false),

0 commit comments

Comments
 (0)