1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
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 pub mod panic_hook;
8
9 use std::collections::BTreeMap;
10 use std::convert::TryFrom;
11 use std::default::Default;
12 use std::fs::{File, OpenOptions};
13 use std::io::{BufRead, BufReader};
14 use std::ops::Deref;
15 #[cfg(feature = "direct")]
16 use std::ops::RangeInclusive;
17 use std::path::{Path, PathBuf};
18 use std::str::FromStr;
19 use std::string::String;
20 use std::thread::sleep;
21 use std::time::Duration;
22
23 use arch::{set_default_serial_parameters, Pstore, VcpuAffinity};
24 use base::{debug, error, getpid, info, kill_process_group, pagesize, reap_child, syslog, warn};
25 #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
26 use crosvm::platform::GpuRenderServerParameters;
27 #[cfg(feature = "direct")]
28 use crosvm::{argument::parse_hex_or_decimal, DirectIoOption, HostPcieRootPortParameters};
29 use crosvm::{
30 argument::{self, print_help, set_arguments, Argument},
31 platform, BindMount, Config, Executable, FileBackedMappingParameters, GidMap, SharedDir,
32 TouchDeviceOption, VfioCommand, VhostUserFsOption, VhostUserOption, VhostUserWlOption,
33 VvuOption,
34 };
35 use devices::serial_device::{SerialHardware, SerialParameters};
36 use devices::virtio::block::block::DiskOption;
37 #[cfg(feature = "audio_cras")]
38 use devices::virtio::snd::cras_backend::Error as CrasSndError;
39 #[cfg(feature = "audio_cras")]
40 use devices::virtio::vhost::user::device::run_cras_snd_device;
41 use devices::virtio::vhost::user::device::{
42 run_block_device, run_console_device, run_fs_device, run_net_device, run_vsock_device,
43 run_wl_device,
44 };
45 use devices::virtio::vhost::vsock::VhostVsockDeviceParameter;
46 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
47 use devices::virtio::VideoBackendType;
48 #[cfg(feature = "gpu")]
49 use devices::virtio::{
50 gpu::{
51 GpuDisplayParameters, GpuMode, GpuParameters, DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH,
52 },
53 vhost::user::device::run_gpu_device,
54 };
55 #[cfg(feature = "direct")]
56 use devices::BusRange;
57 #[cfg(feature = "audio")]
58 use devices::{Ac97Backend, Ac97Parameters};
59 use devices::{PciAddress, PciClassCode, StubPciParameters};
60 use disk::{self, QcowFile};
61 #[cfg(feature = "composite-disk")]
62 use disk::{
63 create_composite_disk, create_disk_file, create_zero_filler, ImagePartitionType, PartitionInfo,
64 };
65 use hypervisor::ProtectionType;
66 use serde_keyvalue::from_key_values;
67 use uuid::Uuid;
68 use vm_control::{
69 client::{
70 do_modify_battery, do_usb_attach, do_usb_detach, do_usb_list, handle_request, vms_request,
71 ModifyUsbError, ModifyUsbResult,
72 },
73 BalloonControlCommand, BatteryType, DiskControlCommand, UsbControlResult, VmRequest,
74 VmResponse,
75 };
76
77 #[cfg(feature = "scudo")]
78 #[global_allocator]
79 static ALLOCATOR: scudo::GlobalScudoAllocator = scudo::GlobalScudoAllocator;
80
executable_is_plugin(executable: &Option<Executable>) -> bool81 fn executable_is_plugin(executable: &Option<Executable>) -> bool {
82 matches!(executable, Some(Executable::Plugin(_)))
83 }
84
85 // Wait for all children to exit. Return true if they have all exited, false
86 // otherwise.
wait_all_children() -> bool87 fn wait_all_children() -> bool {
88 const CHILD_WAIT_MAX_ITER: isize = 100;
89 const CHILD_WAIT_MS: u64 = 10;
90 for _ in 0..CHILD_WAIT_MAX_ITER {
91 loop {
92 match reap_child() {
93 Ok(0) => break,
94 // We expect ECHILD which indicates that there were no children left.
95 Err(e) if e.errno() == libc::ECHILD => return true,
96 Err(e) => {
97 warn!("error while waiting for children: {}", e);
98 return false;
99 }
100 // We reaped one child, so continue reaping.
101 _ => {}
102 }
103 }
104 // There's no timeout option for waitpid which reap_child calls internally, so our only
105 // recourse is to sleep while waiting for the children to exit.
106 sleep(Duration::from_millis(CHILD_WAIT_MS));
107 }
108
109 // If we've made it to this point, not all of the children have exited.
110 false
111 }
112
113 /// Parse a comma-separated list of CPU numbers and ranges and convert it to a Vec of CPU numbers.
parse_cpu_set(s: &str) -> argument::Result<Vec<usize>>114 fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
115 let mut cpuset = Vec::new();
116 for part in s.split(',') {
117 let range: Vec<&str> = part.split('-').collect();
118 if range.is_empty() || range.len() > 2 {
119 return Err(argument::Error::InvalidValue {
120 value: part.to_owned(),
121 expected: String::from("invalid list syntax"),
122 });
123 }
124 let first_cpu: usize = range[0]
125 .parse()
126 .map_err(|_| argument::Error::InvalidValue {
127 value: part.to_owned(),
128 expected: String::from("CPU index must be a non-negative integer"),
129 })?;
130 let last_cpu: usize = if range.len() == 2 {
131 range[1]
132 .parse()
133 .map_err(|_| argument::Error::InvalidValue {
134 value: part.to_owned(),
135 expected: String::from("CPU index must be a non-negative integer"),
136 })?
137 } else {
138 first_cpu
139 };
140
141 if last_cpu < first_cpu {
142 return Err(argument::Error::InvalidValue {
143 value: part.to_owned(),
144 expected: String::from("CPU ranges must be from low to high"),
145 });
146 }
147
148 for cpu in first_cpu..=last_cpu {
149 cpuset.push(cpu);
150 }
151 }
152 Ok(cpuset)
153 }
154
155 /// Parse a list of guest to host CPU mappings.
156 ///
157 /// Each mapping consists of a single guest CPU index mapped to one or more host CPUs in the form
158 /// accepted by `parse_cpu_set`:
159 ///
160 /// `<GUEST-CPU>=<HOST-CPU-SET>[:<GUEST-CPU>=<HOST-CPU-SET>[:...]]`
parse_cpu_affinity(s: &str) -> argument::Result<VcpuAffinity>161 fn parse_cpu_affinity(s: &str) -> argument::Result<VcpuAffinity> {
162 if s.contains('=') {
163 let mut affinity_map = BTreeMap::new();
164 for cpu_pair in s.split(':') {
165 let assignment: Vec<&str> = cpu_pair.split('=').collect();
166 if assignment.len() != 2 {
167 return Err(argument::Error::InvalidValue {
168 value: cpu_pair.to_owned(),
169 expected: String::from("invalid VCPU assignment syntax"),
170 });
171 }
172 let guest_cpu = assignment[0]
173 .parse()
174 .map_err(|_| argument::Error::InvalidValue {
175 value: assignment[0].to_owned(),
176 expected: String::from("CPU index must be a non-negative integer"),
177 })?;
178 let host_cpu_set = parse_cpu_set(assignment[1])?;
179 if affinity_map.insert(guest_cpu, host_cpu_set).is_some() {
180 return Err(argument::Error::InvalidValue {
181 value: cpu_pair.to_owned(),
182 expected: String::from("VCPU index must be unique"),
183 });
184 }
185 }
186 Ok(VcpuAffinity::PerVcpu(affinity_map))
187 } else {
188 Ok(VcpuAffinity::Global(parse_cpu_set(s)?))
189 }
190 }
191
parse_cpu_capacity(s: &str, cpu_capacity: &mut BTreeMap<usize, u32>) -> argument::Result<()>192 fn parse_cpu_capacity(s: &str, cpu_capacity: &mut BTreeMap<usize, u32>) -> argument::Result<()> {
193 for cpu_pair in s.split(',') {
194 let assignment: Vec<&str> = cpu_pair.split('=').collect();
195 if assignment.len() != 2 {
196 return Err(argument::Error::InvalidValue {
197 value: cpu_pair.to_owned(),
198 expected: String::from("invalid CPU capacity syntax"),
199 });
200 }
201 let cpu = assignment[0]
202 .parse()
203 .map_err(|_| argument::Error::InvalidValue {
204 value: assignment[0].to_owned(),
205 expected: String::from("CPU index must be a non-negative integer"),
206 })?;
207 let capacity = assignment[1]
208 .parse()
209 .map_err(|_| argument::Error::InvalidValue {
210 value: assignment[1].to_owned(),
211 expected: String::from("CPU capacity must be a non-negative integer"),
212 })?;
213 if cpu_capacity.insert(cpu, capacity).is_some() {
214 return Err(argument::Error::InvalidValue {
215 value: cpu_pair.to_owned(),
216 expected: String::from("CPU index must be unique"),
217 });
218 }
219 }
220 Ok(())
221 }
222
223 #[cfg(feature = "gpu")]
parse_gpu_options(s: Option<&str>, gpu_params: &mut GpuParameters) -> argument::Result<()>224 fn parse_gpu_options(s: Option<&str>, gpu_params: &mut GpuParameters) -> argument::Result<()> {
225 #[cfg(feature = "gfxstream")]
226 let mut vulkan_specified = false;
227 #[cfg(feature = "gfxstream")]
228 let mut syncfd_specified = false;
229 #[cfg(feature = "gfxstream")]
230 let mut angle_specified = false;
231
232 let mut display_w: Option<u32> = None;
233 let mut display_h: Option<u32> = None;
234
235 if let Some(s) = s {
236 let opts = s
237 .split(',')
238 .map(|frag| frag.split('='))
239 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
240
241 for (k, v) in opts {
242 match k {
243 // Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
244 // times if the user specifies several modes (--gpu=2d,virglrenderer,gfxstream)
245 "2d" | "2D" => {
246 gpu_params.mode = GpuMode::Mode2D;
247 }
248 "3d" | "3D" | "virglrenderer" => {
249 gpu_params.mode = GpuMode::ModeVirglRenderer;
250 }
251 #[cfg(feature = "gfxstream")]
252 "gfxstream" => {
253 gpu_params.mode = GpuMode::ModeGfxstream;
254 }
255 // Preferred: Specifying --gpu,backend=<mode>
256 "backend" => match v {
257 "2d" | "2D" => {
258 gpu_params.mode = GpuMode::Mode2D;
259 }
260 "3d" | "3D" | "virglrenderer" => {
261 gpu_params.mode = GpuMode::ModeVirglRenderer;
262 }
263 #[cfg(feature = "gfxstream")]
264 "gfxstream" => {
265 gpu_params.mode = GpuMode::ModeGfxstream;
266 }
267 _ => {
268 return Err(argument::Error::InvalidValue {
269 value: v.to_string(),
270 expected: String::from(
271 "gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)",
272 ),
273 });
274 }
275 },
276 "egl" => match v {
277 "true" | "" => {
278 gpu_params.renderer_use_egl = true;
279 }
280 "false" => {
281 gpu_params.renderer_use_egl = false;
282 }
283 _ => {
284 return Err(argument::Error::InvalidValue {
285 value: v.to_string(),
286 expected: String::from("gpu parameter 'egl' should be a boolean"),
287 });
288 }
289 },
290 "gles" => match v {
291 "true" | "" => {
292 gpu_params.renderer_use_gles = true;
293 }
294 "false" => {
295 gpu_params.renderer_use_gles = false;
296 }
297 _ => {
298 return Err(argument::Error::InvalidValue {
299 value: v.to_string(),
300 expected: String::from("gpu parameter 'gles' should be a boolean"),
301 });
302 }
303 },
304 "glx" => match v {
305 "true" | "" => {
306 gpu_params.renderer_use_glx = true;
307 }
308 "false" => {
309 gpu_params.renderer_use_glx = false;
310 }
311 _ => {
312 return Err(argument::Error::InvalidValue {
313 value: v.to_string(),
314 expected: String::from("gpu parameter 'glx' should be a boolean"),
315 });
316 }
317 },
318 "surfaceless" => match v {
319 "true" | "" => {
320 gpu_params.renderer_use_surfaceless = true;
321 }
322 "false" => {
323 gpu_params.renderer_use_surfaceless = false;
324 }
325 _ => {
326 return Err(argument::Error::InvalidValue {
327 value: v.to_string(),
328 expected: String::from(
329 "gpu parameter 'surfaceless' should be a boolean",
330 ),
331 });
332 }
333 },
334 #[cfg(feature = "gfxstream")]
335 "syncfd" => {
336 syncfd_specified = true;
337 match v {
338 "true" | "" => {
339 gpu_params.gfxstream_use_syncfd = true;
340 }
341 "false" => {
342 gpu_params.gfxstream_use_syncfd = false;
343 }
344 _ => {
345 return Err(argument::Error::InvalidValue {
346 value: v.to_string(),
347 expected: String::from(
348 "gpu parameter 'syncfd' should be a boolean",
349 ),
350 });
351 }
352 }
353 }
354 #[cfg(feature = "gfxstream")]
355 "angle" => {
356 angle_specified = true;
357 match v {
358 "true" | "" => {
359 gpu_params.gfxstream_use_guest_angle = true;
360 }
361 "false" => {
362 gpu_params.gfxstream_use_guest_angle = false;
363 }
364 _ => {
365 return Err(argument::Error::InvalidValue {
366 value: v.to_string(),
367 expected: String::from("gpu parameter 'angle' should be a boolean"),
368 });
369 }
370 }
371 }
372 "vulkan" => {
373 #[cfg(feature = "gfxstream")]
374 {
375 vulkan_specified = true;
376 }
377 match v {
378 "true" | "" => {
379 gpu_params.use_vulkan = true;
380 }
381 "false" => {
382 gpu_params.use_vulkan = false;
383 }
384 _ => {
385 return Err(argument::Error::InvalidValue {
386 value: v.to_string(),
387 expected: String::from(
388 "gpu parameter 'vulkan' should be a boolean",
389 ),
390 });
391 }
392 }
393 }
394 "width" => {
395 let width = v
396 .parse::<u32>()
397 .map_err(|_| argument::Error::InvalidValue {
398 value: v.to_string(),
399 expected: String::from("gpu parameter 'width' must be a valid integer"),
400 })?;
401 display_w = Some(width);
402 }
403 "height" => {
404 let height = v
405 .parse::<u32>()
406 .map_err(|_| argument::Error::InvalidValue {
407 value: v.to_string(),
408 expected: String::from(
409 "gpu parameter 'height' must be a valid integer",
410 ),
411 })?;
412 display_h = Some(height);
413 }
414 "cache-path" => gpu_params.cache_path = Some(v.to_string()),
415 "cache-size" => gpu_params.cache_size = Some(v.to_string()),
416 "udmabuf" => match v {
417 "true" | "" => {
418 gpu_params.udmabuf = true;
419 }
420 "false" => {
421 gpu_params.udmabuf = false;
422 }
423 _ => {
424 return Err(argument::Error::InvalidValue {
425 value: v.to_string(),
426 expected: String::from("gpu parameter 'udmabuf' should be a boolean"),
427 });
428 }
429 },
430 "" => {}
431 _ => {
432 return Err(argument::Error::UnknownArgument(format!(
433 "gpu parameter {}",
434 k
435 )));
436 }
437 }
438 }
439 }
440
441 if display_w.is_some() || display_h.is_some() {
442 if display_w.is_none() || display_h.is_none() {
443 return Err(argument::Error::InvalidValue {
444 value: s.unwrap_or("").to_string(),
445 expected: String::from(
446 "gpu must include both 'width' and 'height' if either is supplied",
447 ),
448 });
449 }
450
451 gpu_params.displays.push(GpuDisplayParameters {
452 width: display_w.unwrap(),
453 height: display_h.unwrap(),
454 });
455 }
456
457 #[cfg(feature = "gfxstream")]
458 {
459 if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream {
460 gpu_params.use_vulkan = true;
461 }
462
463 if syncfd_specified || angle_specified {
464 match gpu_params.mode {
465 GpuMode::ModeGfxstream => {}
466 _ => {
467 return Err(argument::Error::UnknownArgument(
468 "gpu parameter syncfd and angle are only supported for gfxstream backend"
469 .to_string(),
470 ));
471 }
472 }
473 }
474 }
475
476 Ok(())
477 }
478
479 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
parse_video_options(s: Option<&str>) -> argument::Result<VideoBackendType>480 fn parse_video_options(s: Option<&str>) -> argument::Result<VideoBackendType> {
481 const VALID_VIDEO_BACKENDS: &[&str] = &[
482 #[cfg(feature = "libvda")]
483 "libvda",
484 ];
485
486 match s {
487 None => {
488 cfg_if::cfg_if! {
489 if #[cfg(feature = "libvda")] {
490 Ok(VideoBackendType::Libvda)
491 }
492 }
493 }
494 #[cfg(feature = "libvda")]
495 Some("libvda") => Ok(VideoBackendType::Libvda),
496 #[cfg(feature = "libvda")]
497 Some("libvda-vd") => Ok(VideoBackendType::LibvdaVd),
498 Some(s) => Err(argument::Error::InvalidValue {
499 value: s.to_owned(),
500 expected: format!("should be one of ({})", VALID_VIDEO_BACKENDS.join("|")),
501 }),
502 }
503 }
504
505 #[cfg(feature = "gpu")]
parse_gpu_display_options( s: Option<&str>, gpu_params: &mut GpuParameters, ) -> argument::Result<()>506 fn parse_gpu_display_options(
507 s: Option<&str>,
508 gpu_params: &mut GpuParameters,
509 ) -> argument::Result<()> {
510 let mut display_w: Option<u32> = None;
511 let mut display_h: Option<u32> = None;
512
513 if let Some(s) = s {
514 let opts = s
515 .split(',')
516 .map(|frag| frag.split('='))
517 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
518
519 for (k, v) in opts {
520 match k {
521 "width" => {
522 let width = v
523 .parse::<u32>()
524 .map_err(|_| argument::Error::InvalidValue {
525 value: v.to_string(),
526 expected: String::from("gpu parameter 'width' must be a valid integer"),
527 })?;
528 display_w = Some(width);
529 }
530 "height" => {
531 let height = v
532 .parse::<u32>()
533 .map_err(|_| argument::Error::InvalidValue {
534 value: v.to_string(),
535 expected: String::from(
536 "gpu parameter 'height' must be a valid integer",
537 ),
538 })?;
539 display_h = Some(height);
540 }
541 "" => {}
542 _ => {
543 return Err(argument::Error::UnknownArgument(format!(
544 "gpu-display parameter {}",
545 k
546 )));
547 }
548 }
549 }
550 }
551
552 if display_w.is_none() || display_h.is_none() {
553 return Err(argument::Error::InvalidValue {
554 value: s.unwrap_or("").to_string(),
555 expected: String::from("gpu-display must include both 'width' and 'height'"),
556 });
557 }
558
559 gpu_params.displays.push(GpuDisplayParameters {
560 width: display_w.unwrap(),
561 height: display_h.unwrap(),
562 });
563
564 Ok(())
565 }
566
567 #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
parse_gpu_render_server_options(s: Option<&str>) -> argument::Result<GpuRenderServerParameters>568 fn parse_gpu_render_server_options(s: Option<&str>) -> argument::Result<GpuRenderServerParameters> {
569 let mut path: Option<PathBuf> = None;
570 let mut cache_path = None;
571 let mut cache_size = None;
572
573 if let Some(s) = s {
574 let opts = s
575 .split(',')
576 .map(|frag| frag.split('='))
577 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
578
579 for (k, v) in opts {
580 match k {
581 "path" => {
582 path =
583 Some(
584 PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
585 value: v.to_string(),
586 expected: e.to_string(),
587 })?,
588 )
589 }
590 "cache-path" => cache_path = Some(v.to_string()),
591 "cache-size" => cache_size = Some(v.to_string()),
592 "" => {}
593 _ => {
594 return Err(argument::Error::UnknownArgument(format!(
595 "gpu-render-server parameter {}",
596 k
597 )));
598 }
599 }
600 }
601 }
602
603 if let Some(p) = path {
604 Ok(GpuRenderServerParameters {
605 path: p,
606 cache_path,
607 cache_size,
608 })
609 } else {
610 Err(argument::Error::InvalidValue {
611 value: s.unwrap_or("").to_string(),
612 expected: String::from("gpu-render-server must include 'path'"),
613 })
614 }
615 }
616
617 #[cfg(feature = "audio")]
parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters>618 fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
619 let mut ac97_params: Ac97Parameters = Default::default();
620
621 let opts = s
622 .split(',')
623 .map(|frag| frag.split('='))
624 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
625
626 for (k, v) in opts {
627 match k {
628 "backend" => {
629 ac97_params.backend =
630 v.parse::<Ac97Backend>()
631 .map_err(|e| argument::Error::InvalidValue {
632 value: v.to_string(),
633 expected: e.to_string(),
634 })?;
635 }
636 "capture" => {
637 ac97_params.capture = v.parse::<bool>().map_err(|e| {
638 argument::Error::Syntax(format!("invalid capture option: {}", e))
639 })?;
640 }
641 #[cfg(feature = "audio_cras")]
642 "client_type" => {
643 ac97_params
644 .set_client_type(v)
645 .map_err(|e| argument::Error::InvalidValue {
646 value: v.to_string(),
647 expected: e.to_string(),
648 })?;
649 }
650 #[cfg(feature = "audio_cras")]
651 "socket_type" => {
652 ac97_params
653 .set_socket_type(v)
654 .map_err(|e| argument::Error::InvalidValue {
655 value: v.to_string(),
656 expected: e.to_string(),
657 })?;
658 }
659 #[cfg(any(target_os = "linux", target_os = "android"))]
660 "server" => {
661 ac97_params.vios_server_path =
662 Some(
663 PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
664 value: v.to_string(),
665 expected: e.to_string(),
666 })?,
667 );
668 }
669 _ => {
670 return Err(argument::Error::UnknownArgument(format!(
671 "unknown ac97 parameter {}",
672 k
673 )));
674 }
675 }
676 }
677
678 // server is required for and exclusive to vios backend
679 #[cfg(any(target_os = "linux", target_os = "android"))]
680 match ac97_params.backend {
681 Ac97Backend::VIOS => {
682 if ac97_params.vios_server_path.is_none() {
683 return Err(argument::Error::ExpectedArgument(String::from(
684 "server argument is required for VIOS backend",
685 )));
686 }
687 }
688 _ => {
689 if ac97_params.vios_server_path.is_some() {
690 return Err(argument::Error::UnexpectedValue(String::from(
691 "server argument is exclusive to the VIOS backend",
692 )));
693 }
694 }
695 }
696
697 Ok(ac97_params)
698 }
699
700 enum MsrAction {
701 Invalid,
702 /// Read MSR value from host CPU0 regardless of current vcpu.
703 ReadFromCPU0,
704 }
705
parse_userspace_msr_options(value: &str) -> argument::Result<u32>706 fn parse_userspace_msr_options(value: &str) -> argument::Result<u32> {
707 // TODO(b/215297064): Implement different type of operations, such
708 // as write or reading from the correct CPU.
709 let mut options = argument::parse_key_value_options("userspace-msr", value, ',');
710 let index: u32 = options
711 .next()
712 .ok_or(argument::Error::ExpectedValue(String::from(
713 "userspace-msr: expected index",
714 )))?
715 .key_numeric()?;
716 let mut msr_config = MsrAction::Invalid;
717 for opt in options {
718 match opt.key() {
719 "action" => match opt.value()? {
720 "r0" => msr_config = MsrAction::ReadFromCPU0,
721 _ => return Err(opt.invalid_value_err(String::from("bad action"))),
722 },
723 _ => return Err(opt.invalid_key_err()),
724 }
725 }
726
727 match msr_config {
728 MsrAction::ReadFromCPU0 => Ok(index),
729 _ => Err(argument::Error::UnknownArgument(
730 "userspace-msr action not specified".to_string(),
731 )),
732 }
733 }
734
parse_serial_options(s: &str) -> argument::Result<SerialParameters>735 fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
736 let serial_setting: SerialParameters =
737 from_key_values(s).map_err(|e| argument::Error::ConfigParserError(e.to_string()))?;
738
739 if serial_setting.stdin && serial_setting.input.is_some() {
740 return Err(argument::Error::TooManyArguments(
741 "Cannot specify both stdin and input options".to_string(),
742 ));
743 }
744 if serial_setting.num < 1 {
745 return Err(argument::Error::InvalidValue {
746 value: serial_setting.num.to_string(),
747 expected: String::from("Serial port num must be at least 1"),
748 });
749 }
750
751 if serial_setting.hardware == SerialHardware::Serial && serial_setting.num > 4 {
752 return Err(argument::Error::InvalidValue {
753 value: serial_setting.num.to_string(),
754 expected: String::from("Serial port num must be 4 or less"),
755 });
756 }
757
758 Ok(serial_setting)
759 }
760
parse_plugin_mount_option(value: &str) -> argument::Result<BindMount>761 fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> {
762 let components: Vec<&str> = value.split(':').collect();
763 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
764 return Err(argument::Error::InvalidValue {
765 value: value.to_owned(),
766 expected: String::from(
767 "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
768 ),
769 });
770 }
771
772 let src = PathBuf::from(components[0]);
773 if src.is_relative() {
774 return Err(argument::Error::InvalidValue {
775 value: components[0].to_owned(),
776 expected: String::from("the source path for `plugin-mount` must be absolute"),
777 });
778 }
779 if !src.exists() {
780 return Err(argument::Error::InvalidValue {
781 value: components[0].to_owned(),
782 expected: String::from("the source path for `plugin-mount` does not exist"),
783 });
784 }
785
786 let dst = PathBuf::from(match components.get(1) {
787 None | Some(&"") => components[0],
788 Some(path) => path,
789 });
790 if dst.is_relative() {
791 return Err(argument::Error::InvalidValue {
792 value: components[1].to_owned(),
793 expected: String::from("the destination path for `plugin-mount` must be absolute"),
794 });
795 }
796
797 let writable: bool = match components.get(2) {
798 None => false,
799 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
800 value: components[2].to_owned(),
801 expected: String::from("the <writable> component for `plugin-mount` is not valid bool"),
802 })?,
803 };
804
805 Ok(BindMount { src, dst, writable })
806 }
807
parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap>808 fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> {
809 let components: Vec<&str> = value.split(':').collect();
810 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
811 return Err(argument::Error::InvalidValue {
812 value: value.to_owned(),
813 expected: String::from(
814 "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
815 ),
816 });
817 }
818
819 let inner: libc::gid_t = components[0]
820 .parse()
821 .map_err(|_| argument::Error::InvalidValue {
822 value: components[0].to_owned(),
823 expected: String::from("the <inner> component for `plugin-gid-map` is not valid gid"),
824 })?;
825
826 let outer: libc::gid_t = match components.get(1) {
827 None | Some(&"") => inner,
828 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
829 value: components[1].to_owned(),
830 expected: String::from("the <outer> component for `plugin-gid-map` is not valid gid"),
831 })?,
832 };
833
834 let count: u32 = match components.get(2) {
835 None => 1,
836 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
837 value: components[2].to_owned(),
838 expected: String::from(
839 "the <count> component for `plugin-gid-map` is not valid number",
840 ),
841 })?,
842 };
843
844 Ok(GidMap {
845 inner,
846 outer,
847 count,
848 })
849 }
850
parse_battery_options(s: Option<&str>) -> argument::Result<BatteryType>851 fn parse_battery_options(s: Option<&str>) -> argument::Result<BatteryType> {
852 let mut battery_type: BatteryType = Default::default();
853
854 if let Some(s) = s {
855 let opts = s
856 .split(',')
857 .map(|frag| frag.split('='))
858 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
859
860 for (k, v) in opts {
861 match k {
862 "type" => match v.parse::<BatteryType>() {
863 Ok(type_) => battery_type = type_,
864 Err(e) => {
865 return Err(argument::Error::InvalidValue {
866 value: v.to_string(),
867 expected: e.to_string(),
868 });
869 }
870 },
871 "" => {}
872 _ => {
873 return Err(argument::Error::UnknownArgument(format!(
874 "battery parameter {}",
875 k
876 )));
877 }
878 }
879 }
880 }
881
882 Ok(battery_type)
883 }
884
885 #[cfg(feature = "direct")]
parse_direct_io_options(s: Option<&str>) -> argument::Result<DirectIoOption>886 fn parse_direct_io_options(s: Option<&str>) -> argument::Result<DirectIoOption> {
887 let s = s.ok_or(argument::Error::ExpectedValue(String::from(
888 "expected path@range[,range] value",
889 )))?;
890 let parts: Vec<&str> = s.splitn(2, '@').collect();
891 if parts.len() != 2 {
892 return Err(argument::Error::InvalidValue {
893 value: s.to_string(),
894 expected: String::from("missing port range, use /path@X-Y,Z,.. syntax"),
895 });
896 }
897 let path = PathBuf::from(parts[0]);
898 if !path.exists() {
899 return Err(argument::Error::InvalidValue {
900 value: parts[0].to_owned(),
901 expected: String::from("the path does not exist"),
902 });
903 };
904 let ranges: argument::Result<Vec<BusRange>> = parts[1]
905 .split(',')
906 .map(|frag| frag.split('-'))
907 .map(|mut range| {
908 let base = range
909 .next()
910 .map(|v| parse_hex_or_decimal(v))
911 .map_or(Ok(None), |r| r.map(Some));
912 let last = range
913 .next()
914 .map(|v| parse_hex_or_decimal(v))
915 .map_or(Ok(None), |r| r.map(Some));
916 (base, last)
917 })
918 .map(|range| match range {
919 (Ok(Some(base)), Ok(None)) => Ok(BusRange { base, len: 1 }),
920 (Ok(Some(base)), Ok(Some(last))) => Ok(BusRange {
921 base,
922 len: last.saturating_sub(base).saturating_add(1),
923 }),
924 (Err(_), _) => Err(argument::Error::InvalidValue {
925 value: s.to_owned(),
926 expected: String::from("invalid base range value"),
927 }),
928 (_, Err(_)) => Err(argument::Error::InvalidValue {
929 value: s.to_owned(),
930 expected: String::from("invalid last range value"),
931 }),
932 _ => Err(argument::Error::InvalidValue {
933 value: s.to_owned(),
934 expected: String::from("invalid range format"),
935 }),
936 })
937 .collect();
938 Ok(DirectIoOption {
939 path,
940 ranges: ranges?,
941 })
942 }
943
parse_stub_pci_parameters(s: Option<&str>) -> argument::Result<StubPciParameters>944 fn parse_stub_pci_parameters(s: Option<&str>) -> argument::Result<StubPciParameters> {
945 let s = s.ok_or(argument::Error::ExpectedValue(String::from(
946 "stub-pci-device configuration expected",
947 )))?;
948
949 let mut options = argument::parse_key_value_options("stub-pci-device", s, ',');
950 let addr = options
951 .next()
952 .ok_or(argument::Error::ExpectedValue(String::from(
953 "stub-pci-device: expected device address",
954 )))?
955 .key();
956 let mut params = StubPciParameters {
957 address: PciAddress::from_str(addr).map_err(|e| argument::Error::InvalidValue {
958 value: addr.to_owned(),
959 expected: format!("stub-pci-device: expected PCI address: {}", e),
960 })?,
961 vendor_id: 0,
962 device_id: 0,
963 class: PciClassCode::Other,
964 subclass: 0,
965 programming_interface: 0,
966 subsystem_device_id: 0,
967 subsystem_vendor_id: 0,
968 revision_id: 0,
969 };
970 for opt in options {
971 match opt.key() {
972 "vendor" => params.vendor_id = opt.parse_numeric::<u16>()?,
973 "device" => params.device_id = opt.parse_numeric::<u16>()?,
974 "class" => {
975 let class = opt.parse_numeric::<u32>()?;
976 params.class = PciClassCode::try_from((class >> 16) as u8)
977 .map_err(|_| opt.invalid_value_err(String::from("Unknown class code")))?;
978 params.subclass = (class >> 8) as u8;
979 params.programming_interface = class as u8;
980 }
981 "multifunction" => {} // Ignore but allow the multifunction option for compatibility.
982 "subsystem_vendor" => params.subsystem_vendor_id = opt.parse_numeric::<u16>()?,
983 "subsystem_device" => params.subsystem_device_id = opt.parse_numeric::<u16>()?,
984 "revision" => params.revision_id = opt.parse_numeric::<u8>()?,
985 _ => return Err(opt.invalid_key_err()),
986 }
987 }
988
989 Ok(params)
990 }
991
parse_file_backed_mapping(s: Option<&str>) -> argument::Result<FileBackedMappingParameters>992 fn parse_file_backed_mapping(s: Option<&str>) -> argument::Result<FileBackedMappingParameters> {
993 let s = s.ok_or(argument::Error::ExpectedValue(String::from(
994 "file-backed-mapping: memory mapping option value required",
995 )))?;
996
997 let mut address = None;
998 let mut size = None;
999 let mut path = None;
1000 let mut offset = None;
1001 let mut writable = false;
1002 let mut sync = false;
1003 let mut align = false;
1004 for opt in argument::parse_key_value_options("file-backed-mapping", s, ',') {
1005 match opt.key() {
1006 "addr" => address = Some(opt.parse_numeric::<u64>()?),
1007 "size" => size = Some(opt.parse_numeric::<u64>()?),
1008 "path" => path = Some(PathBuf::from(opt.value()?)),
1009 "offset" => offset = Some(opt.parse_numeric::<u64>()?),
1010 "ro" => writable = !opt.parse_or::<bool>(true)?,
1011 "rw" => writable = opt.parse_or::<bool>(true)?,
1012 "sync" => sync = opt.parse_or::<bool>(true)?,
1013 "align" => align = opt.parse_or::<bool>(true)?,
1014 _ => return Err(opt.invalid_key_err()),
1015 }
1016 }
1017
1018 let (address, path, size) = match (address, path, size) {
1019 (Some(a), Some(p), Some(s)) => (a, p, s),
1020 _ => {
1021 return Err(argument::Error::ExpectedValue(String::from(
1022 "file-backed-mapping: address, size, and path parameters are required",
1023 )))
1024 }
1025 };
1026
1027 let pagesize_mask = pagesize() as u64 - 1;
1028 let aligned_address = address & !pagesize_mask;
1029 let aligned_size = ((address + size + pagesize_mask) & !pagesize_mask) - aligned_address;
1030
1031 if !align && (aligned_address != address || aligned_size != size) {
1032 return Err(argument::Error::InvalidValue {
1033 value: s.to_owned(),
1034 expected: String::from("addr and size parameters must be page size aligned"),
1035 });
1036 }
1037
1038 Ok(FileBackedMappingParameters {
1039 address: aligned_address,
1040 size: aligned_size,
1041 path,
1042 offset: offset.unwrap_or(0),
1043 writable,
1044 sync,
1045 })
1046 }
1047
set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()>1048 fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
1049 match name {
1050 "" => {
1051 if cfg.executable_path.is_some() {
1052 return Err(argument::Error::TooManyArguments(format!(
1053 "A VM executable was already specified: {:?}",
1054 cfg.executable_path
1055 )));
1056 }
1057 let kernel_path = PathBuf::from(value.unwrap());
1058 if !kernel_path.exists() {
1059 return Err(argument::Error::InvalidValue {
1060 value: value.unwrap().to_owned(),
1061 expected: String::from("this kernel path does not exist"),
1062 });
1063 }
1064 cfg.executable_path = Some(Executable::Kernel(kernel_path));
1065 }
1066 "kvm-device" => {
1067 let kvm_device_path = PathBuf::from(value.unwrap());
1068 if !kvm_device_path.exists() {
1069 return Err(argument::Error::InvalidValue {
1070 value: value.unwrap().to_owned(),
1071 expected: String::from("this kvm device path does not exist"),
1072 });
1073 }
1074
1075 cfg.kvm_device_path = kvm_device_path;
1076 }
1077 "vhost-vsock-fd" => {
1078 if cfg.vhost_vsock_device.is_some() {
1079 return Err(argument::Error::InvalidValue {
1080 value: value.unwrap().to_owned(),
1081 expected: String::from("A vhost-vsock device was already specified"),
1082 });
1083 }
1084 cfg.vhost_vsock_device = Some(VhostVsockDeviceParameter::Fd(
1085 value
1086 .unwrap()
1087 .parse()
1088 .map_err(|_| argument::Error::InvalidValue {
1089 value: value.unwrap().to_owned(),
1090 expected: String::from(
1091 "this value for `vhost-vsock-fd` needs to be integer",
1092 ),
1093 })?,
1094 ));
1095 }
1096 "vhost-vsock-device" => {
1097 if cfg.vhost_vsock_device.is_some() {
1098 return Err(argument::Error::InvalidValue {
1099 value: value.unwrap().to_owned(),
1100 expected: String::from("A vhost-vsock device was already specified"),
1101 });
1102 }
1103 let vhost_vsock_device_path = PathBuf::from(value.unwrap());
1104 if !vhost_vsock_device_path.exists() {
1105 return Err(argument::Error::InvalidValue {
1106 value: value.unwrap().to_owned(),
1107 expected: String::from("this vhost-vsock device path does not exist"),
1108 });
1109 }
1110
1111 cfg.vhost_vsock_device = Some(VhostVsockDeviceParameter::Path(vhost_vsock_device_path));
1112 }
1113 "vhost-net-device" => {
1114 let vhost_net_device_path = PathBuf::from(value.unwrap());
1115 if !vhost_net_device_path.exists() {
1116 return Err(argument::Error::InvalidValue {
1117 value: value.unwrap().to_owned(),
1118 expected: String::from("this vhost-vsock device path does not exist"),
1119 });
1120 }
1121
1122 cfg.vhost_net_device_path = vhost_net_device_path;
1123 }
1124 "android-fstab" => {
1125 if cfg.android_fstab.is_some()
1126 && !cfg.android_fstab.as_ref().unwrap().as_os_str().is_empty()
1127 {
1128 return Err(argument::Error::TooManyArguments(
1129 "expected exactly one android fstab path".to_owned(),
1130 ));
1131 } else {
1132 let android_fstab = PathBuf::from(value.unwrap());
1133 if !android_fstab.exists() {
1134 return Err(argument::Error::InvalidValue {
1135 value: value.unwrap().to_owned(),
1136 expected: String::from("this android fstab path does not exist"),
1137 });
1138 }
1139 cfg.android_fstab = Some(android_fstab);
1140 }
1141 }
1142 "params" => {
1143 cfg.params.push(value.unwrap().to_owned());
1144 }
1145 "cpus" => {
1146 if cfg.vcpu_count.is_some() {
1147 return Err(argument::Error::TooManyArguments(
1148 "`cpus` already given".to_owned(),
1149 ));
1150 }
1151 cfg.vcpu_count =
1152 Some(
1153 value
1154 .unwrap()
1155 .parse()
1156 .map_err(|_| argument::Error::InvalidValue {
1157 value: value.unwrap().to_owned(),
1158 expected: String::from("this value for `cpus` needs to be integer"),
1159 })?,
1160 )
1161 }
1162 "cpu-affinity" => {
1163 if cfg.vcpu_affinity.is_some() {
1164 return Err(argument::Error::TooManyArguments(
1165 "`cpu-affinity` already given".to_owned(),
1166 ));
1167 }
1168 cfg.vcpu_affinity = Some(parse_cpu_affinity(value.unwrap())?);
1169 }
1170 "cpu-cluster" => {
1171 cfg.cpu_clusters.push(parse_cpu_set(value.unwrap())?);
1172 }
1173 "cpu-capacity" => {
1174 parse_cpu_capacity(value.unwrap(), &mut cfg.cpu_capacity)?;
1175 }
1176 "per-vm-core-scheduling" => {
1177 cfg.per_vm_core_scheduling = true;
1178 }
1179 "vcpu-cgroup-path" => {
1180 let vcpu_cgroup_path = PathBuf::from(value.unwrap());
1181 if !vcpu_cgroup_path.exists() {
1182 return Err(argument::Error::InvalidValue {
1183 value: value.unwrap().to_owned(),
1184 expected: String::from("This vcpu_cgroup_path path does not exist"),
1185 });
1186 }
1187
1188 cfg.vcpu_cgroup_path = Some(vcpu_cgroup_path);
1189 }
1190 #[cfg(feature = "audio_cras")]
1191 "cras-snd" => {
1192 cfg.cras_snds.push(
1193 value
1194 .unwrap()
1195 .parse()
1196 .map_err(|e: CrasSndError| argument::Error::Syntax(e.to_string()))?,
1197 );
1198 }
1199 "no-smt" => {
1200 cfg.no_smt = true;
1201 }
1202 "rt-cpus" => {
1203 if !cfg.rt_cpus.is_empty() {
1204 return Err(argument::Error::TooManyArguments(
1205 "`rt-cpus` already given".to_owned(),
1206 ));
1207 }
1208 cfg.rt_cpus = parse_cpu_set(value.unwrap())?;
1209 }
1210 "delay-rt" => {
1211 cfg.delay_rt = true;
1212 }
1213 "mem" => {
1214 if cfg.memory.is_some() {
1215 return Err(argument::Error::TooManyArguments(
1216 "`mem` already given".to_owned(),
1217 ));
1218 }
1219 cfg.memory =
1220 Some(
1221 value
1222 .unwrap()
1223 .parse()
1224 .map_err(|_| argument::Error::InvalidValue {
1225 value: value.unwrap().to_owned(),
1226 expected: String::from("this value for `mem` needs to be integer"),
1227 })?,
1228 )
1229 }
1230 #[cfg(target_arch = "aarch64")]
1231 "swiotlb" => {
1232 if cfg.swiotlb.is_some() {
1233 return Err(argument::Error::TooManyArguments(
1234 "`swiotlb` already given".to_owned(),
1235 ));
1236 }
1237 cfg.swiotlb =
1238 Some(
1239 value
1240 .unwrap()
1241 .parse()
1242 .map_err(|_| argument::Error::InvalidValue {
1243 value: value.unwrap().to_owned(),
1244 expected: String::from("this value for `swiotlb` needs to be integer"),
1245 })?,
1246 )
1247 }
1248 "hugepages" => {
1249 cfg.hugepages = true;
1250 }
1251 #[cfg(feature = "audio")]
1252 "ac97" => {
1253 let ac97_params = parse_ac97_options(value.unwrap())?;
1254 // Add kernel parameters related to the intel8x0 driver for ac97 devices once.
1255 if cfg.ac97_parameters.is_empty() {
1256 // Set `inside_vm=1` to save some register read ops in the driver.
1257 cfg.params.push("snd_intel8x0.inside_vm=1".to_string());
1258 // Set `ac97_clock=48000` to save intel8x0_measure_ac97_clock call in the driver.
1259 cfg.params.push("snd_intel8x0.ac97_clock=48000".to_string());
1260 }
1261 cfg.ac97_parameters.push(ac97_params);
1262 }
1263 #[cfg(feature = "audio")]
1264 "sound" => {
1265 let client_path = PathBuf::from(value.unwrap());
1266 cfg.sound = Some(client_path);
1267 }
1268 "serial" => {
1269 let serial_params = parse_serial_options(value.unwrap())?;
1270 let num = serial_params.num;
1271 let key = (serial_params.hardware, num);
1272 if cfg.serial_parameters.contains_key(&key) {
1273 return Err(argument::Error::TooManyArguments(format!(
1274 "serial hardware {} num {}",
1275 serial_params.hardware, num,
1276 )));
1277 }
1278
1279 if serial_params.console {
1280 for params in cfg.serial_parameters.values() {
1281 if params.console {
1282 return Err(argument::Error::TooManyArguments(format!(
1283 "{} device {} already set as console",
1284 params.hardware, params.num,
1285 )));
1286 }
1287 }
1288 }
1289
1290 if serial_params.earlycon {
1291 // Only SerialHardware::Serial supports earlycon= currently.
1292 match serial_params.hardware {
1293 SerialHardware::Serial => {}
1294 _ => {
1295 return Err(argument::Error::InvalidValue {
1296 value: serial_params.hardware.to_string(),
1297 expected: String::from("earlycon not supported for hardware"),
1298 });
1299 }
1300 }
1301 for params in cfg.serial_parameters.values() {
1302 if params.earlycon {
1303 return Err(argument::Error::TooManyArguments(format!(
1304 "{} device {} already set as earlycon",
1305 params.hardware, params.num,
1306 )));
1307 }
1308 }
1309 }
1310
1311 if serial_params.stdin {
1312 if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) {
1313 return Err(argument::Error::TooManyArguments(format!(
1314 "{} device {} already connected to standard input",
1315 previous_stdin.hardware, previous_stdin.num,
1316 )));
1317 }
1318 }
1319
1320 cfg.serial_parameters.insert(key, serial_params);
1321 }
1322 "syslog-tag" => {
1323 if cfg.syslog_tag.is_some() {
1324 return Err(argument::Error::TooManyArguments(
1325 "`syslog-tag` already given".to_owned(),
1326 ));
1327 }
1328 syslog::set_proc_name(value.unwrap());
1329 cfg.syslog_tag = Some(value.unwrap().to_owned());
1330 }
1331 "root" | "rwroot" | "disk" | "rwdisk" => {
1332 let value = value.ok_or(argument::Error::ExpectedArgument(
1333 "path to the disk image is missing".to_owned(),
1334 ))?;
1335 let mut params: DiskOption = from_key_values(value).map_err(|e| {
1336 argument::Error::Syntax(format!("while parsing \"{}\" parameter: {}", name, e))
1337 })?;
1338
1339 if !name.starts_with("rw") {
1340 params.read_only = true;
1341 }
1342
1343 let disk_path = ¶ms.path;
1344 if !disk_path.exists() {
1345 return Err(argument::Error::InvalidValue {
1346 value: disk_path.to_string_lossy().into_owned(),
1347 expected: String::from("this disk path does not exist"),
1348 });
1349 }
1350 if name.ends_with("root") {
1351 if cfg.disks.len() >= 26 {
1352 return Err(argument::Error::TooManyArguments(
1353 "ran out of letters for to assign to root disk".to_owned(),
1354 ));
1355 }
1356 cfg.params.push(format!(
1357 "root=/dev/vd{} {}",
1358 char::from(b'a' + cfg.disks.len() as u8),
1359 if params.read_only { "ro" } else { "rw" }
1360 ));
1361 }
1362
1363 cfg.disks.push(params);
1364 }
1365 "pmem-device" | "rw-pmem-device" => {
1366 let disk_path = PathBuf::from(value.unwrap());
1367 if !disk_path.exists() {
1368 return Err(argument::Error::InvalidValue {
1369 value: value.unwrap().to_owned(),
1370 expected: String::from("this disk path does not exist"),
1371 });
1372 }
1373
1374 cfg.pmem_devices.push(DiskOption {
1375 path: disk_path,
1376 read_only: !name.starts_with("rw"),
1377 sparse: false,
1378 o_direct: false,
1379 block_size: base::pagesize() as u32,
1380 id: None,
1381 });
1382 }
1383 "pstore" => {
1384 if cfg.pstore.is_some() {
1385 return Err(argument::Error::TooManyArguments(
1386 "`pstore` already given".to_owned(),
1387 ));
1388 }
1389
1390 let value = value.unwrap();
1391 let components: Vec<&str> = value.split(',').collect();
1392 if components.len() != 2 {
1393 return Err(argument::Error::InvalidValue {
1394 value: value.to_owned(),
1395 expected: String::from(
1396 "pstore must have exactly 2 components: path=<path>,size=<size>",
1397 ),
1398 });
1399 }
1400 cfg.pstore = Some(Pstore {
1401 path: {
1402 if components[0].len() <= 5 || !components[0].starts_with("path=") {
1403 return Err(argument::Error::InvalidValue {
1404 value: components[0].to_owned(),
1405 expected: String::from("pstore path must follow with `path=`"),
1406 });
1407 };
1408 PathBuf::from(&components[0][5..])
1409 },
1410 size: {
1411 if components[1].len() <= 5 || !components[1].starts_with("size=") {
1412 return Err(argument::Error::InvalidValue {
1413 value: components[1].to_owned(),
1414 expected: String::from("pstore size must follow with `size=`"),
1415 });
1416 };
1417 components[1][5..]
1418 .parse()
1419 .map_err(|_| argument::Error::InvalidValue {
1420 value: value.to_owned(),
1421 expected: String::from("pstore size must be an integer"),
1422 })?
1423 },
1424 });
1425 }
1426 "host_ip" => {
1427 if cfg.host_ip.is_some() {
1428 return Err(argument::Error::TooManyArguments(
1429 "`host_ip` already given".to_owned(),
1430 ));
1431 }
1432 cfg.host_ip =
1433 Some(
1434 value
1435 .unwrap()
1436 .parse()
1437 .map_err(|_| argument::Error::InvalidValue {
1438 value: value.unwrap().to_owned(),
1439 expected: String::from("`host_ip` needs to be in the form \"x.x.x.x\""),
1440 })?,
1441 )
1442 }
1443 "netmask" => {
1444 if cfg.netmask.is_some() {
1445 return Err(argument::Error::TooManyArguments(
1446 "`netmask` already given".to_owned(),
1447 ));
1448 }
1449 cfg.netmask =
1450 Some(
1451 value
1452 .unwrap()
1453 .parse()
1454 .map_err(|_| argument::Error::InvalidValue {
1455 value: value.unwrap().to_owned(),
1456 expected: String::from("`netmask` needs to be in the form \"x.x.x.x\""),
1457 })?,
1458 )
1459 }
1460 "mac" => {
1461 if cfg.mac_address.is_some() {
1462 return Err(argument::Error::TooManyArguments(
1463 "`mac` already given".to_owned(),
1464 ));
1465 }
1466 cfg.mac_address =
1467 Some(
1468 value
1469 .unwrap()
1470 .parse()
1471 .map_err(|_| argument::Error::InvalidValue {
1472 value: value.unwrap().to_owned(),
1473 expected: String::from(
1474 "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
1475 ),
1476 })?,
1477 )
1478 }
1479 "net-vq-pairs" => {
1480 if cfg.net_vq_pairs.is_some() {
1481 return Err(argument::Error::TooManyArguments(
1482 "`net-vq-pairs` already given".to_owned(),
1483 ));
1484 }
1485 cfg.net_vq_pairs =
1486 Some(
1487 value
1488 .unwrap()
1489 .parse()
1490 .map_err(|_| argument::Error::InvalidValue {
1491 value: value.unwrap().to_owned(),
1492 expected: String::from(
1493 "this value for `net-vq-pairs` needs to be integer",
1494 ),
1495 })?,
1496 )
1497 }
1498
1499 "wayland-sock" => {
1500 let mut components = value.unwrap().split(',');
1501 let path =
1502 PathBuf::from(
1503 components
1504 .next()
1505 .ok_or_else(|| argument::Error::InvalidValue {
1506 value: value.unwrap().to_owned(),
1507 expected: String::from("missing socket path"),
1508 })?,
1509 );
1510 let mut name = "";
1511 for c in components {
1512 let mut kv = c.splitn(2, '=');
1513 let (kind, value) = match (kv.next(), kv.next()) {
1514 (Some(kind), Some(value)) => (kind, value),
1515 _ => {
1516 return Err(argument::Error::InvalidValue {
1517 value: c.to_owned(),
1518 expected: String::from("option must be of the form `kind=value`"),
1519 })
1520 }
1521 };
1522 match kind {
1523 "name" => name = value,
1524 _ => {
1525 return Err(argument::Error::InvalidValue {
1526 value: kind.to_owned(),
1527 expected: String::from("unrecognized option"),
1528 })
1529 }
1530 }
1531 }
1532 if cfg.wayland_socket_paths.contains_key(name) {
1533 return Err(argument::Error::TooManyArguments(format!(
1534 "wayland socket name already used: '{}'",
1535 name
1536 )));
1537 }
1538 cfg.wayland_socket_paths.insert(name.to_string(), path);
1539 }
1540 #[cfg(feature = "wl-dmabuf")]
1541 "wayland-dmabuf" => {}
1542 "x-display" => {
1543 if cfg.x_display.is_some() {
1544 return Err(argument::Error::TooManyArguments(
1545 "`x-display` already given".to_owned(),
1546 ));
1547 }
1548 cfg.x_display = Some(value.unwrap().to_owned());
1549 }
1550 "display-window-keyboard" => {
1551 cfg.display_window_keyboard = true;
1552 }
1553 "display-window-mouse" => {
1554 cfg.display_window_mouse = true;
1555 }
1556 "socket" => {
1557 if cfg.socket_path.is_some() {
1558 return Err(argument::Error::TooManyArguments(
1559 "`socket` already given".to_owned(),
1560 ));
1561 }
1562 let mut socket_path = PathBuf::from(value.unwrap());
1563 if socket_path.is_dir() {
1564 socket_path.push(format!("crosvm-{}.sock", getpid()));
1565 }
1566 if socket_path.exists() {
1567 return Err(argument::Error::InvalidValue {
1568 value: socket_path.to_string_lossy().into_owned(),
1569 expected: String::from("this socket path already exists"),
1570 });
1571 }
1572 cfg.socket_path = Some(socket_path);
1573 }
1574 "balloon-control" => {
1575 if cfg.balloon_control.is_some() {
1576 return Err(argument::Error::TooManyArguments(
1577 "`balloon-control` already given".to_owned(),
1578 ));
1579 }
1580 let path = PathBuf::from(value.unwrap());
1581 if path.is_dir() || !path.exists() {
1582 return Err(argument::Error::InvalidValue {
1583 value: path.to_string_lossy().into_owned(),
1584 expected: String::from("path is directory or missing"),
1585 });
1586 }
1587 cfg.balloon_control = Some(path);
1588 }
1589 "disable-sandbox" => {
1590 cfg.jail_config = None;
1591 }
1592 "cid" => {
1593 if cfg.cid.is_some() {
1594 return Err(argument::Error::TooManyArguments(
1595 "`cid` alread given".to_owned(),
1596 ));
1597 }
1598 cfg.cid = Some(
1599 value
1600 .unwrap()
1601 .parse()
1602 .map_err(|_| argument::Error::InvalidValue {
1603 value: value.unwrap().to_owned(),
1604 expected: String::from("this value for `cid` must be an unsigned integer"),
1605 })?,
1606 );
1607 }
1608 "shared-dir" => {
1609 // This is formatted as multiple fields, each separated by ":". The first 2 fields are
1610 // fixed (src:tag). The rest may appear in any order:
1611 //
1612 // * type=TYPE - must be one of "p9" or "fs" (default: p9)
1613 // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
1614 // (default: "0 <current euid> 1")
1615 // * gidmap=GIDMAP - a gid map in the same format as uidmap
1616 // (default: "0 <current egid> 1")
1617 // * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
1618 // performing quota-related operations, these UIDs are treated as if they have
1619 // CAP_FOWNER.
1620 // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
1621 // and directory contents should be considered valid (default: 5)
1622 // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
1623 // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
1624 let param = value.unwrap();
1625 let mut components = param.split(':');
1626 let src =
1627 PathBuf::from(
1628 components
1629 .next()
1630 .ok_or_else(|| argument::Error::InvalidValue {
1631 value: param.to_owned(),
1632 expected: String::from("missing source path for `shared-dir`"),
1633 })?,
1634 );
1635 let tag = components
1636 .next()
1637 .ok_or_else(|| argument::Error::InvalidValue {
1638 value: param.to_owned(),
1639 expected: String::from("missing tag for `shared-dir`"),
1640 })?
1641 .to_owned();
1642
1643 if !src.is_dir() {
1644 return Err(argument::Error::InvalidValue {
1645 value: param.to_owned(),
1646 expected: String::from("source path for `shared-dir` must be a directory"),
1647 });
1648 }
1649
1650 let mut shared_dir = SharedDir {
1651 src,
1652 tag,
1653 ..Default::default()
1654 };
1655 for opt in components {
1656 let mut o = opt.splitn(2, '=');
1657 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
1658 value: opt.to_owned(),
1659 expected: String::from("`shared-dir` options must not be empty"),
1660 })?;
1661 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
1662 value: opt.to_owned(),
1663 expected: String::from("`shared-dir` options must be of the form `kind=value`"),
1664 })?;
1665
1666 match kind {
1667 "type" => {
1668 shared_dir.kind =
1669 value.parse().map_err(|_| argument::Error::InvalidValue {
1670 value: value.to_owned(),
1671 expected: String::from("`type` must be one of `fs` or `9p`"),
1672 })?
1673 }
1674 "uidmap" => shared_dir.uid_map = value.into(),
1675 "gidmap" => shared_dir.gid_map = value.into(),
1676 #[cfg(feature = "chromeos")]
1677 "privileged_quota_uids" => {
1678 shared_dir.fs_cfg.privileged_quota_uids =
1679 value.split(' ').map(|s| s.parse().unwrap()).collect();
1680 }
1681 "timeout" => {
1682 let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
1683 value: value.to_owned(),
1684 expected: String::from("`timeout` must be an integer"),
1685 })?;
1686
1687 let dur = Duration::from_secs(seconds);
1688 shared_dir.fs_cfg.entry_timeout = dur;
1689 shared_dir.fs_cfg.attr_timeout = dur;
1690 }
1691 "cache" => {
1692 let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
1693 value: value.to_owned(),
1694 expected: String::from(
1695 "`cache` must be one of `never`, `always`, or `auto`",
1696 ),
1697 })?;
1698 shared_dir.fs_cfg.cache_policy = policy;
1699 }
1700 "writeback" => {
1701 let writeback =
1702 value.parse().map_err(|_| argument::Error::InvalidValue {
1703 value: value.to_owned(),
1704 expected: String::from("`writeback` must be a boolean"),
1705 })?;
1706 shared_dir.fs_cfg.writeback = writeback;
1707 }
1708 "rewrite-security-xattrs" => {
1709 let rewrite_security_xattrs =
1710 value.parse().map_err(|_| argument::Error::InvalidValue {
1711 value: value.to_owned(),
1712 expected: String::from(
1713 "`rewrite-security-xattrs` must be a boolean",
1714 ),
1715 })?;
1716 shared_dir.fs_cfg.rewrite_security_xattrs = rewrite_security_xattrs;
1717 }
1718 "ascii_casefold" => {
1719 let ascii_casefold =
1720 value.parse().map_err(|_| argument::Error::InvalidValue {
1721 value: value.to_owned(),
1722 expected: String::from("`ascii_casefold` must be a boolean"),
1723 })?;
1724 shared_dir.fs_cfg.ascii_casefold = ascii_casefold;
1725 shared_dir.p9_cfg.ascii_casefold = ascii_casefold;
1726 }
1727 "dax" => {
1728 let use_dax = value.parse().map_err(|_| argument::Error::InvalidValue {
1729 value: value.to_owned(),
1730 expected: String::from("`dax` must be a boolean"),
1731 })?;
1732 shared_dir.fs_cfg.use_dax = use_dax;
1733 }
1734 "posix_acl" => {
1735 let posix_acl =
1736 value.parse().map_err(|_| argument::Error::InvalidValue {
1737 value: value.to_owned(),
1738 expected: String::from("`posix_acl` must be a boolean"),
1739 })?;
1740 shared_dir.fs_cfg.posix_acl = posix_acl;
1741 }
1742 _ => {
1743 return Err(argument::Error::InvalidValue {
1744 value: kind.to_owned(),
1745 expected: String::from("unrecognized option for `shared-dir`"),
1746 })
1747 }
1748 }
1749 }
1750 cfg.shared_dirs.push(shared_dir);
1751 }
1752 "seccomp-policy-dir" => {
1753 if let Some(jail_config) = &mut cfg.jail_config {
1754 // `value` is Some because we are in this match so it's safe to unwrap.
1755 jail_config.seccomp_policy_dir = PathBuf::from(value.unwrap());
1756 }
1757 }
1758 "seccomp-log-failures" => {
1759 // A side-effect of this flag is to force the use of .policy files
1760 // instead of .bpf files (.bpf files are expected and assumed to be
1761 // compiled to fail an unpermitted action with "trap").
1762 // Normally crosvm will first attempt to use a .bpf file, and if
1763 // not present it will then try to use a .policy file. It's up
1764 // to the build to decide which of these files is present for
1765 // crosvm to use (for CrOS the build will use .bpf files for
1766 // x64 builds and .policy files for arm/arm64 builds).
1767 //
1768 // This flag will likely work as expected for builds that use
1769 // .policy files. For builds that only use .bpf files the initial
1770 // result when using this flag is likely to be a file-not-found
1771 // error (since the .policy files are not present).
1772 // For .bpf builds you can either 1) manually add the .policy files,
1773 // or 2) do not use this command-line parameter and instead
1774 // temporarily change the build by passing "log" rather than
1775 // "trap" as the "--default-action" to compile_seccomp_policy.py.
1776 if let Some(jail_config) = &mut cfg.jail_config {
1777 jail_config.seccomp_log_failures = true;
1778 }
1779 }
1780 "plugin" => {
1781 if cfg.executable_path.is_some() {
1782 return Err(argument::Error::TooManyArguments(format!(
1783 "A VM executable was already specified: {:?}",
1784 cfg.executable_path
1785 )));
1786 }
1787 let plugin = PathBuf::from(value.unwrap().to_owned());
1788 if plugin.is_relative() {
1789 return Err(argument::Error::InvalidValue {
1790 value: plugin.to_string_lossy().into_owned(),
1791 expected: String::from("the plugin path must be an absolute path"),
1792 });
1793 }
1794 cfg.executable_path = Some(Executable::Plugin(plugin));
1795 }
1796 "plugin-root" => {
1797 cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned()));
1798 }
1799 "plugin-mount" => {
1800 let mount = parse_plugin_mount_option(value.unwrap())?;
1801 cfg.plugin_mounts.push(mount);
1802 }
1803 "plugin-mount-file" => {
1804 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
1805 value: value.unwrap().to_owned(),
1806 expected: String::from("unable to open `plugin-mount-file` file"),
1807 })?;
1808 let reader = BufReader::new(file);
1809 for l in reader.lines() {
1810 let line = l.unwrap();
1811 let trimmed_line = line.split_once('#').map_or(&*line, |x| x.0).trim();
1812 if !trimmed_line.is_empty() {
1813 let mount = parse_plugin_mount_option(trimmed_line)?;
1814 cfg.plugin_mounts.push(mount);
1815 }
1816 }
1817 }
1818 "plugin-gid-map" => {
1819 let map = parse_plugin_gid_map_option(value.unwrap())?;
1820 cfg.plugin_gid_maps.push(map);
1821 }
1822 "plugin-gid-map-file" => {
1823 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
1824 value: value.unwrap().to_owned(),
1825 expected: String::from("unable to open `plugin-gid-map-file` file"),
1826 })?;
1827 let reader = BufReader::new(file);
1828 for l in reader.lines() {
1829 let line = l.unwrap();
1830 let trimmed_line = line.split_once('#').map_or(&*line, |x| x.0).trim();
1831 if !trimmed_line.is_empty() {
1832 let map = parse_plugin_gid_map_option(trimmed_line)?;
1833 cfg.plugin_gid_maps.push(map);
1834 }
1835 }
1836 }
1837 "vhost-net" => cfg.vhost_net = true,
1838 "tap-fd" => {
1839 cfg.tap_fd.push(
1840 value
1841 .unwrap()
1842 .parse()
1843 .map_err(|_| argument::Error::InvalidValue {
1844 value: value.unwrap().to_owned(),
1845 expected: String::from(
1846 "this value for `tap-fd` must be an unsigned integer",
1847 ),
1848 })?,
1849 );
1850 }
1851 "tap-name" => {
1852 cfg.tap_name.push(value.unwrap().to_owned());
1853 }
1854 #[cfg(feature = "gpu")]
1855 "gpu" => {
1856 let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
1857 parse_gpu_options(value, gpu_parameters)?;
1858 }
1859 #[cfg(feature = "gpu")]
1860 "gpu-display" => {
1861 let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
1862 parse_gpu_display_options(value, gpu_parameters)?;
1863 }
1864 #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
1865 "gpu-render-server" => {
1866 cfg.gpu_render_server_parameters = Some(parse_gpu_render_server_options(value)?);
1867 }
1868 "software-tpm" => {
1869 cfg.software_tpm = true;
1870 }
1871 "single-touch" => {
1872 let mut it = value.unwrap().split(':');
1873
1874 let mut single_touch_spec =
1875 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
1876 if let Some(width) = it.next() {
1877 single_touch_spec.set_width(width.trim().parse().unwrap());
1878 }
1879 if let Some(height) = it.next() {
1880 single_touch_spec.set_height(height.trim().parse().unwrap());
1881 }
1882 cfg.virtio_single_touch.push(single_touch_spec);
1883 }
1884 "multi-touch" => {
1885 let mut it = value.unwrap().split(':');
1886
1887 let mut multi_touch_spec =
1888 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
1889 if let Some(width) = it.next() {
1890 multi_touch_spec.set_width(width.trim().parse().unwrap());
1891 }
1892 if let Some(height) = it.next() {
1893 multi_touch_spec.set_height(height.trim().parse().unwrap());
1894 }
1895 cfg.virtio_multi_touch.push(multi_touch_spec);
1896 }
1897 "trackpad" => {
1898 let mut it = value.unwrap().split(':');
1899
1900 let mut trackpad_spec =
1901 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
1902 if let Some(width) = it.next() {
1903 trackpad_spec.set_width(width.trim().parse().unwrap());
1904 }
1905 if let Some(height) = it.next() {
1906 trackpad_spec.set_height(height.trim().parse().unwrap());
1907 }
1908 cfg.virtio_trackpad.push(trackpad_spec);
1909 }
1910 "mouse" => {
1911 cfg.virtio_mice
1912 .push(PathBuf::from(value.unwrap().to_owned()));
1913 }
1914 "keyboard" => {
1915 cfg.virtio_keyboard
1916 .push(PathBuf::from(value.unwrap().to_owned()));
1917 }
1918 "switches" => {
1919 cfg.virtio_switches
1920 .push(PathBuf::from(value.unwrap().to_owned()));
1921 }
1922 "evdev" => {
1923 let dev_path = PathBuf::from(value.unwrap());
1924 if !dev_path.exists() {
1925 return Err(argument::Error::InvalidValue {
1926 value: value.unwrap().to_owned(),
1927 expected: String::from("this input device path does not exist"),
1928 });
1929 }
1930 cfg.virtio_input_evdevs.push(dev_path);
1931 }
1932 "split-irqchip" => {
1933 cfg.split_irqchip = true;
1934 }
1935 "initrd" => {
1936 cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
1937 }
1938 "bios" => {
1939 if cfg.executable_path.is_some() {
1940 return Err(argument::Error::TooManyArguments(format!(
1941 "A VM executable was already specified: {:?}",
1942 cfg.executable_path
1943 )));
1944 }
1945 cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
1946 }
1947 "vfio" | "vfio-platform" => {
1948 let vfio_type = name.parse().unwrap();
1949 let vfio_dev = VfioCommand::new(vfio_type, value.unwrap())?;
1950 cfg.vfio.push(vfio_dev);
1951 }
1952 "virtio-iommu" => {
1953 cfg.virtio_iommu = true;
1954 }
1955 #[cfg(feature = "video-decoder")]
1956 "video-decoder" => {
1957 cfg.video_dec = Some(parse_video_options(value)?);
1958 }
1959 #[cfg(feature = "video-encoder")]
1960 "video-encoder" => {
1961 cfg.video_enc = Some(parse_video_options(value)?);
1962 }
1963 "acpi-table" => {
1964 let acpi_table = PathBuf::from(value.unwrap());
1965 if !acpi_table.exists() {
1966 return Err(argument::Error::InvalidValue {
1967 value: value.unwrap().to_owned(),
1968 expected: String::from("the acpi-table path does not exist"),
1969 });
1970 }
1971 if !acpi_table.is_file() {
1972 return Err(argument::Error::InvalidValue {
1973 value: value.unwrap().to_owned(),
1974 expected: String::from("the acpi-table path should be a file"),
1975 });
1976 }
1977
1978 cfg.acpi_tables.push(acpi_table);
1979 }
1980 "protected-vm" => {
1981 cfg.protected_vm = ProtectionType::Protected;
1982 // Balloon and USB devices only work for unprotected VMs.
1983 cfg.balloon = false;
1984 cfg.usb = false;
1985 // Protected VMs can't trust the RNG device, so don't provide it.
1986 cfg.rng = false;
1987 }
1988 "protected-vm-without-firmware" => {
1989 cfg.protected_vm = ProtectionType::ProtectedWithoutFirmware;
1990 // Balloon and USB devices only work for unprotected VMs.
1991 cfg.balloon = false;
1992 cfg.usb = false;
1993 // Protected VMs can't trust the RNG device, so don't provide it.
1994 cfg.rng = false;
1995 }
1996 "battery" => {
1997 let params = parse_battery_options(value)?;
1998 cfg.battery_type = Some(params);
1999 }
2000 #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
2001 "gdb" => {
2002 let port = value
2003 .unwrap()
2004 .parse()
2005 .map_err(|_| argument::Error::InvalidValue {
2006 value: value.unwrap().to_owned(),
2007 expected: String::from("expected a valid port number"),
2008 })?;
2009 cfg.gdb = Some(port);
2010 }
2011 "no-balloon" => {
2012 cfg.balloon = false;
2013 }
2014 "no-rng" => {
2015 cfg.rng = false;
2016 }
2017 "no-usb" => {
2018 cfg.usb = false;
2019 }
2020 "balloon_bias_mib" => {
2021 cfg.balloon_bias =
2022 value
2023 .unwrap()
2024 .parse::<i64>()
2025 .map_err(|_| argument::Error::InvalidValue {
2026 value: value.unwrap().to_owned(),
2027 expected: String::from("expected a valid ballon bias in MiB"),
2028 })?
2029 * 1024
2030 * 1024; // cfg.balloon_bias is in bytes.
2031 }
2032 "vhost-user-blk" => cfg.vhost_user_blk.push(VhostUserOption {
2033 socket: PathBuf::from(value.unwrap()),
2034 }),
2035 "vhost-user-console" => cfg.vhost_user_console.push(VhostUserOption {
2036 socket: PathBuf::from(value.unwrap()),
2037 }),
2038 "vhost-user-gpu" => cfg.vhost_user_gpu.push(VhostUserOption {
2039 socket: PathBuf::from(value.unwrap()),
2040 }),
2041 "vhost-user-mac80211-hwsim" => {
2042 cfg.vhost_user_mac80211_hwsim = Some(VhostUserOption {
2043 socket: PathBuf::from(value.unwrap()),
2044 });
2045 }
2046 "vhost-user-net" => cfg.vhost_user_net.push(VhostUserOption {
2047 socket: PathBuf::from(value.unwrap()),
2048 }),
2049 #[cfg(feature = "audio")]
2050 "vhost-user-snd" => cfg.vhost_user_snd.push(VhostUserOption {
2051 socket: PathBuf::from(value.unwrap()),
2052 }),
2053 "vhost-user-vsock" => cfg.vhost_user_vsock.push(VhostUserOption {
2054 socket: PathBuf::from(value.unwrap()),
2055 }),
2056 "vhost-user-wl" => {
2057 let mut components = value.unwrap().splitn(2, ":");
2058 let socket = components.next().map(PathBuf::from).ok_or_else(|| {
2059 argument::Error::InvalidValue {
2060 value: value.unwrap().to_owned(),
2061 expected: String::from("missing socket path"),
2062 }
2063 })?;
2064 let vm_tube = components.next().map(PathBuf::from).ok_or_else(|| {
2065 argument::Error::InvalidValue {
2066 value: value.unwrap().to_owned(),
2067 expected: String::from("missing vm tube path"),
2068 }
2069 })?;
2070 cfg.vhost_user_wl
2071 .push(VhostUserWlOption { socket, vm_tube });
2072 }
2073 "vhost-user-fs" => {
2074 // (socket:tag)
2075 let param = value.unwrap();
2076 let mut components = param.split(':');
2077 let socket =
2078 PathBuf::from(
2079 components
2080 .next()
2081 .ok_or_else(|| argument::Error::InvalidValue {
2082 value: param.to_owned(),
2083 expected: String::from("missing socket path for `vhost-user-fs`"),
2084 })?,
2085 );
2086 let tag = components
2087 .next()
2088 .ok_or_else(|| argument::Error::InvalidValue {
2089 value: param.to_owned(),
2090 expected: String::from("missing tag for `vhost-user-fs`"),
2091 })?
2092 .to_owned();
2093 cfg.vhost_user_fs.push(VhostUserFsOption { socket, tag });
2094 }
2095 #[cfg(feature = "direct")]
2096 "direct-pmio" => {
2097 if cfg.direct_pmio.is_some() {
2098 return Err(argument::Error::TooManyArguments(
2099 "`direct_pmio` already given".to_owned(),
2100 ));
2101 }
2102 cfg.direct_pmio = Some(parse_direct_io_options(value)?);
2103 }
2104 #[cfg(feature = "direct")]
2105 "direct-mmio" => {
2106 if cfg.direct_mmio.is_some() {
2107 return Err(argument::Error::TooManyArguments(
2108 "`direct_mmio` already given".to_owned(),
2109 ));
2110 }
2111 cfg.direct_mmio = Some(parse_direct_io_options(value)?);
2112 }
2113 #[cfg(feature = "direct")]
2114 "direct-level-irq" => {
2115 cfg.direct_level_irq
2116 .push(
2117 value
2118 .unwrap()
2119 .parse()
2120 .map_err(|_| argument::Error::InvalidValue {
2121 value: value.unwrap().to_owned(),
2122 expected: String::from(
2123 "this value for `direct-level-irq` must be an unsigned integer",
2124 ),
2125 })?,
2126 );
2127 }
2128 #[cfg(feature = "direct")]
2129 "direct-edge-irq" => {
2130 cfg.direct_edge_irq
2131 .push(
2132 value
2133 .unwrap()
2134 .parse()
2135 .map_err(|_| argument::Error::InvalidValue {
2136 value: value.unwrap().to_owned(),
2137 expected: String::from(
2138 "this value for `direct-edge-irq` must be an unsigned integer",
2139 ),
2140 })?,
2141 );
2142 }
2143 #[cfg(feature = "direct")]
2144 "direct-wake-irq" => {
2145 cfg.direct_wake_irq
2146 .push(
2147 value
2148 .unwrap()
2149 .parse()
2150 .map_err(|_| argument::Error::InvalidValue {
2151 value: value.unwrap().to_owned(),
2152 expected: String::from(
2153 "this value for `direct-wake-irq` must be an unsigned integer",
2154 ),
2155 })?,
2156 );
2157 }
2158 #[cfg(feature = "direct")]
2159 "direct-gpe" => {
2160 cfg.direct_gpe.push(value.unwrap().parse().map_err(|_| {
2161 argument::Error::InvalidValue {
2162 value: value.unwrap().to_owned(),
2163 expected: String::from(
2164 "this value for `direct-gpe` must be an unsigned integer",
2165 ),
2166 }
2167 })?);
2168 }
2169 "dmi" => {
2170 if cfg.dmi_path.is_some() {
2171 return Err(argument::Error::TooManyArguments(
2172 "`dmi` already given".to_owned(),
2173 ));
2174 }
2175 let dmi_path = PathBuf::from(value.unwrap());
2176 if !dmi_path.exists() {
2177 return Err(argument::Error::InvalidValue {
2178 value: value.unwrap().to_owned(),
2179 expected: String::from("the dmi path does not exist"),
2180 });
2181 }
2182 if !dmi_path.is_dir() {
2183 return Err(argument::Error::InvalidValue {
2184 value: value.unwrap().to_owned(),
2185 expected: String::from("the dmi path should be directory"),
2186 });
2187 }
2188 cfg.dmi_path = Some(dmi_path);
2189 }
2190 "no-legacy" => {
2191 cfg.no_legacy = true;
2192 }
2193 "userspace-msr" => {
2194 let index = parse_userspace_msr_options(value.unwrap())?;
2195 cfg.userspace_msr.insert(index);
2196 }
2197 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2198 "host-cpu-topology" => {
2199 cfg.host_cpu_topology = true;
2200 }
2201 "privileged-vm" => {
2202 cfg.privileged_vm = true;
2203 }
2204 "stub-pci-device" => {
2205 cfg.stub_pci_devices.push(parse_stub_pci_parameters(value)?);
2206 }
2207 "vvu-proxy" => {
2208 let opts: Vec<_> = value.unwrap().splitn(2, ',').collect();
2209 let socket = PathBuf::from(opts[0]);
2210 let mut vvu_opt = VvuOption {
2211 socket,
2212 addr: None,
2213 uuid: Default::default(),
2214 };
2215
2216 if let Some(kvs) = opts.get(1) {
2217 for kv in argument::parse_key_value_options("vvu-proxy", kvs, ',') {
2218 match kv.key() {
2219 "addr" => {
2220 let pci_address = kv.value()?;
2221 if vvu_opt.addr.is_some() {
2222 return Err(argument::Error::TooManyArguments(
2223 "`addr` already given".to_owned(),
2224 ));
2225 }
2226
2227 vvu_opt.addr =
2228 Some(PciAddress::from_str(pci_address).map_err(|e| {
2229 argument::Error::InvalidValue {
2230 value: pci_address.to_string(),
2231 expected: format!("vvu-proxy PCI address: {}", e),
2232 }
2233 })?);
2234 }
2235 "uuid" => {
2236 let value = kv.value()?;
2237 if vvu_opt.uuid.is_some() {
2238 return Err(argument::Error::TooManyArguments(
2239 "`uuid` already given".to_owned(),
2240 ));
2241 }
2242 let uuid = Uuid::parse_str(value).map_err(|e| {
2243 argument::Error::InvalidValue {
2244 value: value.to_string(),
2245 expected: format!("invalid UUID is given for vvu-proxy: {}", e),
2246 }
2247 })?;
2248 vvu_opt.uuid = Some(uuid);
2249 }
2250 _ => {
2251 kv.invalid_key_err();
2252 }
2253 }
2254 }
2255 }
2256
2257 cfg.vvu_proxy.push(vvu_opt);
2258 }
2259 "coiommu" => {
2260 let mut params: devices::CoIommuParameters = Default::default();
2261 if let Some(v) = value {
2262 let opts = v
2263 .split(',')
2264 .map(|frag| frag.splitn(2, '='))
2265 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
2266
2267 for (k, v) in opts {
2268 match k {
2269 "unpin_policy" => {
2270 params.unpin_policy = v
2271 .parse::<devices::CoIommuUnpinPolicy>()
2272 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
2273 }
2274 "unpin_interval" => {
2275 params.unpin_interval =
2276 Duration::from_secs(v.parse::<u64>().map_err(|e| {
2277 argument::Error::UnknownArgument(format!("{}", e))
2278 })?)
2279 }
2280 "unpin_limit" => {
2281 let limit = v
2282 .parse::<u64>()
2283 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?;
2284
2285 if limit == 0 {
2286 return Err(argument::Error::InvalidValue {
2287 value: v.to_owned(),
2288 expected: String::from("Please use non-zero unpin_limit value"),
2289 });
2290 }
2291
2292 params.unpin_limit = Some(limit)
2293 }
2294 "unpin_gen_threshold" => {
2295 params.unpin_gen_threshold = v
2296 .parse::<u64>()
2297 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
2298 }
2299 _ => {
2300 return Err(argument::Error::UnknownArgument(format!(
2301 "coiommu parameter {}",
2302 k
2303 )));
2304 }
2305 }
2306 }
2307 }
2308
2309 if cfg.coiommu_param.is_some() {
2310 return Err(argument::Error::TooManyArguments(
2311 "coiommu param already given".to_owned(),
2312 ));
2313 }
2314 cfg.coiommu_param = Some(params);
2315 }
2316 "file-backed-mapping" => {
2317 cfg.file_backed_mappings
2318 .push(parse_file_backed_mapping(value)?);
2319 }
2320 "init-mem" => {
2321 if cfg.init_memory.is_some() {
2322 return Err(argument::Error::TooManyArguments(
2323 "`init-mem` already given".to_owned(),
2324 ));
2325 }
2326 cfg.init_memory =
2327 Some(
2328 value
2329 .unwrap()
2330 .parse()
2331 .map_err(|_| argument::Error::InvalidValue {
2332 value: value.unwrap().to_owned(),
2333 expected: String::from("this value for `init-mem` needs to be integer"),
2334 })?,
2335 )
2336 }
2337 #[cfg(feature = "direct")]
2338 "pcie-root-port" => {
2339 let opts: Vec<_> = value.unwrap().split(',').collect();
2340 if opts.len() > 2 {
2341 return Err(argument::Error::TooManyArguments(
2342 "pcie-root-port has maxmimum two arguments".to_owned(),
2343 ));
2344 }
2345 let pcie_path = PathBuf::from(opts[0]);
2346 if !pcie_path.exists() {
2347 return Err(argument::Error::InvalidValue {
2348 value: opts[0].to_owned(),
2349 expected: String::from("the pcie root port path does not exist"),
2350 });
2351 }
2352 if !pcie_path.is_dir() {
2353 return Err(argument::Error::InvalidValue {
2354 value: opts[0].to_owned(),
2355 expected: String::from("the pcie root port path should be directory"),
2356 });
2357 }
2358
2359 let hp_gpe = if opts.len() == 2 {
2360 let gpes: Vec<&str> = opts[1].split('=').collect();
2361 if gpes.len() != 2 || gpes[0] != "hp_gpe" {
2362 return Err(argument::Error::InvalidValue {
2363 value: opts[1].to_owned(),
2364 expected: String::from("it should be hp_gpe=Num"),
2365 });
2366 }
2367 match gpes[1].parse::<u32>() {
2368 Ok(gpe) => Some(gpe),
2369 Err(_) => {
2370 return Err(argument::Error::InvalidValue {
2371 value: gpes[1].to_owned(),
2372 expected: String::from("host hp gpe must be a non-negative integer"),
2373 });
2374 }
2375 }
2376 } else {
2377 None
2378 };
2379
2380 cfg.pcie_rp.push(HostPcieRootPortParameters {
2381 host_path: pcie_path,
2382 hp_gpe,
2383 });
2384 }
2385 "pivot-root" => {
2386 if let Some(jail_config) = &mut cfg.jail_config {
2387 jail_config.pivot_root = PathBuf::from(value.unwrap());
2388 }
2389 }
2390 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2391 "s2idle" => {
2392 cfg.force_s2idle = true;
2393 }
2394 "strict-balloon" => {
2395 cfg.strict_balloon = true;
2396 }
2397 #[cfg(feature = "direct")]
2398 "mmio-address-range" => {
2399 let ranges: argument::Result<Vec<RangeInclusive<u64>>> = value
2400 .unwrap()
2401 .split(",")
2402 .map(|s| {
2403 let r: Vec<&str> = s.split("-").collect();
2404 if r.len() != 2 {
2405 return Err(argument::Error::InvalidValue {
2406 value: s.to_string(),
2407 expected: String::from("invalid range"),
2408 });
2409 }
2410 let parse = |s: &str| -> argument::Result<u64> {
2411 match parse_hex_or_decimal(s) {
2412 Ok(v) => Ok(v),
2413 Err(_) => {
2414 return Err(argument::Error::InvalidValue {
2415 value: s.to_owned(),
2416 expected: String::from("expected u64 value"),
2417 });
2418 }
2419 }
2420 };
2421 Ok(RangeInclusive::new(parse(r[0])?, parse(r[1])?))
2422 })
2423 .collect();
2424 cfg.mmio_address_ranges = ranges?;
2425 }
2426 #[cfg(target_os = "android")]
2427 "task-profiles" => {
2428 for name in value.unwrap().split(',') {
2429 cfg.task_profiles.push(name.to_owned());
2430 }
2431 }
2432 "help" => return Err(argument::Error::PrintHelp),
2433 _ => unreachable!(),
2434 }
2435 Ok(())
2436 }
2437
validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Error>2438 fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Error> {
2439 if cfg.executable_path.is_none() {
2440 return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
2441 }
2442 if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
2443 if cfg.host_ip.is_none() {
2444 return Err(argument::Error::ExpectedArgument(
2445 "`host_ip` missing from network config".to_owned(),
2446 ));
2447 }
2448 if cfg.netmask.is_none() {
2449 return Err(argument::Error::ExpectedArgument(
2450 "`netmask` missing from network config".to_owned(),
2451 ));
2452 }
2453 if cfg.mac_address.is_none() {
2454 return Err(argument::Error::ExpectedArgument(
2455 "`mac` missing from network config".to_owned(),
2456 ));
2457 }
2458 }
2459 if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
2460 return Err(argument::Error::ExpectedArgument(
2461 "`plugin-root` requires `plugin`".to_owned(),
2462 ));
2463 }
2464 #[cfg(feature = "gpu")]
2465 {
2466 if let Some(gpu_parameters) = cfg.gpu_parameters.as_mut() {
2467 if gpu_parameters.displays.is_empty() {
2468 gpu_parameters.displays.push(GpuDisplayParameters {
2469 width: DEFAULT_DISPLAY_WIDTH,
2470 height: DEFAULT_DISPLAY_HEIGHT,
2471 });
2472 }
2473
2474 let width = gpu_parameters.displays[0].width;
2475 let height = gpu_parameters.displays[0].height;
2476
2477 if let Some(virtio_multi_touch) = cfg.virtio_multi_touch.first_mut() {
2478 virtio_multi_touch.set_default_size(width, height);
2479 }
2480 if let Some(virtio_single_touch) = cfg.virtio_single_touch.first_mut() {
2481 virtio_single_touch.set_default_size(width, height);
2482 }
2483 }
2484 }
2485 #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
2486 if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
2487 return Err(argument::Error::ExpectedArgument(
2488 "`gdb` requires the number of vCPU to be 1".to_owned(),
2489 ));
2490 }
2491 if cfg.host_cpu_topology {
2492 if cfg.no_smt {
2493 return Err(argument::Error::ExpectedArgument(
2494 "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
2495 the smt of the Guest is the same as that of the Host when \
2496 `host-cpu-topology` is set."
2497 .to_owned(),
2498 ));
2499 }
2500
2501 // Safe because we pass a flag for this call and the host supports this system call
2502 let pcpu_count = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) } as usize;
2503 if cfg.vcpu_count.is_some() {
2504 if pcpu_count != cfg.vcpu_count.unwrap() {
2505 return Err(argument::Error::ExpectedArgument(format!(
2506 "`host-cpu-topology` requires the count of vCPUs({}) to equal the \
2507 count of CPUs({}) on host.",
2508 cfg.vcpu_count.unwrap(),
2509 pcpu_count
2510 )));
2511 }
2512 } else {
2513 cfg.vcpu_count = Some(pcpu_count);
2514 }
2515
2516 match &cfg.vcpu_affinity {
2517 None => {
2518 let mut affinity_map = BTreeMap::new();
2519 for cpu_id in 0..cfg.vcpu_count.unwrap() {
2520 affinity_map.insert(cpu_id, vec![cpu_id]);
2521 }
2522 cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
2523 }
2524 _ => {
2525 return Err(argument::Error::ExpectedArgument(
2526 "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
2527 .to_owned(),
2528 ));
2529 }
2530 }
2531 }
2532 if !cfg.balloon && cfg.balloon_control.is_some() {
2533 return Err(argument::Error::ExpectedArgument(
2534 "'balloon-control' requires enabled balloon".to_owned(),
2535 ));
2536 }
2537
2538 set_default_serial_parameters(
2539 &mut cfg.serial_parameters,
2540 !cfg.vhost_user_console.is_empty(),
2541 );
2542
2543 // Remove jail configuration if it has not been enabled.
2544 if !cfg.jail_enabled {
2545 cfg.jail_config = None;
2546 }
2547
2548 Ok(())
2549 }
2550
2551 enum CommandStatus {
2552 Success,
2553 VmReset,
2554 VmStop,
2555 VmCrash,
2556 GuestPanic,
2557 }
2558
run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()>2559 fn run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()> {
2560 let arguments =
2561 &[Argument::positional("KERNEL", "bzImage of kernel to run"),
2562 Argument::value("kvm-device", "PATH", "Path to the KVM device. (default /dev/kvm)"),
2563 Argument::value("vhost-vsock-fd", "FD", "Open FD to the vhost-vsock device, mutually exclusive with vhost-vsock-device."),
2564 Argument::value("vhost-vsock-device", "PATH", "Path to the vhost-vsock device. (default /dev/vhost-vsock)"),
2565 Argument::value("vhost-net-device", "PATH", "Path to the vhost-net device. (default /dev/vhost-net)"),
2566 Argument::value("android-fstab", "PATH", "Path to Android fstab"),
2567 Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
2568 Argument::short_value('p',
2569 "params",
2570 "PARAMS",
2571 "Extra kernel or plugin command line arguments. Can be given more than once."),
2572 Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
2573 Argument::value("cpu-affinity", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on (e.g. 0,1-3,5)
2574 or colon-separated list of assignments of guest to host CPU assignments (e.g. 0=0:1=1:2=2) (default: no mask)"),
2575 Argument::value("cpu-cluster", "CPUSET", "Group the given CPUs into a cluster (default: no clusters)"),
2576 Argument::value("cpu-capacity", "CPU=CAP[,CPU=CAP[,...]]", "Set the relative capacity of the given CPU (default: no capacity)"),
2577 Argument::flag("per-vm-core-scheduling", "Enable per-VM core scheduling intead of the default one (per-vCPU core scheduing) by
2578 making all vCPU threads share same cookie for core scheduling.
2579 This option is no-op on devices that have neither MDS nor L1TF vulnerability."),
2580 Argument::value("vcpu-cgroup-path", "PATH", "Move all vCPU threads to this CGroup (default: nothing moves)."),
2581 #[cfg(feature = "audio_cras")]
2582 Argument::value("cras-snd",
2583 "[capture=true,client=crosvm,socket=unified,num_output_streams=1,num_input_streams=1]",
2584 "Comma separated key=value pairs for setting up cras snd devices.
2585 Possible key values:
2586 capture - Enable audio capture. Default to false.
2587 client_type - Set specific client type for cras backend.
2588 num_output_streams - Set number of output PCM streams
2589 num_input_streams - Set number of input PCM streams"),
2590 Argument::flag("no-smt", "Don't use SMT in the guest"),
2591 Argument::value("rt-cpus", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on. (e.g. 0,1-3,5) (default: none)"),
2592 Argument::flag("delay-rt", "Don't set VCPUs real-time until make-rt command is run"),
2593 Argument::short_value('m',
2594 "mem",
2595 "N",
2596 "Amount of guest memory in MiB. (default: 256)"),
2597 Argument::value("init-mem",
2598 "N",
2599 "Amount of guest memory outside the balloon at boot in MiB. (default: --mem)"),
2600 Argument::flag("hugepages", "Advise the kernel to use Huge Pages for guest memory mappings."),
2601 Argument::short_value('r',
2602 "root",
2603 "PATH[,key=value[,key=value[,...]]",
2604 "Path to a root disk image followed by optional comma-separated options.
2605 Like `--disk` but adds appropriate kernel command line option.
2606 See --disk for valid options."),
2607 Argument::value("rwroot", "PATH[,key=value[,key=value[,...]]", "Path to a writable root disk image followed by optional comma-separated options.
2608 See --disk for valid options."),
2609 Argument::short_value('d', "disk", "PATH[,key=value[,key=value[,...]]", "Path to a disk image followed by optional comma-separated options.
2610 Valid keys:
2611 sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
2612 block_size=BYTES - Set the reported block size of the disk (default: 512)
2613 id=STRING - Set the block device identifier to an ASCII string, up to 20 characters (default: no ID)
2614 o_direct=BOOL - Use O_DIRECT mode to bypass page cache"),
2615 Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
2616 See --disk for valid options."),
2617 Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
2618 Argument::value("pmem-device", "PATH", "Path to a disk image."),
2619 Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file followed by size."),
2620 Argument::value("host_ip",
2621 "IP",
2622 "IP address to assign to host tap interface."),
2623 Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
2624 Argument::value("mac", "MAC", "MAC address for VM."),
2625 Argument::value("net-vq-pairs", "N", "virtio net virtual queue paris. (default: 1)"),
2626 #[cfg(feature = "audio")]
2627 Argument::value("ac97",
2628 "[backend=BACKEND,capture=true,capture_effect=EFFECT,client_type=TYPE,shm-fd=FD,client-fd=FD,server-fd=FD]",
2629 "Comma separated key=value pairs for setting up Ac97 devices. Can be given more than once .
2630 Possible key values:
2631 backend=(null, cras, vios) - Where to route the audio device. If not provided, backend will default to null.
2632 `null` for /dev/null, cras for CRAS server and vios for VioS server.
2633 capture - Enable audio capture
2634 capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec.
2635 client_type - Set specific client type for cras backend.
2636 socket_type - Set specific socket type for cras backend.
2637 server - The to the VIOS server (unix socket)."),
2638 #[cfg(feature = "audio")]
2639 Argument::value("sound", "[PATH]", "Path to the VioS server socket for setting up virtio-snd devices."),
2640 Argument::value("serial",
2641 "type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]",
2642 "Comma separated key=value pairs for setting up serial devices. Can be given more than once.
2643 Possible key values:
2644 type=(stdout,syslog,sink,file) - Where to route the serial device
2645 hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
2646 num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
2647 path=PATH - The path to the file to write to when type=file
2648 input=PATH - The path to the file to read from when not stdin
2649 console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
2650 earlycon - Use this serial device as the early console. Can only be given once.
2651 stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided.
2652 "),
2653 Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
2654 Argument::value("x-display", "DISPLAY", "X11 display name to use."),
2655 Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
2656 Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
2657 Argument::value("wayland-sock", "PATH[,name=NAME]", "Path to the Wayland socket to use. The unnamed one is used for displaying virtual screens. Named ones are only for IPC."),
2658 #[cfg(feature = "wl-dmabuf")]
2659 Argument::flag("wayland-dmabuf", "DEPRECATED: Enable support for DMABufs in Wayland device."),
2660 Argument::short_value('s',
2661 "socket",
2662 "PATH",
2663 "Path to put the control socket. If PATH is a directory, a name will be generated."),
2664 Argument::value("balloon-control", "PATH", "Path for balloon controller socket."),
2665 Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
2666 Argument::value("cid", "CID", "Context ID for virtual sockets."),
2667 Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE:dax=BOOL,posix_acl=BOOL]",
2668 "Colon-separated options for configuring a directory to be shared with the VM.
2669 The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
2670 The remaining fields are key=value pairs that may appear in any order. Valid keys are:
2671 type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
2672 uidmap=UIDMAP - The uid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current euid> 1).
2673 gidmap=GIDMAP - The gid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current egid> 1).
2674 cache=(never, auto, always) - Indicates whether the VM can cache the contents of the shared directory (default: auto). When set to \"auto\" and the type is \"fs\", the VM will use close-to-open consistency for file contents.
2675 timeout=SECONDS - How long the VM should consider file attributes and directory entries to be valid (default: 5). If the VM has exclusive access to the directory, then this should be a large value. If the directory can be modified by other processes, then this should be 0.
2676 writeback=BOOL - Enables writeback caching (default: false). This is only safe to do when the VM has exclusive access to the files in a directory. Additionally, the server should have read permission for all files as the VM may issue read requests even for files that are opened write-only.
2677 dax=BOOL - Enables DAX support. Enabling DAX can improve performance for frequently accessed files by mapping regions of the file directly into the VM's memory. There is a cost of slightly increased latency the first time the file is accessed. Since the mapping is shared directly from the host kernel's file cache, enabling DAX can improve performance even when the guest cache policy is \"Never\". The default value for this option is \"false\".
2678 posix_acl=BOOL - Indicates whether the shared directory supports POSIX ACLs. This should only be enabled when the underlying file system supports POSIX ACLs. The default value for this option is \"true\".
2679 "),
2680 Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
2681 Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
2682 #[cfg(feature = "plugin")]
2683 Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
2684 #[cfg(feature = "plugin")]
2685 Argument::value("plugin-root", "PATH", "Absolute path to a directory that will become root filesystem for the plugin process."),
2686 #[cfg(feature = "plugin")]
2687 Argument::value("plugin-mount", "PATH:PATH:BOOL", "Path to be mounted into the plugin's root filesystem. Can be given more than once."),
2688 #[cfg(feature = "plugin")]
2689 Argument::value("plugin-mount-file", "PATH", "Path to the file listing paths be mounted into the plugin's root filesystem. Can be given more than once."),
2690 #[cfg(feature = "plugin")]
2691 Argument::value("plugin-gid-map", "GID:GID:INT", "Supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
2692 #[cfg(feature = "plugin")]
2693 Argument::value("plugin-gid-map-file", "PATH", "Path to the file listing supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
2694 Argument::flag("vhost-net", "Use vhost for networking."),
2695 Argument::value("tap-name",
2696 "NAME",
2697 "Name of a configured persistent TAP interface to use for networking. A different virtual network card will be added each time this argument is given."),
2698 Argument::value("tap-fd",
2699 "fd",
2700 "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
2701 #[cfg(feature = "gpu")]
2702 Argument::flag_or_value("gpu",
2703 "[width=INT,height=INT]",
2704 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
2705 Possible key values:
2706 backend=(2d|virglrenderer|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
2707 width=INT - The width of the virtual display connected to the virtio-gpu.
2708 height=INT - The height of the virtual display connected to the virtio-gpu.
2709 egl[=true|=false] - If the backend should use a EGL context for rendering.
2710 glx[=true|=false] - If the backend should use a GLX context for rendering.
2711 surfaceless[=true|=false] - If the backend should use a surfaceless context for rendering.
2712 angle[=true|=false] - If the gfxstream backend should use ANGLE (OpenGL on Vulkan) as its native OpenGL driver.
2713 syncfd[=true|=false] - If the gfxstream backend should support EGL_ANDROID_native_fence_sync
2714 vulkan[=true|=false] - If the backend should support vulkan
2715 cache-path=PATH - The path to the virtio-gpu device shader cache.
2716 cache-size=SIZE - The maximum size of the shader cache."),
2717 #[cfg(feature = "gpu")]
2718 Argument::flag_or_value("gpu-display",
2719 "[width=INT,height=INT]",
2720 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a display on the virtio-gpu device
2721 Possible key values:
2722 width=INT - The width of the virtual display connected to the virtio-gpu.
2723 height=INT - The height of the virtual display connected to the virtio-gpu."),
2724 #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
2725 Argument::flag_or_value("gpu-render-server",
2726 "[path=PATH]",
2727 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a render server for the virtio-gpu device
2728 Possible key values:
2729 path=PATH - The path to the render server executable.
2730 cache-path=PATH - The path to the render server shader cache.
2731 cache-size=SIZE - The maximum size of the shader cache."),
2732 #[cfg(feature = "tpm")]
2733 Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
2734 Argument::value("evdev", "PATH", "Path to an event device node. The device will be grabbed (unusable from the host) and made available to the guest with the same configuration it shows on the host"),
2735 Argument::value("single-touch", "PATH:WIDTH:HEIGHT", "Path to a socket from where to read single touch input events (such as those from a touchscreen) and write status updates to, optionally followed by width and height (defaults to 800x1280)."),
2736 Argument::value("multi-touch", "PATH:WIDTH:HEIGHT", "Path to a socket from where to read multi touch input events (such as those from a touchscreen) and write status updates to, optionally followed by width and height (defaults to 800x1280)."),
2737 Argument::value("trackpad", "PATH:WIDTH:HEIGHT", "Path to a socket from where to read trackpad input events and write status updates to, optionally followed by screen width and height (defaults to 800x1280)."),
2738 Argument::value("mouse", "PATH", "Path to a socket from where to read mouse input events and write status updates to."),
2739 Argument::value("keyboard", "PATH", "Path to a socket from where to read keyboard input events and write status updates to."),
2740 Argument::value("switches", "PATH", "Path to a socket from where to read switch input events and write status updates to."),
2741 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2742 Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
2743 Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
2744 Argument::value("vfio", "PATH[,guest-address=auto|<BUS:DEVICE.FUNCTION>][,iommu=on|off]", "Path to sysfs of PCI pass through or mdev device.
2745 guest-address=auto|<BUS:DEVICE.FUNCTION> - PCI address that the device will be assigned in the guest (default: auto). When set to \"auto\", the device will be assigned an address that mirrors its address in the host.
2746 iommu=on|off - indicates whether to enable virtio IOMMU for this device"),
2747 Argument::value("vfio-platform", "PATH", "Path to sysfs of platform pass through"),
2748 Argument::flag("virtio-iommu", "Add a virtio-iommu device"),
2749 #[cfg(feature = "video-decoder")]
2750 Argument::flag_or_value("video-decoder", "[backend]", "(EXPERIMENTAL) enable virtio-video decoder device
2751 Possible backend values: libvda"),
2752 #[cfg(feature = "video-encoder")]
2753 Argument::flag_or_value("video-encoder", "[backend]", "(EXPERIMENTAL) enable virtio-video encoder device
2754 Possible backend values: libvda"),
2755 Argument::value("acpi-table", "PATH", "Path to user provided ACPI table"),
2756 Argument::flag("protected-vm", "(EXPERIMENTAL) prevent host access to guest memory"),
2757 Argument::flag("protected-vm-without-firmware", "(EXPERIMENTAL) prevent host access to guest memory, but don't use protected VM firmware"),
2758 #[cfg(target_arch = "aarch64")]
2759 Argument::value("swiotlb", "N", "(EXPERIMENTAL) Size of virtio swiotlb buffer in MiB (default: 64 if `--protected-vm` or `--protected-vm-without-firmware` is present)."),
2760 Argument::flag_or_value("battery",
2761 "[type=TYPE]",
2762 "Comma separated key=value pairs for setting up battery device
2763 Possible key values:
2764 type=goldfish - type of battery emulation, defaults to goldfish"),
2765 Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"),
2766 Argument::flag("no-balloon", "Don't use virtio-balloon device in the guest"),
2767 #[cfg(feature = "usb")]
2768 Argument::flag("no-usb", "Don't use usb devices in the guest"),
2769 Argument::flag("no-rng", "Don't create RNG device in the guest"),
2770 Argument::value("balloon_bias_mib", "N", "Amount to bias balance of memory between host and guest as the balloon inflates, in MiB."),
2771 Argument::value("vhost-user-blk", "SOCKET_PATH", "Path to a socket for vhost-user block"),
2772 Argument::value("vhost-user-console", "SOCKET_PATH", "Path to a socket for vhost-user console"),
2773 Argument::value("vhost-user-gpu", "SOCKET_PATH", "Paths to a vhost-user socket for gpu"),
2774 Argument::value("vhost-user-mac80211-hwsim", "SOCKET_PATH", "Path to a socket for vhost-user mac80211_hwsim"),
2775 Argument::value("vhost-user-net", "SOCKET_PATH", "Path to a socket for vhost-user net"),
2776 #[cfg(feature = "audio")]
2777 Argument::value("vhost-user-snd", "SOCKET_PATH", "Path to a socket for vhost-user snd"),
2778 Argument::value("vhost-user-vsock", "SOCKET_PATH", "Path to a socket for vhost-user vsock"),
2779 Argument::value("vhost-user-wl", "SOCKET_PATH:TUBE_PATH", "Paths to a vhost-user socket for wayland and a Tube socket for additional wayland-specific messages"),
2780 Argument::value("vhost-user-fs", "SOCKET_PATH:TAG",
2781 "Path to a socket path for vhost-user fs, and tag for the shared dir"),
2782 Argument::value("vvu-proxy", "SOCKET_PATH[,addr=DOMAIN:BUS:DEVICE.FUNCTION,uuid=UUID]", "Socket path for the Virtio Vhost User proxy device.
2783 Parameters
2784 addr=BUS:DEVICE.FUNCTION - PCI address that the proxy device will be allocated (default: automatically allocated)
2785 uuid=UUID - UUID which will be stored in VVU PCI config space that is readable from guest userspace"),
2786 #[cfg(feature = "direct")]
2787 Argument::value("direct-pmio", "PATH@RANGE[,RANGE[,...]]", "Path and ranges for direct port mapped I/O access. RANGE may be decimal or hex (starting with 0x)."),
2788 #[cfg(feature = "direct")]
2789 Argument::value("direct-mmio", "PATH@RANGE[,RANGE[,...]]", "Path and ranges for direct memory mapped I/O access. RANGE may be decimal or hex (starting with 0x)."),
2790 #[cfg(feature = "direct")]
2791 Argument::value("direct-level-irq", "irq", "Enable interrupt passthrough"),
2792 #[cfg(feature = "direct")]
2793 Argument::value("direct-edge-irq", "irq", "Enable interrupt passthrough"),
2794 #[cfg(feature = "direct")]
2795 Argument::value("direct-wake-irq", "irq", "Enable wakeup interrupt for host"),
2796 #[cfg(feature = "direct")]
2797 Argument::value("direct-gpe", "gpe", "Enable GPE interrupt and register access passthrough"),
2798 Argument::value("dmi", "DIR", "Directory with smbios_entry_point/DMI files"),
2799 Argument::flag("no-legacy", "Don't use legacy KBD/RTC devices emulation"),
2800 Argument::value("userspace-msr", "INDEX,action=r0", "Userspace MSR handling. Takes INDEX of the MSR and how they are handled.
2801 action=r0 - forward RDMSR to host kernel cpu0.
2802 "),
2803 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2804 Argument::flag("host-cpu-topology", "Use mirror cpu topology of Host for Guest VM"),
2805 Argument::flag("privileged-vm", "Grant this Guest VM certian privileges to manage Host resources, such as power management."),
2806 Argument::value("stub-pci-device", "DOMAIN:BUS:DEVICE.FUNCTION[,vendor=NUM][,device=NUM][,class=NUM][,subsystem_vendor=NUM][,subsystem_device=NUM][,revision=NUM]", "Comma-separated key=value pairs for setting up a stub PCI device that just enumerates. The first option in the list must specify a PCI address to claim.
2807 Optional further parameters
2808 vendor=NUM - PCI vendor ID
2809 device=NUM - PCI device ID
2810 class=NUM - PCI class (including class code, subclass, and programming interface)
2811 subsystem_vendor=NUM - PCI subsystem vendor ID
2812 subsystem_device=NUM - PCI subsystem device ID
2813 revision=NUM - revision"),
2814 Argument::flag_or_value("coiommu",
2815 "unpin_policy=POLICY,unpin_interval=NUM,unpin_limit=NUM,unpin_gen_threshold=NUM ",
2816 "Comma separated key=value pairs for setting up coiommu devices.
2817 Possible key values:
2818 unpin_policy=lru - LRU unpin policy.
2819 unpin_interval=NUM - Unpin interval time in seconds.
2820 unpin_limit=NUM - Unpin limit for each unpin cycle, in unit of page count. 0 is invalid.
2821 unpin_gen_threshold=NUM - Number of unpin intervals a pinned page must be busy for to be aged into the older which is less frequently checked generation."),
2822 Argument::value("file-backed-mapping", "addr=NUM,size=SIZE,path=PATH[,offset=NUM][,ro][,rw][,sync]", "Map the given file into guest memory at the specified address.
2823 Parameters (addr, size, path are required):
2824 addr=NUM - guest physical address to map at
2825 size=NUM - amount of memory to map
2826 path=PATH - path to backing file/device to map
2827 offset=NUM - offset in backing file (default 0)
2828 ro - make the mapping readonly (default)
2829 rw - make the mapping writable
2830 sync - open backing file with O_SYNC
2831 align - whether to adjust addr and size to page boundaries implicitly"),
2832 #[cfg(feature = "direct")]
2833 Argument::value("pcie-root-port", "PATH[,hp_gpe=NUM]", "Path to sysfs of host pcie root port and host pcie root port hotplug gpe number"),
2834 Argument::value("pivot-root", "PATH", "Path to empty directory to use for sandbox pivot root."),
2835 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2836 Argument::flag("s2idle", "Set Low Power S0 Idle Capable Flag for guest Fixed ACPI Description Table"),
2837 Argument::flag("strict-balloon", "Don't allow guest to use pages from the balloon"),
2838 Argument::value("mmio-address-range", "STARTADDR-ENDADDR[,STARTADDR-ENDADDR]*",
2839 "Ranges (inclusive) into which to limit guest mmio addresses. Note that
2840 this this may cause mmio allocations to fail if the specified ranges are
2841 incompatible with the default ranges calculated by crosvm."),
2842 #[cfg(target_os = "android")]
2843 Argument::value("task-profiles", "NAME[,...]", "Comma-separated names of the task profiles to apply to all threads in crosvm including the vCPU threads."),
2844 Argument::short_flag('h', "help", "Print help message.")];
2845
2846 let mut cfg = Config::default();
2847 let match_res = set_arguments(args, &arguments[..], |name, value| {
2848 set_argument(&mut cfg, name, value)
2849 })
2850 .and_then(|_| validate_arguments(&mut cfg));
2851
2852 match match_res {
2853 #[cfg(feature = "plugin")]
2854 Ok(()) if executable_is_plugin(&cfg.executable_path) => {
2855 match crosvm::plugin::run_config(cfg) {
2856 Ok(_) => {
2857 info!("crosvm and plugin have exited normally");
2858 Ok(CommandStatus::VmStop)
2859 }
2860 Err(e) => {
2861 error!("{:#}", e);
2862 Err(())
2863 }
2864 }
2865 }
2866 Ok(()) => match platform::run_config(cfg) {
2867 Ok(platform::ExitState::Stop) => {
2868 info!("crosvm has exited normally");
2869 Ok(CommandStatus::VmStop)
2870 }
2871 Ok(platform::ExitState::Reset) => {
2872 info!("crosvm has exited normally due to reset request");
2873 Ok(CommandStatus::VmReset)
2874 }
2875 Ok(platform::ExitState::Crash) => {
2876 info!("crosvm has exited due to a VM crash");
2877 Ok(CommandStatus::VmCrash)
2878 }
2879 Ok(platform::ExitState::GuestPanic) => {
2880 info!("crosvm has exited due to a kernel panic in guest");
2881 Ok(CommandStatus::GuestPanic)
2882 }
2883 Err(e) => {
2884 error!("crosvm has exited with error: {:#}", e);
2885 Err(())
2886 }
2887 },
2888 Err(argument::Error::PrintHelp) => {
2889 print_help("crosvm run", "KERNEL", &arguments[..]);
2890 Ok(CommandStatus::Success)
2891 }
2892 Err(e) => {
2893 error!("{}", e);
2894 Err(())
2895 }
2896 }
2897 }
2898
stop_vms(mut args: std::env::Args) -> std::result::Result<(), ()>2899 fn stop_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
2900 if args.len() == 0 {
2901 print_help("crosvm stop", "VM_SOCKET...", &[]);
2902 println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
2903 return Err(());
2904 }
2905 let socket_path = &args.next().unwrap();
2906 let socket_path = Path::new(&socket_path);
2907 vms_request(&VmRequest::Exit, socket_path)
2908 }
2909
suspend_vms(mut args: std::env::Args) -> std::result::Result<(), ()>2910 fn suspend_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
2911 if args.len() == 0 {
2912 print_help("crosvm suspend", "VM_SOCKET...", &[]);
2913 println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
2914 return Err(());
2915 }
2916 let socket_path = &args.next().unwrap();
2917 let socket_path = Path::new(&socket_path);
2918 vms_request(&VmRequest::Suspend, socket_path)
2919 }
2920
resume_vms(mut args: std::env::Args) -> std::result::Result<(), ()>2921 fn resume_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
2922 if args.len() == 0 {
2923 print_help("crosvm resume", "VM_SOCKET...", &[]);
2924 println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
2925 return Err(());
2926 }
2927 let socket_path = &args.next().unwrap();
2928 let socket_path = Path::new(&socket_path);
2929 vms_request(&VmRequest::Resume, socket_path)
2930 }
2931
powerbtn_vms(mut args: std::env::Args) -> std::result::Result<(), ()>2932 fn powerbtn_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
2933 if args.len() == 0 {
2934 print_help("crosvm powerbtn", "VM_SOCKET...", &[]);
2935 println!("Triggers a power button event in the crosvm instance listening on each `VM_SOCKET` given.");
2936 return Err(());
2937 }
2938 let socket_path = &args.next().unwrap();
2939 let socket_path = Path::new(&socket_path);
2940 vms_request(&VmRequest::Powerbtn, socket_path)
2941 }
2942
inject_gpe(mut args: std::env::Args) -> std::result::Result<(), ()>2943 fn inject_gpe(mut args: std::env::Args) -> std::result::Result<(), ()> {
2944 if args.len() < 2 {
2945 print_help("crosvm gpe", "GPE# VM_SOCKET...", &[]);
2946 println!("Injects a general-purpose event (GPE#) into the crosvm instance listening on each `VM_SOCKET` given.");
2947 return Err(());
2948 }
2949 let gpe = match args.next().unwrap().parse::<u32>() {
2950 Ok(n) => n,
2951 Err(_) => {
2952 error!("Failed to parse GPE#");
2953 return Err(());
2954 }
2955 };
2956
2957 let socket_path = &args.next().unwrap();
2958 let socket_path = Path::new(&socket_path);
2959 vms_request(&VmRequest::Gpe(gpe), socket_path)
2960 }
2961
balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()>2962 fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
2963 if args.len() < 2 {
2964 print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
2965 println!("Set the ballon size of the crosvm instance to `SIZE` bytes.");
2966 return Err(());
2967 }
2968 let num_bytes = match args.next().unwrap().parse::<u64>() {
2969 Ok(n) => n,
2970 Err(_) => {
2971 error!("Failed to parse number of bytes");
2972 return Err(());
2973 }
2974 };
2975
2976 let command = BalloonControlCommand::Adjust { num_bytes };
2977 let socket_path = &args.next().unwrap();
2978 let socket_path = Path::new(&socket_path);
2979 vms_request(&VmRequest::BalloonCommand(command), socket_path)
2980 }
2981
balloon_stats(mut args: std::env::Args) -> std::result::Result<(), ()>2982 fn balloon_stats(mut args: std::env::Args) -> std::result::Result<(), ()> {
2983 if args.len() != 1 {
2984 print_help("crosvm balloon_stats", "VM_SOCKET", &[]);
2985 println!("Prints virtio balloon statistics for a `VM_SOCKET`.");
2986 return Err(());
2987 }
2988 let command = BalloonControlCommand::Stats {};
2989 let request = &VmRequest::BalloonCommand(command);
2990 let socket_path = &args.next().unwrap();
2991 let socket_path = Path::new(&socket_path);
2992 let response = handle_request(request, socket_path)?;
2993 match serde_json::to_string_pretty(&response) {
2994 Ok(response_json) => println!("{}", response_json),
2995 Err(e) => {
2996 error!("Failed to serialize into JSON: {}", e);
2997 return Err(());
2998 }
2999 }
3000 match response {
3001 VmResponse::BalloonStats { .. } => Ok(()),
3002 _ => Err(()),
3003 }
3004 }
3005
modify_battery(mut args: std::env::Args) -> std::result::Result<(), ()>3006 fn modify_battery(mut args: std::env::Args) -> std::result::Result<(), ()> {
3007 if args.len() < 4 {
3008 print_help(
3009 "crosvm battery BATTERY_TYPE ",
3010 "[status STATUS | \
3011 present PRESENT | \
3012 health HEALTH | \
3013 capacity CAPACITY | \
3014 aconline ACONLINE ] \
3015 VM_SOCKET...",
3016 &[],
3017 );
3018 return Err(());
3019 }
3020
3021 // This unwrap will not panic because of the above length check.
3022 let battery_type = args.next().unwrap();
3023 let property = args.next().unwrap();
3024 let target = args.next().unwrap();
3025
3026 let socket_path = args.next().unwrap();
3027 let socket_path = Path::new(&socket_path);
3028
3029 do_modify_battery(socket_path, &*battery_type, &*property, &*target)
3030 }
3031
modify_vfio(mut args: std::env::Args) -> std::result::Result<(), ()>3032 fn modify_vfio(mut args: std::env::Args) -> std::result::Result<(), ()> {
3033 if args.len() < 3 {
3034 print_help(
3035 "crosvm vfio",
3036 "[add | remove host_vfio_sysfs] VM_SOCKET...",
3037 &[],
3038 );
3039 return Err(());
3040 }
3041
3042 // This unwrap will not panic because of the above length check.
3043 let command = args.next().unwrap();
3044 let path_str = args.next().unwrap();
3045 let vfio_path = PathBuf::from(&path_str);
3046 if !vfio_path.exists() || !vfio_path.is_dir() {
3047 error!("Invalid host sysfs path: {}", path_str);
3048 return Err(());
3049 }
3050
3051 let socket_path = args.next().unwrap();
3052 let socket_path = Path::new(&socket_path);
3053
3054 let add = match command.as_ref() {
3055 "add" => true,
3056 "remove" => false,
3057 other => {
3058 error!("Invalid vfio command {}", other);
3059 return Err(());
3060 }
3061 };
3062
3063 let request = VmRequest::VfioCommand { vfio_path, add };
3064 handle_request(&request, socket_path)?;
3065 Ok(())
3066 }
3067
3068 #[cfg(feature = "composite-disk")]
create_composite(mut args: std::env::Args) -> std::result::Result<(), ()>3069 fn create_composite(mut args: std::env::Args) -> std::result::Result<(), ()> {
3070 if args.len() < 1 {
3071 print_help("crosvm create_composite", "PATH [LABEL:PARTITION]..", &[]);
3072 println!("Creates a new composite disk image containing the given partition images");
3073 return Err(());
3074 }
3075
3076 let composite_image_path = args.next().unwrap();
3077 let zero_filler_path = format!("{}.filler", composite_image_path);
3078 let header_path = format!("{}.header", composite_image_path);
3079 let footer_path = format!("{}.footer", composite_image_path);
3080
3081 let mut composite_image_file = OpenOptions::new()
3082 .create(true)
3083 .read(true)
3084 .write(true)
3085 .truncate(true)
3086 .open(&composite_image_path)
3087 .map_err(|e| {
3088 error!(
3089 "Failed opening composite disk image file at '{}': {}",
3090 composite_image_path, e
3091 );
3092 })?;
3093 create_zero_filler(&zero_filler_path).map_err(|e| {
3094 error!(
3095 "Failed to create zero filler file at '{}': {}",
3096 &zero_filler_path, e
3097 );
3098 })?;
3099 let mut header_file = OpenOptions::new()
3100 .create(true)
3101 .read(true)
3102 .write(true)
3103 .truncate(true)
3104 .open(&header_path)
3105 .map_err(|e| {
3106 error!(
3107 "Failed opening header image file at '{}': {}",
3108 header_path, e
3109 );
3110 })?;
3111 let mut footer_file = OpenOptions::new()
3112 .create(true)
3113 .read(true)
3114 .write(true)
3115 .truncate(true)
3116 .open(&footer_path)
3117 .map_err(|e| {
3118 error!(
3119 "Failed opening footer image file at '{}': {}",
3120 footer_path, e
3121 );
3122 })?;
3123
3124 let partitions = args
3125 .into_iter()
3126 .map(|partition_arg| {
3127 if let [label, path] = partition_arg.split(":").collect::<Vec<_>>()[..] {
3128 let partition_file = File::open(path)
3129 .map_err(|e| error!("Failed to open partition image: {}", e))?;
3130 let size =
3131 create_disk_file(partition_file, disk::MAX_NESTING_DEPTH, Path::new(path))
3132 .map_err(|e| error!("Failed to create DiskFile instance: {}", e))?
3133 .get_len()
3134 .map_err(|e| error!("Failed to get length of partition image: {}", e))?;
3135 Ok(PartitionInfo {
3136 label: label.to_owned(),
3137 path: Path::new(path).to_owned(),
3138 partition_type: ImagePartitionType::LinuxFilesystem,
3139 writable: false,
3140 size,
3141 })
3142 } else {
3143 error!(
3144 "Must specify label and path for partition '{}', like LABEL:PATH",
3145 partition_arg
3146 );
3147 Err(())
3148 }
3149 })
3150 .collect::<Result<Vec<_>, _>>()?;
3151
3152 create_composite_disk(
3153 &partitions,
3154 &PathBuf::from(zero_filler_path),
3155 &PathBuf::from(header_path),
3156 &mut header_file,
3157 &PathBuf::from(footer_path),
3158 &mut footer_file,
3159 &mut composite_image_file,
3160 )
3161 .map_err(|e| {
3162 error!(
3163 "Failed to create composite disk image at '{}': {}",
3164 composite_image_path, e
3165 );
3166 })?;
3167
3168 Ok(())
3169 }
3170
create_qcow2(args: std::env::Args) -> std::result::Result<(), ()>3171 fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> {
3172 let arguments = [
3173 Argument::positional("PATH", "where to create the qcow2 image"),
3174 Argument::positional("[SIZE]", "the expanded size of the image"),
3175 Argument::value(
3176 "backing_file",
3177 "path/to/file",
3178 " the file to back the image",
3179 ),
3180 ];
3181 let mut positional_index = 0;
3182 let mut file_path = String::from("");
3183 let mut size: Option<u64> = None;
3184 let mut backing_file: Option<String> = None;
3185 set_arguments(args, &arguments[..], |name, value| {
3186 match (name, positional_index) {
3187 ("", 0) => {
3188 // NAME
3189 positional_index += 1;
3190 file_path = value.unwrap().to_owned();
3191 }
3192 ("", 1) => {
3193 // [SIZE]
3194 positional_index += 1;
3195 size = Some(value.unwrap().parse::<u64>().map_err(|_| {
3196 argument::Error::InvalidValue {
3197 value: value.unwrap().to_owned(),
3198 expected: String::from("SIZE should be a nonnegative integer"),
3199 }
3200 })?);
3201 }
3202 ("", _) => {
3203 return Err(argument::Error::TooManyArguments(
3204 "Expected at most 2 positional arguments".to_owned(),
3205 ));
3206 }
3207 ("backing_file", _) => {
3208 backing_file = value.map(|x| x.to_owned());
3209 }
3210 _ => unreachable!(),
3211 };
3212 Ok(())
3213 })
3214 .map_err(|e| {
3215 error!("Unable to parse command line arguments: {}", e);
3216 })?;
3217 if file_path.is_empty() || !(size.is_some() ^ backing_file.is_some()) {
3218 print_help("crosvm create_qcow2", "PATH [SIZE]", &arguments);
3219 println!(
3220 "Create a new QCOW2 image at `PATH` of either the specified `SIZE` in bytes or
3221 with a '--backing_file'."
3222 );
3223 return Err(());
3224 }
3225
3226 let file = OpenOptions::new()
3227 .create(true)
3228 .read(true)
3229 .write(true)
3230 .truncate(true)
3231 .open(&file_path)
3232 .map_err(|e| {
3233 error!("Failed opening qcow file at '{}': {}", file_path, e);
3234 })?;
3235
3236 match (size, backing_file) {
3237 (Some(size), None) => QcowFile::new(file, size).map_err(|e| {
3238 error!("Failed to create qcow file at '{}': {}", file_path, e);
3239 })?,
3240 (None, Some(backing_file)) => {
3241 QcowFile::new_from_backing(file, &backing_file, disk::MAX_NESTING_DEPTH).map_err(
3242 |e| {
3243 error!("Failed to create qcow file at '{}': {}", file_path, e);
3244 },
3245 )?
3246 }
3247 _ => unreachable!(),
3248 };
3249 Ok(())
3250 }
3251
start_device(mut args: std::env::Args) -> std::result::Result<(), ()>3252 fn start_device(mut args: std::env::Args) -> std::result::Result<(), ()> {
3253 let print_usage = || {
3254 print_help(
3255 "crosvm device",
3256 " (block|console|cras-snd|fs|gpu|net|wl) <device-specific arguments>",
3257 &[],
3258 );
3259 };
3260
3261 if args.len() == 0 {
3262 print_usage();
3263 return Err(());
3264 }
3265
3266 let device = args.next().unwrap();
3267
3268 let program_name = format!("crosvm device {}", device);
3269
3270 let args = args.collect::<Vec<_>>();
3271 let args = args.iter().map(Deref::deref).collect::<Vec<_>>();
3272 let args = args.as_slice();
3273
3274 let result = match device.as_str() {
3275 "block" => run_block_device(&program_name, args),
3276 "console" => run_console_device(&program_name, args),
3277 #[cfg(feature = "audio_cras")]
3278 "cras-snd" => run_cras_snd_device(&program_name, args),
3279 "fs" => run_fs_device(&program_name, args),
3280 #[cfg(feature = "gpu")]
3281 "gpu" => run_gpu_device(&program_name, args),
3282 "net" => run_net_device(&program_name, args),
3283 "vsock" => run_vsock_device(&program_name, args),
3284 "wl" => run_wl_device(&program_name, args),
3285 _ => {
3286 println!("Unknown device name: {}", device);
3287 print_usage();
3288 return Err(());
3289 }
3290 };
3291
3292 result.map_err(|e| {
3293 error!("Failed to run {} device: {:#}", device, e);
3294 })
3295 }
3296
disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()>3297 fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
3298 if args.len() < 2 {
3299 print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
3300 println!("Manage attached virtual disk devices.");
3301 println!("Subcommands:");
3302 println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET");
3303 return Err(());
3304 }
3305 let subcommand: &str = &args.next().unwrap();
3306
3307 let request = match subcommand {
3308 "resize" => {
3309 let disk_index = match args.next().unwrap().parse::<usize>() {
3310 Ok(n) => n,
3311 Err(_) => {
3312 error!("Failed to parse disk index");
3313 return Err(());
3314 }
3315 };
3316
3317 let new_size = match args.next().unwrap().parse::<u64>() {
3318 Ok(n) => n,
3319 Err(_) => {
3320 error!("Failed to parse disk size");
3321 return Err(());
3322 }
3323 };
3324
3325 VmRequest::DiskCommand {
3326 disk_index,
3327 command: DiskControlCommand::Resize { new_size },
3328 }
3329 }
3330 _ => {
3331 error!("Unknown disk subcommand '{}'", subcommand);
3332 return Err(());
3333 }
3334 };
3335
3336 let socket_path = &args.next().unwrap();
3337 let socket_path = Path::new(&socket_path);
3338 vms_request(&request, socket_path)
3339 }
3340
make_rt(mut args: std::env::Args) -> std::result::Result<(), ()>3341 fn make_rt(mut args: std::env::Args) -> std::result::Result<(), ()> {
3342 if args.len() == 0 {
3343 print_help("crosvm make_rt", "VM_SOCKET...", &[]);
3344 println!("Makes the crosvm instance listening on each `VM_SOCKET` given RT.");
3345 return Err(());
3346 }
3347 let socket_path = &args.next().unwrap();
3348 let socket_path = Path::new(&socket_path);
3349 vms_request(&VmRequest::MakeRT, socket_path)
3350 }
3351
parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)>3352 fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
3353 debug!("parse_bus_id_addr: {}", v);
3354 let mut ids = v.split(':');
3355 match (ids.next(), ids.next(), ids.next(), ids.next()) {
3356 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
3357 let bus_id = bus_id
3358 .parse::<u8>()
3359 .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
3360 let addr = addr
3361 .parse::<u8>()
3362 .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
3363 let vid = u16::from_str_radix(vid, 16)
3364 .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
3365 let pid = u16::from_str_radix(pid, 16)
3366 .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
3367 Ok((bus_id, addr, vid, pid))
3368 }
3369 _ => Err(ModifyUsbError::ArgParse(
3370 "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
3371 v.to_owned(),
3372 )),
3373 }
3374 }
3375
usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult>3376 fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
3377 let val = args
3378 .next()
3379 .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
3380 let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
3381 let dev_path = PathBuf::from(
3382 args.next()
3383 .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
3384 );
3385
3386 let socket_path = args
3387 .next()
3388 .ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
3389 let socket_path = Path::new(&socket_path);
3390
3391 do_usb_attach(socket_path, bus, addr, vid, pid, &dev_path)
3392 }
3393
usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult>3394 fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
3395 let port: u8 = args
3396 .next()
3397 .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
3398 p.parse::<u8>()
3399 .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
3400 })?;
3401 let socket_path = args
3402 .next()
3403 .ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
3404 let socket_path = Path::new(&socket_path);
3405 do_usb_detach(socket_path, port)
3406 }
3407
usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult>3408 fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
3409 let socket_path = args
3410 .next()
3411 .ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
3412 let socket_path = Path::new(&socket_path);
3413 do_usb_list(socket_path)
3414 }
3415
modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()>3416 fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
3417 if args.len() < 2 {
3418 print_help("crosvm usb",
3419 "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
3420 return Err(());
3421 }
3422
3423 // This unwrap will not panic because of the above length check.
3424 let command = &args.next().unwrap();
3425 let result = match command.as_ref() {
3426 "attach" => usb_attach(args),
3427 "detach" => usb_detach(args),
3428 "list" => usb_list(args),
3429 other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
3430 };
3431 match result {
3432 Ok(response) => {
3433 println!("{}", response);
3434 Ok(())
3435 }
3436 Err(e) => {
3437 println!("error {}", e);
3438 Err(())
3439 }
3440 }
3441 }
3442
3443 #[allow(clippy::unnecessary_wraps)]
pkg_version() -> std::result::Result<(), ()>3444 fn pkg_version() -> std::result::Result<(), ()> {
3445 const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
3446 const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
3447
3448 print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
3449 match PKG_VERSION {
3450 Some(v) => println!("-{}", v),
3451 None => println!(),
3452 }
3453 Ok(())
3454 }
3455
print_usage()3456 fn print_usage() {
3457 print_help("crosvm", "[--extended-status] [command]", &[]);
3458 println!("Commands:");
3459 println!(" balloon - Set balloon size of the crosvm instance.");
3460 println!(" balloon_stats - Prints virtio balloon statistics.");
3461 println!(" battery - Modify battery.");
3462 #[cfg(feature = "composite-disk")]
3463 println!(" create_composite - Create a new composite disk image file.");
3464 println!(" create_qcow2 - Create a new qcow2 disk image file.");
3465 println!(" device - Start a device process.");
3466 println!(" disk - Manage attached virtual disk devices.");
3467 println!(
3468 " make_rt - Enables real-time vcpu priority for crosvm instances started with \
3469 `--delay-rt`."
3470 );
3471 println!(" resume - Resumes the crosvm instance.");
3472 println!(" run - Start a new crosvm instance.");
3473 println!(" stop - Stops crosvm instances via their control sockets.");
3474 println!(" suspend - Suspends the crosvm instance.");
3475 println!(" powerbtn - Triggers a power button event in the crosvm instance.");
3476 println!(" gpe - Injects a general-purpose event into the crosvm instance.");
3477 println!(" usb - Manage attached virtual USB devices.");
3478 println!(" version - Show package version.");
3479 println!(" vfio - add/remove host vfio pci device into guest.");
3480 }
3481
crosvm_main() -> std::result::Result<CommandStatus, ()>3482 fn crosvm_main() -> std::result::Result<CommandStatus, ()> {
3483 if let Err(e) = syslog::init() {
3484 println!("failed to initialize syslog: {}", e);
3485 return Err(());
3486 }
3487
3488 panic_hook::set_panic_hook();
3489
3490 let mut args = std::env::args();
3491 if args.next().is_none() {
3492 error!("expected executable name");
3493 return Err(());
3494 }
3495
3496 let mut cmd_arg = args.next();
3497 let extended_status = match cmd_arg.as_ref().map(|s| s.as_ref()) {
3498 Some("--extended-status") => {
3499 cmd_arg = args.next();
3500 true
3501 }
3502 _ => false,
3503 };
3504
3505 let command = match cmd_arg {
3506 Some(c) => c,
3507 None => {
3508 print_usage();
3509 return Ok(CommandStatus::Success);
3510 }
3511 };
3512
3513 // Past this point, usage of exit is in danger of leaking zombie processes.
3514 let ret = if command == "run" {
3515 // We handle run_vm separately because it does not simply signal success/error
3516 // but also indicates whether the guest requested reset or stop.
3517 run_vm(args)
3518 } else {
3519 match &command[..] {
3520 "balloon" => balloon_vms(args),
3521 "balloon_stats" => balloon_stats(args),
3522 "battery" => modify_battery(args),
3523 #[cfg(feature = "composite-disk")]
3524 "create_composite" => create_composite(args),
3525 "create_qcow2" => create_qcow2(args),
3526 "device" => start_device(args),
3527 "disk" => disk_cmd(args),
3528 "make_rt" => make_rt(args),
3529 "resume" => resume_vms(args),
3530 "stop" => stop_vms(args),
3531 "suspend" => suspend_vms(args),
3532 "powerbtn" => powerbtn_vms(args),
3533 "gpe" => inject_gpe(args),
3534 "usb" => modify_usb(args),
3535 "version" => pkg_version(),
3536 "vfio" => modify_vfio(args),
3537 c => {
3538 println!("invalid subcommand: {:?}", c);
3539 print_usage();
3540 Err(())
3541 }
3542 }
3543 .map(|_| CommandStatus::Success)
3544 };
3545
3546 // Reap exit status from any child device processes. At this point, all devices should have been
3547 // dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
3548 // take some time for the processes to shut down.
3549 if !wait_all_children() {
3550 // We gave them a chance, and it's too late.
3551 warn!("not all child processes have exited; sending SIGKILL");
3552 if let Err(e) = kill_process_group() {
3553 // We're now at the mercy of the OS to clean up after us.
3554 warn!("unable to kill all child processes: {}", e);
3555 }
3556 }
3557
3558 // WARNING: Any code added after this point is not guaranteed to run
3559 // since we may forcibly kill this process (and its children) above.
3560 ret.map(|s| {
3561 if extended_status {
3562 s
3563 } else {
3564 CommandStatus::Success
3565 }
3566 })
3567 }
3568
main()3569 fn main() {
3570 let exit_code = match crosvm_main() {
3571 Ok(CommandStatus::Success | CommandStatus::VmStop) => 0,
3572 Ok(CommandStatus::VmReset) => 32,
3573 Ok(CommandStatus::VmCrash) => 33,
3574 Ok(CommandStatus::GuestPanic) => 34,
3575 Err(_) => 1,
3576 };
3577 std::process::exit(exit_code);
3578 }
3579
3580 #[cfg(test)]
3581 mod tests {
3582 use super::*;
3583 use crosvm::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH};
3584
3585 #[test]
parse_cpu_set_single()3586 fn parse_cpu_set_single() {
3587 assert_eq!(parse_cpu_set("123").expect("parse failed"), vec![123]);
3588 }
3589
3590 #[test]
parse_cpu_set_list()3591 fn parse_cpu_set_list() {
3592 assert_eq!(
3593 parse_cpu_set("0,1,2,3").expect("parse failed"),
3594 vec![0, 1, 2, 3]
3595 );
3596 }
3597
3598 #[test]
parse_cpu_set_range()3599 fn parse_cpu_set_range() {
3600 assert_eq!(
3601 parse_cpu_set("0-3").expect("parse failed"),
3602 vec![0, 1, 2, 3]
3603 );
3604 }
3605
3606 #[test]
parse_cpu_set_list_of_ranges()3607 fn parse_cpu_set_list_of_ranges() {
3608 assert_eq!(
3609 parse_cpu_set("3-4,7-9,18").expect("parse failed"),
3610 vec![3, 4, 7, 8, 9, 18]
3611 );
3612 }
3613
3614 #[test]
parse_cpu_set_repeated()3615 fn parse_cpu_set_repeated() {
3616 // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
3617 assert_eq!(parse_cpu_set("1,1,1").expect("parse failed"), vec![1, 1, 1]);
3618 }
3619
3620 #[test]
parse_cpu_set_negative()3621 fn parse_cpu_set_negative() {
3622 // Negative CPU numbers are not allowed.
3623 parse_cpu_set("-3").expect_err("parse should have failed");
3624 }
3625
3626 #[test]
parse_cpu_set_reverse_range()3627 fn parse_cpu_set_reverse_range() {
3628 // Ranges must be from low to high.
3629 parse_cpu_set("5-2").expect_err("parse should have failed");
3630 }
3631
3632 #[test]
parse_cpu_set_open_range()3633 fn parse_cpu_set_open_range() {
3634 parse_cpu_set("3-").expect_err("parse should have failed");
3635 }
3636
3637 #[test]
parse_cpu_set_extra_comma()3638 fn parse_cpu_set_extra_comma() {
3639 parse_cpu_set("0,1,2,").expect_err("parse should have failed");
3640 }
3641
3642 #[test]
parse_cpu_affinity_global()3643 fn parse_cpu_affinity_global() {
3644 assert_eq!(
3645 parse_cpu_affinity("0,5-7,9").expect("parse failed"),
3646 VcpuAffinity::Global(vec![0, 5, 6, 7, 9]),
3647 );
3648 }
3649
3650 #[test]
parse_cpu_affinity_per_vcpu_one_to_one()3651 fn parse_cpu_affinity_per_vcpu_one_to_one() {
3652 let mut expected_map = BTreeMap::new();
3653 expected_map.insert(0, vec![0]);
3654 expected_map.insert(1, vec![1]);
3655 expected_map.insert(2, vec![2]);
3656 expected_map.insert(3, vec![3]);
3657 assert_eq!(
3658 parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
3659 VcpuAffinity::PerVcpu(expected_map),
3660 );
3661 }
3662
3663 #[test]
parse_cpu_affinity_per_vcpu_sets()3664 fn parse_cpu_affinity_per_vcpu_sets() {
3665 let mut expected_map = BTreeMap::new();
3666 expected_map.insert(0, vec![0, 1, 2]);
3667 expected_map.insert(1, vec![3, 4, 5]);
3668 expected_map.insert(2, vec![6, 7, 8]);
3669 assert_eq!(
3670 parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
3671 VcpuAffinity::PerVcpu(expected_map),
3672 );
3673 }
3674
3675 #[cfg(feature = "audio_cras")]
3676 #[test]
parse_ac97_vaild()3677 fn parse_ac97_vaild() {
3678 parse_ac97_options("backend=cras").expect("parse should have succeded");
3679 }
3680
3681 #[cfg(feature = "audio")]
3682 #[test]
parse_ac97_null_vaild()3683 fn parse_ac97_null_vaild() {
3684 parse_ac97_options("backend=null").expect("parse should have succeded");
3685 }
3686
3687 #[cfg(feature = "audio_cras")]
3688 #[test]
parse_ac97_capture_vaild()3689 fn parse_ac97_capture_vaild() {
3690 parse_ac97_options("backend=cras,capture=true").expect("parse should have succeded");
3691 }
3692
3693 #[cfg(feature = "audio_cras")]
3694 #[test]
parse_ac97_client_type()3695 fn parse_ac97_client_type() {
3696 parse_ac97_options("backend=cras,capture=true,client_type=crosvm")
3697 .expect("parse should have succeded");
3698 parse_ac97_options("backend=cras,capture=true,client_type=arcvm")
3699 .expect("parse should have succeded");
3700 parse_ac97_options("backend=cras,capture=true,client_type=none")
3701 .expect_err("parse should have failed");
3702 }
3703
3704 #[cfg(feature = "audio_cras")]
3705 #[test]
parse_ac97_socket_type()3706 fn parse_ac97_socket_type() {
3707 parse_ac97_options("socket_type=unified").expect("parse should have succeded");
3708 parse_ac97_options("socket_type=legacy").expect("parse should have succeded");
3709 }
3710
3711 #[cfg(feature = "audio")]
3712 #[test]
parse_ac97_vios_valid()3713 fn parse_ac97_vios_valid() {
3714 parse_ac97_options("backend=vios,server=/path/to/server")
3715 .expect("parse should have succeded");
3716 }
3717
3718 #[test]
parse_serial_vaild()3719 fn parse_serial_vaild() {
3720 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
3721 .expect("parse should have succeded");
3722 }
3723
3724 #[test]
parse_serial_virtio_console_vaild()3725 fn parse_serial_virtio_console_vaild() {
3726 parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
3727 .expect("parse should have succeded");
3728 }
3729
3730 #[test]
parse_serial_valid_no_num()3731 fn parse_serial_valid_no_num() {
3732 parse_serial_options("type=syslog").expect("parse should have succeded");
3733 }
3734
3735 #[test]
parse_serial_equals_in_value()3736 fn parse_serial_equals_in_value() {
3737 let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
3738 .expect("parse should have succeded");
3739 assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
3740 }
3741
3742 #[test]
parse_serial_invalid_type()3743 fn parse_serial_invalid_type() {
3744 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
3745 }
3746
3747 #[test]
parse_serial_invalid_num_upper()3748 fn parse_serial_invalid_num_upper() {
3749 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
3750 }
3751
3752 #[test]
parse_serial_invalid_num_lower()3753 fn parse_serial_invalid_num_lower() {
3754 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
3755 }
3756
3757 #[test]
parse_serial_virtio_console_invalid_num_lower()3758 fn parse_serial_virtio_console_invalid_num_lower() {
3759 parse_serial_options("type=syslog,hardware=virtio-console,num=0")
3760 .expect_err("parse should have failed");
3761 }
3762
3763 #[test]
parse_serial_invalid_num_string()3764 fn parse_serial_invalid_num_string() {
3765 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
3766 }
3767
3768 #[test]
parse_serial_invalid_option()3769 fn parse_serial_invalid_option() {
3770 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
3771 }
3772
3773 #[test]
parse_serial_invalid_two_stdin()3774 fn parse_serial_invalid_two_stdin() {
3775 let mut config = Config::default();
3776 set_argument(&mut config, "serial", Some("num=1,type=stdout,stdin=true"))
3777 .expect("should parse the first serial argument");
3778 set_argument(&mut config, "serial", Some("num=2,type=stdout,stdin=true"))
3779 .expect_err("should fail to parse a second serial port connected to stdin");
3780 }
3781
3782 #[test]
parse_plugin_mount_valid()3783 fn parse_plugin_mount_valid() {
3784 let mut config = Config::default();
3785 set_argument(
3786 &mut config,
3787 "plugin-mount",
3788 Some("/dev/null:/dev/zero:true"),
3789 )
3790 .expect("parse should succeed");
3791 assert_eq!(config.plugin_mounts[0].src, PathBuf::from("/dev/null"));
3792 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/zero"));
3793 assert!(config.plugin_mounts[0].writable);
3794 }
3795
3796 #[test]
parse_plugin_mount_valid_shorthand()3797 fn parse_plugin_mount_valid_shorthand() {
3798 let mut config = Config::default();
3799 set_argument(&mut config, "plugin-mount", Some("/dev/null")).expect("parse should succeed");
3800 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/null"));
3801 assert!(!config.plugin_mounts[0].writable);
3802 set_argument(&mut config, "plugin-mount", Some("/dev/null:/dev/zero"))
3803 .expect("parse should succeed");
3804 assert_eq!(config.plugin_mounts[1].dst, PathBuf::from("/dev/zero"));
3805 assert!(!config.plugin_mounts[1].writable);
3806 set_argument(&mut config, "plugin-mount", Some("/dev/null::true"))
3807 .expect("parse should succeed");
3808 assert_eq!(config.plugin_mounts[2].dst, PathBuf::from("/dev/null"));
3809 assert!(config.plugin_mounts[2].writable);
3810 }
3811
3812 #[test]
parse_plugin_mount_invalid()3813 fn parse_plugin_mount_invalid() {
3814 let mut config = Config::default();
3815 set_argument(&mut config, "plugin-mount", Some("")).expect_err("parse should fail");
3816 set_argument(
3817 &mut config,
3818 "plugin-mount",
3819 Some("/dev/null:/dev/null:true:false"),
3820 )
3821 .expect_err("parse should fail because too many arguments");
3822 set_argument(&mut config, "plugin-mount", Some("null:/dev/null:true"))
3823 .expect_err("parse should fail because source is not absolute");
3824 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:true"))
3825 .expect_err("parse should fail because source is not absolute");
3826 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:blah"))
3827 .expect_err("parse should fail because flag is not boolean");
3828 }
3829
3830 #[test]
parse_plugin_gid_map_valid()3831 fn parse_plugin_gid_map_valid() {
3832 let mut config = Config::default();
3833 set_argument(&mut config, "plugin-gid-map", Some("1:2:3")).expect("parse should succeed");
3834 assert_eq!(config.plugin_gid_maps[0].inner, 1);
3835 assert_eq!(config.plugin_gid_maps[0].outer, 2);
3836 assert_eq!(config.plugin_gid_maps[0].count, 3);
3837 }
3838
3839 #[test]
parse_plugin_gid_map_valid_shorthand()3840 fn parse_plugin_gid_map_valid_shorthand() {
3841 let mut config = Config::default();
3842 set_argument(&mut config, "plugin-gid-map", Some("1")).expect("parse should succeed");
3843 assert_eq!(config.plugin_gid_maps[0].inner, 1);
3844 assert_eq!(config.plugin_gid_maps[0].outer, 1);
3845 assert_eq!(config.plugin_gid_maps[0].count, 1);
3846 set_argument(&mut config, "plugin-gid-map", Some("1:2")).expect("parse should succeed");
3847 assert_eq!(config.plugin_gid_maps[1].inner, 1);
3848 assert_eq!(config.plugin_gid_maps[1].outer, 2);
3849 assert_eq!(config.plugin_gid_maps[1].count, 1);
3850 set_argument(&mut config, "plugin-gid-map", Some("1::3")).expect("parse should succeed");
3851 assert_eq!(config.plugin_gid_maps[2].inner, 1);
3852 assert_eq!(config.plugin_gid_maps[2].outer, 1);
3853 assert_eq!(config.plugin_gid_maps[2].count, 3);
3854 }
3855
3856 #[test]
parse_plugin_gid_map_invalid()3857 fn parse_plugin_gid_map_invalid() {
3858 let mut config = Config::default();
3859 set_argument(&mut config, "plugin-gid-map", Some("")).expect_err("parse should fail");
3860 set_argument(&mut config, "plugin-gid-map", Some("1:2:3:4"))
3861 .expect_err("parse should fail because too many arguments");
3862 set_argument(&mut config, "plugin-gid-map", Some("blah:2:3"))
3863 .expect_err("parse should fail because inner is not a number");
3864 set_argument(&mut config, "plugin-gid-map", Some("1:blah:3"))
3865 .expect_err("parse should fail because outer is not a number");
3866 set_argument(&mut config, "plugin-gid-map", Some("1:2:blah"))
3867 .expect_err("parse should fail because count is not a number");
3868 }
3869
3870 #[test]
single_touch_spec_and_track_pad_spec_default_size()3871 fn single_touch_spec_and_track_pad_spec_default_size() {
3872 let mut config = Config::default();
3873 config
3874 .executable_path
3875 .replace(Executable::Kernel(PathBuf::from("kernel")));
3876 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
3877 set_argument(&mut config, "trackpad", Some("/dev/single-touch-test")).unwrap();
3878 validate_arguments(&mut config).unwrap();
3879 assert_eq!(
3880 config.virtio_single_touch.first().unwrap().get_size(),
3881 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
3882 );
3883 assert_eq!(
3884 config.virtio_trackpad.first().unwrap().get_size(),
3885 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
3886 );
3887 }
3888
3889 #[cfg(feature = "gpu")]
3890 #[test]
single_touch_spec_default_size_from_gpu()3891 fn single_touch_spec_default_size_from_gpu() {
3892 let width = 12345u32;
3893 let height = 54321u32;
3894 let mut config = Config::default();
3895 config
3896 .executable_path
3897 .replace(Executable::Kernel(PathBuf::from("kernel")));
3898 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
3899 set_argument(
3900 &mut config,
3901 "gpu",
3902 Some(&format!("width={},height={}", width, height)),
3903 )
3904 .unwrap();
3905 validate_arguments(&mut config).unwrap();
3906 assert_eq!(
3907 config.virtio_single_touch.first().unwrap().get_size(),
3908 (width, height)
3909 );
3910 }
3911
3912 #[test]
single_touch_spec_and_track_pad_spec_with_size()3913 fn single_touch_spec_and_track_pad_spec_with_size() {
3914 let width = 12345u32;
3915 let height = 54321u32;
3916 let mut config = Config::default();
3917 config
3918 .executable_path
3919 .replace(Executable::Kernel(PathBuf::from("kernel")));
3920 set_argument(
3921 &mut config,
3922 "single-touch",
3923 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
3924 )
3925 .unwrap();
3926 set_argument(
3927 &mut config,
3928 "trackpad",
3929 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
3930 )
3931 .unwrap();
3932 validate_arguments(&mut config).unwrap();
3933 assert_eq!(
3934 config.virtio_single_touch.first().unwrap().get_size(),
3935 (width, height)
3936 );
3937 assert_eq!(
3938 config.virtio_trackpad.first().unwrap().get_size(),
3939 (width, height)
3940 );
3941 }
3942
3943 #[cfg(feature = "gpu")]
3944 #[test]
single_touch_spec_with_size_independent_from_gpu()3945 fn single_touch_spec_with_size_independent_from_gpu() {
3946 let touch_width = 12345u32;
3947 let touch_height = 54321u32;
3948 let display_width = 1234u32;
3949 let display_height = 5432u32;
3950 let mut config = Config::default();
3951 config
3952 .executable_path
3953 .replace(Executable::Kernel(PathBuf::from("kernel")));
3954 set_argument(
3955 &mut config,
3956 "single-touch",
3957 Some(&format!(
3958 "/dev/single-touch-test:{}:{}",
3959 touch_width, touch_height
3960 )),
3961 )
3962 .unwrap();
3963 set_argument(
3964 &mut config,
3965 "gpu",
3966 Some(&format!(
3967 "width={},height={}",
3968 display_width, display_height
3969 )),
3970 )
3971 .unwrap();
3972 validate_arguments(&mut config).unwrap();
3973 assert_eq!(
3974 config.virtio_single_touch.first().unwrap().get_size(),
3975 (touch_width, touch_height)
3976 );
3977 }
3978
3979 #[test]
virtio_switches()3980 fn virtio_switches() {
3981 let mut config = Config::default();
3982 config
3983 .executable_path
3984 .replace(Executable::Kernel(PathBuf::from("kernel")));
3985 set_argument(&mut config, "switches", Some("/dev/switches-test")).unwrap();
3986 validate_arguments(&mut config).unwrap();
3987 assert_eq!(
3988 config.virtio_switches.pop().unwrap(),
3989 PathBuf::from("/dev/switches-test")
3990 );
3991 }
3992
3993 #[cfg(feature = "gpu")]
3994 #[test]
parse_gpu_options_default_vulkan_support()3995 fn parse_gpu_options_default_vulkan_support() {
3996 {
3997 let mut gpu_params: GpuParameters = Default::default();
3998 assert!(parse_gpu_options(Some("backend=virglrenderer"), &mut gpu_params).is_ok());
3999 assert!(!gpu_params.use_vulkan);
4000 }
4001
4002 #[cfg(feature = "gfxstream")]
4003 {
4004 let mut gpu_params: GpuParameters = Default::default();
4005 assert!(parse_gpu_options(Some("backend=gfxstream"), &mut gpu_params).is_ok());
4006 assert!(gpu_params.use_vulkan);
4007 }
4008 }
4009
4010 #[cfg(feature = "gpu")]
4011 #[test]
parse_gpu_options_with_vulkan_specified()4012 fn parse_gpu_options_with_vulkan_specified() {
4013 {
4014 let mut gpu_params: GpuParameters = Default::default();
4015 assert!(parse_gpu_options(Some("vulkan=true"), &mut gpu_params).is_ok());
4016 assert!(gpu_params.use_vulkan);
4017 }
4018 {
4019 let mut gpu_params: GpuParameters = Default::default();
4020 assert!(
4021 parse_gpu_options(Some("backend=virglrenderer,vulkan=true"), &mut gpu_params)
4022 .is_ok()
4023 );
4024 assert!(gpu_params.use_vulkan);
4025 }
4026 {
4027 let mut gpu_params: GpuParameters = Default::default();
4028 assert!(
4029 parse_gpu_options(Some("vulkan=true,backend=virglrenderer"), &mut gpu_params)
4030 .is_ok()
4031 );
4032 assert!(gpu_params.use_vulkan);
4033 }
4034 {
4035 let mut gpu_params: GpuParameters = Default::default();
4036 assert!(parse_gpu_options(Some("vulkan=false"), &mut gpu_params).is_ok());
4037 assert!(!gpu_params.use_vulkan);
4038 }
4039 {
4040 let mut gpu_params: GpuParameters = Default::default();
4041 assert!(
4042 parse_gpu_options(Some("backend=virglrenderer,vulkan=false"), &mut gpu_params)
4043 .is_ok()
4044 );
4045 assert!(!gpu_params.use_vulkan);
4046 }
4047 {
4048 let mut gpu_params: GpuParameters = Default::default();
4049 assert!(
4050 parse_gpu_options(Some("vulkan=false,backend=virglrenderer"), &mut gpu_params)
4051 .is_ok()
4052 );
4053 assert!(!gpu_params.use_vulkan);
4054 }
4055 {
4056 let mut gpu_params: GpuParameters = Default::default();
4057 assert!(parse_gpu_options(
4058 Some("backend=virglrenderer,vulkan=invalid_value"),
4059 &mut gpu_params
4060 )
4061 .is_err());
4062 }
4063 {
4064 let mut gpu_params: GpuParameters = Default::default();
4065 assert!(parse_gpu_options(
4066 Some("vulkan=invalid_value,backend=virglrenderer"),
4067 &mut gpu_params
4068 )
4069 .is_err());
4070 }
4071 }
4072
4073 #[cfg(all(feature = "gpu", feature = "gfxstream"))]
4074 #[test]
parse_gpu_options_gfxstream_with_syncfd_specified()4075 fn parse_gpu_options_gfxstream_with_syncfd_specified() {
4076 {
4077 let mut gpu_params: GpuParameters = Default::default();
4078 assert!(
4079 parse_gpu_options(Some("backend=gfxstream,syncfd=true"), &mut gpu_params).is_ok()
4080 );
4081 assert!(gpu_params.gfxstream_use_syncfd);
4082 }
4083 {
4084 let mut gpu_params: GpuParameters = Default::default();
4085 assert!(
4086 parse_gpu_options(Some("syncfd=true,backend=gfxstream"), &mut gpu_params).is_ok()
4087 );
4088 assert!(gpu_params.gfxstream_use_syncfd);
4089 }
4090 {
4091 let mut gpu_params: GpuParameters = Default::default();
4092 assert!(
4093 parse_gpu_options(Some("backend=gfxstream,syncfd=false"), &mut gpu_params).is_ok()
4094 );
4095 assert!(!gpu_params.gfxstream_use_syncfd);
4096 }
4097 {
4098 let mut gpu_params: GpuParameters = Default::default();
4099 assert!(
4100 parse_gpu_options(Some("syncfd=false,backend=gfxstream"), &mut gpu_params).is_ok()
4101 );
4102 assert!(!gpu_params.gfxstream_use_syncfd);
4103 }
4104 {
4105 let mut gpu_params: GpuParameters = Default::default();
4106 assert!(parse_gpu_options(
4107 Some("backend=gfxstream,syncfd=invalid_value"),
4108 &mut gpu_params
4109 )
4110 .is_err());
4111 }
4112 {
4113 let mut gpu_params: GpuParameters = Default::default();
4114 assert!(parse_gpu_options(
4115 Some("syncfd=invalid_value,backend=gfxstream"),
4116 &mut gpu_params
4117 )
4118 .is_err());
4119 }
4120 }
4121
4122 #[cfg(all(feature = "gpu", feature = "gfxstream"))]
4123 #[test]
parse_gpu_options_not_gfxstream_with_syncfd_specified()4124 fn parse_gpu_options_not_gfxstream_with_syncfd_specified() {
4125 {
4126 let mut gpu_params: GpuParameters = Default::default();
4127 assert!(
4128 parse_gpu_options(Some("backend=virglrenderer,syncfd=true"), &mut gpu_params)
4129 .is_err()
4130 );
4131 }
4132 {
4133 let mut gpu_params: GpuParameters = Default::default();
4134 assert!(
4135 parse_gpu_options(Some("syncfd=true,backend=virglrenderer"), &mut gpu_params)
4136 .is_err()
4137 );
4138 }
4139 }
4140
4141 #[cfg(feature = "gpu")]
4142 #[test]
parse_gpu_display_options_valid()4143 fn parse_gpu_display_options_valid() {
4144 {
4145 let mut gpu_params: GpuParameters = Default::default();
4146 assert!(
4147 parse_gpu_display_options(Some("width=500,height=600"), &mut gpu_params).is_ok()
4148 );
4149 assert_eq!(gpu_params.displays.len(), 1);
4150 assert_eq!(gpu_params.displays[0].width, 500);
4151 assert_eq!(gpu_params.displays[0].height, 600);
4152 }
4153 }
4154
4155 #[cfg(feature = "gpu")]
4156 #[test]
parse_gpu_display_options_invalid()4157 fn parse_gpu_display_options_invalid() {
4158 {
4159 let mut gpu_params: GpuParameters = Default::default();
4160 assert!(parse_gpu_display_options(Some("width=500"), &mut gpu_params).is_err());
4161 }
4162 {
4163 let mut gpu_params: GpuParameters = Default::default();
4164 assert!(parse_gpu_display_options(Some("height=500"), &mut gpu_params).is_err());
4165 }
4166 {
4167 let mut gpu_params: GpuParameters = Default::default();
4168 assert!(parse_gpu_display_options(Some("width"), &mut gpu_params).is_err());
4169 }
4170 {
4171 let mut gpu_params: GpuParameters = Default::default();
4172 assert!(parse_gpu_display_options(Some("blah"), &mut gpu_params).is_err());
4173 }
4174 }
4175
4176 #[cfg(feature = "gpu")]
4177 #[test]
parse_gpu_options_and_gpu_display_options_valid()4178 fn parse_gpu_options_and_gpu_display_options_valid() {
4179 {
4180 let mut gpu_params: GpuParameters = Default::default();
4181 assert!(parse_gpu_options(Some("2D,width=500,height=600"), &mut gpu_params).is_ok());
4182 assert!(
4183 parse_gpu_display_options(Some("width=700,height=800"), &mut gpu_params).is_ok()
4184 );
4185 assert_eq!(gpu_params.displays.len(), 2);
4186 assert_eq!(gpu_params.displays[0].width, 500);
4187 assert_eq!(gpu_params.displays[0].height, 600);
4188 assert_eq!(gpu_params.displays[1].width, 700);
4189 assert_eq!(gpu_params.displays[1].height, 800);
4190 }
4191 {
4192 let mut gpu_params: GpuParameters = Default::default();
4193 assert!(parse_gpu_options(Some("2D"), &mut gpu_params).is_ok());
4194 assert!(
4195 parse_gpu_display_options(Some("width=700,height=800"), &mut gpu_params).is_ok()
4196 );
4197 assert_eq!(gpu_params.displays.len(), 1);
4198 assert_eq!(gpu_params.displays[0].width, 700);
4199 assert_eq!(gpu_params.displays[0].height, 800);
4200 }
4201 }
4202
4203 #[test]
parse_battery_vaild()4204 fn parse_battery_vaild() {
4205 parse_battery_options(Some("type=goldfish")).expect("parse should have succeded");
4206 }
4207
4208 #[test]
parse_battery_vaild_no_type()4209 fn parse_battery_vaild_no_type() {
4210 parse_battery_options(None).expect("parse should have succeded");
4211 }
4212
4213 #[test]
parse_battery_invaild_parameter()4214 fn parse_battery_invaild_parameter() {
4215 parse_battery_options(Some("tyep=goldfish")).expect_err("parse should have failed");
4216 }
4217
4218 #[test]
parse_battery_invaild_type_value()4219 fn parse_battery_invaild_type_value() {
4220 parse_battery_options(Some("type=xxx")).expect_err("parse should have failed");
4221 }
4222
4223 #[test]
parse_stub_pci()4224 fn parse_stub_pci() {
4225 let params = parse_stub_pci_parameters(Some("0000:01:02.3,vendor=0xfffe,device=0xfffd,class=0xffc1c2,subsystem_vendor=0xfffc,subsystem_device=0xfffb,revision=0xa")).unwrap();
4226 assert_eq!(params.address.bus, 1);
4227 assert_eq!(params.address.dev, 2);
4228 assert_eq!(params.address.func, 3);
4229 assert_eq!(params.vendor_id, 0xfffe);
4230 assert_eq!(params.device_id, 0xfffd);
4231 assert_eq!(params.class as u8, PciClassCode::Other as u8);
4232 assert_eq!(params.subclass, 0xc1);
4233 assert_eq!(params.programming_interface, 0xc2);
4234 assert_eq!(params.subsystem_vendor_id, 0xfffc);
4235 assert_eq!(params.subsystem_device_id, 0xfffb);
4236 assert_eq!(params.revision_id, 0xa);
4237 }
4238
4239 #[cfg(feature = "direct")]
4240 #[test]
parse_direct_io_options_valid()4241 fn parse_direct_io_options_valid() {
4242 let params = parse_direct_io_options(Some("/dev/mem@1,100-110")).unwrap();
4243 assert_eq!(params.path.to_str(), Some("/dev/mem"));
4244 assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
4245 assert_eq!(params.ranges[1], BusRange { base: 100, len: 11 });
4246 }
4247
4248 #[cfg(feature = "direct")]
4249 #[test]
parse_direct_io_options_hex()4250 fn parse_direct_io_options_hex() {
4251 let params = parse_direct_io_options(Some("/dev/mem@1,0x10,100-110,0x10-0x20")).unwrap();
4252 assert_eq!(params.path.to_str(), Some("/dev/mem"));
4253 assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
4254 assert_eq!(params.ranges[1], BusRange { base: 0x10, len: 1 });
4255 assert_eq!(params.ranges[2], BusRange { base: 100, len: 11 });
4256 assert_eq!(
4257 params.ranges[3],
4258 BusRange {
4259 base: 0x10,
4260 len: 0x11
4261 }
4262 );
4263 }
4264
4265 #[cfg(feature = "direct")]
4266 #[test]
parse_direct_io_options_invalid()4267 fn parse_direct_io_options_invalid() {
4268 assert!(parse_direct_io_options(Some("/dev/mem@0y10"))
4269 .unwrap_err()
4270 .to_string()
4271 .contains("invalid base range value"));
4272
4273 assert!(parse_direct_io_options(Some("/dev/mem@"))
4274 .unwrap_err()
4275 .to_string()
4276 .contains("invalid base range value"));
4277 }
4278
4279 #[test]
parse_file_backed_mapping_valid()4280 fn parse_file_backed_mapping_valid() {
4281 let params = parse_file_backed_mapping(Some(
4282 "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,ro,rw,sync",
4283 ))
4284 .unwrap();
4285 assert_eq!(params.address, 0x1000);
4286 assert_eq!(params.size, 0x2000);
4287 assert_eq!(params.path, PathBuf::from("/dev/mem"));
4288 assert_eq!(params.offset, 0x3000);
4289 assert!(params.writable);
4290 assert!(params.sync);
4291 }
4292
4293 #[test]
parse_file_backed_mapping_incomplete()4294 fn parse_file_backed_mapping_incomplete() {
4295 assert!(parse_file_backed_mapping(Some("addr=0x1000,size=0x2000"))
4296 .unwrap_err()
4297 .to_string()
4298 .contains("required"));
4299 assert!(parse_file_backed_mapping(Some("size=0x2000,path=/dev/mem"))
4300 .unwrap_err()
4301 .to_string()
4302 .contains("required"));
4303 assert!(parse_file_backed_mapping(Some("addr=0x1000,path=/dev/mem"))
4304 .unwrap_err()
4305 .to_string()
4306 .contains("required"));
4307 }
4308
4309 #[test]
parse_file_backed_mapping_unaligned()4310 fn parse_file_backed_mapping_unaligned() {
4311 assert!(
4312 parse_file_backed_mapping(Some("addr=0x1001,size=0x2000,path=/dev/mem"))
4313 .unwrap_err()
4314 .to_string()
4315 .contains("aligned")
4316 );
4317 assert!(
4318 parse_file_backed_mapping(Some("addr=0x1000,size=0x2001,path=/dev/mem"))
4319 .unwrap_err()
4320 .to_string()
4321 .contains("aligned")
4322 );
4323 }
4324
4325 #[test]
parse_file_backed_mapping_align()4326 fn parse_file_backed_mapping_align() {
4327 let params =
4328 parse_file_backed_mapping(Some("addr=0x3042,size=0xff0,path=/dev/mem,align")).unwrap();
4329 assert_eq!(params.address, 0x3000);
4330 assert_eq!(params.size, 0x2000);
4331 }
4332
4333 #[test]
parse_userspace_msr_options_test()4334 fn parse_userspace_msr_options_test() {
4335 let index = parse_userspace_msr_options("0x10,action=r0").unwrap();
4336 assert_eq!(index, 0x10);
4337 assert!(parse_userspace_msr_options("0x10,action=none").is_err());
4338 assert!(parse_userspace_msr_options("0x10").is_err());
4339 assert!(parse_userspace_msr_options("hoge").is_err());
4340 }
4341 }
4342