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