• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Runs a virtual machine
6 //!
7 //! ## Feature flags
8 #![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
9 
10 #[cfg(any(feature = "composite-disk", feature = "qcow"))]
11 use std::fs::OpenOptions;
12 use std::path::Path;
13 
14 use anyhow::anyhow;
15 use anyhow::Context;
16 use anyhow::Result;
17 use argh::FromArgs;
18 use base::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), &params.socket_path),
210         Trim(params) => (VmRequest::Swap(SwapCommand::Trim), &params.socket_path),
211         SwapOut(params) => (VmRequest::Swap(SwapCommand::SwapOut), &params.socket_path),
212         Disable(params) => (
213             VmRequest::Swap(SwapCommand::Disable {
214                 slow_file_cleanup: params.slow_file_cleanup,
215             }),
216             &params.socket_path,
217         ),
218         Status(params) => (VmRequest::Swap(SwapCommand::Status), &params.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