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