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