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 {
vmm_ping(&self) -> zbus::Result<String>78 fn vmm_ping(&self) -> zbus::Result<String>;
vmm_shutdown(&self) -> zbus::Result<()>79 fn vmm_shutdown(&self) -> zbus::Result<()>;
vm_add_device(&self, device_config: &str) -> zbus::Result<Optional<String>>80 fn vm_add_device(&self, device_config: &str) -> zbus::Result<Optional<String>>;
vm_add_disk(&self, disk_config: &str) -> zbus::Result<Optional<String>>81 fn vm_add_disk(&self, disk_config: &str) -> zbus::Result<Optional<String>>;
vm_add_fs(&self, fs_config: &str) -> zbus::Result<Optional<String>>82 fn vm_add_fs(&self, fs_config: &str) -> zbus::Result<Optional<String>>;
vm_add_net(&self, net_config: &str) -> zbus::Result<Optional<String>>83 fn vm_add_net(&self, net_config: &str) -> zbus::Result<Optional<String>>;
vm_add_pmem(&self, pmem_config: &str) -> zbus::Result<Optional<String>>84 fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result<Optional<String>>;
vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result<Optional<String>>85 fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result<Optional<String>>;
vm_add_vdpa(&self, vdpa_config: &str) -> zbus::Result<Optional<String>>86 fn vm_add_vdpa(&self, vdpa_config: &str) -> zbus::Result<Optional<String>>;
vm_add_vsock(&self, vsock_config: &str) -> zbus::Result<Optional<String>>87 fn vm_add_vsock(&self, vsock_config: &str) -> zbus::Result<Optional<String>>;
vm_boot(&self) -> zbus::Result<()>88 fn vm_boot(&self) -> zbus::Result<()>;
vm_coredump(&self, vm_coredump_data: &str) -> zbus::Result<()>89 fn vm_coredump(&self, vm_coredump_data: &str) -> zbus::Result<()>;
vm_counters(&self) -> zbus::Result<Optional<String>>90 fn vm_counters(&self) -> zbus::Result<Optional<String>>;
vm_create(&self, vm_config: &str) -> zbus::Result<()>91 fn vm_create(&self, vm_config: &str) -> zbus::Result<()>;
vm_delete(&self) -> zbus::Result<()>92 fn vm_delete(&self) -> zbus::Result<()>;
vm_info(&self) -> zbus::Result<String>93 fn vm_info(&self) -> zbus::Result<String>;
vm_pause(&self) -> zbus::Result<()>94 fn vm_pause(&self) -> zbus::Result<()>;
vm_power_button(&self) -> zbus::Result<()>95 fn vm_power_button(&self) -> zbus::Result<()>;
vm_reboot(&self) -> zbus::Result<()>96 fn vm_reboot(&self) -> zbus::Result<()>;
vm_remove_device(&self, vm_remove_device: &str) -> zbus::Result<()>97 fn vm_remove_device(&self, vm_remove_device: &str) -> zbus::Result<()>;
vm_resize(&self, vm_resize: &str) -> zbus::Result<()>98 fn vm_resize(&self, vm_resize: &str) -> zbus::Result<()>;
vm_resize_zone(&self, vm_resize_zone: &str) -> zbus::Result<()>99 fn vm_resize_zone(&self, vm_resize_zone: &str) -> zbus::Result<()>;
vm_restore(&self, restore_config: &str) -> zbus::Result<()>100 fn vm_restore(&self, restore_config: &str) -> zbus::Result<()>;
vm_receive_migration(&self, receive_migration_data: &str) -> zbus::Result<()>101 fn vm_receive_migration(&self, receive_migration_data: &str) -> zbus::Result<()>;
vm_send_migration(&self, receive_migration_data: &str) -> zbus::Result<()>102 fn vm_send_migration(&self, receive_migration_data: &str) -> zbus::Result<()>;
vm_resume(&self) -> zbus::Result<()>103 fn vm_resume(&self) -> zbus::Result<()>;
vm_shutdown(&self) -> zbus::Result<()>104 fn vm_shutdown(&self) -> zbus::Result<()>;
vm_snapshot(&self, vm_snapshot_config: &str) -> 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> {
new_connection(name: &'a str, path: &'a str, system_bus: bool) -> Result<Self, zbus::Error>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
print_response(&self, result: zbus::Result<Optional<String>>) -> ApiResult123 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
api_vmm_ping(&self) -> ApiResult133 fn api_vmm_ping(&self) -> ApiResult {
134 self.vmm_ping()
135 .map(|ping| println!("{ping}"))
136 .map_err(Error::DBusApiClient)
137 }
138
api_vmm_shutdown(&self) -> ApiResult139 fn api_vmm_shutdown(&self) -> ApiResult {
140 self.vmm_shutdown().map_err(Error::DBusApiClient)
141 }
142
api_vm_add_device(&self, device_config: &str) -> ApiResult143 fn api_vm_add_device(&self, device_config: &str) -> ApiResult {
144 self.print_response(self.vm_add_device(device_config))
145 }
146
api_vm_add_disk(&self, disk_config: &str) -> ApiResult147 fn api_vm_add_disk(&self, disk_config: &str) -> ApiResult {
148 self.print_response(self.vm_add_disk(disk_config))
149 }
150
api_vm_add_fs(&self, fs_config: &str) -> ApiResult151 fn api_vm_add_fs(&self, fs_config: &str) -> ApiResult {
152 self.print_response(self.vm_add_fs(fs_config))
153 }
154
api_vm_add_net(&self, net_config: &str) -> ApiResult155 fn api_vm_add_net(&self, net_config: &str) -> ApiResult {
156 self.print_response(self.vm_add_net(net_config))
157 }
158
api_vm_add_pmem(&self, pmem_config: &str) -> ApiResult159 fn api_vm_add_pmem(&self, pmem_config: &str) -> ApiResult {
160 self.print_response(self.vm_add_pmem(pmem_config))
161 }
162
api_vm_add_user_device(&self, vm_add_user_device: &str) -> ApiResult163 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
api_vm_add_vdpa(&self, vdpa_config: &str) -> ApiResult167 fn api_vm_add_vdpa(&self, vdpa_config: &str) -> ApiResult {
168 self.print_response(self.vm_add_vdpa(vdpa_config))
169 }
170
api_vm_add_vsock(&self, vsock_config: &str) -> ApiResult171 fn api_vm_add_vsock(&self, vsock_config: &str) -> ApiResult {
172 self.print_response(self.vm_add_vsock(vsock_config))
173 }
174
api_vm_boot(&self) -> ApiResult175 fn api_vm_boot(&self) -> ApiResult {
176 self.vm_boot().map_err(Error::DBusApiClient)
177 }
178
api_vm_coredump(&self, vm_coredump_data: &str) -> ApiResult179 fn api_vm_coredump(&self, vm_coredump_data: &str) -> ApiResult {
180 self.vm_coredump(vm_coredump_data)
181 .map_err(Error::DBusApiClient)
182 }
183
api_vm_counters(&self) -> ApiResult184 fn api_vm_counters(&self) -> ApiResult {
185 self.print_response(self.vm_counters())
186 }
187
api_vm_create(&self, vm_config: &str) -> ApiResult188 fn api_vm_create(&self, vm_config: &str) -> ApiResult {
189 self.vm_create(vm_config).map_err(Error::DBusApiClient)
190 }
191
api_vm_delete(&self) -> ApiResult192 fn api_vm_delete(&self) -> ApiResult {
193 self.vm_delete().map_err(Error::DBusApiClient)
194 }
195
api_vm_info(&self) -> ApiResult196 fn api_vm_info(&self) -> ApiResult {
197 self.vm_info()
198 .map(|info| println!("{info}"))
199 .map_err(Error::DBusApiClient)
200 }
201
api_vm_pause(&self) -> ApiResult202 fn api_vm_pause(&self) -> ApiResult {
203 self.vm_pause().map_err(Error::DBusApiClient)
204 }
205
api_vm_power_button(&self) -> ApiResult206 fn api_vm_power_button(&self) -> ApiResult {
207 self.vm_power_button().map_err(Error::DBusApiClient)
208 }
209
api_vm_reboot(&self) -> ApiResult210 fn api_vm_reboot(&self) -> ApiResult {
211 self.vm_reboot().map_err(Error::DBusApiClient)
212 }
213
api_vm_remove_device(&self, vm_remove_device: &str) -> ApiResult214 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
api_vm_resize(&self, vm_resize: &str) -> ApiResult219 fn api_vm_resize(&self, vm_resize: &str) -> ApiResult {
220 self.vm_resize(vm_resize).map_err(Error::DBusApiClient)
221 }
222
api_vm_resize_zone(&self, vm_resize_zone: &str) -> ApiResult223 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
api_vm_restore(&self, restore_config: &str) -> ApiResult228 fn api_vm_restore(&self, restore_config: &str) -> ApiResult {
229 self.vm_restore(restore_config)
230 .map_err(Error::DBusApiClient)
231 }
232
api_vm_receive_migration(&self, receive_migration_data: &str) -> ApiResult233 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
api_vm_send_migration(&self, send_migration_data: &str) -> ApiResult238 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
api_vm_resume(&self) -> ApiResult243 fn api_vm_resume(&self) -> ApiResult {
244 self.vm_resume().map_err(Error::DBusApiClient)
245 }
246
api_vm_shutdown(&self) -> ApiResult247 fn api_vm_shutdown(&self) -> ApiResult {
248 self.vm_shutdown().map_err(Error::DBusApiClient)
249 }
250
api_vm_snapshot(&self, vm_snapshot_config: &str) -> ApiResult251 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<'_> {
do_command(&mut self, matches: &ArgMatches) -> ApiResult258 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
rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResult267 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")]
dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>) -> ApiResult516 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
resize_config( cpus: Option<&str>, memory: Option<&str>, balloon: Option<&str>, ) -> Result<String, Error>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
resize_zone_config(id: &str, size: &str) -> Result<String, Error>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
add_device_config(config: &str) -> Result<String, Error>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
add_user_device_config(config: &str) -> Result<String, Error>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
remove_device_config(id: &str) -> String790 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
add_disk_config(config: &str) -> Result<String, Error>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
add_fs_config(config: &str) -> Result<String, Error>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
add_pmem_config(config: &str) -> Result<String, Error>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
add_net_config(config: &str) -> Result<(String, Vec<i32>), Error>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
add_vdpa_config(config: &str) -> Result<String, Error>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
add_vsock_config(config: &str) -> Result<String, Error>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
snapshot_config(url: &str) -> String844 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
restore_config(config: &str) -> Result<(String, Vec<i32>), Error>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
coredump_config(destination_url: &str) -> String868 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
receive_migration_data(url: &str) -> String876 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
send_migration_data(url: &str, local: bool) -> String884 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
create_data(path: &str) -> Result<String, Error>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.
get_cli_args() -> Box<[Arg]>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.
get_cli_commands_sorted() -> Box<[Command]>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
main()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(top_error) = target_api.do_command(&matches) {
1135 // Helper to join strings with a newline.
1136 fn join_strs(mut acc: String, next: String) -> String {
1137 if !acc.is_empty() {
1138 acc.push('\n');
1139 }
1140 acc.push_str(&next);
1141 acc
1142 }
1143
1144 // This function helps to modify the Display representation of remote
1145 // API failures so that it aligns with the regular output of error
1146 // messages. As we transfer a deep/rich chain of errors as String via
1147 // the HTTP API, the nested error chain is lost. We retrieve it from
1148 // the error response.
1149 //
1150 // In case the repose itself is broken, the error is printed directly
1151 // by using the `X` level.
1152 fn server_api_error_display_modifier(
1153 level: usize,
1154 indention: usize,
1155 error: &(dyn std::error::Error + 'static),
1156 ) -> Option<String> {
1157 if let Some(api_client::Error::ServerResponse(status_code, body)) =
1158 error.downcast_ref::<api_client::Error>()
1159 {
1160 let body = body.as_ref().map(|body| body.as_str()).unwrap_or("");
1161
1162 // Retrieve the list of error messages back.
1163 let lines: Vec<&str> = match serde_json::from_str(body) {
1164 Ok(json) => json,
1165 Err(e) => {
1166 return Some(format!(
1167 "{idention}X: Can't get remote's error messages from JSON response: {e}: body='{body}'",
1168 idention = " ".repeat(indention)
1169 ));
1170 }
1171 };
1172
1173 let error_status = format!("Server responded with {status_code:?}");
1174 // Prepend the error status line to the lines iter.
1175 let lines = std::iter::once(error_status.as_str()).chain(lines);
1176 let error_msg_multiline = lines
1177 .enumerate()
1178 .map(|(index, error_msg)| (index + level, error_msg))
1179 .map(|(level, error_msg)| {
1180 format!(
1181 "{idention}{level}: {error_msg}",
1182 idention = " ".repeat(indention)
1183 )
1184 })
1185 .fold(String::new(), join_strs);
1186
1187 return Some(error_msg_multiline);
1188 }
1189
1190 None
1191 }
1192
1193 let top_error: &dyn std::error::Error = &top_error;
1194 cloud_hypervisor::cli_print_error_chain(
1195 top_error,
1196 "ch-remote",
1197 server_api_error_display_modifier,
1198 );
1199 process::exit(1)
1200 };
1201 }
1202
1203 #[cfg(test)]
1204 mod tests {
1205 use std::cmp::Ordering;
1206
1207 use super::*;
1208 use crate::test_util::assert_args_sorted;
1209
1210 #[test]
test_cli_args_sorted()1211 fn test_cli_args_sorted() {
1212 let args = get_cli_args();
1213 assert_args_sorted(|| args.iter());
1214 }
1215
1216 #[test]
test_cli_commands_sorted()1217 fn test_cli_commands_sorted() {
1218 let commands = get_cli_commands_sorted();
1219
1220 // check commands itself are sorted
1221 let iter = commands.iter().zip(commands.iter().skip(1));
1222 for (command, next) in iter {
1223 assert_ne!(
1224 command.get_name().cmp(next.get_name()),
1225 Ordering::Greater,
1226 "commands not alphabetically sorted: command={}, next={}",
1227 command.get_name(),
1228 next.get_name()
1229 );
1230 }
1231
1232 // check args of commands sorted
1233 for command in commands {
1234 assert_args_sorted(|| command.get_arguments());
1235 }
1236 }
1237 }
1238