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