#![cfg(all(target_os = "windows", feature = "windows-debug"))]
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Notify;
use crate::error::GcsResult;
use crate::transport::{HvSockListener, WINDOWS_LOGGING_HVSOCK_SERVICE_ID};
const READ_CHUNK: usize = 64 * 1024;
pub struct LogForwardListener {
listener: HvSockListener,
sink_path: PathBuf,
stop: Arc<Notify>,
}
impl LogForwardListener {
pub async fn bind(uvm_runtime_id: windows::core::GUID, debug_dir: &Path) -> GcsResult<Self> {
let listener =
HvSockListener::bind(uvm_runtime_id, WINDOWS_LOGGING_HVSOCK_SERVICE_ID).await?;
Ok(Self {
listener,
sink_path: debug_dir.join("gcs-forward.log"),
stop: Arc::new(Notify::new()),
})
}
pub fn spawn(&self) {
let listener = self.listener.clone();
let sink_path = self.sink_path.clone();
let stop = Arc::clone(&self.stop);
tokio::spawn(async move {
eprintln!(
"[t=+{}us] gcs-logfwd: listening on WindowsLoggingHvsockServiceID -> {}",
crate::diagnostics::ts_us(),
sink_path.display(),
);
loop {
tokio::select! {
() = stop.notified() => {
eprintln!(
"[t=+{}us] gcs-logfwd: stop signalled; exiting accept loop",
crate::diagnostics::ts_us(),
);
break;
}
accepted = listener.accept() => {
match accepted {
Ok(stream) => {
eprintln!(
"[t=+{}us] gcs-logfwd: guest log connection accepted",
crate::diagnostics::ts_us(),
);
loop {
tokio::select! {
() = stop.notified() => return,
chunk = stream.read_some(READ_CHUNK) => {
match chunk {
Ok(bytes) if bytes.is_empty() => break, Ok(bytes) => append_and_echo(&sink_path, &bytes),
Err(e) => {
eprintln!(
"[t=+{}us] gcs-logfwd: read error: {e}",
crate::diagnostics::ts_us(),
);
break;
}
}
}
}
}
}
Err(e) => {
eprintln!(
"[t=+{}us] gcs-logfwd: accept ended: {e}",
crate::diagnostics::ts_us(),
);
break;
}
}
}
}
}
});
}
pub fn stop(&self) {
self.stop.notify_waiters();
}
}
impl Drop for LogForwardListener {
fn drop(&mut self) {
self.stop.notify_waiters();
}
}
fn append_and_echo(sink_path: &Path, bytes: &[u8]) {
use std::io::Write;
let text = String::from_utf8_lossy(bytes);
eprintln!(
"[t=+{}us] gcs-logfwd: guest <<< {}",
crate::diagnostics::ts_us(),
text.trim_end(),
);
match std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(sink_path)
{
Ok(mut f) => {
if let Err(e) = f.write_all(bytes) {
eprintln!(
"[t=+{}us] gcs-logfwd: sink write failed: {e}",
crate::diagnostics::ts_us(),
);
}
}
Err(e) => {
eprintln!(
"[t=+{}us] gcs-logfwd: sink open failed ({}): {e}",
crate::diagnostics::ts_us(),
sink_path.display(),
);
}
}
}