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