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