diff --git a/Cargo.lock b/Cargo.lock index a6efbbde80..38b83d1f0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5176,6 +5176,7 @@ dependencies = [ "fs-err", "futures", "futures-concurrency", + "guid", "inspect", "kmsg", "mesh", diff --git a/openhcl/diag_client/src/lib.rs b/openhcl/diag_client/src/lib.rs index afd2ec3dfb..a801bcbbaf 100644 --- a/openhcl/diag_client/src/lib.rs +++ b/openhcl/diag_client/src/lib.rs @@ -50,6 +50,8 @@ pub mod hyperv { pub enum ComPortAccessInfo<'a> { /// Access by number NameAndPortNumber(&'a str, u32), + /// Access by VM ID and port number + IdAndPortNumber(Guid, u32), /// Access through a named pipe PortPipePath(&'a str), } @@ -109,6 +111,32 @@ pub mod hyperv { Ok(socket.convert().into_inner()) } + fn query_vm_com_port(port: ComPortAccessInfo<'_>) -> anyhow::Result { + let script = match port { + ComPortAccessInfo::NameAndPortNumber(vm, num) => { + format!(r#"$x = Get-VMComPort "{vm}" -Number {num} -ErrorAction Stop; $x.Path"#) + } + ComPortAccessInfo::IdAndPortNumber(id, num) => { + format!( + r#"$x = Get-VMComPort -VMId "{id}" -Number {num} -ErrorAction Stop; $x.Path"# + ) + } + ComPortAccessInfo::PortPipePath(path) => return Ok(path.to_owned()), + }; + + let output = Command::new("powershell.exe") + .arg("-NoProfile") + .arg(&script) + .output() + .context("failed to query VM com port")?; + + if !output.status.success() { + let _ = std::io::stderr().write_all(&output.stderr); + anyhow::bail!("failed to query VM com port: exit status {}", output.status); + } + Ok(String::from_utf8(output.stdout)?) + } + /// Opens a serial port on a Hyper-V VM. /// /// If the VM is not running, it will periodically try to connect to the @@ -121,27 +149,7 @@ pub mod hyperv { driver: &(impl Driver + ?Sized), port: ComPortAccessInfo<'_>, ) -> anyhow::Result { - let path = match port { - ComPortAccessInfo::NameAndPortNumber(vm, num) => { - let output = Command::new("powershell.exe") - .arg("-NoProfile") - .arg(format!( - r#"$x = Get-VMComPort "{vm}" -Number {num} -ErrorAction Stop; $x.Path"#, - )) - .output() - .context("failed to query VM com port")?; - - if !output.status.success() { - let _ = std::io::stderr().write_all(&output.stderr); - anyhow::bail!( - "failed to query VM com port: exit status {}", - output.status.code().unwrap() - ); - } - &String::from_utf8(output.stdout)? - } - ComPortAccessInfo::PortPipePath(path) => path, - }; + let path = query_vm_com_port(port)?; let path = path.trim(); if path.is_empty() { diff --git a/openhcl/ohcldiag-dev/Cargo.toml b/openhcl/ohcldiag-dev/Cargo.toml index d7b4f0f4db..f408653adf 100644 --- a/openhcl/ohcldiag-dev/Cargo.toml +++ b/openhcl/ohcldiag-dev/Cargo.toml @@ -27,6 +27,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } unicycle.workspace = true [target.'cfg(windows)'.dependencies] +guid.workspace = true pal.workspace = true socket2.workspace = true diff --git a/openhcl/ohcldiag-dev/src/main.rs b/openhcl/ohcldiag-dev/src/main.rs index 111007e2eb..f0fbfc8f6c 100644 --- a/openhcl/ohcldiag-dev/src/main.rs +++ b/openhcl/ohcldiag-dev/src/main.rs @@ -331,7 +331,13 @@ pub struct VmArg { )] #[cfg_attr( windows, - doc = "* NAME_OR_PATH - Either a Hyper-V VM name, or a path as in vsock:PATH>" + doc = "* hyperv:GUID - A Hyper-V VM ID (with or without braces) + + " + )] + #[cfg_attr( + windows, + doc = "* NAME_OR_GUID_OR_PATH - Either a Hyper-V VM name or GUID, or a path as in vsock:PATH" )] #[cfg_attr(not(windows), doc = "* PATH - A path as in vsock:PATH")] #[clap(name = "VM")] @@ -342,6 +348,8 @@ pub struct VmArg { enum VmId { #[cfg(windows)] HyperV(String), + #[cfg(windows)] + HyperVId(guid::Guid), HybridVsock(PathBuf), } @@ -353,10 +361,21 @@ impl FromStr for VmId { Ok(Self::HybridVsock(Path::new(s).to_owned())) } else { #[cfg(windows)] - if let Some(s) = s.strip_prefix("hyperv:") { - return Ok(Self::HyperV(s.to_owned())); - } else if !pal::windows::fs::is_unix_socket(s.as_ref()).unwrap_or(false) { - return Ok(Self::HyperV(s.to_owned())); + { + let (value, had_prefix) = match s.strip_prefix("hyperv:") { + Some(rest) => (rest, true), + None => (s, false), + }; + + // Try parsing as a GUID first (with or without braces). + if let Ok(guid) = value.parse::() { + return Ok(Self::HyperVId(guid)); + } + + if had_prefix || !pal::windows::fs::is_unix_socket(value.as_ref()).unwrap_or(false) + { + return Ok(Self::HyperV(value.to_owned())); + } } // Default to hybrid vsock since this is what OpenVMM supports for // Underhill. @@ -475,6 +494,8 @@ fn new_client(driver: impl Driver + Spawn + Clone, input: &VmArg) -> anyhow::Res let client = match &input.id { #[cfg(windows)] VmId::HyperV(name) => DiagClient::from_hyperv_name(driver, name)?, + #[cfg(windows)] + VmId::HyperVId(guid) => DiagClient::from_hyperv_id(driver, *guid), VmId::HybridVsock(path) => DiagClient::from_hybrid_vsock(driver, path), }; Ok(client) @@ -655,15 +676,15 @@ pub fn main() -> anyhow::Result<()> { use diag_client::hyperv::ComPortAccessInfo; use futures::AsyncBufReadExt; - let vm_name = match &vm.id { - VmId::HyperV(name) => name, - _ => anyhow::bail!("--serial is only supported for Hyper-V VMs"), - }; - let port_access_info = if let Some(pipe_path) = pipe_path.as_ref() { ComPortAccessInfo::PortPipePath(pipe_path) } else { - ComPortAccessInfo::NameAndPortNumber(vm_name, 3) + match &vm.id { + VmId::HyperV(name) => ComPortAccessInfo::NameAndPortNumber(name, 3), + #[cfg(windows)] + VmId::HyperVId(guid) => ComPortAccessInfo::IdAndPortNumber(*guid, 3), + _ => anyhow::bail!("--serial is only supported for Hyper-V VMs"), + } }; let pipe = @@ -762,6 +783,12 @@ pub fn main() -> anyhow::Result<()> { diag_client::hyperv::connect_vsock(&driver, vm_id, port).await?; PolledSocket::new(&driver, socket2::Socket::from(stream))? } + #[cfg(windows)] + VmId::HyperVId(vm_id) => { + let stream = + diag_client::hyperv::connect_vsock(&driver, vm_id, port).await?; + PolledSocket::new(&driver, socket2::Socket::from(stream))? + } }; let vsock = Arc::new(vsock.into_inner()); @@ -906,6 +933,13 @@ pub fn main() -> anyhow::Result<()> { .await?; PolledSocket::new(&driver, socket2::Socket::from(stream))? } + #[cfg(windows)] + VmId::HyperVId(ref vm_id) => { + let stream = + diag_client::hyperv::connect_vsock(&driver, *vm_id, vsock_port) + .await?; + PolledSocket::new(&driver, socket2::Socket::from(stream))? + } }; println!("VSOCK connect to port {:?}", vsock_port);