• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Runs a virtual machine
6 //!
7 //! ## Feature flags
8 #![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
9 
10 #[cfg(any(feature = "composite-disk", feature = "qcow"))]
11 use std::fs::OpenOptions;
12 use std::path::Path;
13 
14 use anyhow::anyhow;
15 use anyhow::Context;
16 use anyhow::Result;
17 use argh::FromArgs;
18 use base::error;
19 use base::info;
20 use base::syslog;
21 use base::syslog::LogConfig;
22 use cmdline::RunCommand;
23 use cmdline::UsbAttachCommand;
24 mod crosvm;
25 use crosvm::cmdline;
26 #[cfg(feature = "plugin")]
27 use crosvm::config::executable_is_plugin;
28 use crosvm::config::Config;
29 use devices::virtio::vhost::user::device::run_block_device;
30 #[cfg(feature = "gpu")]
31 use devices::virtio::vhost::user::device::run_gpu_device;
32 use devices::virtio::vhost::user::device::run_net_device;
33 #[cfg(feature = "audio")]
34 use devices::virtio::vhost::user::device::run_snd_device;
35 #[cfg(feature = "composite-disk")]
36 use disk::create_composite_disk;
37 #[cfg(feature = "composite-disk")]
38 use disk::create_disk_file;
39 #[cfg(feature = "composite-disk")]
40 use disk::create_zero_filler;
41 #[cfg(feature = "composite-disk")]
42 use disk::ImagePartitionType;
43 #[cfg(feature = "composite-disk")]
44 use disk::PartitionInfo;
45 #[cfg(feature = "qcow")]
46 use disk::QcowFile;
47 mod sys;
48 use crosvm::cmdline::Command;
49 use crosvm::cmdline::CrossPlatformCommands;
50 use crosvm::cmdline::CrossPlatformDevicesCommands;
51 #[cfg(windows)]
52 use sys::windows::setup_metrics_reporting;
53 #[cfg(feature = "gpu")]
54 use vm_control::client::do_gpu_display_add;
55 #[cfg(feature = "gpu")]
56 use vm_control::client::do_gpu_display_list;
57 #[cfg(feature = "gpu")]
58 use vm_control::client::do_gpu_display_remove;
59 use vm_control::client::do_modify_battery;
60 use vm_control::client::do_swap_status;
61 use vm_control::client::do_usb_attach;
62 use vm_control::client::do_usb_detach;
63 use vm_control::client::do_usb_list;
64 #[cfg(feature = "balloon")]
65 use vm_control::client::handle_request;
66 use vm_control::client::vms_request;
67 #[cfg(feature = "gpu")]
68 use vm_control::client::ModifyGpuResult;
69 use vm_control::client::ModifyUsbResult;
70 #[cfg(feature = "balloon")]
71 use vm_control::BalloonControlCommand;
72 use vm_control::DiskControlCommand;
73 use vm_control::HotPlugDeviceInfo;
74 use vm_control::HotPlugDeviceType;
75 use vm_control::RestoreCommand;
76 use vm_control::SnapshotCommand;
77 use vm_control::SwapCommand;
78 use vm_control::UsbControlResult;
79 use vm_control::VmRequest;
80 #[cfg(feature = "balloon")]
81 use vm_control::VmResponse;
82 
83 use crate::sys::error_to_exit_code;
84 use crate::sys::init_log;
85 
86 #[cfg(feature = "scudo")]
87 #[global_allocator]
88 static ALLOCATOR: scudo::GlobalScudoAllocator = scudo::GlobalScudoAllocator;
89 
90 #[repr(i32)]
91 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
92 /// Exit code from crosvm,
93 enum CommandStatus {
94     /// Exit with success. Also used to mean VM stopped successfully.
95     SuccessOrVmStop = 0,
96     /// VM requested reset.
97     VmReset = 32,
98     /// VM crashed.
99     VmCrash = 33,
100     /// VM exit due to kernel panic in guest.
101     GuestPanic = 34,
102     /// Invalid argument was given to crosvm.
103     InvalidArgs = 35,
104     /// VM exit due to vcpu stall detection.
105     WatchdogReset = 36,
106 }
107 
108 impl CommandStatus {
message(&self) -> &'static str109     fn message(&self) -> &'static str {
110         match self {
111             Self::SuccessOrVmStop => "exiting with success",
112             Self::VmReset => "exiting with reset",
113             Self::VmCrash => "exiting with crash",
114             Self::GuestPanic => "exiting with guest panic",
115             Self::InvalidArgs => "invalid argument",
116             Self::WatchdogReset => "exiting with watchdog reset",
117         }
118     }
119 }
120 
121 impl From<sys::ExitState> for CommandStatus {
from(result: sys::ExitState) -> CommandStatus122     fn from(result: sys::ExitState) -> CommandStatus {
123         match result {
124             sys::ExitState::Stop => CommandStatus::SuccessOrVmStop,
125             sys::ExitState::Reset => CommandStatus::VmReset,
126             sys::ExitState::Crash => CommandStatus::VmCrash,
127             sys::ExitState::GuestPanic => CommandStatus::GuestPanic,
128             sys::ExitState::WatchdogReset => CommandStatus::WatchdogReset,
129         }
130     }
131 }
132 
run_vm<F: 'static>(cmd: RunCommand, log_config: LogConfig<F>) -> Result<CommandStatus> where F: Fn(&mut syslog::fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,133 fn run_vm<F: 'static>(cmd: RunCommand, log_config: LogConfig<F>) -> Result<CommandStatus>
134 where
135     F: Fn(&mut syslog::fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,
136 {
137     let cfg = match TryInto::<Config>::try_into(cmd) {
138         Ok(cfg) => cfg,
139         Err(e) => {
140             eprintln!("{}", e);
141             return Err(anyhow!("{}", e));
142         }
143     };
144 
145     #[cfg(feature = "plugin")]
146     if executable_is_plugin(&cfg.executable_path) {
147         let res = match crosvm::plugin::run_config(cfg) {
148             Ok(_) => {
149                 info!("crosvm and plugin have exited normally");
150                 Ok(CommandStatus::SuccessOrVmStop)
151             }
152             Err(e) => {
153                 eprintln!("{:#}", e);
154                 Err(e)
155             }
156         };
157         return res;
158     }
159 
160     #[cfg(feature = "crash-report")]
161     crosvm::sys::setup_emulator_crash_reporting(&cfg)?;
162 
163     #[cfg(windows)]
164     setup_metrics_reporting()?;
165 
166     init_log(log_config, &cfg)?;
167     cros_tracing::init();
168     let exit_state = crate::sys::run_config(cfg)?;
169     Ok(CommandStatus::from(exit_state))
170 }
171 
stop_vms(cmd: cmdline::StopCommand) -> std::result::Result<(), ()>172 fn stop_vms(cmd: cmdline::StopCommand) -> std::result::Result<(), ()> {
173     vms_request(&VmRequest::Exit, cmd.socket_path)
174 }
175 
suspend_vms(cmd: cmdline::SuspendCommand) -> std::result::Result<(), ()>176 fn suspend_vms(cmd: cmdline::SuspendCommand) -> std::result::Result<(), ()> {
177     vms_request(&VmRequest::Suspend, cmd.socket_path)
178 }
179 
swap_vms(cmd: cmdline::SwapCommand) -> std::result::Result<(), ()>180 fn swap_vms(cmd: cmdline::SwapCommand) -> std::result::Result<(), ()> {
181     use cmdline::SwapSubcommands::*;
182     let (req, path) = match &cmd.nested {
183         Enable(params) => (VmRequest::Swap(SwapCommand::Enable), &params.socket_path),
184         Trim(params) => (VmRequest::Swap(SwapCommand::Trim), &params.socket_path),
185         SwapOut(params) => (VmRequest::Swap(SwapCommand::SwapOut), &params.socket_path),
186         Disable(params) => (VmRequest::Swap(SwapCommand::Disable), &params.socket_path),
187         Status(params) => (VmRequest::Swap(SwapCommand::Status), &params.socket_path),
188     };
189     if let VmRequest::Swap(SwapCommand::Status) = req {
190         do_swap_status(path)
191     } else {
192         vms_request(&req, path)
193     }
194 }
195 
resume_vms(cmd: cmdline::ResumeCommand) -> std::result::Result<(), ()>196 fn resume_vms(cmd: cmdline::ResumeCommand) -> std::result::Result<(), ()> {
197     vms_request(&VmRequest::Resume, cmd.socket_path)
198 }
199 
powerbtn_vms(cmd: cmdline::PowerbtnCommand) -> std::result::Result<(), ()>200 fn powerbtn_vms(cmd: cmdline::PowerbtnCommand) -> std::result::Result<(), ()> {
201     vms_request(&VmRequest::Powerbtn, cmd.socket_path)
202 }
203 
sleepbtn_vms(cmd: cmdline::SleepCommand) -> std::result::Result<(), ()>204 fn sleepbtn_vms(cmd: cmdline::SleepCommand) -> std::result::Result<(), ()> {
205     vms_request(&VmRequest::Sleepbtn, cmd.socket_path)
206 }
207 
inject_gpe(cmd: cmdline::GpeCommand) -> std::result::Result<(), ()>208 fn inject_gpe(cmd: cmdline::GpeCommand) -> std::result::Result<(), ()> {
209     vms_request(&VmRequest::Gpe(cmd.gpe), cmd.socket_path)
210 }
211 
212 #[cfg(feature = "balloon")]
balloon_vms(cmd: cmdline::BalloonCommand) -> std::result::Result<(), ()>213 fn balloon_vms(cmd: cmdline::BalloonCommand) -> std::result::Result<(), ()> {
214     let command = BalloonControlCommand::Adjust {
215         num_bytes: cmd.num_bytes,
216     };
217     vms_request(&VmRequest::BalloonCommand(command), cmd.socket_path)
218 }
219 
220 #[cfg(feature = "balloon")]
balloon_stats(cmd: cmdline::BalloonStatsCommand) -> std::result::Result<(), ()>221 fn balloon_stats(cmd: cmdline::BalloonStatsCommand) -> std::result::Result<(), ()> {
222     let command = BalloonControlCommand::Stats {};
223     let request = &VmRequest::BalloonCommand(command);
224     let response = handle_request(request, cmd.socket_path)?;
225     match serde_json::to_string_pretty(&response) {
226         Ok(response_json) => println!("{}", response_json),
227         Err(e) => {
228             error!("Failed to serialize into JSON: {}", e);
229             return Err(());
230         }
231     }
232     match response {
233         VmResponse::BalloonStats { .. } => Ok(()),
234         _ => Err(()),
235     }
236 }
237 
modify_battery(cmd: cmdline::BatteryCommand) -> std::result::Result<(), ()>238 fn modify_battery(cmd: cmdline::BatteryCommand) -> std::result::Result<(), ()> {
239     do_modify_battery(
240         cmd.socket_path,
241         &cmd.battery_type,
242         &cmd.property,
243         &cmd.target,
244     )
245 }
246 
modify_vfio(cmd: cmdline::VfioCrosvmCommand) -> std::result::Result<(), ()>247 fn modify_vfio(cmd: cmdline::VfioCrosvmCommand) -> std::result::Result<(), ()> {
248     let (request, socket_path, vfio_path) = match cmd.command {
249         cmdline::VfioSubCommand::Add(c) => {
250             let request = VmRequest::HotPlugCommand {
251                 device: HotPlugDeviceInfo {
252                     device_type: HotPlugDeviceType::EndPoint,
253                     path: c.vfio_path.clone(),
254                     hp_interrupt: true,
255                 },
256                 add: true,
257             };
258             (request, c.socket_path, c.vfio_path)
259         }
260         cmdline::VfioSubCommand::Remove(c) => {
261             let request = VmRequest::HotPlugCommand {
262                 device: HotPlugDeviceInfo {
263                     device_type: HotPlugDeviceType::EndPoint,
264                     path: c.vfio_path.clone(),
265                     hp_interrupt: false,
266                 },
267                 add: false,
268             };
269             (request, c.socket_path, c.vfio_path)
270         }
271     };
272     if !vfio_path.exists() || !vfio_path.is_dir() {
273         error!("Invalid host sysfs path: {:?}", vfio_path);
274         return Err(());
275     }
276 
277     vms_request(&request, socket_path)?;
278     Ok(())
279 }
280 
281 #[cfg(feature = "composite-disk")]
create_composite(cmd: cmdline::CreateCompositeCommand) -> std::result::Result<(), ()>282 fn create_composite(cmd: cmdline::CreateCompositeCommand) -> std::result::Result<(), ()> {
283     use std::fs::File;
284     use std::path::PathBuf;
285 
286     let composite_image_path = &cmd.path;
287     let zero_filler_path = format!("{}.filler", composite_image_path);
288     let header_path = format!("{}.header", composite_image_path);
289     let footer_path = format!("{}.footer", composite_image_path);
290 
291     let mut composite_image_file = OpenOptions::new()
292         .create(true)
293         .read(true)
294         .write(true)
295         .truncate(true)
296         .open(composite_image_path)
297         .map_err(|e| {
298             error!(
299                 "Failed opening composite disk image file at '{}': {}",
300                 composite_image_path, e
301             );
302         })?;
303     create_zero_filler(&zero_filler_path).map_err(|e| {
304         error!(
305             "Failed to create zero filler file at '{}': {}",
306             &zero_filler_path, e
307         );
308     })?;
309     let mut header_file = OpenOptions::new()
310         .create(true)
311         .read(true)
312         .write(true)
313         .truncate(true)
314         .open(&header_path)
315         .map_err(|e| {
316             error!(
317                 "Failed opening header image file at '{}': {}",
318                 header_path, e
319             );
320         })?;
321     let mut footer_file = OpenOptions::new()
322         .create(true)
323         .read(true)
324         .write(true)
325         .truncate(true)
326         .open(&footer_path)
327         .map_err(|e| {
328             error!(
329                 "Failed opening footer image file at '{}': {}",
330                 footer_path, e
331             );
332         })?;
333 
334     let partitions = cmd
335         .partitions
336         .into_iter()
337         .map(|partition_arg| {
338             if let [label, path] = partition_arg.split(":").collect::<Vec<_>>()[..] {
339                 let partition_file = File::open(path)
340                     .map_err(|e| error!("Failed to open partition image: {}", e))?;
341 
342                 // Sparseness for composite disks is not user provided on Linux
343                 // (e.g. via an option), and it has no runtime effect.
344                 let size = create_disk_file(
345                     partition_file,
346                     /* is_sparse_file= */ true,
347                     disk::MAX_NESTING_DEPTH,
348                     Path::new(path),
349                 )
350                 .map_err(|e| error!("Failed to create DiskFile instance: {}", e))?
351                 .get_len()
352                 .map_err(|e| error!("Failed to get length of partition image: {}", e))?;
353                 Ok(PartitionInfo {
354                     label: label.to_owned(),
355                     path: Path::new(path).to_owned(),
356                     partition_type: ImagePartitionType::LinuxFilesystem,
357                     writable: false,
358                     size,
359                 })
360             } else {
361                 error!(
362                     "Must specify label and path for partition '{}', like LABEL:PATH",
363                     partition_arg
364                 );
365                 Err(())
366             }
367         })
368         .collect::<Result<Vec<_>, _>>()?;
369 
370     create_composite_disk(
371         &partitions,
372         &PathBuf::from(zero_filler_path),
373         &PathBuf::from(header_path),
374         &mut header_file,
375         &PathBuf::from(footer_path),
376         &mut footer_file,
377         &mut composite_image_file,
378     )
379     .map_err(|e| {
380         error!(
381             "Failed to create composite disk image at '{}': {}",
382             composite_image_path, e
383         );
384     })?;
385 
386     Ok(())
387 }
388 
389 #[cfg(feature = "qcow")]
create_qcow2(cmd: cmdline::CreateQcow2Command) -> std::result::Result<(), ()>390 fn create_qcow2(cmd: cmdline::CreateQcow2Command) -> std::result::Result<(), ()> {
391     if !(cmd.size.is_some() ^ cmd.backing_file.is_some()) {
392         println!(
393             "Create a new QCOW2 image at `PATH` of either the specified `SIZE` in bytes or
394     with a '--backing_file'."
395         );
396         return Err(());
397     }
398 
399     let file = OpenOptions::new()
400         .create(true)
401         .read(true)
402         .write(true)
403         .truncate(true)
404         .open(&cmd.file_path)
405         .map_err(|e| {
406             error!("Failed opening qcow file at '{}': {}", cmd.file_path, e);
407         })?;
408 
409     match (cmd.size, cmd.backing_file) {
410         (Some(size), None) => QcowFile::new(file, size).map_err(|e| {
411             error!("Failed to create qcow file at '{}': {}", cmd.file_path, e);
412         })?,
413         (None, Some(backing_file)) => {
414             QcowFile::new_from_backing(file, &backing_file, disk::MAX_NESTING_DEPTH).map_err(
415                 |e| {
416                     error!("Failed to create qcow file at '{}': {}", cmd.file_path, e);
417                 },
418             )?
419         }
420         _ => unreachable!(),
421     };
422     Ok(())
423 }
424 
start_device(opts: cmdline::DeviceCommand) -> std::result::Result<(), ()>425 fn start_device(opts: cmdline::DeviceCommand) -> std::result::Result<(), ()> {
426     if let Some(async_executor) = opts.async_executor {
427         cros_async::Executor::set_default_executor_kind(async_executor)
428             .map_err(|e| error!("Failed to set the default async executor: {:#}", e))?;
429     }
430 
431     let result = match opts.command {
432         cmdline::DeviceSubcommand::CrossPlatform(command) => match command {
433             CrossPlatformDevicesCommands::Block(cfg) => run_block_device(cfg),
434             #[cfg(feature = "gpu")]
435             CrossPlatformDevicesCommands::Gpu(cfg) => run_gpu_device(cfg),
436             CrossPlatformDevicesCommands::Net(cfg) => run_net_device(cfg),
437             #[cfg(feature = "audio")]
438             CrossPlatformDevicesCommands::Snd(cfg) => run_snd_device(cfg),
439         },
440         cmdline::DeviceSubcommand::Sys(command) => sys::start_device(command),
441     };
442 
443     result.map_err(|e| {
444         error!("Failed to run device: {:#}", e);
445     })
446 }
447 
disk_cmd(cmd: cmdline::DiskCommand) -> std::result::Result<(), ()>448 fn disk_cmd(cmd: cmdline::DiskCommand) -> std::result::Result<(), ()> {
449     match cmd.command {
450         cmdline::DiskSubcommand::Resize(cmd) => {
451             let request = VmRequest::DiskCommand {
452                 disk_index: cmd.disk_index,
453                 command: DiskControlCommand::Resize {
454                     new_size: cmd.disk_size,
455                 },
456             };
457             vms_request(&request, cmd.socket_path)
458         }
459     }
460 }
461 
make_rt(cmd: cmdline::MakeRTCommand) -> std::result::Result<(), ()>462 fn make_rt(cmd: cmdline::MakeRTCommand) -> std::result::Result<(), ()> {
463     vms_request(&VmRequest::MakeRT, cmd.socket_path)
464 }
465 
466 #[cfg(feature = "gpu")]
gpu_display_add(cmd: cmdline::GpuAddDisplaysCommand) -> ModifyGpuResult467 fn gpu_display_add(cmd: cmdline::GpuAddDisplaysCommand) -> ModifyGpuResult {
468     do_gpu_display_add(cmd.socket_path, cmd.gpu_display)
469 }
470 
471 #[cfg(feature = "gpu")]
gpu_display_list(cmd: cmdline::GpuListDisplaysCommand) -> ModifyGpuResult472 fn gpu_display_list(cmd: cmdline::GpuListDisplaysCommand) -> ModifyGpuResult {
473     do_gpu_display_list(cmd.socket_path)
474 }
475 
476 #[cfg(feature = "gpu")]
gpu_display_remove(cmd: cmdline::GpuRemoveDisplaysCommand) -> ModifyGpuResult477 fn gpu_display_remove(cmd: cmdline::GpuRemoveDisplaysCommand) -> ModifyGpuResult {
478     do_gpu_display_remove(cmd.socket_path, cmd.display_id)
479 }
480 
481 #[cfg(feature = "gpu")]
modify_gpu(cmd: cmdline::GpuCommand) -> std::result::Result<(), ()>482 fn modify_gpu(cmd: cmdline::GpuCommand) -> std::result::Result<(), ()> {
483     let result = match cmd.command {
484         cmdline::GpuSubCommand::AddDisplays(cmd) => gpu_display_add(cmd),
485         cmdline::GpuSubCommand::ListDisplays(cmd) => gpu_display_list(cmd),
486         cmdline::GpuSubCommand::RemoveDisplays(cmd) => gpu_display_remove(cmd),
487     };
488     match result {
489         Ok(response) => {
490             println!("{}", response);
491             Ok(())
492         }
493         Err(e) => {
494             println!("error {}", e);
495             Err(())
496         }
497     }
498 }
499 
usb_attach(cmd: UsbAttachCommand) -> ModifyUsbResult<UsbControlResult>500 fn usb_attach(cmd: UsbAttachCommand) -> ModifyUsbResult<UsbControlResult> {
501     let dev_path = Path::new(&cmd.dev_path);
502 
503     do_usb_attach(cmd.socket_path, dev_path)
504 }
505 
usb_detach(cmd: cmdline::UsbDetachCommand) -> ModifyUsbResult<UsbControlResult>506 fn usb_detach(cmd: cmdline::UsbDetachCommand) -> ModifyUsbResult<UsbControlResult> {
507     do_usb_detach(cmd.socket_path, cmd.port)
508 }
509 
usb_list(cmd: cmdline::UsbListCommand) -> ModifyUsbResult<UsbControlResult>510 fn usb_list(cmd: cmdline::UsbListCommand) -> ModifyUsbResult<UsbControlResult> {
511     do_usb_list(cmd.socket_path)
512 }
513 
modify_usb(cmd: cmdline::UsbCommand) -> std::result::Result<(), ()>514 fn modify_usb(cmd: cmdline::UsbCommand) -> std::result::Result<(), ()> {
515     let result = match cmd.command {
516         cmdline::UsbSubCommand::Attach(cmd) => usb_attach(cmd),
517         cmdline::UsbSubCommand::Detach(cmd) => usb_detach(cmd),
518         cmdline::UsbSubCommand::List(cmd) => usb_list(cmd),
519     };
520     match result {
521         Ok(response) => {
522             println!("{}", response);
523             Ok(())
524         }
525         Err(e) => {
526             println!("error {}", e);
527             Err(())
528         }
529     }
530 }
531 
snapshot_vm(cmd: cmdline::SnapshotCommand) -> std::result::Result<(), ()>532 fn snapshot_vm(cmd: cmdline::SnapshotCommand) -> std::result::Result<(), ()> {
533     use cmdline::SnapshotSubCommands::*;
534     let (socket_path, request) = match cmd.snapshot_command {
535         Take(path) => {
536             let req = VmRequest::Snapshot(SnapshotCommand::Take {
537                 snapshot_path: path.snapshot_path,
538             });
539             (path.socket_path, req)
540         }
541         Restore(path) => {
542             let req = VmRequest::Restore(RestoreCommand::Apply {
543                 restore_path: path.snapshot_path,
544             });
545             (path.socket_path, req)
546         }
547     };
548     let socket_path = Path::new(&socket_path);
549     vms_request(&request, socket_path)
550 }
551 
552 #[allow(clippy::unnecessary_wraps)]
pkg_version() -> std::result::Result<(), ()>553 fn pkg_version() -> std::result::Result<(), ()> {
554     const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
555     const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
556 
557     print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
558     match PKG_VERSION {
559         Some(v) => println!("-{}", v),
560         None => println!(),
561     }
562     Ok(())
563 }
564 
565 // Returns true if the argument is a flag (e.g. `-s` or `--long`).
566 //
567 // As a special case, `-` is not treated as a flag, since it is typically used to represent
568 // `stdin`/`stdout`.
is_flag(arg: &str) -> bool569 fn is_flag(arg: &str) -> bool {
570     arg.len() > 1 && arg.starts_with('-')
571 }
572 
573 // Perform transformations on `args_iter` to produce arguments suitable for parsing by `argh`.
prepare_argh_args<I: IntoIterator<Item = String>>(args_iter: I) -> Vec<String>574 fn prepare_argh_args<I: IntoIterator<Item = String>>(args_iter: I) -> Vec<String> {
575     let mut args: Vec<String> = Vec::default();
576     // http://b/235882579
577     for arg in args_iter {
578         match arg.as_str() {
579             "--host_ip" => {
580                 eprintln!("`--host_ip` option is deprecated!");
581                 eprintln!("Please use `--host-ip` instead");
582                 args.push("--host-ip".to_string());
583             }
584             "--balloon_bias_mib" => {
585                 eprintln!("`--balloon_bias_mib` option is deprecated!");
586                 eprintln!("Please use `--balloon-bias-mib` instead");
587                 args.push("--balloon-bias-mib".to_string());
588             }
589             "-h" => args.push("--help".to_string()),
590             arg if is_flag(arg) => {
591                 // Split `--arg=val` into `--arg val`, since argh doesn't support the former.
592                 if let Some((key, value)) = arg.split_once("=") {
593                     args.push(key.to_string());
594                     args.push(value.to_string());
595                 } else {
596                     args.push(arg.to_string());
597                 }
598             }
599             arg => args.push(arg.to_string()),
600         }
601     }
602 
603     args
604 }
605 
crosvm_main<I: IntoIterator<Item = String>>(args: I) -> Result<CommandStatus>606 fn crosvm_main<I: IntoIterator<Item = String>>(args: I) -> Result<CommandStatus> {
607     let _library_watcher = sys::get_library_watcher();
608 
609     // The following panic hook will stop our crashpad hook on windows.
610     // Only initialize when the crash-pad feature is off.
611     #[cfg(not(feature = "crash-report"))]
612     sys::set_panic_hook();
613 
614     // Ensure all processes detach from metrics on exit.
615     #[cfg(windows)]
616     let _metrics_destructor = metrics::get_destructor();
617 
618     let args = prepare_argh_args(args);
619     let args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
620     let args = match crosvm::cmdline::CrosvmCmdlineArgs::from_args(&args[..1], &args[1..]) {
621         Ok(args) => args,
622         Err(e) if e.status.is_ok() => {
623             // If parsing succeeded and the user requested --help, print the usage message to stdout
624             // and exit with success.
625             println!("{}", e.output);
626             return Ok(CommandStatus::SuccessOrVmStop);
627         }
628         Err(e) => {
629             error!("arg parsing failed: {}", e.output);
630             return Ok(CommandStatus::InvalidArgs);
631         }
632     };
633     let extended_status = args.extended_status;
634 
635     info!("CLI arguments parsed.");
636 
637     let mut log_config = LogConfig {
638         filter: &args.log_level,
639         proc_name: args.syslog_tag.unwrap_or("crosvm".to_string()),
640         syslog: !args.no_syslog,
641         ..Default::default()
642     };
643 
644     let ret = match args.command {
645         Command::CrossPlatform(command) => {
646             // Past this point, usage of exit is in danger of leaking zombie processes.
647             if let CrossPlatformCommands::Run(cmd) = command {
648                 if let Some(syslog_tag) = &cmd.syslog_tag {
649                     log_config.proc_name = syslog_tag.clone();
650                 }
651                 // We handle run_vm separately because it does not simply signal success/error
652                 // but also indicates whether the guest requested reset or stop.
653                 run_vm(cmd, log_config)
654             } else if let CrossPlatformCommands::Device(cmd) = command {
655                 // On windows, the device command handles its own logging setup, so we can't handle it below
656                 // otherwise logging will double init.
657                 if cfg!(unix) {
658                     syslog::init_with(log_config).context("failed to initialize syslog")?;
659                 }
660                 start_device(cmd)
661                     .map_err(|_| anyhow!("start_device subcommand failed"))
662                     .map(|_| CommandStatus::SuccessOrVmStop)
663             } else {
664                 syslog::init_with(log_config).context("failed to initialize syslog")?;
665 
666                 match command {
667                     #[cfg(feature = "balloon")]
668                     CrossPlatformCommands::Balloon(cmd) => {
669                         balloon_vms(cmd).map_err(|_| anyhow!("balloon subcommand failed"))
670                     }
671                     #[cfg(feature = "balloon")]
672                     CrossPlatformCommands::BalloonStats(cmd) => {
673                         balloon_stats(cmd).map_err(|_| anyhow!("balloon_stats subcommand failed"))
674                     }
675                     CrossPlatformCommands::Battery(cmd) => {
676                         modify_battery(cmd).map_err(|_| anyhow!("battery subcommand failed"))
677                     }
678                     #[cfg(feature = "composite-disk")]
679                     CrossPlatformCommands::CreateComposite(cmd) => create_composite(cmd)
680                         .map_err(|_| anyhow!("create_composite subcommand failed")),
681                     #[cfg(feature = "qcow")]
682                     CrossPlatformCommands::CreateQcow2(cmd) => {
683                         create_qcow2(cmd).map_err(|_| anyhow!("create_qcow2 subcommand failed"))
684                     }
685                     CrossPlatformCommands::Device(_) => unreachable!(),
686                     CrossPlatformCommands::Disk(cmd) => {
687                         disk_cmd(cmd).map_err(|_| anyhow!("disk subcommand failed"))
688                     }
689                     #[cfg(feature = "gpu")]
690                     CrossPlatformCommands::Gpu(cmd) => {
691                         modify_gpu(cmd).map_err(|_| anyhow!("gpu subcommand failed"))
692                     }
693                     CrossPlatformCommands::MakeRT(cmd) => {
694                         make_rt(cmd).map_err(|_| anyhow!("make_rt subcommand failed"))
695                     }
696                     CrossPlatformCommands::Resume(cmd) => {
697                         resume_vms(cmd).map_err(|_| anyhow!("resume subcommand failed"))
698                     }
699                     CrossPlatformCommands::Run(_) => unreachable!(),
700                     CrossPlatformCommands::Stop(cmd) => {
701                         stop_vms(cmd).map_err(|_| anyhow!("stop subcommand failed"))
702                     }
703                     CrossPlatformCommands::Suspend(cmd) => {
704                         suspend_vms(cmd).map_err(|_| anyhow!("suspend subcommand failed"))
705                     }
706                     CrossPlatformCommands::Swap(cmd) => {
707                         swap_vms(cmd).map_err(|_| anyhow!("swap subcommand failed"))
708                     }
709                     CrossPlatformCommands::Powerbtn(cmd) => {
710                         powerbtn_vms(cmd).map_err(|_| anyhow!("powerbtn subcommand failed"))
711                     }
712                     CrossPlatformCommands::Sleepbtn(cmd) => {
713                         sleepbtn_vms(cmd).map_err(|_| anyhow!("sleepbtn subcommand failed"))
714                     }
715                     CrossPlatformCommands::Gpe(cmd) => {
716                         inject_gpe(cmd).map_err(|_| anyhow!("gpe subcommand failed"))
717                     }
718                     CrossPlatformCommands::Usb(cmd) => {
719                         modify_usb(cmd).map_err(|_| anyhow!("usb subcommand failed"))
720                     }
721                     CrossPlatformCommands::Version(_) => {
722                         pkg_version().map_err(|_| anyhow!("version subcommand failed"))
723                     }
724                     CrossPlatformCommands::Vfio(cmd) => {
725                         modify_vfio(cmd).map_err(|_| anyhow!("vfio subcommand failed"))
726                     }
727                     CrossPlatformCommands::Snapshot(cmd) => {
728                         snapshot_vm(cmd).map_err(|_| anyhow!("snapshot subcommand failed"))
729                     }
730                 }
731                 .map(|_| CommandStatus::SuccessOrVmStop)
732             }
733         }
734         cmdline::Command::Sys(command) => {
735             // On windows, the sys commands handle their own logging setup, so we can't handle it
736             // below otherwise logging will double init.
737             if cfg!(unix) {
738                 syslog::init_with(log_config).context("failed to initialize syslog")?;
739             }
740             sys::run_command(command).map(|_| CommandStatus::SuccessOrVmStop)
741         }
742     };
743 
744     sys::cleanup();
745 
746     // WARNING: Any code added after this point is not guaranteed to run
747     // since we may forcibly kill this process (and its children) above.
748     ret.map(|s| {
749         if extended_status {
750             s
751         } else {
752             CommandStatus::SuccessOrVmStop
753         }
754     })
755 }
756 
main()757 fn main() {
758     syslog::early_init();
759     info!("crosvm started.");
760     let res = crosvm_main(std::env::args());
761 
762     let exit_code = match &res {
763         Ok(code) => {
764             info!("{}", code.message());
765             *code as i32
766         }
767         Err(e) => {
768             let exit_code = error_to_exit_code(&res);
769             error!("exiting with error {}: {:?}", exit_code, e);
770             exit_code
771         }
772     };
773     std::process::exit(exit_code);
774 }
775 
776 #[cfg(test)]
777 mod tests {
778     use super::*;
779 
780     #[test]
args_is_flag()781     fn args_is_flag() {
782         assert!(is_flag("--test"));
783         assert!(is_flag("-s"));
784 
785         assert!(!is_flag("-"));
786         assert!(!is_flag("no-leading-dash"));
787     }
788 
789     #[test]
args_split_long()790     fn args_split_long() {
791         assert_eq!(
792             prepare_argh_args(
793                 ["crosvm", "run", "--something=options", "vm_kernel"].map(|x| x.to_string())
794             ),
795             ["crosvm", "run", "--something", "options", "vm_kernel"]
796         );
797     }
798 
799     #[test]
args_split_short()800     fn args_split_short() {
801         assert_eq!(
802             prepare_argh_args(
803                 ["crosvm", "run", "-p=init=/bin/bash", "vm_kernel"].map(|x| x.to_string())
804             ),
805             ["crosvm", "run", "-p", "init=/bin/bash", "vm_kernel"]
806         );
807     }
808 
809     #[test]
args_host_ip()810     fn args_host_ip() {
811         assert_eq!(
812             prepare_argh_args(
813                 ["crosvm", "run", "--host_ip", "1.2.3.4", "vm_kernel"].map(|x| x.to_string())
814             ),
815             ["crosvm", "run", "--host-ip", "1.2.3.4", "vm_kernel"]
816         );
817     }
818 
819     #[test]
args_balloon_bias_mib()820     fn args_balloon_bias_mib() {
821         assert_eq!(
822             prepare_argh_args(
823                 ["crosvm", "run", "--balloon_bias_mib", "1234", "vm_kernel"].map(|x| x.to_string())
824             ),
825             ["crosvm", "run", "--balloon-bias-mib", "1234", "vm_kernel"]
826         );
827     }
828 
829     #[test]
args_h()830     fn args_h() {
831         assert_eq!(
832             prepare_argh_args(["crosvm", "run", "-h"].map(|x| x.to_string())),
833             ["crosvm", "run", "--help"]
834         );
835     }
836 
837     #[test]
args_battery_option()838     fn args_battery_option() {
839         assert_eq!(
840             prepare_argh_args(
841                 [
842                     "crosvm",
843                     "run",
844                     "--battery",
845                     "type=goldfish",
846                     "-p",
847                     "init=/bin/bash",
848                     "vm_kernel"
849                 ]
850                 .map(|x| x.to_string())
851             ),
852             [
853                 "crosvm",
854                 "run",
855                 "--battery",
856                 "type=goldfish",
857                 "-p",
858                 "init=/bin/bash",
859                 "vm_kernel"
860             ]
861         );
862     }
863 
864     #[test]
help_success()865     fn help_success() {
866         let args = ["crosvm", "--help"];
867         let res = crosvm_main(args.iter().map(|s| s.to_string()));
868         let status = res.expect("arg parsing should succeed");
869         assert_eq!(status, CommandStatus::SuccessOrVmStop);
870     }
871 
872     #[test]
invalid_arg_failure()873     fn invalid_arg_failure() {
874         let args = ["crosvm", "--heeeelp"];
875         let res = crosvm_main(args.iter().map(|s| s.to_string()));
876         let status = res.expect("arg parsing should succeed");
877         assert_eq!(status, CommandStatus::InvalidArgs);
878     }
879 }
880