use std::os::unix::io::RawFd;
use std::str::FromStr;
use std::sync::Mutex;
use anyhow::anyhow;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use strum::VariantNames;
use crate::config::ShutdownAction;
use crate::qmp::qmp_response::{Response, Version};
use crate::qmp::qmp_schema::{
BlockDevAddArgument, BlockdevSnapshotInternalArgument, CameraDevAddArgument,
CharDevAddArgument, ChardevInfo, Cmd, CmdLine, CmdParameter, DeviceAddArgument, DeviceProps,
Events, GetViomemArgument, GicCap, HumanMonitorCmdArgument, IothreadInfo, KvmInfo, MachineInfo,
MigrateCapabilities, NetDevAddArgument, NetDevReplaceArgument, NetLinkSetArgument, PropList,
QmpCommand, QmpErrorClass, QmpEvent, QueryMemGpaArgument, QueryVcpuRegArgument,
SetViomemArgument, Target, TypeLists, UpdateRegionArgument,
};
#[derive(Clone)]
pub struct PathInfo {
pub path: String,
pub label: String,
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum VmState {
Created = 1,
Running = 2,
InMigrating = 3,
Migrated = 4,
Paused = 5,
Shutdown = 6,
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum HypervisorType {
#[default]
Kvm,
Test,
}
impl FromStr for HypervisorType {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"kvm" | "kvm:tcg" => Ok(HypervisorType::Kvm),
"test" => Ok(HypervisorType::Test),
_ => Err(anyhow!("Not supported or invalid hypervisor type {}.", s)),
}
}
}
pub trait MachineLifecycle {
fn start(&self) -> bool {
self.notify_lifecycle(VmState::Created, VmState::Paused)
}
fn pause(&self) -> bool {
self.notify_lifecycle(VmState::Running, VmState::Paused)
}
fn resume(&self) -> bool {
self.notify_lifecycle(VmState::Paused, VmState::Running)
}
fn destroy(&self) -> bool {
self.notify_lifecycle(VmState::Running, VmState::Shutdown)
}
fn powerdown(&self) -> bool {
self.notify_lifecycle(VmState::Running, VmState::Shutdown)
}
fn reset(&mut self) -> bool {
self.notify_lifecycle(VmState::Running, VmState::Shutdown)
}
fn notify_lifecycle(&self, old: VmState, new: VmState) -> bool;
fn get_shutdown_action(&self) -> ShutdownAction {
ShutdownAction::ShutdownActionPoweroff
}
}
pub trait MachineAddressInterface {
#[cfg(target_arch = "x86_64")]
fn pio_in(&self, port: u64, data: &mut [u8]) -> bool;
#[cfg(target_arch = "x86_64")]
fn pio_out(&self, port: u64, data: &[u8]) -> bool;
fn mmio_read(&self, addr: u64, data: &mut [u8]) -> bool;
fn mmio_write(&self, addr: u64, data: &[u8]) -> bool;
}
pub trait DeviceInterface {
fn query_status(&self) -> Response;
fn query_cpus(&self) -> Response;
fn query_hotpluggable_cpus(&self) -> Response;
fn device_add(&mut self, args: Box<DeviceAddArgument>) -> Response;
fn device_del(&mut self, device_id: String) -> Response;
fn blockdev_add(&self, args: Box<BlockDevAddArgument>) -> Response;
fn blockdev_del(&self, node_name: String) -> Response;
fn netdev_add(&mut self, args: Box<NetDevAddArgument>) -> Response;
fn netdev_del(&mut self, id: String) -> Response;
fn netdev_replace(&mut self, _args: NetDevReplaceArgument) -> Response {
Response::create_response(
serde_json::to_value("netdev_replace not supported for VM".to_string()).unwrap(),
None,
)
}
fn netlink_set(&self, _args: NetLinkSetArgument) -> Response {
Response::create_response(
serde_json::to_value("netlink_set not supported for VM".to_string()).unwrap(),
None,
)
}
fn chardev_add(&mut self, _args: CharDevAddArgument) -> Response;
fn chardev_remove(&mut self, _id: String) -> Response;
fn cameradev_add(&mut self, _args: CameraDevAddArgument) -> Response {
Response::create_response(
serde_json::to_value("cameradev_add not supported for VM".to_string()).unwrap(),
None,
)
}
fn cameradev_del(&mut self, _id: String) -> Response {
Response::create_response(
serde_json::to_value("cameradev_del not supported for VM".to_string()).unwrap(),
None,
)
}
fn query_ohui_status(&self) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query_ohui_status not supported for VM".to_string()),
None,
)
}
fn switch_audio_record(&self, _authorized: String) -> Response {
Response::create_response(
serde_json::to_value("switch_audio_record not supported for VM".to_string()).unwrap(),
None,
)
}
fn getfd(&self, fd_name: String, if_fd: Option<RawFd>) -> Response;
fn query_balloon(&self) -> Response;
fn query_mem(&self) -> Response;
fn query_mem_mappings(&self) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query-mem-mappings not implemented".to_string()),
None,
)
}
fn query_mem_page_state(&self) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query-mem-page-state not implemented".to_string()),
None,
)
}
fn query_mem_dirty_bitmap(&self) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query-mem-dirty-bitmap not implemented".to_string()),
None,
)
}
fn query_vnc(&self) -> Response;
fn query_display_image(&self) -> Response;
fn set_viomem(&mut self, args: Box<SetViomemArgument>) -> Response;
fn get_viomem(&self, args: Box<GetViomemArgument>) -> Response;
fn query_workloads(&self) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query_workloads not supported for VM".to_string()),
None,
)
}
fn balloon(&self, size: u64) -> Response;
fn query_version(&self) -> Response {
let version = Version::new(1, 0, 5);
Response::create_response(serde_json::to_value(version).unwrap(), None)
}
fn query_commands(&self) -> Response {
let mut vec_cmd = Vec::new();
for qmp_cmd in QmpCommand::VARIANTS {
let cmd = Cmd {
name: String::from(*qmp_cmd),
};
vec_cmd.push(cmd);
}
Response::create_response(serde_json::to_value(&vec_cmd).unwrap(), None)
}
fn query_target(&self) -> Response {
#[cfg(target_arch = "x86_64")]
let target = Target {
arch: "x86_64".to_string(),
};
#[cfg(target_arch = "aarch64")]
let target = Target {
arch: "aarch64".to_string(),
};
Response::create_response(serde_json::to_value(target).unwrap(), None)
}
fn query_events(&self) -> Response {
let mut vec_events = Vec::new();
for event in QmpEvent::VARIANTS {
let cmd = Events {
name: String::from(*event),
};
vec_events.push(cmd);
}
Response::create_response(serde_json::to_value(&vec_events).unwrap(), None)
}
fn query_kvm(&self) -> Response {
let kvm = KvmInfo {
enabled: true,
present: true,
};
Response::create_response(serde_json::to_value(kvm).unwrap(), None)
}
fn query_machines(&self) -> Response {
let mut vec_machine = Vec::new();
let machine_info = MachineInfo {
hotplug: false,
name: "none".to_string(),
numa_mem_support: false,
cpu_max: 255,
deprecated: false,
};
vec_machine.push(machine_info);
let machine_info = MachineInfo {
hotplug: false,
name: "microvm".to_string(),
numa_mem_support: false,
cpu_max: 255,
deprecated: false,
};
vec_machine.push(machine_info);
#[cfg(target_arch = "x86_64")]
let machine_info = MachineInfo {
hotplug: false,
name: "q35".to_string(),
numa_mem_support: false,
cpu_max: 255,
deprecated: false,
};
#[cfg(target_arch = "x86_64")]
vec_machine.push(machine_info);
#[cfg(target_arch = "aarch64")]
let machine_info = MachineInfo {
hotplug: false,
name: "virt".to_string(),
numa_mem_support: false,
cpu_max: 255,
deprecated: false,
};
#[cfg(target_arch = "aarch64")]
vec_machine.push(machine_info);
Response::create_response(serde_json::to_value(&vec_machine).unwrap(), None)
}
fn list_type(&self) -> Response {
let mut vec_types = Vec::new();
let list_types: Vec<(&str, &str)> = vec![
("ioh3420", "pcie-root-port-base"),
("pcie-root-port", "pcie-root-port-base"),
("pcie-pci-bridge", "base-pci-bridge"),
("pci-bridge", "base-pci-bridge"),
("virtio-blk-pci-transitional", "virtio-blk-pci-base"),
("memory-backend-file", "memory-backend"),
("virtio-rng-device", "virtio-device"),
("rng-random", "rng-backend"),
("vfio-pci", "pci-device"),
("vhost-vsock-device", "virtio-device"),
("iothread", "object"),
#[cfg(target_arch = "aarch64")]
("gpex-pcihost", "pcie-host-bridge"),
#[cfg(feature = "usb_base")]
("nec-usb-xhci", "base-xhci"),
#[cfg(feature = "usb_base")]
("usb-tablet", "usb-hid"),
#[cfg(feature = "usb_base")]
("usb-kbd", "usb-hid"),
#[cfg(feature = "usb_consumer")]
("usb-consumer", "usb-hid"),
#[cfg(feature = "usb_storage")]
("usb-storage", "usb-storage-dev"),
("virtio-gpu-pci", "virtio-gpu"),
];
for list in list_types {
let re = TypeLists::new(String::from(list.0), String::from(list.1));
vec_types.push(re);
}
Response::create_response(serde_json::to_value(&vec_types).unwrap(), None)
}
fn device_list_properties(&self, typename: String) -> Response {
let mut vec_props = Vec::<DeviceProps>::new();
let prop = DeviceProps {
name: "disable-legacy".to_string(),
prop_type: "OnOffAuto".to_string(),
};
vec_props.push(prop);
if typename.contains("virtio-balloon") {
let prop = DeviceProps {
name: "deflate-on-oom".to_string(),
prop_type: "bool".to_string(),
};
vec_props.push(prop);
}
if typename.contains("virtio-blk") {
let prop = DeviceProps {
name: "num-queues".to_string(),
prop_type: "uint16".to_string(),
};
vec_props.push(prop);
}
Response::create_response(serde_json::to_value(&vec_props).unwrap(), None)
}
fn query_tpm_models(&self) -> Response {
let tpm_models = Vec::<String>::new();
Response::create_response(serde_json::to_value(tpm_models).unwrap(), None)
}
fn query_tpm_types(&self) -> Response {
let tpm_types = Vec::<String>::new();
Response::create_response(serde_json::to_value(tpm_types).unwrap(), None)
}
fn query_command_line_options(&self) -> Response {
let parameters = vec![
CmdParameter {
name: "discard".to_string(),
help: "discard operation (unmap|ignore)".to_string(),
parameter_type: "string".to_string(),
},
CmdParameter {
name: "detect-zeroes".to_string(),
help: "optimize zero writes (unmap|on|off)".to_string(),
parameter_type: "string".to_string(),
},
];
let cmd_lines = vec![CmdLine {
parameters,
option: "drive".to_string(),
}];
Response::create_response(serde_json::to_value(cmd_lines).unwrap(), None)
}
fn query_migrate_capabilities(&self) -> Response {
let caps = Vec::<MigrateCapabilities>::new();
Response::create_response(serde_json::to_value(caps).unwrap(), None)
}
fn query_qmp_schema(&self) -> Response {
Response::create_empty_response()
}
fn query_sev_capabilities(&self) -> Response {
Response::create_empty_response()
}
fn query_chardev(&self) -> Response {
let mut vec_chardev_info: Vec<ChardevInfo> = Vec::new();
let locked_paths = PTY_PATH.lock().unwrap().clone();
for path in locked_paths.iter() {
let chardev_path = &path.path;
let chardev_label = &path.label;
let info = ChardevInfo {
open: true,
filename: chardev_path.to_string().replace('\"', ""),
label: chardev_label.to_string().replace('\"', ""),
};
vec_chardev_info.push(info);
}
Response::create_response(serde_json::to_value(&vec_chardev_info).unwrap(), None)
}
fn qom_list(&self) -> Response {
let vec_cmd: Vec<PropList> = Vec::new();
Response::create_response(serde_json::to_value(vec_cmd).unwrap(), None)
}
fn qom_get(&self) -> Response {
let vec_cmd: Vec<ChardevInfo> = Vec::new();
Response::create_response(serde_json::to_value(vec_cmd).unwrap(), None)
}
fn query_block(&self) -> Response {
let vec_cmd: Vec<ChardevInfo> = Vec::new();
Response::create_response(serde_json::to_value(vec_cmd).unwrap(), None)
}
fn query_named_block_nodes(&self) -> Response {
let vec_cmd: Vec<ChardevInfo> = Vec::new();
Response::create_response(serde_json::to_value(vec_cmd).unwrap(), None)
}
fn query_blockstats(&self) -> Response {
let vec_cmd: Vec<ChardevInfo> = Vec::new();
Response::create_response(serde_json::to_value(vec_cmd).unwrap(), None)
}
fn query_block_jobs(&self) -> Response {
let vec_cmd: Vec<ChardevInfo> = Vec::new();
Response::create_response(serde_json::to_value(vec_cmd).unwrap(), None)
}
fn query_gic_capabilities(&self) -> Response {
let vec_gic: Vec<GicCap> = Vec::new();
Response::create_response(serde_json::to_value(vec_gic).unwrap(), None)
}
fn query_iothreads(&self) -> Response {
let mut vec_iothreads: Vec<IothreadInfo> = Vec::new();
let locked_threads = IOTHREADS.lock().unwrap();
for thread in locked_threads.iter() {
vec_iothreads.push(thread.clone());
}
Response::create_response(serde_json::to_value(&vec_iothreads).unwrap(), None)
}
fn update_region(&mut self, args: UpdateRegionArgument) -> Response;
fn input_event(&self, _k: String, _v: String) -> Response {
Response::create_empty_response()
}
fn human_monitor_command(&self, _args: HumanMonitorCmdArgument) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("human-monitor-command is not supported yet".to_string()),
None,
)
}
fn blockdev_snapshot_internal_sync(&self, _args: BlockdevSnapshotInternalArgument) -> Response {
Response::create_empty_response()
}
fn blockdev_snapshot_delete_internal_sync(
&self,
_args: BlockdevSnapshotInternalArgument,
) -> Response {
Response::create_empty_response()
}
fn query_vcpu_reg(&self, _args: QueryVcpuRegArgument) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query_vcpu_reg is not supported yet".to_string()),
None,
)
}
fn query_mem_gpa(&self, _args: QueryMemGpaArgument) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("query_mem_gpa is not supported yet".to_string()),
None,
)
}
fn mmds_put(&self, _args: crate::qmp::qmp_schema::MmdsPutArgs) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("put-mmds not supported for this VM type".to_string()),
None,
)
}
fn mmds_patch(&self, _args: crate::qmp::qmp_schema::MmdsPatchArgs) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("patch-mmds not supported for this VM type".to_string()),
None,
)
}
fn mmds_get(&self) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError("get-mmds not supported for this VM type".to_string()),
None,
)
}
fn mmds_config(&self, _args: crate::qmp::qmp_schema::MmdsConfigArgs) -> Response {
Response::create_error_response(
QmpErrorClass::GenericError(
"put-mmds-config not supported for this VM type".to_string(),
),
None,
)
}
}
pub trait MigrateInterface {
fn migrate(&self, _uri: String) -> Response {
Response::create_empty_response()
}
fn query_migrate(&self) -> Response {
Response::create_empty_response()
}
fn cancel_migrate(&self) -> Response {
Response::create_empty_response()
}
}
pub trait MachineInterface: MachineLifecycle + MachineAddressInterface {}
pub trait MachineExternalInterface: MachineLifecycle + DeviceInterface + MigrateInterface {}
pub trait MachineTestInterface: MachineAddressInterface {}
pub static PTY_PATH: Lazy<Mutex<Vec<PathInfo>>> = Lazy::new(|| Mutex::new(Vec::new()));
pub static IOTHREADS: Lazy<Mutex<Vec<IothreadInfo>>> = Lazy::new(|| Mutex::new(Vec::new()));