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