// Copyright © 2020 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // use std::io::Read; use std::marker::PhantomData; use std::os::unix::net::UnixStream; use std::{fmt, process}; use api_client::{ simple_api_command, simple_api_command_with_fds, simple_api_full_command, Error as ApiClientError, }; use clap::{Arg, ArgAction, ArgMatches, Command}; use option_parser::{ByteSized, ByteSizedParseError}; use vmm::config::RestoreConfig; use vmm::vm_config::{ DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VsockConfig, }; #[cfg(feature = "dbus_api")] use zbus::{proxy, zvariant::Optional}; type ApiResult = Result<(), Error>; #[derive(Debug)] enum Error { HttpApiClient(ApiClientError), #[cfg(feature = "dbus_api")] DBusApiClient(zbus::Error), InvalidCpuCount(std::num::ParseIntError), InvalidMemorySize(ByteSizedParseError), InvalidBalloonSize(ByteSizedParseError), AddDeviceConfig(vmm::config::Error), AddDiskConfig(vmm::config::Error), AddFsConfig(vmm::config::Error), AddPmemConfig(vmm::config::Error), AddNetConfig(vmm::config::Error), AddUserDeviceConfig(vmm::config::Error), AddVdpaConfig(vmm::config::Error), AddVsockConfig(vmm::config::Error), Restore(vmm::config::Error), ReadingStdin(std::io::Error), ReadingFile(std::io::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match self { HttpApiClient(e) => e.fmt(f), #[cfg(feature = "dbus_api")] DBusApiClient(e) => write!(f, "Error D-Bus proxy: {e}"), InvalidCpuCount(e) => write!(f, "Error parsing CPU count: {e}"), InvalidMemorySize(e) => write!(f, "Error parsing memory size: {e:?}"), InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {e:?}"), AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {e}"), AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {e}"), AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {e}"), AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {e}"), AddNetConfig(e) => write!(f, "Error parsing network syntax: {e}"), AddUserDeviceConfig(e) => write!(f, "Error parsing user device syntax: {e}"), AddVdpaConfig(e) => write!(f, "Error parsing vDPA device syntax: {e}"), AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {e}"), Restore(e) => write!(f, "Error parsing restore syntax: {e}"), ReadingStdin(e) => write!(f, "Error reading from stdin: {e}"), ReadingFile(e) => write!(f, "Error reading from file: {e}"), } } } enum TargetApi<'a> { HttpApi(UnixStream, PhantomData<&'a ()>), #[cfg(feature = "dbus_api")] DBusApi(DBusApi1ProxyBlocking<'a>), } #[cfg(feature = "dbus_api")] #[proxy(name = "org.cloudhypervisor.DBusApi1", assume_defaults = false)] trait DBusApi1 { fn vmm_ping(&self) -> zbus::Result; fn vmm_shutdown(&self) -> zbus::Result<()>; fn vm_add_device(&self, device_config: &str) -> zbus::Result>; fn vm_add_disk(&self, disk_config: &str) -> zbus::Result>; fn vm_add_fs(&self, fs_config: &str) -> zbus::Result>; fn vm_add_net(&self, net_config: &str) -> zbus::Result>; fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result>; fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result>; fn vm_add_vdpa(&self, vdpa_config: &str) -> zbus::Result>; fn vm_add_vsock(&self, vsock_config: &str) -> zbus::Result>; fn vm_boot(&self) -> zbus::Result<()>; fn vm_coredump(&self, vm_coredump_data: &str) -> zbus::Result<()>; fn vm_counters(&self) -> zbus::Result>; fn vm_create(&self, vm_config: &str) -> zbus::Result<()>; fn vm_delete(&self) -> zbus::Result<()>; fn vm_info(&self) -> zbus::Result; fn vm_pause(&self) -> zbus::Result<()>; fn vm_power_button(&self) -> zbus::Result<()>; fn vm_reboot(&self) -> zbus::Result<()>; fn vm_remove_device(&self, vm_remove_device: &str) -> zbus::Result<()>; fn vm_resize(&self, vm_resize: &str) -> zbus::Result<()>; fn vm_resize_zone(&self, vm_resize_zone: &str) -> zbus::Result<()>; fn vm_restore(&self, restore_config: &str) -> zbus::Result<()>; fn vm_receive_migration(&self, receive_migration_data: &str) -> zbus::Result<()>; fn vm_send_migration(&self, receive_migration_data: &str) -> zbus::Result<()>; fn vm_resume(&self) -> zbus::Result<()>; fn vm_shutdown(&self) -> zbus::Result<()>; fn vm_snapshot(&self, vm_snapshot_config: &str) -> zbus::Result<()>; } #[cfg(feature = "dbus_api")] impl<'a> DBusApi1ProxyBlocking<'a> { fn new_connection(name: &'a str, path: &'a str, system_bus: bool) -> Result { let connection = if system_bus { zbus::blocking::Connection::system()? } else { zbus::blocking::Connection::session()? }; Self::builder(&connection) .destination(name)? .path(path)? .build() } fn print_response(&self, result: zbus::Result>) -> ApiResult { result .map(|ret| { if let Some(ref output) = *ret { println!("{output}"); } }) .map_err(Error::DBusApiClient) } fn api_vmm_ping(&self) -> ApiResult { self.vmm_ping() .map(|ping| println!("{ping}")) .map_err(Error::DBusApiClient) } fn api_vmm_shutdown(&self) -> ApiResult { self.vmm_shutdown().map_err(Error::DBusApiClient) } fn api_vm_add_device(&self, device_config: &str) -> ApiResult { self.print_response(self.vm_add_device(device_config)) } fn api_vm_add_disk(&self, disk_config: &str) -> ApiResult { self.print_response(self.vm_add_disk(disk_config)) } fn api_vm_add_fs(&self, fs_config: &str) -> ApiResult { self.print_response(self.vm_add_fs(fs_config)) } fn api_vm_add_net(&self, net_config: &str) -> ApiResult { self.print_response(self.vm_add_net(net_config)) } fn api_vm_add_pmem(&self, pmem_config: &str) -> ApiResult { self.print_response(self.vm_add_pmem(pmem_config)) } fn api_vm_add_user_device(&self, vm_add_user_device: &str) -> ApiResult { self.print_response(self.vm_add_user_device(vm_add_user_device)) } fn api_vm_add_vdpa(&self, vdpa_config: &str) -> ApiResult { self.print_response(self.vm_add_vdpa(vdpa_config)) } fn api_vm_add_vsock(&self, vsock_config: &str) -> ApiResult { self.print_response(self.vm_add_vsock(vsock_config)) } fn api_vm_boot(&self) -> ApiResult { self.vm_boot().map_err(Error::DBusApiClient) } fn api_vm_coredump(&self, vm_coredump_data: &str) -> ApiResult { self.vm_coredump(vm_coredump_data) .map_err(Error::DBusApiClient) } fn api_vm_counters(&self) -> ApiResult { self.print_response(self.vm_counters()) } fn api_vm_create(&self, vm_config: &str) -> ApiResult { self.vm_create(vm_config).map_err(Error::DBusApiClient) } fn api_vm_delete(&self) -> ApiResult { self.vm_delete().map_err(Error::DBusApiClient) } fn api_vm_info(&self) -> ApiResult { self.vm_info() .map(|info| println!("{info}")) .map_err(Error::DBusApiClient) } fn api_vm_pause(&self) -> ApiResult { self.vm_pause().map_err(Error::DBusApiClient) } fn api_vm_power_button(&self) -> ApiResult { self.vm_power_button().map_err(Error::DBusApiClient) } fn api_vm_reboot(&self) -> ApiResult { self.vm_reboot().map_err(Error::DBusApiClient) } fn api_vm_remove_device(&self, vm_remove_device: &str) -> ApiResult { self.vm_remove_device(vm_remove_device) .map_err(Error::DBusApiClient) } fn api_vm_resize(&self, vm_resize: &str) -> ApiResult { self.vm_resize(vm_resize).map_err(Error::DBusApiClient) } fn api_vm_resize_zone(&self, vm_resize_zone: &str) -> ApiResult { self.vm_resize_zone(vm_resize_zone) .map_err(Error::DBusApiClient) } fn api_vm_restore(&self, restore_config: &str) -> ApiResult { self.vm_restore(restore_config) .map_err(Error::DBusApiClient) } fn api_vm_receive_migration(&self, receive_migration_data: &str) -> ApiResult { self.vm_receive_migration(receive_migration_data) .map_err(Error::DBusApiClient) } fn api_vm_send_migration(&self, send_migration_data: &str) -> ApiResult { self.vm_send_migration(send_migration_data) .map_err(Error::DBusApiClient) } fn api_vm_resume(&self) -> ApiResult { self.vm_resume().map_err(Error::DBusApiClient) } fn api_vm_shutdown(&self) -> ApiResult { self.vm_shutdown().map_err(Error::DBusApiClient) } fn api_vm_snapshot(&self, vm_snapshot_config: &str) -> ApiResult { self.vm_snapshot(vm_snapshot_config) .map_err(Error::DBusApiClient) } } impl TargetApi<'_> { fn do_command(&mut self, matches: &ArgMatches) -> ApiResult { match self { Self::HttpApi(api_socket, _) => rest_api_do_command(matches, api_socket), #[cfg(feature = "dbus_api")] Self::DBusApi(proxy) => dbus_api_do_command(matches, proxy), } } } fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResult { match matches.subcommand_name() { Some("boot") => { simple_api_command(socket, "PUT", "boot", None).map_err(Error::HttpApiClient) } Some("delete") => { simple_api_command(socket, "PUT", "delete", None).map_err(Error::HttpApiClient) } Some("shutdown-vmm") => simple_api_full_command(socket, "PUT", "vmm.shutdown", None) .map_err(Error::HttpApiClient), Some("resume") => { simple_api_command(socket, "PUT", "resume", None).map_err(Error::HttpApiClient) } Some("power-button") => { simple_api_command(socket, "PUT", "power-button", None).map_err(Error::HttpApiClient) } Some("reboot") => { simple_api_command(socket, "PUT", "reboot", None).map_err(Error::HttpApiClient) } Some("pause") => { simple_api_command(socket, "PUT", "pause", None).map_err(Error::HttpApiClient) } Some("info") => { simple_api_command(socket, "GET", "info", None).map_err(Error::HttpApiClient) } Some("counters") => { simple_api_command(socket, "GET", "counters", None).map_err(Error::HttpApiClient) } Some("ping") => { simple_api_full_command(socket, "GET", "vmm.ping", None).map_err(Error::HttpApiClient) } Some("shutdown") => { simple_api_command(socket, "PUT", "shutdown", None).map_err(Error::HttpApiClient) } Some("nmi") => simple_api_command(socket, "PUT", "nmi", None).map_err(Error::HttpApiClient), Some("resize") => { let resize = resize_config( matches .subcommand_matches("resize") .unwrap() .get_one::("cpus") .map(|x| x as &str), matches .subcommand_matches("resize") .unwrap() .get_one::("memory") .map(|x| x as &str), matches .subcommand_matches("resize") .unwrap() .get_one::("balloon") .map(|x| x as &str), )?; simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient) } Some("resize-zone") => { let resize_zone = resize_zone_config( matches .subcommand_matches("resize-zone") .unwrap() .get_one::("id") .unwrap(), matches .subcommand_matches("resize-zone") .unwrap() .get_one::("size") .unwrap(), )?; simple_api_command(socket, "PUT", "resize-zone", Some(&resize_zone)) .map_err(Error::HttpApiClient) } Some("add-device") => { let device_config = add_device_config( matches .subcommand_matches("add-device") .unwrap() .get_one::("device_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-device", Some(&device_config)) .map_err(Error::HttpApiClient) } Some("remove-device") => { let remove_device_data = remove_device_config( matches .subcommand_matches("remove-device") .unwrap() .get_one::("id") .unwrap(), ); simple_api_command(socket, "PUT", "remove-device", Some(&remove_device_data)) .map_err(Error::HttpApiClient) } Some("add-disk") => { let disk_config = add_disk_config( matches .subcommand_matches("add-disk") .unwrap() .get_one::("disk_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-disk", Some(&disk_config)) .map_err(Error::HttpApiClient) } Some("add-fs") => { let fs_config = add_fs_config( matches .subcommand_matches("add-fs") .unwrap() .get_one::("fs_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-fs", Some(&fs_config)) .map_err(Error::HttpApiClient) } Some("add-pmem") => { let pmem_config = add_pmem_config( matches .subcommand_matches("add-pmem") .unwrap() .get_one::("pmem_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-pmem", Some(&pmem_config)) .map_err(Error::HttpApiClient) } Some("add-net") => { let (net_config, fds) = add_net_config( matches .subcommand_matches("add-net") .unwrap() .get_one::("net_config") .unwrap(), )?; simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), fds) .map_err(Error::HttpApiClient) } Some("add-user-device") => { let device_config = add_user_device_config( matches .subcommand_matches("add-user-device") .unwrap() .get_one::("device_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-user-device", Some(&device_config)) .map_err(Error::HttpApiClient) } Some("add-vdpa") => { let vdpa_config = add_vdpa_config( matches .subcommand_matches("add-vdpa") .unwrap() .get_one::("vdpa_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-vdpa", Some(&vdpa_config)) .map_err(Error::HttpApiClient) } Some("add-vsock") => { let vsock_config = add_vsock_config( matches .subcommand_matches("add-vsock") .unwrap() .get_one::("vsock_config") .unwrap(), )?; simple_api_command(socket, "PUT", "add-vsock", Some(&vsock_config)) .map_err(Error::HttpApiClient) } Some("snapshot") => { let snapshot_config = snapshot_config( matches .subcommand_matches("snapshot") .unwrap() .get_one::("snapshot_config") .unwrap(), ); simple_api_command(socket, "PUT", "snapshot", Some(&snapshot_config)) .map_err(Error::HttpApiClient) } Some("restore") => { let (restore_config, fds) = restore_config( matches .subcommand_matches("restore") .unwrap() .get_one::("restore_config") .unwrap(), )?; simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), fds) .map_err(Error::HttpApiClient) } Some("coredump") => { let coredump_config = coredump_config( matches .subcommand_matches("coredump") .unwrap() .get_one::("coredump_config") .unwrap(), ); simple_api_command(socket, "PUT", "coredump", Some(&coredump_config)) .map_err(Error::HttpApiClient) } Some("send-migration") => { let send_migration_data = send_migration_data( matches .subcommand_matches("send-migration") .unwrap() .get_one::("send_migration_config") .unwrap(), matches .subcommand_matches("send-migration") .unwrap() .get_flag("send_migration_local"), ); simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data)) .map_err(Error::HttpApiClient) } Some("receive-migration") => { let receive_migration_data = receive_migration_data( matches .subcommand_matches("receive-migration") .unwrap() .get_one::("receive_migration_config") .unwrap(), ); simple_api_command( socket, "PUT", "receive-migration", Some(&receive_migration_data), ) .map_err(Error::HttpApiClient) } Some("create") => { let data = create_data( matches .subcommand_matches("create") .unwrap() .get_one::("path") .unwrap(), )?; simple_api_command(socket, "PUT", "create", Some(&data)).map_err(Error::HttpApiClient) } _ => unreachable!(), } } #[cfg(feature = "dbus_api")] fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>) -> ApiResult { match matches.subcommand_name() { Some("boot") => proxy.api_vm_boot(), Some("delete") => proxy.api_vm_delete(), Some("shutdown-vmm") => proxy.api_vmm_shutdown(), Some("resume") => proxy.api_vm_resume(), Some("power-button") => proxy.api_vm_power_button(), Some("reboot") => proxy.api_vm_reboot(), Some("pause") => proxy.api_vm_pause(), Some("info") => proxy.api_vm_info(), Some("counters") => proxy.api_vm_counters(), Some("ping") => proxy.api_vmm_ping(), Some("shutdown") => proxy.api_vm_shutdown(), Some("resize") => { let resize = resize_config( matches .subcommand_matches("resize") .unwrap() .get_one::("cpus") .map(|x| x as &str), matches .subcommand_matches("resize") .unwrap() .get_one::("memory") .map(|x| x as &str), matches .subcommand_matches("resize") .unwrap() .get_one::("balloon") .map(|x| x as &str), )?; proxy.api_vm_resize(&resize) } Some("resize-zone") => { let resize_zone = resize_zone_config( matches .subcommand_matches("resize-zone") .unwrap() .get_one::("id") .unwrap(), matches .subcommand_matches("resize-zone") .unwrap() .get_one::("size") .unwrap(), )?; proxy.api_vm_resize_zone(&resize_zone) } Some("add-device") => { let device_config = add_device_config( matches .subcommand_matches("add-device") .unwrap() .get_one::("device_config") .unwrap(), )?; proxy.api_vm_add_device(&device_config) } Some("remove-device") => { let remove_device_data = remove_device_config( matches .subcommand_matches("remove-device") .unwrap() .get_one::("id") .unwrap(), ); proxy.api_vm_remove_device(&remove_device_data) } Some("add-disk") => { let disk_config = add_disk_config( matches .subcommand_matches("add-disk") .unwrap() .get_one::("disk_config") .unwrap(), )?; proxy.api_vm_add_disk(&disk_config) } Some("add-fs") => { let fs_config = add_fs_config( matches .subcommand_matches("add-fs") .unwrap() .get_one::("fs_config") .unwrap(), )?; proxy.api_vm_add_fs(&fs_config) } Some("add-pmem") => { let pmem_config = add_pmem_config( matches .subcommand_matches("add-pmem") .unwrap() .get_one::("pmem_config") .unwrap(), )?; proxy.api_vm_add_pmem(&pmem_config) } Some("add-net") => { let (net_config, _fds) = add_net_config( matches .subcommand_matches("add-net") .unwrap() .get_one::("net_config") .unwrap(), )?; proxy.api_vm_add_net(&net_config) } Some("add-user-device") => { let device_config = add_user_device_config( matches .subcommand_matches("add-user-device") .unwrap() .get_one::("device_config") .unwrap(), )?; proxy.api_vm_add_user_device(&device_config) } Some("add-vdpa") => { let vdpa_config = add_vdpa_config( matches .subcommand_matches("add-vdpa") .unwrap() .get_one::("vdpa_config") .unwrap(), )?; proxy.api_vm_add_vdpa(&vdpa_config) } Some("add-vsock") => { let vsock_config = add_vsock_config( matches .subcommand_matches("add-vsock") .unwrap() .get_one::("vsock_config") .unwrap(), )?; proxy.api_vm_add_vsock(&vsock_config) } Some("snapshot") => { let snapshot_config = snapshot_config( matches .subcommand_matches("snapshot") .unwrap() .get_one::("snapshot_config") .unwrap(), ); proxy.api_vm_snapshot(&snapshot_config) } Some("restore") => { let (restore_config, _fds) = restore_config( matches .subcommand_matches("restore") .unwrap() .get_one::("restore_config") .unwrap(), )?; proxy.api_vm_restore(&restore_config) } Some("coredump") => { let coredump_config = coredump_config( matches .subcommand_matches("coredump") .unwrap() .get_one::("coredump_config") .unwrap(), ); proxy.api_vm_coredump(&coredump_config) } Some("send-migration") => { let send_migration_data = send_migration_data( matches .subcommand_matches("send-migration") .unwrap() .get_one::("send_migration_config") .unwrap(), matches .subcommand_matches("send-migration") .unwrap() .get_flag("send_migration_local"), ); proxy.api_vm_send_migration(&send_migration_data) } Some("receive-migration") => { let receive_migration_data = receive_migration_data( matches .subcommand_matches("receive-migration") .unwrap() .get_one::("receive_migration_config") .unwrap(), ); proxy.api_vm_receive_migration(&receive_migration_data) } Some("create") => { let data = create_data( matches .subcommand_matches("create") .unwrap() .get_one::("path") .unwrap(), )?; proxy.api_vm_create(&data) } _ => unreachable!(), } } fn resize_config( cpus: Option<&str>, memory: Option<&str>, balloon: Option<&str>, ) -> Result { let desired_vcpus: Option = if let Some(cpus) = cpus { Some(cpus.parse().map_err(Error::InvalidCpuCount)?) } else { None }; let desired_ram: Option = if let Some(memory) = memory { Some( memory .parse::() .map_err(Error::InvalidMemorySize)? .0, ) } else { None }; let desired_balloon: Option = if let Some(balloon) = balloon { Some( balloon .parse::() .map_err(Error::InvalidBalloonSize)? .0, ) } else { None }; let resize = vmm::api::VmResizeData { desired_vcpus, desired_ram, desired_balloon, }; Ok(serde_json::to_string(&resize).unwrap()) } fn resize_zone_config(id: &str, size: &str) -> Result { let resize_zone = vmm::api::VmResizeZoneData { id: id.to_owned(), desired_ram: size .parse::() .map_err(Error::InvalidMemorySize)? .0, }; Ok(serde_json::to_string(&resize_zone).unwrap()) } fn add_device_config(config: &str) -> Result { let device_config = DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?; let device_config = serde_json::to_string(&device_config).unwrap(); Ok(device_config) } fn add_user_device_config(config: &str) -> Result { let device_config = UserDeviceConfig::parse(config).map_err(Error::AddUserDeviceConfig)?; let device_config = serde_json::to_string(&device_config).unwrap(); Ok(device_config) } fn remove_device_config(id: &str) -> String { let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() }; serde_json::to_string(&remove_device_data).unwrap() } fn add_disk_config(config: &str) -> Result { let disk_config = DiskConfig::parse(config).map_err(Error::AddDiskConfig)?; let disk_config = serde_json::to_string(&disk_config).unwrap(); Ok(disk_config) } fn add_fs_config(config: &str) -> Result { let fs_config = FsConfig::parse(config).map_err(Error::AddFsConfig)?; let fs_config = serde_json::to_string(&fs_config).unwrap(); Ok(fs_config) } fn add_pmem_config(config: &str) -> Result { let pmem_config = PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; let pmem_config = serde_json::to_string(&pmem_config).unwrap(); Ok(pmem_config) } fn add_net_config(config: &str) -> Result<(String, Vec), Error> { let mut net_config = NetConfig::parse(config).map_err(Error::AddNetConfig)?; // NetConfig is modified on purpose here by taking the list of file // descriptors out. Keeping the list and send it to the server side // process would not make any sense since the file descriptor may be // represented with different values. let fds = net_config.fds.take().unwrap_or_default(); let net_config = serde_json::to_string(&net_config).unwrap(); Ok((net_config, fds)) } fn add_vdpa_config(config: &str) -> Result { let vdpa_config = VdpaConfig::parse(config).map_err(Error::AddVdpaConfig)?; let vdpa_config = serde_json::to_string(&vdpa_config).unwrap(); Ok(vdpa_config) } fn add_vsock_config(config: &str) -> Result { let vsock_config = VsockConfig::parse(config).map_err(Error::AddVsockConfig)?; let vsock_config = serde_json::to_string(&vsock_config).unwrap(); Ok(vsock_config) } fn snapshot_config(url: &str) -> String { let snapshot_config = vmm::api::VmSnapshotConfig { destination_url: String::from(url), }; serde_json::to_string(&snapshot_config).unwrap() } fn restore_config(config: &str) -> Result<(String, Vec), Error> { let mut restore_config = RestoreConfig::parse(config).map_err(Error::Restore)?; // RestoreConfig is modified on purpose to take out the file descriptors. // These fds are passed to the server side process via SCM_RIGHTS let fds = match &mut restore_config.net_fds { Some(net_fds) => net_fds .iter_mut() .flat_map(|net| net.fds.take().unwrap_or_default()) .collect(), None => Vec::new(), }; let restore_config = serde_json::to_string(&restore_config).unwrap(); Ok((restore_config, fds)) } fn coredump_config(destination_url: &str) -> String { let coredump_config = vmm::api::VmCoredumpData { destination_url: String::from(destination_url), }; serde_json::to_string(&coredump_config).unwrap() } fn receive_migration_data(url: &str) -> String { let receive_migration_data = vmm::api::VmReceiveMigrationData { receiver_url: url.to_owned(), }; serde_json::to_string(&receive_migration_data).unwrap() } fn send_migration_data(url: &str, local: bool) -> String { let send_migration_data = vmm::api::VmSendMigrationData { destination_url: url.to_owned(), local, }; serde_json::to_string(&send_migration_data).unwrap() } fn create_data(path: &str) -> Result { let mut data = String::default(); if path == "-" { std::io::stdin() .read_to_string(&mut data) .map_err(Error::ReadingStdin)?; } else { data = std::fs::read_to_string(path).map_err(Error::ReadingFile)?; } Ok(data) } fn main() { let app = Command::new("ch-remote") .author(env!("CARGO_PKG_AUTHORS")) .version(env!("BUILD_VERSION")) .about("Remotely control a cloud-hypervisor VMM.") .arg_required_else_help(true) .subcommand_required(true) .args([ Arg::new("api-socket") .long("api-socket") .help("HTTP API socket path (UNIX domain socket).") .num_args(1), #[cfg(feature = "dbus_api")] Arg::new("dbus-service-name") .long("dbus-service-name") .help("Well known name of the dbus service") .num_args(1), #[cfg(feature = "dbus_api")] Arg::new("dbus-object-path") .long("dbus-object-path") .help("Object path which the interface is being served at") .num_args(1), #[cfg(feature = "dbus_api")] Arg::new("dbus-system-bus") .long("dbus-system-bus") .action(ArgAction::SetTrue) .num_args(0) .help("Use the system bus instead of a session bus"), ]) .subcommand( Command::new("add-device").about("Add VFIO device").arg( Arg::new("device_config") .index(1) .help(DeviceConfig::SYNTAX), ), ) .subcommand( Command::new("add-disk") .about("Add block device") .arg(Arg::new("disk_config").index(1).help(DiskConfig::SYNTAX)), ) .subcommand( Command::new("add-fs") .about("Add virtio-fs backed fs device") .arg( Arg::new("fs_config") .index(1) .help(vmm::vm_config::FsConfig::SYNTAX), ), ) .subcommand( Command::new("add-pmem") .about("Add persistent memory device") .arg( Arg::new("pmem_config") .index(1) .help(vmm::vm_config::PmemConfig::SYNTAX), ), ) .subcommand( Command::new("add-net") .about("Add network device") .arg(Arg::new("net_config").index(1).help(NetConfig::SYNTAX)), ) .subcommand( Command::new("add-user-device") .about("Add userspace device") .arg( Arg::new("device_config") .index(1) .help(UserDeviceConfig::SYNTAX), ), ) .subcommand( Command::new("add-vdpa") .about("Add vDPA device") .arg(Arg::new("vdpa_config").index(1).help(VdpaConfig::SYNTAX)), ) .subcommand( Command::new("add-vsock") .about("Add vsock device") .arg(Arg::new("vsock_config").index(1).help(VsockConfig::SYNTAX)), ) .subcommand( Command::new("remove-device") .about("Remove VFIO and PCI device") .arg(Arg::new("id").index(1).help("")), ) .subcommand(Command::new("info").about("Info on the VM")) .subcommand(Command::new("counters").about("Counters from the VM")) .subcommand(Command::new("pause").about("Pause the VM")) .subcommand(Command::new("reboot").about("Reboot the VM")) .subcommand(Command::new("power-button").about("Trigger a power button in the VM")) .subcommand( Command::new("resize") .about("Resize the VM") .arg( Arg::new("cpus") .long("cpus") .help("New vCPUs count") .num_args(1), ) .arg( Arg::new("memory") .long("memory") .help("New memory size in bytes (supports K/M/G suffix)") .num_args(1), ) .arg( Arg::new("balloon") .long("balloon") .help("New balloon size in bytes (supports K/M/G suffix)") .num_args(1), ), ) .subcommand( Command::new("resize-zone") .about("Resize a memory zone") .arg( Arg::new("id") .long("id") .help("Memory zone identifier") .num_args(1), ) .arg( Arg::new("size") .long("size") .help("New memory zone size in bytes (supports K/M/G suffix)") .num_args(1), ), ) .subcommand(Command::new("resume").about("Resume the VM")) .subcommand(Command::new("boot").about("Boot a created VM")) .subcommand(Command::new("delete").about("Delete a VM")) .subcommand(Command::new("shutdown").about("Shutdown the VM")) .subcommand( Command::new("snapshot") .about("Create a snapshot from VM") .arg( Arg::new("snapshot_config") .index(1) .help(""), ), ) .subcommand( Command::new("restore") .about("Restore VM from a snapshot") .arg( Arg::new("restore_config") .index(1) .help(RestoreConfig::SYNTAX), ), ) .subcommand( Command::new("coredump") .about("Create a coredump from VM") .arg(Arg::new("coredump_config").index(1).help("")), ) .subcommand( Command::new("send-migration") .about("Initiate a VM migration") .arg( Arg::new("send_migration_config") .index(1) .help(""), ) .arg( Arg::new("send_migration_local") .long("local") .num_args(0) .action(ArgAction::SetTrue), ), ) .subcommand( Command::new("receive-migration") .about("Receive a VM migration") .arg( Arg::new("receive_migration_config") .index(1) .help(""), ), ) .subcommand( Command::new("create") .about("Create VM from a JSON configuration") .arg(Arg::new("path").index(1).default_value("-")), ) .subcommand(Command::new("ping").about("Ping the VMM to check for API server availability")) .subcommand(Command::new("shutdown-vmm").about("Shutdown the VMM")) .subcommand(Command::new("nmi").about("Trigger NMI")); let matches = app.get_matches(); let mut target_api = match ( matches.get_one::("api-socket"), #[cfg(feature = "dbus_api")] matches.get_one::("dbus-service-name"), #[cfg(feature = "dbus_api")] matches.get_one::("dbus-object-path"), ) { #[cfg(not(feature = "dbus_api"))] (Some(api_sock),) => TargetApi::HttpApi( UnixStream::connect(api_sock).unwrap_or_else(|e| { eprintln!("Error opening HTTP socket: {e}"); process::exit(1) }), PhantomData, ), #[cfg(feature = "dbus_api")] (Some(api_sock), None, None) => TargetApi::HttpApi( UnixStream::connect(api_sock).unwrap_or_else(|e| { eprintln!("Error opening HTTP socket: {e}"); process::exit(1) }), PhantomData, ), #[cfg(feature = "dbus_api")] (None, Some(dbus_name), Some(dbus_path)) => TargetApi::DBusApi( DBusApi1ProxyBlocking::new_connection( dbus_name, dbus_path, matches.get_flag("dbus-system-bus"), ) .map_err(Error::DBusApiClient) .unwrap_or_else(|e| { eprintln!("Error creating D-Bus proxy: {e}"); process::exit(1) }), ), #[cfg(feature = "dbus_api")] (Some(_), Some(_) | None, Some(_) | None) => { println!( "`api-socket` and (dbus-service-name or dbus-object-path) are mutually exclusive" ); process::exit(1); } _ => { println!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); process::exit(1); } }; if let Err(e) = target_api.do_command(&matches) { eprintln!("Error running command: {e}"); process::exit(1) }; }