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