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