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), ¶ms.socket_path),
184 Trim(params) => (VmRequest::Swap(SwapCommand::Trim), ¶ms.socket_path),
185 SwapOut(params) => (VmRequest::Swap(SwapCommand::SwapOut), ¶ms.socket_path),
186 Disable(params) => (VmRequest::Swap(SwapCommand::Disable), ¶ms.socket_path),
187 Status(params) => (VmRequest::Swap(SwapCommand::Status), ¶ms.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