mirror of
https://github.com/aphrody-labs/WSL.git
synced 2026-10-21 14:44:55 +00:00
Add more robust RPC handling + tests
This commit is contained in:
1 parent
eb59238333
commit
ff9c777705
8 files changed
+1449
-275
No files matched your search
@@ -26,7 +26,7 @@ add_custom_command(
|
||||
OUTPUT ${WSLOPENVMM_DLL}
|
||||
COMMAND ${CMAKE_COMMAND} -E env "VM_SERVICE_PROTO=${VM_SERVICE_PROTO}" ${CARGO_EXECUTABLE} build --manifest-path ${CMAKE_CURRENT_LIST_DIR}/Cargo.toml --target ${RUST_TARGET} --target-dir ${CARGO_TARGET_DIR} ${CARGO_BUILD_ARGUMENTS}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CARGO_OUTPUT} ${WSLOPENVMM_DLL}
|
||||
DEPENDS ${VM_SERVICE_PROTO} Cargo.lock Cargo.toml build.rs src/client.rs src/lib.rs src/af_unix.rs wslopenvmm.h
|
||||
DEPENDS ${VM_SERVICE_PROTO} Cargo.lock Cargo.toml build.rs src/client.rs src/lib.rs src/af_unix.rs src/rpc.rs wslopenvmm.h
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
VERBATIM)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ prost = "0.14"
|
||||
prost-types = "0.14"
|
||||
tonic = "0.14"
|
||||
tonic-prost = "0.14"
|
||||
tokio = { version = "1", features = ["net", "rt", "time"] }
|
||||
tokio = { version = "1", features = ["net", "rt", "time", "macros", "sync"] }
|
||||
tower = "0.5"
|
||||
windows = { version = "0.62", features = ["Win32_Foundation"] }
|
||||
windows-sys = { version = "0.61", features = ["Win32_Networking_WinSock"] }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
@@ -14,14 +15,33 @@ use tower::service_fn;
|
||||
pub async fn connect_channel(
|
||||
socket_path: PathBuf,
|
||||
timeout: Duration,
|
||||
) -> Result<Channel, tonic::transport::Error> {
|
||||
Endpoint::from_static("http://[::]:50051")
|
||||
.connect_timeout(timeout)
|
||||
.connect_with_connector(service_fn(move |_| {
|
||||
let socket_path = socket_path.clone();
|
||||
async move { open_socket(&socket_path, timeout).await.map(TokioIo::new) }
|
||||
}))
|
||||
) -> Result<Channel, io::Error> {
|
||||
let endpoint = Endpoint::from_static("http://[::]:50051").connect_timeout(timeout);
|
||||
let connection = endpoint.connect_with_connector(service_fn(move |_| {
|
||||
let socket_path = socket_path.clone();
|
||||
async move { open_socket(&socket_path, timeout).await.map(TokioIo::new) }
|
||||
}));
|
||||
tokio::time::timeout(timeout, connection)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"OpenVMM connection deadline expired",
|
||||
)
|
||||
})?
|
||||
.map_err(|error| {
|
||||
let mut source = error.source();
|
||||
while let Some(cause) = source {
|
||||
if let Some(error) = cause.downcast_ref::<io::Error>() {
|
||||
return match error.raw_os_error() {
|
||||
Some(code) => io::Error::from_raw_os_error(code),
|
||||
None => io::Error::new(error.kind(), error.to_string()),
|
||||
};
|
||||
}
|
||||
source = cause.source();
|
||||
}
|
||||
io::Error::new(io::ErrorKind::ConnectionAborted, error)
|
||||
})
|
||||
}
|
||||
|
||||
async fn open_socket(path: &Path, timeout: Duration) -> io::Result<TcpStream> {
|
||||
@@ -62,4 +82,4 @@ async fn connect_socket(address: &SockAddr) -> io::Result<TcpStream> {
|
||||
return Err(error);
|
||||
}
|
||||
Ok(stream)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,101 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
use std::future::Future;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::sync::watch;
|
||||
use tonic::Request;
|
||||
use vmservice::{
|
||||
ConsommeBackend, CreateVmRequest, DirectBoot, DiskType, ModifyResourceRequest, ModifyType,
|
||||
NicConfig, PortConfig, ScsiDisk, VirtioConsoleConfig, VmConfig, vm_client::VmClient,
|
||||
};
|
||||
use windows::Win32::Foundation::{E_FAIL, E_INVALIDARG, ERROR_ALREADY_EXISTS, ERROR_CONNECTION_ABORTED, ERROR_NOT_FOUND, S_OK, WAIT_TIMEOUT};
|
||||
use windows::Win32::Foundation::{
|
||||
E_FAIL, E_INVALIDARG, ERROR_ALREADY_EXISTS, ERROR_NOT_FOUND, S_OK,
|
||||
};
|
||||
use windows::core::{GUID, HRESULT};
|
||||
use windows_sys::Win32::Networking::WinSock::{AF_INET, AF_INET6};
|
||||
|
||||
use crate::{af_unix, vmservice};
|
||||
use crate::{af_unix, rpc, vmservice};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum VmConfigStatus {
|
||||
Ready,
|
||||
CreationOutcomeUnknown,
|
||||
}
|
||||
|
||||
pub struct VmConfigBuilder {
|
||||
inner: VmConfig,
|
||||
config: VmConfig,
|
||||
status: VmConfigStatus,
|
||||
}
|
||||
|
||||
pub struct VmConfigHandle {
|
||||
builder: Mutex<VmConfigBuilder>,
|
||||
}
|
||||
|
||||
impl VmConfigHandle {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
builder: Mutex::new(VmConfigBuilder::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self, operation: impl FnOnce(&mut VmConfigBuilder) -> HRESULT) -> HRESULT {
|
||||
operation(&mut self.builder.lock())
|
||||
}
|
||||
|
||||
pub fn create_vm(&self, socket_path: String, timeout_ms: u32) -> Result<VmHandle, HRESULT> {
|
||||
if timeout_ms == 0 {
|
||||
return Err(E_INVALIDARG);
|
||||
}
|
||||
let timeout = Duration::from_millis(u64::from(timeout_ms));
|
||||
let deadline = Instant::now() + timeout;
|
||||
let mut builder = self.builder.try_lock_until(deadline).ok_or(rpc::TIMEOUT)?;
|
||||
builder.create_vm(socket_path, timeout, deadline)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VmHandle {
|
||||
inner: VmHandleInner,
|
||||
inner: Mutex<VmHandleInner>,
|
||||
timeout: Duration,
|
||||
cancellation: watch::Sender<bool>,
|
||||
}
|
||||
|
||||
struct VmHandleInner {
|
||||
runtime: Runtime,
|
||||
client: VmClient<tonic::transport::Channel>,
|
||||
timeout: Duration,
|
||||
shares: BTreeMap<String, String>,
|
||||
status: VmStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum VmStatus {
|
||||
Active,
|
||||
RecoveryRequired,
|
||||
}
|
||||
|
||||
impl VmConfigBuilder {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: VmConfig::default(),
|
||||
config: VmConfig::default(),
|
||||
status: VmConfigStatus::Ready,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_vm(&self, socket_path: String, timeout_ms: u32) -> Result<VmHandle, HRESULT> {
|
||||
fn create_vm(
|
||||
&mut self,
|
||||
socket_path: String,
|
||||
timeout: Duration,
|
||||
deadline: Instant,
|
||||
) -> Result<VmHandle, HRESULT> {
|
||||
if self.status == VmConfigStatus::CreationOutcomeUnknown {
|
||||
return Err(rpc::INVALID_STATE);
|
||||
}
|
||||
let runtime = match tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
@@ -48,33 +103,36 @@ impl VmConfigBuilder {
|
||||
Ok(runtime) => runtime,
|
||||
Err(_) => return Err(E_FAIL),
|
||||
};
|
||||
let timeout = Duration::from_millis(u64::from(timeout_ms));
|
||||
let channel = match runtime.block_on(af_unix::connect_channel(socket_path.into(), timeout)) {
|
||||
Ok(channel) => channel,
|
||||
Err(error) if error.to_string().contains("timed out") => {
|
||||
return Err(HRESULT::from_win32(WAIT_TIMEOUT.0));
|
||||
}
|
||||
Err(_) => return Err(HRESULT::from_win32(ERROR_CONNECTION_ABORTED.0)),
|
||||
};
|
||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||
if remaining.is_zero() {
|
||||
return Err(rpc::TIMEOUT);
|
||||
}
|
||||
let channel =
|
||||
match runtime.block_on(af_unix::connect_channel(socket_path.into(), remaining)) {
|
||||
Ok(channel) => channel,
|
||||
Err(error) => return Err(rpc::io_error_to_hresult(&error)),
|
||||
};
|
||||
let mut client = VmClient::new(channel);
|
||||
let request = CreateVmRequest {
|
||||
config: Some(self.inner.clone()),
|
||||
config: Some(self.config.clone()),
|
||||
log_id: String::new(),
|
||||
};
|
||||
match runtime.block_on(async {
|
||||
let mut request = Request::new(request);
|
||||
request.set_timeout(timeout);
|
||||
client.create_vm(request).await
|
||||
}) {
|
||||
Ok(_) => Ok(VmHandle {
|
||||
inner: VmHandleInner {
|
||||
runtime,
|
||||
client,
|
||||
timeout,
|
||||
shares: BTreeMap::new(),
|
||||
},
|
||||
}),
|
||||
Err(error) => Err(rpc_status_to_hresult(error)),
|
||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||
if remaining.is_zero() {
|
||||
return Err(rpc::TIMEOUT);
|
||||
}
|
||||
match runtime.block_on(rpc::execute(
|
||||
deadline,
|
||||
None,
|
||||
client.create_vm(request_with_timeout(request, remaining)),
|
||||
)) {
|
||||
Ok(_) => Ok(VmHandle::new(runtime, client, timeout)),
|
||||
Err(error) => {
|
||||
if error.uncertain {
|
||||
self.status = VmConfigStatus::CreationOutcomeUnknown;
|
||||
}
|
||||
Err(error.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +152,12 @@ impl VmConfigBuilder {
|
||||
}
|
||||
|
||||
pub fn set_memory_mb(&mut self, memory_mb: u64) -> HRESULT {
|
||||
self.inner.memory_config.get_or_insert_default().memory_mb = memory_mb;
|
||||
self.config.memory_config.get_or_insert_default().memory_mb = memory_mb;
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub fn set_processor_count(&mut self, count: u32) -> HRESULT {
|
||||
self.inner
|
||||
self.config
|
||||
.processor_config
|
||||
.get_or_insert_default()
|
||||
.processor_count = count;
|
||||
@@ -107,7 +165,7 @@ impl VmConfigBuilder {
|
||||
}
|
||||
|
||||
pub fn set_hvsocket_path(&mut self, path: String) -> HRESULT {
|
||||
self.inner.hvsocket_config.get_or_insert_default().path = path;
|
||||
self.config.hvsocket_config.get_or_insert_default().path = path;
|
||||
S_OK
|
||||
}
|
||||
|
||||
@@ -118,7 +176,7 @@ impl VmConfigBuilder {
|
||||
host_path: String,
|
||||
read_only: bool,
|
||||
) -> HRESULT {
|
||||
self.inner
|
||||
self.config
|
||||
.devices_config
|
||||
.get_or_insert_default()
|
||||
.scsi_disks
|
||||
@@ -133,7 +191,7 @@ impl VmConfigBuilder {
|
||||
}
|
||||
|
||||
pub fn set_consomme_nic(&mut self, nic_id: String, mac_address: String) -> HRESULT {
|
||||
self.inner
|
||||
self.config
|
||||
.devices_config
|
||||
.get_or_insert_default()
|
||||
.nic_config
|
||||
@@ -149,18 +207,20 @@ impl VmConfigBuilder {
|
||||
}
|
||||
|
||||
pub fn add_serial_port(&mut self, port: u32, pipe_name: String) -> HRESULT {
|
||||
self.inner.serial_config.get_or_insert_default().ports.push(
|
||||
vmservice::serial_config::Config {
|
||||
self.config
|
||||
.serial_config
|
||||
.get_or_insert_default()
|
||||
.ports
|
||||
.push(vmservice::serial_config::Config {
|
||||
port,
|
||||
socket_path: pipe_name,
|
||||
connect: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub fn set_virtio_console_path(&mut self, path: String) -> HRESULT {
|
||||
self.inner
|
||||
self.config
|
||||
.devices_config
|
||||
.get_or_insert_default()
|
||||
.virtio_console = Some(VirtioConsoleConfig {
|
||||
@@ -171,7 +231,7 @@ impl VmConfigBuilder {
|
||||
}
|
||||
|
||||
fn direct_boot(&mut self) -> &mut DirectBoot {
|
||||
let boot_config = self.inner.boot_config.get_or_insert_with(|| {
|
||||
let boot_config = self.config.boot_config.get_or_insert_with(|| {
|
||||
vmservice::vm_config::BootConfig::DirectBoot(DirectBoot::default())
|
||||
});
|
||||
match boot_config {
|
||||
@@ -182,24 +242,99 @@ impl VmConfigBuilder {
|
||||
}
|
||||
|
||||
impl VmHandle {
|
||||
pub fn resume_vm(&mut self) -> HRESULT {
|
||||
self.empty_rpc(|client, request| Box::pin(async move { client.resume_vm(request).await }))
|
||||
fn new(
|
||||
runtime: Runtime,
|
||||
client: VmClient<tonic::transport::Channel>,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(VmHandleInner {
|
||||
runtime,
|
||||
client,
|
||||
shares: BTreeMap::new(),
|
||||
status: VmStatus::Active,
|
||||
}),
|
||||
timeout,
|
||||
cancellation: watch::channel(false).0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn teardown_vm(&mut self) -> HRESULT {
|
||||
let result = self.empty_rpc(|client, request| Box::pin(async move { client.teardown_vm(request).await }));
|
||||
if result.is_ok() {
|
||||
self.inner.shares.clear();
|
||||
pub fn cancel_requests(&self) -> HRESULT {
|
||||
self.cancellation.send_replace(true);
|
||||
S_OK
|
||||
}
|
||||
|
||||
fn with_operation(
|
||||
&self,
|
||||
cleanup: bool,
|
||||
operation: impl FnOnce(&mut VmHandleInner, Instant, Option<watch::Receiver<bool>>) -> HRESULT,
|
||||
) -> HRESULT {
|
||||
let deadline = Instant::now() + self.timeout;
|
||||
let Some(mut inner) = self.inner.try_lock_until(deadline) else {
|
||||
// No request was sent, so lock contention alone does not invalidate the VM.
|
||||
return rpc::TIMEOUT;
|
||||
};
|
||||
if *self.cancellation.borrow() {
|
||||
inner.status = VmStatus::RecoveryRequired;
|
||||
}
|
||||
if !cleanup && inner.status == VmStatus::RecoveryRequired {
|
||||
return rpc::INVALID_STATE;
|
||||
}
|
||||
let cancellation = if cleanup {
|
||||
None
|
||||
} else {
|
||||
Some(self.cancellation.subscribe())
|
||||
};
|
||||
let result = operation(&mut inner, deadline, cancellation);
|
||||
// Cancellation can race a successful response, including during cleanup.
|
||||
if *self.cancellation.borrow() {
|
||||
inner.status = VmStatus::RecoveryRequired;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) -> HRESULT {
|
||||
self.empty_rpc(|client, request| Box::pin(async move { client.quit(request).await }))
|
||||
pub fn resume_vm(&self) -> HRESULT {
|
||||
self.with_operation(false, |inner, deadline, cancellation| {
|
||||
self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
(),
|
||||
|mut client, request| async move { client.resume_vm(request).await },
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn teardown_vm(&self) -> HRESULT {
|
||||
self.with_operation(true, |inner, deadline, cancellation| {
|
||||
let result = self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
(),
|
||||
|mut client, request| async move { client.teardown_vm(request).await },
|
||||
);
|
||||
if result.is_ok() {
|
||||
inner.shares.clear();
|
||||
}
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn quit(&self) -> HRESULT {
|
||||
self.with_operation(true, |inner, deadline, cancellation| {
|
||||
self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
(),
|
||||
|mut client, request| async move { client.quit(request).await },
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attach_scsi_disk(
|
||||
&mut self,
|
||||
&self,
|
||||
controller: u32,
|
||||
lun: u32,
|
||||
host_path: String,
|
||||
@@ -208,155 +343,187 @@ impl VmHandle {
|
||||
self.modify_disk(ModifyType::Add, controller, lun, host_path, read_only)
|
||||
}
|
||||
|
||||
pub fn detach_scsi_disk(&mut self, controller: u32, lun: u32) -> HRESULT {
|
||||
pub fn detach_scsi_disk(&self, controller: u32, lun: u32) -> HRESULT {
|
||||
self.modify_disk(ModifyType::Remove, controller, lun, String::new(), false)
|
||||
}
|
||||
|
||||
pub fn bind_port(&mut self, host_port: u16, guest_port: u16, tcp: bool, family: i32) -> HRESULT {
|
||||
pub fn bind_port(&self, host_port: u16, guest_port: u16, tcp: bool, family: i32) -> HRESULT {
|
||||
self.modify_port(ModifyType::Update, host_port, guest_port, tcp, family)
|
||||
}
|
||||
|
||||
pub fn unbind_port(&mut self, host_port: u16, guest_port: u16, tcp: bool, family: i32) -> HRESULT {
|
||||
pub fn unbind_port(&self, host_port: u16, guest_port: u16, tcp: bool, family: i32) -> HRESULT {
|
||||
self.modify_port(ModifyType::Remove, host_port, guest_port, tcp, family)
|
||||
}
|
||||
|
||||
pub fn add_share(&mut self, tag: String, host_path: String, read_only: bool) -> HRESULT {
|
||||
if self.inner.shares.contains_key(&tag) {
|
||||
return HRESULT::from_win32(ERROR_ALREADY_EXISTS.0);
|
||||
}
|
||||
let instance_id = match GUID::new() {
|
||||
Ok(instance_id) => format!("{instance_id:?}"),
|
||||
Err(error) => return error.code(),
|
||||
};
|
||||
let request = vmservice::AddVpciDeviceRequest {
|
||||
instance_id: instance_id.clone(),
|
||||
device: Some(vmservice::PcieDeviceKind {
|
||||
kind: Some(vmservice::pcie_device_kind::Kind::Virtio(vmservice::VirtioDevice {
|
||||
kind: Some(vmservice::virtio_device::Kind::Fs(vmservice::VirtioFs {
|
||||
tag: tag.clone(),
|
||||
root_path: host_path,
|
||||
read_only,
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
};
|
||||
match self.inner.runtime.block_on(self.inner.client.add_vpci_device(
|
||||
request_with_timeout(request, self.inner.timeout),
|
||||
)) {
|
||||
Ok(_) => {
|
||||
self.inner.shares.insert(tag, instance_id);
|
||||
S_OK
|
||||
pub fn add_share(&self, tag: String, host_path: String, read_only: bool) -> HRESULT {
|
||||
self.with_operation(false, |inner, deadline, cancellation| {
|
||||
if inner.shares.contains_key(&tag) {
|
||||
return HRESULT::from_win32(ERROR_ALREADY_EXISTS.0);
|
||||
}
|
||||
Err(error) => rpc_status_to_hresult(error),
|
||||
}
|
||||
let instance_id = match GUID::new() {
|
||||
Ok(instance_id) => format!("{instance_id:?}"),
|
||||
Err(error) => return error.code(),
|
||||
};
|
||||
let request = vmservice::AddVpciDeviceRequest {
|
||||
instance_id: instance_id.clone(),
|
||||
device: Some(vmservice::PcieDeviceKind {
|
||||
kind: Some(vmservice::pcie_device_kind::Kind::Virtio(
|
||||
vmservice::VirtioDevice {
|
||||
kind: Some(vmservice::virtio_device::Kind::Fs(vmservice::VirtioFs {
|
||||
tag: tag.clone(),
|
||||
root_path: host_path,
|
||||
read_only,
|
||||
})),
|
||||
},
|
||||
)),
|
||||
}),
|
||||
};
|
||||
let result = self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
request,
|
||||
|mut client, request| async move { client.add_vpci_device(request).await },
|
||||
);
|
||||
if result.is_ok() {
|
||||
inner.shares.insert(tag, instance_id);
|
||||
}
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_share(&mut self, tag: &str) -> HRESULT {
|
||||
let Some(instance_id) = self.inner.shares.get(tag) else {
|
||||
return HRESULT::from_win32(ERROR_NOT_FOUND.0);
|
||||
};
|
||||
let request = vmservice::RemoveVpciDeviceRequest {
|
||||
instance_id: instance_id.clone(),
|
||||
};
|
||||
match self.inner.runtime.block_on(self.inner.client.remove_vpci_device(
|
||||
request_with_timeout(request, self.inner.timeout),
|
||||
)) {
|
||||
Ok(_) => {
|
||||
self.inner.shares.remove(tag);
|
||||
S_OK
|
||||
pub fn remove_share(&self, tag: &str) -> HRESULT {
|
||||
self.with_operation(false, |inner, deadline, cancellation| {
|
||||
let Some(instance_id) = inner.shares.get(tag) else {
|
||||
return HRESULT::from_win32(ERROR_NOT_FOUND.0);
|
||||
};
|
||||
let request = vmservice::RemoveVpciDeviceRequest {
|
||||
instance_id: instance_id.clone(),
|
||||
};
|
||||
let result = self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
request,
|
||||
|mut client, request| async move { client.remove_vpci_device(request).await },
|
||||
);
|
||||
if result.is_ok() {
|
||||
inner.shares.remove(tag);
|
||||
}
|
||||
Err(error) => rpc_status_to_hresult(error),
|
||||
}
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn empty_rpc<F>(&mut self, operation: F) -> HRESULT
|
||||
fn rpc<T, F, R>(
|
||||
&self,
|
||||
inner: &mut VmHandleInner,
|
||||
deadline: Instant,
|
||||
cancellation: Option<watch::Receiver<bool>>,
|
||||
message: T,
|
||||
operation: F,
|
||||
) -> HRESULT
|
||||
where
|
||||
F: for<'a> FnOnce(
|
||||
&'a mut VmClient<tonic::transport::Channel>,
|
||||
Request<()>,
|
||||
) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = Result<tonic::Response<()>, tonic::Status>> + 'a>,
|
||||
>,
|
||||
F: FnOnce(VmClient<tonic::transport::Channel>, Request<T>) -> R,
|
||||
R: Future<Output = Result<tonic::Response<()>, tonic::Status>>,
|
||||
{
|
||||
match self.inner.runtime.block_on(operation(
|
||||
&mut self.inner.client,
|
||||
request_with_timeout((), self.inner.timeout),
|
||||
)) {
|
||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||
if remaining.is_zero() {
|
||||
return rpc::TIMEOUT;
|
||||
}
|
||||
let result = inner.runtime.block_on(rpc::execute(
|
||||
deadline,
|
||||
cancellation,
|
||||
operation(
|
||||
inner.client.clone(),
|
||||
request_with_timeout(message, remaining),
|
||||
),
|
||||
));
|
||||
match result {
|
||||
Ok(_) => S_OK,
|
||||
Err(error) => rpc_status_to_hresult(error),
|
||||
Err(error) => {
|
||||
if error.uncertain {
|
||||
inner.status = VmStatus::RecoveryRequired;
|
||||
}
|
||||
error.result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_disk(
|
||||
&mut self,
|
||||
&self,
|
||||
modify_type: ModifyType,
|
||||
controller: u32,
|
||||
lun: u32,
|
||||
host_path: String,
|
||||
read_only: bool,
|
||||
) -> HRESULT {
|
||||
let request = ModifyResourceRequest {
|
||||
r#type: modify_type as i32,
|
||||
resource: Some(vmservice::modify_resource_request::Resource::ScsiDisk(
|
||||
ScsiDisk {
|
||||
controller,
|
||||
lun,
|
||||
r#type: disk_type(&host_path),
|
||||
host_path,
|
||||
read_only,
|
||||
},
|
||||
)),
|
||||
};
|
||||
self.modify_resource(request)
|
||||
self.with_operation(false, |inner, deadline, cancellation| {
|
||||
let request = ModifyResourceRequest {
|
||||
r#type: modify_type as i32,
|
||||
resource: Some(vmservice::modify_resource_request::Resource::ScsiDisk(
|
||||
ScsiDisk {
|
||||
controller,
|
||||
lun,
|
||||
r#type: disk_type(&host_path),
|
||||
host_path,
|
||||
read_only,
|
||||
},
|
||||
)),
|
||||
};
|
||||
self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
request,
|
||||
|mut client, request| async move { client.modify_resource(request).await },
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn modify_port(
|
||||
&mut self,
|
||||
&self,
|
||||
modify_type: ModifyType,
|
||||
host_port: u16,
|
||||
guest_port: u16,
|
||||
tcp: bool,
|
||||
family: i32,
|
||||
) -> HRESULT {
|
||||
let host_address = match family {
|
||||
family if family == i32::from(AF_INET) => "127.0.0.1",
|
||||
family if family == i32::from(AF_INET6) => "::1",
|
||||
_ => return E_INVALIDARG,
|
||||
};
|
||||
let protocol = if tcp {
|
||||
vmservice::IpProtocol::Tcp
|
||||
} else {
|
||||
vmservice::IpProtocol::Udp
|
||||
};
|
||||
let request = ModifyResourceRequest {
|
||||
r#type: modify_type as i32,
|
||||
resource: Some(vmservice::modify_resource_request::Resource::NicConfig(
|
||||
NicConfig {
|
||||
backend: Some(vmservice::nic_config::Backend::Consomme(ConsommeBackend {
|
||||
cidr: String::new(),
|
||||
ports: vec![PortConfig {
|
||||
host_port: u32::from(host_port),
|
||||
guest_port: u32::from(guest_port),
|
||||
protocol: protocol as i32,
|
||||
host_address: host_address.to_string(),
|
||||
}],
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
};
|
||||
self.modify_resource(request)
|
||||
}
|
||||
|
||||
fn modify_resource(&mut self, request: ModifyResourceRequest) -> HRESULT {
|
||||
match self.inner.runtime.block_on(
|
||||
self.inner
|
||||
.client
|
||||
.modify_resource(request_with_timeout(request, self.inner.timeout)),
|
||||
) {
|
||||
Ok(_) => S_OK,
|
||||
Err(error) => rpc_status_to_hresult(error),
|
||||
}
|
||||
self.with_operation(false, |inner, deadline, cancellation| {
|
||||
let host_address = match family {
|
||||
family if family == i32::from(AF_INET) => "127.0.0.1",
|
||||
family if family == i32::from(AF_INET6) => "::1",
|
||||
_ => return E_INVALIDARG,
|
||||
};
|
||||
let protocol = if tcp {
|
||||
vmservice::IpProtocol::Tcp
|
||||
} else {
|
||||
vmservice::IpProtocol::Udp
|
||||
};
|
||||
let request = ModifyResourceRequest {
|
||||
r#type: modify_type as i32,
|
||||
resource: Some(vmservice::modify_resource_request::Resource::NicConfig(
|
||||
NicConfig {
|
||||
backend: Some(vmservice::nic_config::Backend::Consomme(ConsommeBackend {
|
||||
cidr: String::new(),
|
||||
ports: vec![PortConfig {
|
||||
host_port: u32::from(host_port),
|
||||
guest_port: u32::from(guest_port),
|
||||
protocol: protocol as i32,
|
||||
host_address: host_address.to_string(),
|
||||
}],
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
};
|
||||
self.rpc(
|
||||
inner,
|
||||
deadline,
|
||||
cancellation,
|
||||
request,
|
||||
|mut client, request| async move { client.modify_resource(request).await },
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,11 +540,3 @@ fn disk_type(path: &str) -> i32 {
|
||||
DiskType::ScsiDiskTypeVhd1 as i32
|
||||
}
|
||||
}
|
||||
|
||||
fn rpc_status_to_hresult(error: tonic::Status) -> HRESULT {
|
||||
if error.code() == tonic::Code::DeadlineExceeded {
|
||||
HRESULT::from_win32(WAIT_TIMEOUT.0)
|
||||
} else {
|
||||
HRESULT::from_win32(ERROR_CONNECTION_ABORTED.0)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,20 +2,26 @@
|
||||
|
||||
#![expect(clippy::missing_safety_doc)]
|
||||
|
||||
mod af_unix;
|
||||
mod client;
|
||||
pub mod af_unix;
|
||||
mod rpc;
|
||||
|
||||
pub mod vmservice {
|
||||
mod vmservice {
|
||||
tonic::include_proto!("vmservice");
|
||||
}
|
||||
|
||||
use client::{VmConfigBuilder, VmHandle};
|
||||
use parking_lot::Mutex;
|
||||
use client::{VmConfigBuilder, VmConfigHandle, VmHandle};
|
||||
use windows::Win32::Foundation::{E_INVALIDARG, E_POINTER, S_OK};
|
||||
use windows::core::{HRESULT, PCWSTR};
|
||||
|
||||
pub struct WslOpenVmmConfig(Mutex<VmConfigBuilder>);
|
||||
pub struct WslOpenVmmVm(Mutex<VmHandle>);
|
||||
pub struct WslOpenVmmConfig(VmConfigHandle);
|
||||
pub struct WslOpenVmmVm(VmHandle);
|
||||
|
||||
impl WslOpenVmmVm {
|
||||
fn new(client: VmHandle) -> Self {
|
||||
Self(client)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn string_from_wide(value: *const u16) -> Result<String, HRESULT> {
|
||||
if value.is_null() {
|
||||
@@ -32,17 +38,17 @@ unsafe fn with_config(
|
||||
let Some(config) = (unsafe { handle.as_ref() }) else {
|
||||
return E_POINTER;
|
||||
};
|
||||
operation(&mut config.0.lock())
|
||||
config.0.update(operation)
|
||||
}
|
||||
|
||||
unsafe fn with_vm(
|
||||
handle: *mut WslOpenVmmVm,
|
||||
operation: impl FnOnce(&mut VmHandle) -> HRESULT,
|
||||
operation: impl FnOnce(&VmHandle) -> HRESULT,
|
||||
) -> HRESULT {
|
||||
let Some(vm) = (unsafe { handle.as_ref() }) else {
|
||||
return E_POINTER;
|
||||
};
|
||||
operation(&mut vm.0.lock())
|
||||
operation(&vm.0)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -52,9 +58,7 @@ pub unsafe extern "C" fn WslOpenVmmCreateConfig(config: *mut *mut WslOpenVmmConf
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*config = Box::into_raw(Box::new(WslOpenVmmConfig(Mutex::new(
|
||||
VmConfigBuilder::new(),
|
||||
))));
|
||||
*config = Box::into_raw(Box::new(WslOpenVmmConfig(VmConfigHandle::new())));
|
||||
}
|
||||
S_OK.0
|
||||
}
|
||||
@@ -220,21 +224,17 @@ pub unsafe extern "C" fn WslOpenVmmCreateVm(
|
||||
Err(error) => return error.0,
|
||||
};
|
||||
|
||||
let config_box = unsafe { Box::from_raw(config_handle) };
|
||||
let result = config_box.0.lock().create_vm(socket_path, timeout_ms);
|
||||
let config_ref = unsafe { &*config_handle };
|
||||
let result = config_ref.0.create_vm(socket_path, timeout_ms);
|
||||
let client = match result {
|
||||
Ok(client) => client,
|
||||
Err(error) => {
|
||||
unsafe {
|
||||
*config = Box::into_raw(config_box);
|
||||
}
|
||||
return error.0;
|
||||
}
|
||||
Err(error) => return error.0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*config = std::ptr::null_mut();
|
||||
*vm = Box::into_raw(Box::new(WslOpenVmmVm(Mutex::new(client))));
|
||||
drop(Box::from_raw(config_handle));
|
||||
*vm = Box::into_raw(Box::new(WslOpenVmmVm::new(client)));
|
||||
}
|
||||
S_OK.0
|
||||
}
|
||||
@@ -246,6 +246,11 @@ pub unsafe extern "C" fn WslOpenVmmDestroyVm(vm: *mut WslOpenVmmVm) {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn WslOpenVmmVmCancelRequests(vm: *mut WslOpenVmmVm) -> i32 {
|
||||
unsafe { with_vm(vm, VmHandle::cancel_requests).0 }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn WslOpenVmmVmResume(vm: *mut WslOpenVmmVm) -> i32 {
|
||||
unsafe { with_vm(vm, VmHandle::resume_vm).0 }
|
||||
@@ -298,7 +303,12 @@ pub unsafe extern "C" fn WslOpenVmmVmBindPort(
|
||||
tcp: i32,
|
||||
family: i32,
|
||||
) -> i32 {
|
||||
unsafe { with_vm(vm, |vm| vm.bind_port(host_port, guest_port, tcp != 0, family)).0 }
|
||||
unsafe {
|
||||
with_vm(vm, |vm| {
|
||||
vm.bind_port(host_port, guest_port, tcp != 0, family)
|
||||
})
|
||||
.0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -309,7 +319,12 @@ pub unsafe extern "C" fn WslOpenVmmVmUnbindPort(
|
||||
tcp: i32,
|
||||
family: i32,
|
||||
) -> i32 {
|
||||
unsafe { with_vm(vm, |vm| vm.unbind_port(host_port, guest_port, tcp != 0, family)).0 }
|
||||
unsafe {
|
||||
with_vm(vm, |vm| {
|
||||
vm.unbind_port(host_port, guest_port, tcp != 0, family)
|
||||
})
|
||||
.0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -331,10 +346,7 @@ pub unsafe extern "C" fn WslOpenVmmVmAddShare(
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn WslOpenVmmVmRemoveShare(
|
||||
vm: *mut WslOpenVmmVm,
|
||||
tag: *const u16,
|
||||
) -> i32 {
|
||||
pub unsafe extern "C" fn WslOpenVmmVmRemoveShare(vm: *mut WslOpenVmmVm, tag: *const u16) -> i32 {
|
||||
let tag = match unsafe { string_from_wide(tag) } {
|
||||
Ok(tag) => tag,
|
||||
Err(error) => return error.0,
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
use std::error::Error;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::time::Instant;
|
||||
|
||||
use tokio::sync::watch;
|
||||
use tonic::Code;
|
||||
use windows::Win32::Foundation::{
|
||||
E_ABORT, E_ACCESSDENIED, E_FAIL, E_INVALIDARG, E_NOTIMPL, ERROR_ALREADY_EXISTS,
|
||||
ERROR_CONNECTION_ABORTED, ERROR_INVALID_DATA, ERROR_INVALID_STATE, ERROR_LOGON_FAILURE,
|
||||
ERROR_NOT_ENOUGH_MEMORY, ERROR_NOT_FOUND, ERROR_RETRY, WAIT_TIMEOUT,
|
||||
};
|
||||
use windows::core::HRESULT;
|
||||
|
||||
pub const INVALID_STATE: HRESULT = HRESULT::from_win32(ERROR_INVALID_STATE.0);
|
||||
pub const TIMEOUT: HRESULT = HRESULT::from_win32(WAIT_TIMEOUT.0);
|
||||
|
||||
pub struct RpcFailure {
|
||||
pub result: HRESULT,
|
||||
pub uncertain: bool,
|
||||
}
|
||||
|
||||
pub async fn execute<T>(
|
||||
deadline: Instant,
|
||||
cancellation: Option<watch::Receiver<bool>>,
|
||||
operation: impl Future<Output = Result<T, tonic::Status>>,
|
||||
) -> Result<T, RpcFailure> {
|
||||
// A closed sender also cancels the operation; no lifecycle owner is required here.
|
||||
let cancellation = async {
|
||||
if let Some(mut cancellation) = cancellation {
|
||||
let _ = cancellation.wait_for(|cancelled| *cancelled).await;
|
||||
} else {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
};
|
||||
// Prefer the local deadline when the peer's cancellation arrives in the same poll.
|
||||
// This also bounds peers that ignore grpc-timeout.
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = tokio::time::sleep_until(deadline.into()) =>
|
||||
Err(RpcFailure { result: TIMEOUT, uncertain: true }),
|
||||
_ = cancellation =>
|
||||
Err(RpcFailure { result: E_ABORT, uncertain: true }),
|
||||
result = operation => result.map_err(failure_from_status),
|
||||
}
|
||||
}
|
||||
|
||||
fn failure_from_status(status: tonic::Status) -> RpcFailure {
|
||||
let mut source = status.source();
|
||||
while let Some(error) = source {
|
||||
// Tonic reports its local request timeout with Code::Cancelled.
|
||||
if error.is::<tonic::TimeoutExpired>() {
|
||||
return RpcFailure {
|
||||
result: TIMEOUT,
|
||||
uncertain: true,
|
||||
};
|
||||
}
|
||||
source = error.source();
|
||||
}
|
||||
RpcFailure {
|
||||
result: status_to_hresult(status.code()),
|
||||
uncertain: !is_definite_rejection(status.code()),
|
||||
}
|
||||
}
|
||||
|
||||
fn status_to_hresult(code: Code) -> HRESULT {
|
||||
match code {
|
||||
Code::Ok => windows::Win32::Foundation::S_OK,
|
||||
Code::Cancelled => E_ABORT,
|
||||
Code::InvalidArgument | Code::OutOfRange => E_INVALIDARG,
|
||||
Code::DeadlineExceeded => TIMEOUT,
|
||||
Code::NotFound => HRESULT::from_win32(ERROR_NOT_FOUND.0),
|
||||
Code::AlreadyExists => HRESULT::from_win32(ERROR_ALREADY_EXISTS.0),
|
||||
Code::PermissionDenied => E_ACCESSDENIED,
|
||||
Code::Unauthenticated => HRESULT::from_win32(ERROR_LOGON_FAILURE.0),
|
||||
Code::ResourceExhausted => HRESULT::from_win32(ERROR_NOT_ENOUGH_MEMORY.0),
|
||||
Code::FailedPrecondition => INVALID_STATE,
|
||||
Code::Aborted => HRESULT::from_win32(ERROR_RETRY.0),
|
||||
Code::Unimplemented => E_NOTIMPL,
|
||||
Code::Unavailable => HRESULT::from_win32(ERROR_CONNECTION_ABORTED.0),
|
||||
Code::DataLoss => HRESULT::from_win32(ERROR_INVALID_DATA.0),
|
||||
Code::Unknown | Code::Internal => E_FAIL,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_definite_rejection(code: Code) -> bool {
|
||||
// Only validation/authorization rejections are safe to correct and resubmit.
|
||||
// Internal, aborted, resource exhaustion and transport errors may follow a mutation.
|
||||
matches!(
|
||||
code,
|
||||
Code::InvalidArgument
|
||||
| Code::OutOfRange
|
||||
| Code::NotFound
|
||||
| Code::AlreadyExists
|
||||
| Code::PermissionDenied
|
||||
| Code::Unauthenticated
|
||||
| Code::FailedPrecondition
|
||||
| Code::Unimplemented
|
||||
)
|
||||
}
|
||||
|
||||
pub fn io_error_to_hresult(error: &io::Error) -> HRESULT {
|
||||
match error.kind() {
|
||||
io::ErrorKind::TimedOut => TIMEOUT,
|
||||
io::ErrorKind::PermissionDenied => E_ACCESSDENIED,
|
||||
io::ErrorKind::InvalidInput => E_INVALIDARG,
|
||||
_ => match error.raw_os_error() {
|
||||
Some(code) => HRESULT::from_win32(code as u32),
|
||||
None => HRESULT::from_win32(ERROR_CONNECTION_ABORTED.0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn execution_accepts_cancellation_without_vm_state() {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
for cancelled in [true, false] {
|
||||
let (send, receive) = watch::channel(cancelled);
|
||||
if !cancelled {
|
||||
drop(send);
|
||||
}
|
||||
let result = runtime.block_on(execute::<()>(
|
||||
Instant::now() + std::time::Duration::from_secs(1),
|
||||
Some(receive),
|
||||
std::future::pending(),
|
||||
));
|
||||
let error = result.unwrap_err();
|
||||
assert_eq!(error.result, E_ABORT);
|
||||
assert!(error.uncertain);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_deadline_does_not_depend_on_peer_timeout_support() {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let start = Instant::now();
|
||||
let result = runtime.block_on(execute::<()>(
|
||||
start + std::time::Duration::from_millis(50),
|
||||
None,
|
||||
std::future::pending(),
|
||||
));
|
||||
let error = result.unwrap_err();
|
||||
assert_eq!(error.result, TIMEOUT);
|
||||
assert!(error.uncertain);
|
||||
assert!(start.elapsed() < std::time::Duration::from_secs(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_and_io_errors_have_explicit_hresult_categories() {
|
||||
for (code, expected) in [
|
||||
(Code::Ok, windows::Win32::Foundation::S_OK),
|
||||
(Code::Cancelled, E_ABORT),
|
||||
(Code::Unknown, E_FAIL),
|
||||
(Code::InvalidArgument, E_INVALIDARG),
|
||||
(Code::DeadlineExceeded, TIMEOUT),
|
||||
(Code::NotFound, HRESULT::from_win32(ERROR_NOT_FOUND.0)),
|
||||
(
|
||||
Code::AlreadyExists,
|
||||
HRESULT::from_win32(ERROR_ALREADY_EXISTS.0),
|
||||
),
|
||||
(Code::PermissionDenied, E_ACCESSDENIED),
|
||||
(
|
||||
Code::ResourceExhausted,
|
||||
HRESULT::from_win32(ERROR_NOT_ENOUGH_MEMORY.0),
|
||||
),
|
||||
(Code::FailedPrecondition, INVALID_STATE),
|
||||
(Code::Aborted, HRESULT::from_win32(ERROR_RETRY.0)),
|
||||
(Code::OutOfRange, E_INVALIDARG),
|
||||
(Code::Unimplemented, E_NOTIMPL),
|
||||
(Code::Internal, E_FAIL),
|
||||
(
|
||||
Code::Unavailable,
|
||||
HRESULT::from_win32(ERROR_CONNECTION_ABORTED.0),
|
||||
),
|
||||
(Code::DataLoss, HRESULT::from_win32(ERROR_INVALID_DATA.0)),
|
||||
(
|
||||
Code::Unauthenticated,
|
||||
HRESULT::from_win32(ERROR_LOGON_FAILURE.0),
|
||||
),
|
||||
] {
|
||||
assert_eq!(status_to_hresult(code), expected);
|
||||
}
|
||||
assert_eq!(
|
||||
io_error_to_hresult(&io::Error::from(io::ErrorKind::TimedOut)),
|
||||
TIMEOUT
|
||||
);
|
||||
assert_eq!(
|
||||
io_error_to_hresult(&io::Error::from(io::ErrorKind::InvalidInput)),
|
||||
E_INVALIDARG
|
||||
);
|
||||
assert_eq!(
|
||||
io_error_to_hresult(&io::Error::from(io::ErrorKind::PermissionDenied)),
|
||||
E_ACCESSDENIED
|
||||
);
|
||||
assert_eq!(
|
||||
io_error_to_hresult(&io::Error::from_raw_os_error(10054)),
|
||||
HRESULT::from_win32(10054)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ typedef struct WslOpenVmmConfig WslOpenVmmConfig;
|
||||
typedef struct WslOpenVmmVm WslOpenVmmVm;
|
||||
|
||||
// Config and VM handles are opaque Rust-owned objects. Destroy each returned handle exactly once.
|
||||
// Destruction (and successful CreateVm consumption of Config) must not race any use of that handle.
|
||||
__declspec(dllimport) HRESULT WslOpenVmmCreateConfig(_Out_ WslOpenVmmConfig** Config);
|
||||
__declspec(dllimport) void WslOpenVmmDestroyConfig(_In_opt_ WslOpenVmmConfig* Config);
|
||||
__declspec(dllimport) HRESULT WslOpenVmmConfigSetKernelPath(_Inout_ WslOpenVmmConfig* Config, _In_ LPCWSTR Path);
|
||||
@@ -26,9 +27,15 @@ __declspec(dllimport) HRESULT WslOpenVmmConfigSetConsommeNic(_Inout_ WslOpenVmmC
|
||||
__declspec(dllimport) HRESULT WslOpenVmmConfigAddSerialPort(_Inout_ WslOpenVmmConfig* Config, _In_ UINT32 Port, _In_ LPCWSTR PipeName);
|
||||
__declspec(dllimport) HRESULT WslOpenVmmConfigSetVirtioConsolePath(_Inout_ WslOpenVmmConfig* Config, _In_ LPCWSTR Path);
|
||||
// On success, this consumes Config and sets it to nullptr. On failure, Config remains valid and Vm is nullptr.
|
||||
// TimeoutMs must be nonzero and bounds lock acquisition, connection establishment, and CreateVm together.
|
||||
// After an ambiguous CreateVm failure, discard both the config and the owning process before retrying.
|
||||
__declspec(dllimport) HRESULT WslOpenVmmCreateVm(_Inout_ WslOpenVmmConfig** Config, _In_ LPCWSTR SocketPath, _In_ UINT32 TimeoutMs, _Out_ WslOpenVmmVm** Vm);
|
||||
|
||||
__declspec(dllimport) void WslOpenVmmDestroyVm(_In_opt_ WslOpenVmmVm* Vm);
|
||||
// VM calls use TimeoutMs from creation, including time waiting for the per-VM lock.
|
||||
// Ambiguous RPC failures permanently invalidate mutations; teardown/quit remain available with deadlines.
|
||||
// Cancellation is nonblocking and may race an RPC, but not destruction. It also requires VM recreation.
|
||||
__declspec(dllimport) HRESULT WslOpenVmmVmCancelRequests(_Inout_ WslOpenVmmVm* Vm);
|
||||
__declspec(dllimport) HRESULT WslOpenVmmVmResume(_Inout_ WslOpenVmmVm* Vm);
|
||||
__declspec(dllimport) HRESULT WslOpenVmmVmTeardown(_Inout_ WslOpenVmmVm* Vm);
|
||||
__declspec(dllimport) HRESULT WslOpenVmmVmQuit(_Inout_ WslOpenVmmVm* Vm);
|
||||
|
||||
Reference in new issue
Block a user