use std::{
fs,
io::{self, Write},
path::{Path, PathBuf},
sync::Arc,
};
use super::symbolize::BacktraceSymbolizeSession;
#[derive(Clone)]
pub(crate) struct BacktraceQemuCapture {
pub log_path: PathBuf,
pub stream_symbolize: Option<Arc<BacktraceSymbolizeSession>>,
pub suppress_terminal_raw_blocks: bool,
pub write_log_during_capture: bool,
pub captured_blocks: Arc<std::sync::Mutex<Vec<Vec<String>>>>,
}
pub(crate) struct BacktraceBlockCapture {
log: Option<fs::File>,
pending_stream_blocks: Option<Arc<std::sync::Mutex<Vec<Vec<String>>>>>,
state: BlockCaptureState,
line_buf: String,
block_lines: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BlockCaptureState {
Idle,
InBlock,
}
impl BacktraceBlockCapture {
pub(crate) fn create(
log_path: Option<&Path>,
pending_stream_blocks: Option<Arc<std::sync::Mutex<Vec<Vec<String>>>>>,
) -> io::Result<Self> {
let log = if let Some(path) = log_path {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
Some(
fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?,
)
} else {
None
};
Ok(Self {
log,
pending_stream_blocks,
state: BlockCaptureState::Idle,
line_buf: String::new(),
block_lines: Vec::new(),
})
}
pub(crate) fn push_bytes(&mut self, data: &[u8]) -> io::Result<()> {
self.line_buf.push_str(&String::from_utf8_lossy(data));
while let Some(newline) = self.line_buf.find('\n') {
let line = self.line_buf[..newline].to_string();
self.line_buf.drain(..=newline);
let _ = self.process_line(&line)?;
}
Ok(())
}
pub(crate) fn push_bytes_for_tee(
&mut self,
data: &[u8],
suppress_raw_blocks: bool,
) -> io::Result<Vec<u8>> {
if !suppress_raw_blocks {
self.push_bytes(data)?;
return Ok(data.to_vec());
}
let mut terminal_out = Vec::new();
self.line_buf.push_str(&String::from_utf8_lossy(data));
while let Some(newline) = self.line_buf.find('\n') {
let line = self.line_buf[..newline].to_string();
self.line_buf.drain(..=newline);
if self.process_line(&line)? {
terminal_out.extend_from_slice(line.as_bytes());
terminal_out.push(b'\n');
}
}
Ok(terminal_out)
}
pub(crate) fn finish(&mut self) -> io::Result<()> {
if !self.line_buf.is_empty() {
let line = std::mem::take(&mut self.line_buf);
let _ = self.process_line(&line)?;
}
if self.state == BlockCaptureState::InBlock {
self.flush_block()?;
self.state = BlockCaptureState::Idle;
}
if let Some(log) = &mut self.log {
log.flush()?;
}
Ok(())
}
fn process_line(&mut self, line: &str) -> io::Result<bool> {
let has_begin = line.contains("BACKTRACE_BEGIN");
let has_end = line.contains("BACKTRACE_END");
let mut emit_terminal = true;
match self.state {
BlockCaptureState::Idle => {
if has_begin {
emit_terminal = false;
self.block_lines.clear();
self.block_lines.push(line.to_string());
self.state = BlockCaptureState::InBlock;
if has_end {
self.flush_block()?;
self.state = BlockCaptureState::Idle;
}
}
}
BlockCaptureState::InBlock => {
emit_terminal = false;
if has_begin && !self.block_lines.is_empty() {
self.flush_block()?;
self.block_lines.clear();
}
self.block_lines.push(line.to_string());
if has_end {
self.flush_block()?;
self.block_lines.clear();
self.state = BlockCaptureState::Idle;
}
}
}
Ok(emit_terminal)
}
fn flush_block(&mut self) -> io::Result<()> {
if self.block_lines.is_empty() {
return Ok(());
}
if let Some(log) = &mut self.log {
for line in &self.block_lines {
writeln!(log, "{line}")?;
}
}
if let Some(pending) = &self.pending_stream_blocks
&& let Ok(mut queue) = pending.lock()
{
queue.push(self.block_lines.clone());
}
self.block_lines.clear();
Ok(())
}
}
pub(crate) fn flush_pending_stream_symbolize(
session: &BacktraceSymbolizeSession,
pending: &std::sync::Mutex<Vec<Vec<String>>>,
) {
let blocks: Vec<Vec<String>> = match pending.lock() {
Ok(queue) => queue.clone(),
Err(_) => return,
};
for lines in &blocks {
session.on_block_complete(lines);
}
}
#[cfg(test)]
pub(crate) fn write_raw_blocks_from_output(output: &str, log_path: &Path) -> io::Result<()> {
let mut capture = BacktraceBlockCapture::create(Some(log_path), None)?;
capture.push_bytes(output.as_bytes())?;
capture.finish()
}