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