1 // Copyright © 2020 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 use api_client::simple_api_command; 7 use api_client::simple_api_command_with_fds; 8 use api_client::simple_api_full_command; 9 use api_client::Error as ApiClientError; 10 use argh::FromArgs; 11 use option_parser::{ByteSized, ByteSizedParseError}; 12 use std::fmt; 13 use std::io::Read; 14 use std::marker::PhantomData; 15 use std::os::unix::net::UnixStream; 16 use std::process; 17 #[cfg(feature = "dbus_api")] 18 use zbus::{dbus_proxy, zvariant::Optional}; 19 20 type ApiResult = Result<(), Error>; 21 22 #[derive(Debug)] 23 enum Error { 24 HttpApiClient(ApiClientError), 25 #[cfg(feature = "dbus_api")] 26 DBusApiClient(zbus::Error), 27 InvalidMemorySize(ByteSizedParseError), 28 InvalidBalloonSize(ByteSizedParseError), 29 AddDeviceConfig(vmm::config::Error), 30 AddDiskConfig(vmm::config::Error), 31 AddFsConfig(vmm::config::Error), 32 AddPmemConfig(vmm::config::Error), 33 AddNetConfig(vmm::config::Error), 34 AddUserDeviceConfig(vmm::config::Error), 35 AddVdpaConfig(vmm::config::Error), 36 AddVsockConfig(vmm::config::Error), 37 Restore(vmm::config::Error), 38 ReadingStdin(std::io::Error), 39 ReadingFile(std::io::Error), 40 } 41 42 impl fmt::Display for Error { 43 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 use Error::*; 45 match self { 46 HttpApiClient(e) => e.fmt(f), 47 #[cfg(feature = "dbus_api")] 48 DBusApiClient(e) => write!(f, "Error D-Bus proxy: {e}"), 49 InvalidMemorySize(e) => write!(f, "Error parsing memory size: {e:?}"), 50 InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {e:?}"), 51 AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {e}"), 52 AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {e}"), 53 AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {e}"), 54 AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {e}"), 55 AddNetConfig(e) => write!(f, "Error parsing network syntax: {e}"), 56 AddUserDeviceConfig(e) => write!(f, "Error parsing user device syntax: {e}"), 57 AddVdpaConfig(e) => write!(f, "Error parsing vDPA device syntax: {e}"), 58 AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {e}"), 59 Restore(e) => write!(f, "Error parsing restore syntax: {e}"), 60 ReadingStdin(e) => write!(f, "Error reading from stdin: {e}"), 61 ReadingFile(e) => write!(f, "Error reading from file: {e}"), 62 } 63 } 64 } 65 66 enum TargetApi<'a> { 67 HttpApi(UnixStream, PhantomData<&'a ()>), 68 #[cfg(feature = "dbus_api")] 69 DBusApi(DBusApi1ProxyBlocking<'a>), 70 } 71 72 #[cfg(feature = "dbus_api")] 73 #[dbus_proxy(name = "org.cloudhypervisor.DBusApi1", assume_defaults = false)] 74 trait DBusApi1 { 75 fn vmm_ping(&self) -> zbus::Result<String>; 76 fn vmm_shutdown(&self) -> zbus::Result<()>; 77 fn vm_add_device(&self, device_config: &str) -> zbus::Result<Optional<String>>; 78 fn vm_add_disk(&self, disk_config: &str) -> zbus::Result<Optional<String>>; 79 fn vm_add_fs(&self, fs_config: &str) -> zbus::Result<Optional<String>>; 80 fn vm_add_net(&self, net_config: &str) -> zbus::Result<Optional<String>>; 81 fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result<Optional<String>>; 82 fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result<Optional<String>>; 83 fn vm_add_vdpa(&self, vdpa_config: &str) -> zbus::Result<Optional<String>>; 84 fn vm_add_vsock(&self, vsock_config: &str) -> zbus::Result<Optional<String>>; 85 fn vm_boot(&self) -> zbus::Result<()>; 86 fn vm_coredump(&self, vm_coredump_data: &str) -> zbus::Result<()>; 87 fn vm_counters(&self) -> zbus::Result<Optional<String>>; 88 fn vm_create(&self, vm_config: &str) -> zbus::Result<()>; 89 fn vm_delete(&self) -> zbus::Result<()>; 90 fn vm_info(&self) -> zbus::Result<String>; 91 fn vm_pause(&self) -> zbus::Result<()>; 92 fn vm_power_button(&self) -> zbus::Result<()>; 93 fn vm_reboot(&self) -> zbus::Result<()>; 94 fn vm_remove_device(&self, vm_remove_device: &str) -> zbus::Result<()>; 95 fn vm_resize(&self, vm_resize: &str) -> zbus::Result<()>; 96 fn vm_resize_zone(&self, vm_resize_zone: &str) -> zbus::Result<()>; 97 fn vm_restore(&self, restore_config: &str) -> zbus::Result<()>; 98 fn vm_receive_migration(&self, receive_migration_data: &str) -> zbus::Result<()>; 99 fn vm_send_migration(&self, receive_migration_data: &str) -> zbus::Result<()>; 100 fn vm_resume(&self) -> zbus::Result<()>; 101 fn vm_shutdown(&self) -> zbus::Result<()>; 102 fn vm_snapshot(&self, vm_snapshot_config: &str) -> zbus::Result<()>; 103 } 104 105 #[cfg(feature = "dbus_api")] 106 impl<'a> DBusApi1ProxyBlocking<'a> { 107 fn new_connection(name: &'a str, path: &'a str, system_bus: bool) -> Result<Self, zbus::Error> { 108 let connection = if system_bus { 109 zbus::blocking::Connection::system()? 110 } else { 111 zbus::blocking::Connection::session()? 112 }; 113 114 Self::builder(&connection) 115 .destination(name)? 116 .path(path)? 117 .build() 118 } 119 120 fn print_response(&self, result: zbus::Result<Optional<String>>) -> ApiResult { 121 result 122 .map(|ret| { 123 if let Some(ref output) = *ret { 124 println!("{output}"); 125 } 126 }) 127 .map_err(Error::DBusApiClient) 128 } 129 130 fn api_vmm_ping(&self) -> ApiResult { 131 self.vmm_ping() 132 .map(|ping| println!("{ping}")) 133 .map_err(Error::DBusApiClient) 134 } 135 136 fn api_vmm_shutdown(&self) -> ApiResult { 137 self.vmm_shutdown().map_err(Error::DBusApiClient) 138 } 139 140 fn api_vm_add_device(&self, device_config: &str) -> ApiResult { 141 self.print_response(self.vm_add_device(device_config)) 142 } 143 144 fn api_vm_add_disk(&self, disk_config: &str) -> ApiResult { 145 self.print_response(self.vm_add_disk(disk_config)) 146 } 147 148 fn api_vm_add_fs(&self, fs_config: &str) -> ApiResult { 149 self.print_response(self.vm_add_fs(fs_config)) 150 } 151 152 fn api_vm_add_net(&self, net_config: &str) -> ApiResult { 153 self.print_response(self.vm_add_net(net_config)) 154 } 155 156 fn api_vm_add_pmem(&self, pmem_config: &str) -> ApiResult { 157 self.print_response(self.vm_add_pmem(pmem_config)) 158 } 159 160 fn api_vm_add_user_device(&self, vm_add_user_device: &str) -> ApiResult { 161 self.print_response(self.vm_add_user_device(vm_add_user_device)) 162 } 163 164 fn api_vm_add_vdpa(&self, vdpa_config: &str) -> ApiResult { 165 self.print_response(self.vm_add_vdpa(vdpa_config)) 166 } 167 168 fn api_vm_add_vsock(&self, vsock_config: &str) -> ApiResult { 169 self.print_response(self.vm_add_vsock(vsock_config)) 170 } 171 172 fn api_vm_boot(&self) -> ApiResult { 173 self.vm_boot().map_err(Error::DBusApiClient) 174 } 175 176 fn api_vm_coredump(&self, vm_coredump_data: &str) -> ApiResult { 177 self.vm_coredump(vm_coredump_data) 178 .map_err(Error::DBusApiClient) 179 } 180 181 fn api_vm_counters(&self) -> ApiResult { 182 self.print_response(self.vm_counters()) 183 } 184 185 fn api_vm_create(&self, vm_config: &str) -> ApiResult { 186 self.vm_create(vm_config).map_err(Error::DBusApiClient) 187 } 188 189 fn api_vm_delete(&self) -> ApiResult { 190 self.vm_delete().map_err(Error::DBusApiClient) 191 } 192 193 fn api_vm_info(&self) -> ApiResult { 194 self.vm_info() 195 .map(|info| println!("{info}")) 196 .map_err(Error::DBusApiClient) 197 } 198 199 fn api_vm_pause(&self) -> ApiResult { 200 self.vm_pause().map_err(Error::DBusApiClient) 201 } 202 203 fn api_vm_power_button(&self) -> ApiResult { 204 self.vm_power_button().map_err(Error::DBusApiClient) 205 } 206 207 fn api_vm_reboot(&self) -> ApiResult { 208 self.vm_reboot().map_err(Error::DBusApiClient) 209 } 210 211 fn api_vm_remove_device(&self, vm_remove_device: &str) -> ApiResult { 212 self.vm_remove_device(vm_remove_device) 213 .map_err(Error::DBusApiClient) 214 } 215 216 fn api_vm_resize(&self, vm_resize: &str) -> ApiResult { 217 self.vm_resize(vm_resize).map_err(Error::DBusApiClient) 218 } 219 220 fn api_vm_resize_zone(&self, vm_resize_zone: &str) -> ApiResult { 221 self.vm_resize_zone(vm_resize_zone) 222 .map_err(Error::DBusApiClient) 223 } 224 225 fn api_vm_restore(&self, restore_config: &str) -> ApiResult { 226 self.vm_restore(restore_config) 227 .map_err(Error::DBusApiClient) 228 } 229 230 fn api_vm_receive_migration(&self, receive_migration_data: &str) -> ApiResult { 231 self.vm_receive_migration(receive_migration_data) 232 .map_err(Error::DBusApiClient) 233 } 234 235 fn api_vm_send_migration(&self, send_migration_data: &str) -> ApiResult { 236 self.vm_send_migration(send_migration_data) 237 .map_err(Error::DBusApiClient) 238 } 239 240 fn api_vm_resume(&self) -> ApiResult { 241 self.vm_resume().map_err(Error::DBusApiClient) 242 } 243 244 fn api_vm_shutdown(&self) -> ApiResult { 245 self.vm_shutdown().map_err(Error::DBusApiClient) 246 } 247 248 fn api_vm_snapshot(&self, vm_snapshot_config: &str) -> ApiResult { 249 self.vm_snapshot(vm_snapshot_config) 250 .map_err(Error::DBusApiClient) 251 } 252 } 253 254 impl<'a> TargetApi<'a> { 255 fn do_command(&mut self, toplevel: &TopLevel) -> ApiResult { 256 match self { 257 Self::HttpApi(api_socket, _) => rest_api_do_command(toplevel, api_socket), 258 #[cfg(feature = "dbus_api")] 259 Self::DBusApi(proxy) => dbus_api_do_command(toplevel, proxy), 260 } 261 } 262 } 263 264 fn rest_api_do_command(toplevel: &TopLevel, socket: &mut UnixStream) -> ApiResult { 265 match toplevel.command { 266 SubCommandEnum::Boot(_) => { 267 simple_api_command(socket, "PUT", "boot", None).map_err(Error::HttpApiClient) 268 } 269 SubCommandEnum::Delete(_) => { 270 simple_api_command(socket, "PUT", "delete", None).map_err(Error::HttpApiClient) 271 } 272 SubCommandEnum::ShutdownVmm(_) => { 273 simple_api_full_command(socket, "PUT", "vmm.shutdown", None) 274 .map_err(Error::HttpApiClient) 275 } 276 SubCommandEnum::Resume(_) => { 277 simple_api_command(socket, "PUT", "resume", None).map_err(Error::HttpApiClient) 278 } 279 SubCommandEnum::PowerButton(_) => { 280 simple_api_command(socket, "PUT", "power-button", None).map_err(Error::HttpApiClient) 281 } 282 SubCommandEnum::Reboot(_) => { 283 simple_api_command(socket, "PUT", "reboot", None).map_err(Error::HttpApiClient) 284 } 285 SubCommandEnum::Pause(_) => { 286 simple_api_command(socket, "PUT", "pause", None).map_err(Error::HttpApiClient) 287 } 288 SubCommandEnum::Info(_) => { 289 simple_api_command(socket, "GET", "info", None).map_err(Error::HttpApiClient) 290 } 291 SubCommandEnum::Counters(_) => { 292 simple_api_command(socket, "GET", "counters", None).map_err(Error::HttpApiClient) 293 } 294 SubCommandEnum::Ping(_) => { 295 simple_api_full_command(socket, "GET", "vmm.ping", None).map_err(Error::HttpApiClient) 296 } 297 SubCommandEnum::Shutdown(_) => { 298 simple_api_command(socket, "PUT", "shutdown", None).map_err(Error::HttpApiClient) 299 } 300 SubCommandEnum::Resize(ref config) => { 301 let resize = resize_config(config.cpus, &config.memory, &config.balloon)?; 302 simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient) 303 } 304 SubCommandEnum::ResizeZone(ref config) => { 305 let resize_zone = resize_zone_config(&config.id, &config.size)?; 306 simple_api_command(socket, "PUT", "resize-zone", Some(&resize_zone)) 307 .map_err(Error::HttpApiClient) 308 } 309 SubCommandEnum::AddDevice(ref config) => { 310 let device_config = add_device_config(&config.device_config)?; 311 simple_api_command(socket, "PUT", "add-device", Some(&device_config)) 312 .map_err(Error::HttpApiClient) 313 } 314 SubCommandEnum::RemoveDevice(ref config) => { 315 let remove_device_data = remove_device_config(&config.device_config); 316 simple_api_command(socket, "PUT", "remove-device", Some(&remove_device_data)) 317 .map_err(Error::HttpApiClient) 318 } 319 SubCommandEnum::AddDisk(ref config) => { 320 let disk_config = add_disk_config(&config.disk_config)?; 321 simple_api_command(socket, "PUT", "add-disk", Some(&disk_config)) 322 .map_err(Error::HttpApiClient) 323 } 324 SubCommandEnum::AddFs(ref config) => { 325 let fs_config = add_fs_config(&config.fs_config)?; 326 simple_api_command(socket, "PUT", "add-fs", Some(&fs_config)) 327 .map_err(Error::HttpApiClient) 328 } 329 SubCommandEnum::AddPmem(ref config) => { 330 let pmem_config = add_pmem_config(&config.pmem_config)?; 331 simple_api_command(socket, "PUT", "add-pmem", Some(&pmem_config)) 332 .map_err(Error::HttpApiClient) 333 } 334 SubCommandEnum::AddNet(ref config) => { 335 let (net_config, fds) = add_net_config(&config.net_config)?; 336 simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), fds) 337 .map_err(Error::HttpApiClient) 338 } 339 SubCommandEnum::AddUserDevice(ref config) => { 340 let device_config = add_user_device_config(&config.device_config)?; 341 simple_api_command(socket, "PUT", "add-user-device", Some(&device_config)) 342 .map_err(Error::HttpApiClient) 343 } 344 SubCommandEnum::AddVdpa(ref config) => { 345 let vdpa_config = add_vdpa_config(&config.vdpa_config)?; 346 simple_api_command(socket, "PUT", "add-vdpa", Some(&vdpa_config)) 347 .map_err(Error::HttpApiClient) 348 } 349 SubCommandEnum::AddVsock(ref config) => { 350 let vsock_config = add_vsock_config(&config.vsock_config)?; 351 simple_api_command(socket, "PUT", "add-vsock", Some(&vsock_config)) 352 .map_err(Error::HttpApiClient) 353 } 354 SubCommandEnum::Snapshot(ref config) => { 355 let snapshot_config = snapshot_api_config(&config.snapshot_config); 356 simple_api_command(socket, "PUT", "snapshot", Some(&snapshot_config)) 357 .map_err(Error::HttpApiClient) 358 } 359 SubCommandEnum::Restore(ref config) => { 360 let restore_config = restore_config(&config.restore_config)?; 361 simple_api_command(socket, "PUT", "restore", Some(&restore_config)) 362 .map_err(Error::HttpApiClient) 363 } 364 SubCommandEnum::Coredump(ref config) => { 365 let coredump_config = coredump_config(&config.coredump_config); 366 simple_api_command(socket, "PUT", "coredump", Some(&coredump_config)) 367 .map_err(Error::HttpApiClient) 368 } 369 SubCommandEnum::SendMigration(ref config) => { 370 let send_migration_data = 371 send_migration_data(&config.send_migration_config, config.send_migration_local); 372 simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data)) 373 .map_err(Error::HttpApiClient) 374 } 375 SubCommandEnum::ReceiveMigration(ref config) => { 376 let receive_migration_data = receive_migration_data(&config.receive_migration_config); 377 simple_api_command( 378 socket, 379 "PUT", 380 "receive-migration", 381 Some(&receive_migration_data), 382 ) 383 .map_err(Error::HttpApiClient) 384 } 385 SubCommandEnum::Create(ref config) => { 386 let data = create_data(&config.vm_config)?; 387 simple_api_command(socket, "PUT", "create", Some(&data)).map_err(Error::HttpApiClient) 388 } 389 SubCommandEnum::Version(_) => { 390 // Already handled outside of this function 391 panic!() 392 } 393 } 394 } 395 396 #[cfg(feature = "dbus_api")] 397 fn dbus_api_do_command(toplevel: &TopLevel, proxy: &DBusApi1ProxyBlocking<'_>) -> ApiResult { 398 match toplevel.command { 399 SubCommandEnum::Boot(_) => proxy.api_vm_boot(), 400 SubCommandEnum::Delete(_) => proxy.api_vm_delete(), 401 SubCommandEnum::ShutdownVmm(_) => proxy.api_vmm_shutdown(), 402 SubCommandEnum::Resume(_) => proxy.api_vm_resume(), 403 SubCommandEnum::PowerButton(_) => proxy.api_vm_power_button(), 404 SubCommandEnum::Reboot(_) => proxy.api_vm_reboot(), 405 SubCommandEnum::Pause(_) => proxy.api_vm_pause(), 406 SubCommandEnum::Info(_) => proxy.api_vm_info(), 407 SubCommandEnum::Counters(_) => proxy.api_vm_counters(), 408 SubCommandEnum::Ping(_) => proxy.api_vmm_ping(), 409 SubCommandEnum::Shutdown(_) => proxy.api_vm_shutdown(), 410 SubCommandEnum::Resize(ref config) => { 411 let resize = resize_config(config.cpus, &config.memory, &config.balloon)?; 412 proxy.api_vm_resize(&resize) 413 } 414 SubCommandEnum::ResizeZone(ref config) => { 415 let resize_zone = resize_zone_config(&config.id, &config.size)?; 416 proxy.api_vm_resize_zone(&resize_zone) 417 } 418 SubCommandEnum::AddDevice(ref config) => { 419 let device_config = add_device_config(&config.device_config)?; 420 proxy.api_vm_add_device(&device_config) 421 } 422 SubCommandEnum::RemoveDevice(ref config) => { 423 let remove_device_data = remove_device_config(&config.device_config); 424 proxy.api_vm_remove_device(&remove_device_data) 425 } 426 SubCommandEnum::AddDisk(ref config) => { 427 let disk_config = add_disk_config(&config.disk_config)?; 428 proxy.api_vm_add_disk(&disk_config) 429 } 430 SubCommandEnum::AddFs(ref config) => { 431 let fs_config = add_fs_config(&config.fs_config)?; 432 proxy.api_vm_add_fs(&fs_config) 433 } 434 SubCommandEnum::AddPmem(ref config) => { 435 let pmem_config = add_pmem_config(&config.pmem_config)?; 436 proxy.api_vm_add_pmem(&pmem_config) 437 } 438 SubCommandEnum::AddNet(ref config) => { 439 let (net_config, _fds) = add_net_config(&config.net_config)?; 440 proxy.api_vm_add_net(&net_config) 441 } 442 SubCommandEnum::AddUserDevice(ref config) => { 443 let device_config = add_user_device_config(&config.device_config)?; 444 proxy.api_vm_add_user_device(&device_config) 445 } 446 SubCommandEnum::AddVdpa(ref config) => { 447 let vdpa_config = add_vdpa_config(&config.vdpa_config)?; 448 proxy.api_vm_add_vdpa(&vdpa_config) 449 } 450 SubCommandEnum::AddVsock(ref config) => { 451 let vsock_config = add_vsock_config(&config.vsock_config)?; 452 proxy.api_vm_add_vsock(&vsock_config) 453 } 454 SubCommandEnum::Snapshot(ref config) => { 455 let snapshot_config = snapshot_api_config(&config.snapshot_config); 456 proxy.api_vm_snapshot(&snapshot_config) 457 } 458 SubCommandEnum::Restore(ref config) => { 459 let restore_config = restore_config(&config.restore_config)?; 460 proxy.api_vm_restore(&restore_config) 461 } 462 SubCommandEnum::Coredump(ref config) => { 463 let coredump_config = coredump_config(&config.coredump_config); 464 proxy.api_vm_coredump(&coredump_config) 465 } 466 SubCommandEnum::SendMigration(ref config) => { 467 let send_migration_data = 468 send_migration_data(&config.send_migration_config, config.send_migration_local); 469 proxy.api_vm_send_migration(&send_migration_data) 470 } 471 SubCommandEnum::ReceiveMigration(ref config) => { 472 let receive_migration_data = receive_migration_data(&config.receive_migration_config); 473 proxy.api_vm_receive_migration(&receive_migration_data) 474 } 475 SubCommandEnum::Create(ref config) => { 476 let data = create_data(&config.vm_config)?; 477 proxy.api_vm_create(&data) 478 } 479 SubCommandEnum::Version(_) => { 480 // Already handled outside of this function 481 panic!() 482 } 483 } 484 } 485 fn resize_config( 486 desired_vcpus: Option<u8>, 487 memory: &Option<String>, 488 balloon: &Option<String>, 489 ) -> Result<String, Error> { 490 let desired_ram: Option<u64> = if let Some(memory) = memory { 491 Some( 492 memory 493 .parse::<ByteSized>() 494 .map_err(Error::InvalidMemorySize)? 495 .0, 496 ) 497 } else { 498 None 499 }; 500 501 let desired_balloon: Option<u64> = if let Some(balloon) = balloon { 502 Some( 503 balloon 504 .parse::<ByteSized>() 505 .map_err(Error::InvalidBalloonSize)? 506 .0, 507 ) 508 } else { 509 None 510 }; 511 512 let resize = vmm::api::VmResizeData { 513 desired_vcpus, 514 desired_ram, 515 desired_balloon, 516 }; 517 518 Ok(serde_json::to_string(&resize).unwrap()) 519 } 520 521 fn resize_zone_config(id: &str, size: &str) -> Result<String, Error> { 522 let resize_zone = vmm::api::VmResizeZoneData { 523 id: id.to_owned(), 524 desired_ram: size 525 .parse::<ByteSized>() 526 .map_err(Error::InvalidMemorySize)? 527 .0, 528 }; 529 530 Ok(serde_json::to_string(&resize_zone).unwrap()) 531 } 532 533 fn add_device_config(config: &str) -> Result<String, Error> { 534 let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?; 535 let device_config = serde_json::to_string(&device_config).unwrap(); 536 537 Ok(device_config) 538 } 539 540 fn add_user_device_config(config: &str) -> Result<String, Error> { 541 let device_config = 542 vmm::config::UserDeviceConfig::parse(config).map_err(Error::AddUserDeviceConfig)?; 543 let device_config = serde_json::to_string(&device_config).unwrap(); 544 545 Ok(device_config) 546 } 547 548 fn remove_device_config(id: &str) -> String { 549 let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() }; 550 551 serde_json::to_string(&remove_device_data).unwrap() 552 } 553 554 fn add_disk_config(config: &str) -> Result<String, Error> { 555 let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?; 556 let disk_config = serde_json::to_string(&disk_config).unwrap(); 557 558 Ok(disk_config) 559 } 560 561 fn add_fs_config(config: &str) -> Result<String, Error> { 562 let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?; 563 let fs_config = serde_json::to_string(&fs_config).unwrap(); 564 565 Ok(fs_config) 566 } 567 568 fn add_pmem_config(config: &str) -> Result<String, Error> { 569 let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; 570 let pmem_config = serde_json::to_string(&pmem_config).unwrap(); 571 572 Ok(pmem_config) 573 } 574 575 fn add_net_config(config: &str) -> Result<(String, Vec<i32>), Error> { 576 let mut net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?; 577 578 // NetConfig is modified on purpose here by taking the list of file 579 // descriptors out. Keeping the list and send it to the server side 580 // process would not make any sense since the file descriptor may be 581 // represented with different values. 582 let fds = net_config.fds.take().unwrap_or_default(); 583 let net_config = serde_json::to_string(&net_config).unwrap(); 584 585 Ok((net_config, fds)) 586 } 587 588 fn add_vdpa_config(config: &str) -> Result<String, Error> { 589 let vdpa_config = vmm::config::VdpaConfig::parse(config).map_err(Error::AddVdpaConfig)?; 590 let vdpa_config = serde_json::to_string(&vdpa_config).unwrap(); 591 592 Ok(vdpa_config) 593 } 594 595 fn add_vsock_config(config: &str) -> Result<String, Error> { 596 let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?; 597 let vsock_config = serde_json::to_string(&vsock_config).unwrap(); 598 599 Ok(vsock_config) 600 } 601 602 fn snapshot_api_config(url: &str) -> String { 603 let snapshot_config = vmm::api::VmSnapshotConfig { 604 destination_url: String::from(url), 605 }; 606 607 serde_json::to_string(&snapshot_config).unwrap() 608 } 609 610 fn restore_config(config: &str) -> Result<String, Error> { 611 let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?; 612 let restore_config = serde_json::to_string(&restore_config).unwrap(); 613 614 Ok(restore_config) 615 } 616 617 fn coredump_config(destination_url: &str) -> String { 618 let coredump_config = vmm::api::VmCoredumpData { 619 destination_url: String::from(destination_url), 620 }; 621 622 serde_json::to_string(&coredump_config).unwrap() 623 } 624 625 fn receive_migration_data(url: &str) -> String { 626 let receive_migration_data = vmm::api::VmReceiveMigrationData { 627 receiver_url: url.to_owned(), 628 }; 629 630 serde_json::to_string(&receive_migration_data).unwrap() 631 } 632 633 fn send_migration_data(url: &str, local: bool) -> String { 634 let send_migration_data = vmm::api::VmSendMigrationData { 635 destination_url: url.to_owned(), 636 local, 637 }; 638 639 serde_json::to_string(&send_migration_data).unwrap() 640 } 641 642 fn create_data(path: &str) -> Result<String, Error> { 643 let mut data = String::default(); 644 if path == "-" { 645 std::io::stdin() 646 .read_to_string(&mut data) 647 .map_err(Error::ReadingStdin)?; 648 } else { 649 data = std::fs::read_to_string(path).map_err(Error::ReadingFile)?; 650 } 651 652 Ok(data) 653 } 654 655 #[derive(FromArgs, PartialEq, Debug)] 656 #[doc = "Remotely control a cloud-hypervisor VMM.\n\nPlease refer to cloud-hypervisor for configuration syntaxes."] 657 struct TopLevel { 658 #[argh(subcommand)] 659 command: SubCommandEnum, 660 661 #[argh(option, long = "api-socket")] 662 /// HTTP API socket path (UNIX domain socket) 663 api_socket: Option<String>, 664 665 #[cfg(feature = "dbus_api")] 666 #[argh(option, long = "dbus-service-name")] 667 /// well known name of the dbus service 668 dbus_name: Option<String>, 669 670 #[cfg(feature = "dbus_api")] 671 #[argh(option, long = "dbus-object-path")] 672 /// object path which the interface is being served at 673 dbus_path: Option<String>, 674 675 #[cfg(feature = "dbus_api")] 676 #[argh(switch, long = "dbus-system-bus")] 677 /// use the system bus instead of a session bus 678 dbus_system_bus: bool, 679 } 680 681 #[derive(FromArgs, PartialEq, Debug)] 682 #[argh(subcommand)] 683 enum SubCommandEnum { 684 AddDevice(AddDeviceSubcommand), 685 AddDisk(AddDiskSubcommand), 686 AddFs(AddFsSubcommand), 687 AddPmem(AddPmemSubcommand), 688 AddNet(AddNetSubcommand), 689 AddUserDevice(AddUserDeviceSubcommand), 690 AddVdpa(AddVdpaSubcommand), 691 AddVsock(AddVsockSubcommand), 692 RemoveDevice(RemoveDeviceSubcommand), 693 Info(InfoSubcommand), 694 Counters(CountersSubcommand), 695 Pause(PauseSubcommand), 696 Reboot(RebootSubcommand), 697 PowerButton(PowerButtonSubcommand), 698 Resume(ResumeSubcommand), 699 Boot(BootSubcommand), 700 Delete(DeleteSubcommand), 701 Shutdown(ShutdownSubcommand), 702 Ping(PingSubcommand), 703 ShutdownVmm(ShutdownVmmSubcommand), 704 Resize(ResizeSubcommand), 705 ResizeZone(ResizeZoneSubcommand), 706 Snapshot(SnapshotSubcommand), 707 Restore(RestoreSubcommand), 708 Coredump(CoredumpSubcommand), 709 SendMigration(SendMigrationSubcommand), 710 ReceiveMigration(ReceiveMigrationSubcommand), 711 Create(CreateSubcommand), 712 Version(VersionSubcommand), 713 } 714 715 #[derive(FromArgs, PartialEq, Debug)] 716 #[argh(subcommand, name = "add-device")] 717 /// Add VFIO device 718 struct AddDeviceSubcommand { 719 #[argh(positional)] 720 /// device config 721 device_config: String, 722 } 723 724 #[derive(FromArgs, PartialEq, Debug)] 725 #[argh(subcommand, name = "add-disk")] 726 /// Add block device 727 struct AddDiskSubcommand { 728 #[argh(positional)] 729 /// disk config 730 disk_config: String, 731 } 732 733 #[derive(FromArgs, PartialEq, Debug)] 734 #[argh(subcommand, name = "add-fs")] 735 /// Add virtio-fs backed fs device 736 struct AddFsSubcommand { 737 #[argh(positional)] 738 /// virtio-fs config 739 fs_config: String, 740 } 741 742 #[derive(FromArgs, PartialEq, Debug)] 743 #[argh(subcommand, name = "add-pmem")] 744 /// Add virtio-fs backed fs device 745 struct AddPmemSubcommand { 746 #[argh(positional)] 747 /// pmem config 748 pmem_config: String, 749 } 750 751 #[derive(FromArgs, PartialEq, Debug)] 752 #[argh(subcommand, name = "add-net")] 753 /// Add virtio-fs backed fs device 754 struct AddNetSubcommand { 755 #[argh(positional)] 756 /// net config 757 net_config: String, 758 } 759 760 #[derive(FromArgs, PartialEq, Debug)] 761 #[argh(subcommand, name = "add-user-device")] 762 /// Add userspace device 763 struct AddUserDeviceSubcommand { 764 #[argh(positional)] 765 /// device config 766 device_config: String, 767 } 768 769 #[derive(FromArgs, PartialEq, Debug)] 770 #[argh(subcommand, name = "add-vdpa")] 771 /// Add vdpa device 772 struct AddVdpaSubcommand { 773 #[argh(positional)] 774 /// vdpa config 775 vdpa_config: String, 776 } 777 778 #[derive(FromArgs, PartialEq, Debug)] 779 #[argh(subcommand, name = "add-vsock")] 780 /// Add vsock device 781 struct AddVsockSubcommand { 782 #[argh(positional)] 783 /// vsock config 784 vsock_config: String, 785 } 786 787 #[derive(FromArgs, PartialEq, Debug)] 788 #[argh(subcommand, name = "remove-device")] 789 /// Remove VFIO device 790 struct RemoveDeviceSubcommand { 791 #[argh(positional)] 792 /// device config 793 device_config: String, 794 } 795 796 #[derive(FromArgs, PartialEq, Debug)] 797 #[argh(subcommand, name = "info")] 798 /// Information on the VM 799 struct InfoSubcommand {} 800 801 #[derive(FromArgs, PartialEq, Debug)] 802 #[argh(subcommand, name = "counters")] 803 /// Counters from the VM 804 struct CountersSubcommand {} 805 806 #[derive(FromArgs, PartialEq, Debug)] 807 #[argh(subcommand, name = "pause")] 808 /// Pause the VM 809 struct PauseSubcommand {} 810 811 #[derive(FromArgs, PartialEq, Debug)] 812 #[argh(subcommand, name = "reboot")] 813 /// Reboot the VM 814 struct RebootSubcommand {} 815 816 #[derive(FromArgs, PartialEq, Debug)] 817 #[argh(subcommand, name = "power-button")] 818 /// Trigger a power button in the VM 819 struct PowerButtonSubcommand {} 820 821 #[derive(FromArgs, PartialEq, Debug)] 822 #[argh(subcommand, name = "resume")] 823 /// Resume the VM 824 struct ResumeSubcommand {} 825 826 #[derive(FromArgs, PartialEq, Debug)] 827 #[argh(subcommand, name = "boot")] 828 /// Boot a created VM 829 struct BootSubcommand {} 830 831 #[derive(FromArgs, PartialEq, Debug)] 832 #[argh(subcommand, name = "delete")] 833 /// Delete a VM 834 struct DeleteSubcommand {} 835 836 #[derive(FromArgs, PartialEq, Debug)] 837 #[argh(subcommand, name = "shutdown")] 838 /// Shutdown a VM 839 struct ShutdownSubcommand {} 840 841 #[derive(FromArgs, PartialEq, Debug)] 842 #[argh(subcommand, name = "ping")] 843 /// Ping the VMM to check for API server availability 844 struct PingSubcommand {} 845 846 #[derive(FromArgs, PartialEq, Debug)] 847 #[argh(subcommand, name = "shutdown-vmm")] 848 /// Shutdown the VMM 849 struct ShutdownVmmSubcommand {} 850 851 #[derive(FromArgs, PartialEq, Debug)] 852 #[argh(subcommand, name = "resize")] 853 /// Resize the VM 854 struct ResizeSubcommand { 855 #[argh(option, long = "cpus")] 856 /// new VCPUs count 857 cpus: Option<u8>, 858 859 #[argh(option, long = "memory")] 860 /// new memory size in bytes (supports K/M/G suffix)" 861 memory: Option<String>, 862 863 #[argh(option, long = "balloon")] 864 /// new balloon size in bytes (supports K/M/G suffix)" 865 balloon: Option<String>, 866 } 867 868 #[derive(FromArgs, PartialEq, Debug)] 869 #[argh(subcommand, name = "resize-zone")] 870 /// Resize a memory zone 871 struct ResizeZoneSubcommand { 872 #[argh(option, long = "id")] 873 /// memory zone identifier 874 id: String, 875 876 #[argh(option, long = "size")] 877 /// new memory size in bytes (supports K/M/G suffix)" 878 size: String, 879 } 880 881 #[derive(FromArgs, PartialEq, Debug)] 882 #[argh(subcommand, name = "snapshot")] 883 /// Create a snapshot from VM 884 struct SnapshotSubcommand { 885 #[argh(positional)] 886 /// destination_url 887 snapshot_config: String, 888 } 889 890 #[derive(FromArgs, PartialEq, Debug)] 891 #[argh(subcommand, name = "restore")] 892 /// Restore VM from a snapshot 893 struct RestoreSubcommand { 894 #[argh(positional)] 895 /// restore config 896 restore_config: String, 897 } 898 899 #[derive(FromArgs, PartialEq, Debug)] 900 #[argh(subcommand, name = "coredump")] 901 /// Create a coredump from VM 902 struct CoredumpSubcommand { 903 #[argh(positional)] 904 /// coredump config 905 coredump_config: String, 906 } 907 908 #[derive(FromArgs, PartialEq, Debug)] 909 #[argh(subcommand, name = "send-migration")] 910 /// Initiate a VM migration 911 struct SendMigrationSubcommand { 912 #[argh(switch, long = "local")] 913 /// local migration 914 send_migration_local: bool, 915 916 #[argh(positional)] 917 /// destination_url 918 send_migration_config: String, 919 } 920 921 #[derive(FromArgs, PartialEq, Debug)] 922 #[argh(subcommand, name = "receive-migration")] 923 /// Receive a VM migration 924 struct ReceiveMigrationSubcommand { 925 #[argh(positional)] 926 /// receiver url 927 receive_migration_config: String, 928 } 929 930 #[derive(FromArgs, PartialEq, Debug)] 931 #[argh(subcommand, name = "create")] 932 /// Create a VM from a JSON configuration 933 struct CreateSubcommand { 934 #[argh(positional, default = "String::from(\"-\")")] 935 /// vm config 936 vm_config: String, 937 } 938 939 #[derive(FromArgs, PartialEq, Debug)] 940 #[argh(subcommand, name = "version")] 941 /// Print version information 942 struct VersionSubcommand {} 943 944 fn main() { 945 let toplevel: TopLevel = argh::from_env(); 946 947 if matches!(toplevel.command, SubCommandEnum::Version(_)) { 948 println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION")); 949 return; 950 } 951 952 let mut target_api = match ( 953 &toplevel.api_socket, 954 #[cfg(feature = "dbus_api")] 955 &toplevel.dbus_name, 956 #[cfg(feature = "dbus_api")] 957 &toplevel.dbus_path, 958 ) { 959 #[cfg(not(feature = "dbus_api"))] 960 (Some(ref api_socket),) => TargetApi::HttpApi( 961 UnixStream::connect(api_socket).unwrap_or_else(|e| { 962 eprintln!("Error opening HTTP socket: {e}"); 963 process::exit(1) 964 }), 965 PhantomData, 966 ), 967 #[cfg(feature = "dbus_api")] 968 (Some(ref api_socket), None, None) => TargetApi::HttpApi( 969 UnixStream::connect(api_socket).unwrap_or_else(|e| { 970 eprintln!("Error opening HTTP socket: {e}"); 971 process::exit(1) 972 }), 973 PhantomData, 974 ), 975 #[cfg(feature = "dbus_api")] 976 (None, Some(ref dbus_name), Some(ref dbus_path)) => TargetApi::DBusApi( 977 DBusApi1ProxyBlocking::new_connection(dbus_name, dbus_path, toplevel.dbus_system_bus) 978 .map_err(Error::DBusApiClient) 979 .unwrap_or_else(|e| { 980 eprintln!("Error creating D-Bus proxy: {e}"); 981 process::exit(1) 982 }), 983 ), 984 #[cfg(feature = "dbus_api")] 985 (Some(_), Some(_) | None, Some(_) | None) => { 986 println!( 987 "`api-socket` and (dbus-service-name or dbus-object-path) are mutually exclusive" 988 ); 989 process::exit(1); 990 } 991 _ => { 992 println!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); 993 process::exit(1); 994 } 995 }; 996 997 if let Err(e) = target_api.do_command(&toplevel) { 998 eprintln!("Error running command: {e}"); 999 process::exit(1) 1000 }; 1001 } 1002