blob: af5d62bd79e300f68399c589471d8f5f5d8554af [file] [log] [blame]
Zach Reizner639d9672017-05-01 17:57:18 -07001// 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
Steven Richmanf32d0b42020-06-20 21:45:32 -07005//! Runs a virtual machine
Zach Reizner639d9672017-05-01 17:57:18 -07006
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08007pub mod panic_hook;
Zach Reizner29ad3c72017-08-04 15:12:58 -07008
Daniel Verkampc677fb42020-09-08 13:47:49 -07009use std::collections::BTreeMap;
Judy Hsiaod5c1e962020-02-04 12:30:01 +080010use std::default::Default;
Jingkui Wang100e6e42019-03-08 20:41:57 -080011use std::fmt;
12use std::fs::{File, OpenOptions};
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -080013use std::io::{BufRead, BufReader};
Jingkui Wang100e6e42019-03-08 20:41:57 -080014use std::num::ParseIntError;
15use std::os::unix::io::{FromRawFd, RawFd};
16use std::path::{Path, PathBuf};
Zach Reizner639d9672017-05-01 17:57:18 -070017use std::string::String;
Zach Reizner39aa26b2017-12-12 18:03:23 -080018use std::thread::sleep;
Stephen Barber56fbf092017-06-29 16:12:14 -070019use std::time::Duration;
Zach Reizner639d9672017-05-01 17:57:18 -070020
Daniel Verkampc677fb42020-09-08 13:47:49 -070021use arch::{
22 set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType,
23 VcpuAffinity,
24};
Michael Hoyle6b196952020-08-02 20:09:41 -070025use base::{
26 debug, error, getpid, info, kill_process_group, net::UnixSeqpacket, reap_child, syslog,
27 validate_raw_fd, warn,
28};
Zach Reizner267f2c82019-07-31 17:07:27 -070029use crosvm::{
30 argument::{self, print_help, set_arguments, Argument},
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +090031 linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
Zach Reizner267f2c82019-07-31 17:07:27 -070032};
Jason Macnakcc7070b2019-11-06 14:48:12 -080033#[cfg(feature = "gpu")]
Kaiyi Libccb4eb2020-02-06 17:53:11 -080034use devices::virtio::gpu::{GpuMode, GpuParameters};
Andrew Scull1590e6f2020-03-18 18:00:47 +000035#[cfg(feature = "audio")]
Daniel Verkampfbd61222020-02-14 16:46:36 -080036use devices::{Ac97Backend, Ac97Parameters};
Daniel Verkampf2eecc42019-12-17 17:04:58 -080037use disk::QcowFile;
David Tolnayfe3ef7d2019-03-08 15:57:49 -080038use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
Jakub Starone7c59052019-04-09 12:31:14 -070039use vm_control::{
Jakub Staron1f828d72019-04-11 12:49:29 -070040 BalloonControlCommand, DiskControlCommand, MaybeOwnedFd, UsbControlCommand, UsbControlResult,
Zach Reizneraff94ca2019-03-18 20:58:31 -070041 VmControlRequestSocket, VmRequest, VmResponse, USB_CONTROL_MAX_PORTS,
Jakub Starone7c59052019-04-09 12:31:14 -070042};
Zach Reizner639d9672017-05-01 17:57:18 -070043
Cody Schuffelen6d1ab502019-05-21 12:12:38 -070044fn executable_is_plugin(executable: &Option<Executable>) -> bool {
45 match executable {
46 Some(Executable::Plugin(_)) => true,
47 _ => false,
48 }
49}
50
Stephen Barbera00753b2017-07-18 13:57:26 -070051// Wait for all children to exit. Return true if they have all exited, false
52// otherwise.
53fn wait_all_children() -> bool {
Stephen Barber49dd2e22018-10-29 18:29:58 -070054 const CHILD_WAIT_MAX_ITER: isize = 100;
Stephen Barbera00753b2017-07-18 13:57:26 -070055 const CHILD_WAIT_MS: u64 = 10;
56 for _ in 0..CHILD_WAIT_MAX_ITER {
Stephen Barbera00753b2017-07-18 13:57:26 -070057 loop {
Zach Reizner56158c82017-08-24 13:50:14 -070058 match reap_child() {
59 Ok(0) => break,
60 // We expect ECHILD which indicates that there were no children left.
61 Err(e) if e.errno() == libc::ECHILD => return true,
62 Err(e) => {
David Tolnayb4bd00f2019-02-12 17:51:26 -080063 warn!("error while waiting for children: {}", e);
Zach Reizner56158c82017-08-24 13:50:14 -070064 return false;
Stephen Barbera00753b2017-07-18 13:57:26 -070065 }
Zach Reizner56158c82017-08-24 13:50:14 -070066 // We reaped one child, so continue reaping.
Zach Reizner55a9e502018-10-03 10:22:32 -070067 _ => {}
Stephen Barbera00753b2017-07-18 13:57:26 -070068 }
69 }
Zach Reizner56158c82017-08-24 13:50:14 -070070 // There's no timeout option for waitpid which reap_child calls internally, so our only
71 // recourse is to sleep while waiting for the children to exit.
Stephen Barbera00753b2017-07-18 13:57:26 -070072 sleep(Duration::from_millis(CHILD_WAIT_MS));
73 }
74
75 // If we've made it to this point, not all of the children have exited.
David Tolnay5bbbf612018-12-01 17:49:30 -080076 false
Stephen Barbera00753b2017-07-18 13:57:26 -070077}
78
Daniel Verkamp107edb32019-04-05 09:58:48 -070079/// Parse a comma-separated list of CPU numbers and ranges and convert it to a Vec of CPU numbers.
80fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
81 let mut cpuset = Vec::new();
82 for part in s.split(',') {
83 let range: Vec<&str> = part.split('-').collect();
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +090084 if range.is_empty() || range.len() > 2 {
Daniel Verkamp107edb32019-04-05 09:58:48 -070085 return Err(argument::Error::InvalidValue {
86 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +080087 expected: String::from("invalid list syntax"),
Daniel Verkamp107edb32019-04-05 09:58:48 -070088 });
89 }
90 let first_cpu: usize = range[0]
91 .parse()
92 .map_err(|_| argument::Error::InvalidValue {
93 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +080094 expected: String::from("CPU index must be a non-negative integer"),
Daniel Verkamp107edb32019-04-05 09:58:48 -070095 })?;
96 let last_cpu: usize = if range.len() == 2 {
97 range[1]
98 .parse()
99 .map_err(|_| argument::Error::InvalidValue {
100 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800101 expected: String::from("CPU index must be a non-negative integer"),
Daniel Verkamp107edb32019-04-05 09:58:48 -0700102 })?
103 } else {
104 first_cpu
105 };
106
107 if last_cpu < first_cpu {
108 return Err(argument::Error::InvalidValue {
109 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800110 expected: String::from("CPU ranges must be from low to high"),
Daniel Verkamp107edb32019-04-05 09:58:48 -0700111 });
112 }
113
114 for cpu in first_cpu..=last_cpu {
115 cpuset.push(cpu);
116 }
117 }
118 Ok(cpuset)
119}
120
Daniel Verkampc677fb42020-09-08 13:47:49 -0700121/// Parse a list of guest to host CPU mappings.
122///
123/// Each mapping consists of a single guest CPU index mapped to one or more host CPUs in the form
124/// accepted by `parse_cpu_set`:
125///
126/// `<GUEST-CPU>=<HOST-CPU-SET>[:<GUEST-CPU>=<HOST-CPU-SET>[:...]]`
127fn parse_cpu_affinity(s: &str) -> argument::Result<VcpuAffinity> {
128 if s.contains('=') {
129 let mut affinity_map = BTreeMap::new();
130 for cpu_pair in s.split(':') {
131 let assignment: Vec<&str> = cpu_pair.split('=').collect();
132 if assignment.len() != 2 {
133 return Err(argument::Error::InvalidValue {
134 value: cpu_pair.to_owned(),
135 expected: String::from("invalid VCPU assignment syntax"),
136 });
137 }
138 let guest_cpu = assignment[0]
139 .parse()
140 .map_err(|_| argument::Error::InvalidValue {
141 value: assignment[0].to_owned(),
142 expected: String::from("CPU index must be a non-negative integer"),
143 })?;
144 let host_cpu_set = parse_cpu_set(assignment[1])?;
145 if affinity_map.insert(guest_cpu, host_cpu_set).is_some() {
146 return Err(argument::Error::InvalidValue {
147 value: cpu_pair.to_owned(),
148 expected: String::from("VCPU index must be unique"),
149 });
150 }
151 }
152 Ok(VcpuAffinity::PerVcpu(affinity_map))
153 } else {
154 Ok(VcpuAffinity::Global(parse_cpu_set(s)?))
155 }
156}
157
Jason Macnakcc7070b2019-11-06 14:48:12 -0800158#[cfg(feature = "gpu")]
159fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
Kaiyi Libccb4eb2020-02-06 17:53:11 -0800160 let mut gpu_params: GpuParameters = Default::default();
Kaiyi Lidd348a42020-07-13 11:49:46 -0700161 #[cfg(feature = "gfxstream")]
162 let mut vulkan_specified = false;
163 #[cfg(feature = "gfxstream")]
164 let mut syncfd_specified = false;
Jason Macnakcc7070b2019-11-06 14:48:12 -0800165
166 if let Some(s) = s {
167 let opts = s
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900168 .split(',')
169 .map(|frag| frag.split('='))
Jason Macnakcc7070b2019-11-06 14:48:12 -0800170 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
171
172 for (k, v) in opts {
173 match k {
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800174 // Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
175 // times if the user specifies several modes (--gpu=2d,3d,gfxstream)
Jason Macnak327fc242020-01-10 12:45:36 -0800176 "2d" | "2D" => {
177 gpu_params.mode = GpuMode::Mode2D;
178 }
179 "3d" | "3D" => {
180 gpu_params.mode = GpuMode::Mode3D;
181 }
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800182 #[cfg(feature = "gfxstream")]
183 "gfxstream" => {
184 gpu_params.mode = GpuMode::ModeGfxStream;
185 }
186 // Preferred: Specifying --gpu,backend=<mode>
187 "backend" => match v {
188 "2d" | "2D" => {
189 gpu_params.mode = GpuMode::Mode2D;
190 }
191 "3d" | "3D" => {
192 gpu_params.mode = GpuMode::Mode3D;
193 }
194 #[cfg(feature = "gfxstream")]
195 "gfxstream" => {
196 gpu_params.mode = GpuMode::ModeGfxStream;
197 }
198 _ => {
199 return Err(argument::Error::InvalidValue {
200 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800201 expected: String::from(
202 "gpu parameter 'backend' should be one of (2d|3d|gfxstream)",
203 ),
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800204 });
205 }
206 },
Jason Macnakbf195582019-11-20 16:25:49 -0800207 "egl" => match v {
208 "true" | "" => {
209 gpu_params.renderer_use_egl = true;
210 }
211 "false" => {
212 gpu_params.renderer_use_egl = false;
213 }
214 _ => {
215 return Err(argument::Error::InvalidValue {
216 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800217 expected: String::from("gpu parameter 'egl' should be a boolean"),
Jason Macnakbf195582019-11-20 16:25:49 -0800218 });
219 }
220 },
221 "gles" => match v {
222 "true" | "" => {
223 gpu_params.renderer_use_gles = true;
224 }
225 "false" => {
226 gpu_params.renderer_use_gles = false;
227 }
228 _ => {
229 return Err(argument::Error::InvalidValue {
230 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800231 expected: String::from("gpu parameter 'gles' should be a boolean"),
Jason Macnakbf195582019-11-20 16:25:49 -0800232 });
233 }
234 },
235 "glx" => match v {
236 "true" | "" => {
237 gpu_params.renderer_use_glx = true;
238 }
239 "false" => {
240 gpu_params.renderer_use_glx = false;
241 }
242 _ => {
243 return Err(argument::Error::InvalidValue {
244 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800245 expected: String::from("gpu parameter 'glx' should be a boolean"),
Jason Macnakbf195582019-11-20 16:25:49 -0800246 });
247 }
248 },
249 "surfaceless" => match v {
250 "true" | "" => {
251 gpu_params.renderer_use_surfaceless = true;
252 }
253 "false" => {
254 gpu_params.renderer_use_surfaceless = false;
255 }
256 _ => {
257 return Err(argument::Error::InvalidValue {
258 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800259 expected: String::from(
260 "gpu parameter 'surfaceless' should be a boolean",
261 ),
Jason Macnakbf195582019-11-20 16:25:49 -0800262 });
263 }
264 },
Kaiyi Li6404e452020-07-07 19:12:03 -0700265 #[cfg(feature = "gfxstream")]
Kaiyi Lidd348a42020-07-13 11:49:46 -0700266 "syncfd" => {
267 syncfd_specified = true;
268 match v {
269 "true" | "" => {
270 gpu_params.gfxstream_use_syncfd = true;
271 }
272 "false" => {
273 gpu_params.gfxstream_use_syncfd = false;
274 }
275 _ => {
276 return Err(argument::Error::InvalidValue {
277 value: v.to_string(),
278 expected: String::from(
279 "gpu parameter 'syncfd' should be a boolean",
280 ),
281 });
282 }
Kaiyi Li6404e452020-07-07 19:12:03 -0700283 }
Kaiyi Lidd348a42020-07-13 11:49:46 -0700284 }
Kaiyi Li6c52a2e2020-07-07 19:28:32 -0700285 #[cfg(feature = "gfxstream")]
Kaiyi Lidd348a42020-07-13 11:49:46 -0700286 "vulkan" => {
287 vulkan_specified = true;
288 match v {
289 "true" | "" => {
290 gpu_params.gfxstream_support_vulkan = true;
291 }
292 "false" => {
293 gpu_params.gfxstream_support_vulkan = false;
294 }
295 _ => {
296 return Err(argument::Error::InvalidValue {
297 value: v.to_string(),
298 expected: String::from(
299 "gpu parameter 'vulkan' should be a boolean",
300 ),
301 });
302 }
Kaiyi Li6c52a2e2020-07-07 19:28:32 -0700303 }
Kaiyi Lidd348a42020-07-13 11:49:46 -0700304 }
Jason Macnakcc7070b2019-11-06 14:48:12 -0800305 "width" => {
306 gpu_params.display_width =
307 v.parse::<u32>()
308 .map_err(|_| argument::Error::InvalidValue {
309 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800310 expected: String::from(
311 "gpu parameter 'width' must be a valid integer",
312 ),
Jason Macnakcc7070b2019-11-06 14:48:12 -0800313 })?;
314 }
315 "height" => {
316 gpu_params.display_height =
317 v.parse::<u32>()
318 .map_err(|_| argument::Error::InvalidValue {
319 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800320 expected: String::from(
321 "gpu parameter 'height' must be a valid integer",
322 ),
Jason Macnakcc7070b2019-11-06 14:48:12 -0800323 })?;
324 }
John Batesb220eac2020-09-14 17:03:02 -0700325 "cache-path" => gpu_params.cache_path = Some(v.to_string()),
326 "cache-size" => gpu_params.cache_size = Some(v.to_string()),
Jason Macnakcc7070b2019-11-06 14:48:12 -0800327 "" => {}
328 _ => {
329 return Err(argument::Error::UnknownArgument(format!(
330 "gpu parameter {}",
331 k
332 )));
333 }
334 }
335 }
336 }
337
Kaiyi Lidd348a42020-07-13 11:49:46 -0700338 #[cfg(feature = "gfxstream")]
339 {
340 if vulkan_specified || syncfd_specified {
341 match gpu_params.mode {
342 GpuMode::ModeGfxStream => {}
343 _ => {
344 return Err(argument::Error::UnknownArgument(
345 "gpu parameter vulkan and syncfd are only supported for gfxstream backend"
346 .to_string(),
347 ));
348 }
349 }
350 }
351 }
352
Jason Macnakcc7070b2019-11-06 14:48:12 -0800353 Ok(gpu_params)
354}
355
Andrew Scull1590e6f2020-03-18 18:00:47 +0000356#[cfg(feature = "audio")]
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800357fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
358 let mut ac97_params: Ac97Parameters = Default::default();
359
360 let opts = s
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900361 .split(',')
362 .map(|frag| frag.split('='))
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800363 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
364
365 for (k, v) in opts {
366 match k {
367 "backend" => {
368 ac97_params.backend =
369 v.parse::<Ac97Backend>()
370 .map_err(|e| argument::Error::InvalidValue {
371 value: v.to_string(),
372 expected: e.to_string(),
373 })?;
374 }
375 "capture" => {
376 ac97_params.capture = v.parse::<bool>().map_err(|e| {
377 argument::Error::Syntax(format!("invalid capture option: {}", e))
378 })?;
379 }
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800380 _ => {
381 return Err(argument::Error::UnknownArgument(format!(
382 "unknown ac97 parameter {}",
383 k
384 )));
385 }
386 }
387 }
388
389 Ok(ac97_params)
390}
391
Trent Begin17ccaad2019-04-17 13:51:25 -0600392fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
393 let mut serial_setting = SerialParameters {
394 type_: SerialType::Sink,
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700395 hardware: SerialHardware::Serial,
Trent Begin17ccaad2019-04-17 13:51:25 -0600396 path: None,
Iliyan Malchev2c1417b2020-04-14 09:40:41 -0700397 input: None,
Trent Begin923bab02019-06-17 13:48:06 -0600398 num: 1,
Trent Begin17ccaad2019-04-17 13:51:25 -0600399 console: false,
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700400 earlycon: false,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700401 stdin: false,
Trent Begin17ccaad2019-04-17 13:51:25 -0600402 };
403
404 let opts = s
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900405 .split(',')
Nicholas Hollingumc76c2da2020-09-18 15:53:16 +1000406 .map(|frag| frag.splitn(2, '='))
Trent Begin17ccaad2019-04-17 13:51:25 -0600407 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
408
409 for (k, v) in opts {
410 match k {
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700411 "hardware" => {
412 serial_setting.hardware = v
413 .parse::<SerialHardware>()
414 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
415 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600416 "type" => {
417 serial_setting.type_ = v
418 .parse::<SerialType>()
419 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
420 }
421 "num" => {
422 let num = v.parse::<u8>().map_err(|e| {
423 argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
424 })?;
425 if num < 1 || num > 4 {
426 return Err(argument::Error::InvalidValue {
427 value: num.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800428 expected: String::from("Serial port num must be between 1 - 4"),
Trent Begin17ccaad2019-04-17 13:51:25 -0600429 });
430 }
431 serial_setting.num = num;
432 }
433 "console" => {
434 serial_setting.console = v.parse::<bool>().map_err(|e| {
435 argument::Error::Syntax(format!(
436 "serial device console is not parseable: {}",
437 e
438 ))
439 })?
440 }
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700441 "earlycon" => {
442 serial_setting.earlycon = v.parse::<bool>().map_err(|e| {
443 argument::Error::Syntax(format!(
444 "serial device earlycon is not parseable: {}",
445 e,
446 ))
447 })?
448 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700449 "stdin" => {
450 serial_setting.stdin = v.parse::<bool>().map_err(|e| {
451 argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e))
Iliyan Malchev2c1417b2020-04-14 09:40:41 -0700452 })?;
453 if serial_setting.stdin && serial_setting.input.is_some() {
454 return Err(argument::Error::TooManyArguments(
455 "Cannot specify both stdin and input options".to_string(),
456 ));
457 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700458 }
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700459 "path" => serial_setting.path = Some(PathBuf::from(v)),
Iliyan Malchev2c1417b2020-04-14 09:40:41 -0700460 "input" => {
461 if serial_setting.stdin {
462 return Err(argument::Error::TooManyArguments(
463 "Cannot specify both stdin and input options".to_string(),
464 ));
465 }
466 serial_setting.input = Some(PathBuf::from(v));
467 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600468 _ => {
469 return Err(argument::Error::UnknownArgument(format!(
470 "serial parameter {}",
471 k
472 )));
473 }
474 }
475 }
476
477 Ok(serial_setting)
478}
479
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800480fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900481 let components: Vec<&str> = value.split(':').collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800482 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800483 return Err(argument::Error::InvalidValue {
484 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800485 expected: String::from(
486 "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
487 ),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800488 });
489 }
490
491 let src = PathBuf::from(components[0]);
492 if src.is_relative() {
493 return Err(argument::Error::InvalidValue {
494 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800495 expected: String::from("the source path for `plugin-mount` must be absolute"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800496 });
497 }
498 if !src.exists() {
499 return Err(argument::Error::InvalidValue {
500 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800501 expected: String::from("the source path for `plugin-mount` does not exist"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800502 });
503 }
504
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800505 let dst = PathBuf::from(match components.get(1) {
506 None | Some(&"") => components[0],
507 Some(path) => path,
508 });
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800509 if dst.is_relative() {
510 return Err(argument::Error::InvalidValue {
511 value: components[1].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800512 expected: String::from("the destination path for `plugin-mount` must be absolute"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800513 });
514 }
515
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800516 let writable: bool = match components.get(2) {
517 None => false,
518 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800519 value: components[2].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800520 expected: String::from("the <writable> component for `plugin-mount` is not valid bool"),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800521 })?,
522 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800523
524 Ok(BindMount { src, dst, writable })
525}
526
527fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900528 let components: Vec<&str> = value.split(':').collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800529 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800530 return Err(argument::Error::InvalidValue {
531 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800532 expected: String::from(
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800533 "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
Judy Hsiao59343052020-03-16 15:58:03 +0800534 ),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800535 });
536 }
537
538 let inner: libc::gid_t = components[0]
539 .parse()
540 .map_err(|_| argument::Error::InvalidValue {
541 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800542 expected: String::from("the <inner> component for `plugin-gid-map` is not valid gid"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800543 })?;
544
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800545 let outer: libc::gid_t = match components.get(1) {
546 None | Some(&"") => inner,
547 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800548 value: components[1].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800549 expected: String::from("the <outer> component for `plugin-gid-map` is not valid gid"),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800550 })?,
551 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800552
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800553 let count: u32 = match components.get(2) {
554 None => 1,
555 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800556 value: components[2].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800557 expected: String::from(
558 "the <count> component for `plugin-gid-map` is not valid number",
559 ),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800560 })?,
561 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800562
563 Ok(GidMap {
564 inner,
565 outer,
566 count,
567 })
568}
569
Zach Reiznerefe95782017-08-26 18:05:48 -0700570fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
571 match name {
572 "" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700573 if cfg.executable_path.is_some() {
574 return Err(argument::Error::TooManyArguments(format!(
575 "A VM executable was already specified: {:?}",
576 cfg.executable_path
577 )));
Zach Reiznerefe95782017-08-26 18:05:48 -0700578 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700579 let kernel_path = PathBuf::from(value.unwrap());
580 if !kernel_path.exists() {
581 return Err(argument::Error::InvalidValue {
582 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800583 expected: String::from("this kernel path does not exist"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700584 });
585 }
586 cfg.executable_path = Some(Executable::Kernel(kernel_path));
Zach Reiznerefe95782017-08-26 18:05:48 -0700587 }
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800588 "android-fstab" => {
589 if cfg.android_fstab.is_some()
590 && !cfg.android_fstab.as_ref().unwrap().as_os_str().is_empty()
591 {
592 return Err(argument::Error::TooManyArguments(
593 "expected exactly one android fstab path".to_owned(),
594 ));
595 } else {
596 let android_fstab = PathBuf::from(value.unwrap());
597 if !android_fstab.exists() {
598 return Err(argument::Error::InvalidValue {
599 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800600 expected: String::from("this android fstab path does not exist"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800601 });
602 }
603 cfg.android_fstab = Some(android_fstab);
604 }
605 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700606 "params" => {
Zach Reiznerbb678712018-01-30 18:13:04 -0800607 cfg.params.push(value.unwrap().to_owned());
Zach Reiznerefe95782017-08-26 18:05:48 -0700608 }
609 "cpus" => {
610 if cfg.vcpu_count.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700611 return Err(argument::Error::TooManyArguments(
612 "`cpus` already given".to_owned(),
613 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700614 }
615 cfg.vcpu_count =
Zach Reizner55a9e502018-10-03 10:22:32 -0700616 Some(
617 value
618 .unwrap()
619 .parse()
620 .map_err(|_| argument::Error::InvalidValue {
621 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800622 expected: String::from("this value for `cpus` needs to be integer"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700623 })?,
624 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700625 }
Daniel Verkamp107edb32019-04-05 09:58:48 -0700626 "cpu-affinity" => {
Daniel Verkampc677fb42020-09-08 13:47:49 -0700627 if cfg.vcpu_affinity.is_some() {
Daniel Verkamp107edb32019-04-05 09:58:48 -0700628 return Err(argument::Error::TooManyArguments(
629 "`cpu-affinity` already given".to_owned(),
630 ));
631 }
Daniel Verkampc677fb42020-09-08 13:47:49 -0700632 cfg.vcpu_affinity = Some(parse_cpu_affinity(value.unwrap())?);
Daniel Verkamp107edb32019-04-05 09:58:48 -0700633 }
Kansho Nishidaab205af2020-08-13 18:17:50 +0900634 "rt-cpus" => {
635 if !cfg.rt_cpus.is_empty() {
636 return Err(argument::Error::TooManyArguments(
637 "`rt-cpus` already given".to_owned(),
638 ));
639 }
640 cfg.rt_cpus = parse_cpu_set(value.unwrap())?;
641 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700642 "mem" => {
643 if cfg.memory.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700644 return Err(argument::Error::TooManyArguments(
645 "`mem` already given".to_owned(),
646 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700647 }
648 cfg.memory =
Zach Reizner55a9e502018-10-03 10:22:32 -0700649 Some(
650 value
651 .unwrap()
652 .parse()
653 .map_err(|_| argument::Error::InvalidValue {
654 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800655 expected: String::from("this value for `mem` needs to be integer"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700656 })?,
657 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700658 }
Andrew Scull1590e6f2020-03-18 18:00:47 +0000659 #[cfg(feature = "audio")]
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800660 "ac97" => {
661 let ac97_params = parse_ac97_options(value.unwrap())?;
662 cfg.ac97_parameters.push(ac97_params);
Dylan Reid3082e8e2019-01-07 10:33:48 -0800663 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600664 "serial" => {
665 let serial_params = parse_serial_options(value.unwrap())?;
666 let num = serial_params.num;
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700667 let key = (serial_params.hardware, num);
668 if cfg.serial_parameters.contains_key(&key) {
Trent Begin17ccaad2019-04-17 13:51:25 -0600669 return Err(argument::Error::TooManyArguments(format!(
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700670 "serial hardware {} num {}",
671 serial_params.hardware, num,
Trent Begin17ccaad2019-04-17 13:51:25 -0600672 )));
673 }
674
675 if serial_params.console {
Jakub Staronb6515a92019-06-05 15:18:25 -0700676 for params in cfg.serial_parameters.values() {
Trent Begin17ccaad2019-04-17 13:51:25 -0600677 if params.console {
678 return Err(argument::Error::TooManyArguments(format!(
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700679 "{} device {} already set as console",
680 params.hardware, params.num,
681 )));
682 }
683 }
684 }
685
686 if serial_params.earlycon {
687 // Only SerialHardware::Serial supports earlycon= currently.
688 match serial_params.hardware {
689 SerialHardware::Serial => {}
690 _ => {
691 return Err(argument::Error::InvalidValue {
692 value: serial_params.hardware.to_string().to_owned(),
693 expected: String::from("earlycon not supported for hardware"),
694 });
695 }
696 }
697 for params in cfg.serial_parameters.values() {
698 if params.earlycon {
699 return Err(argument::Error::TooManyArguments(format!(
700 "{} device {} already set as earlycon",
701 params.hardware, params.num,
Trent Begin17ccaad2019-04-17 13:51:25 -0600702 )));
703 }
704 }
705 }
706
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700707 if serial_params.stdin {
708 if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) {
709 return Err(argument::Error::TooManyArguments(format!(
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700710 "{} device {} already connected to standard input",
711 previous_stdin.hardware, previous_stdin.num,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700712 )));
713 }
714 }
715
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700716 cfg.serial_parameters.insert(key, serial_params);
Trent Begin17ccaad2019-04-17 13:51:25 -0600717 }
718 "syslog-tag" => {
719 if cfg.syslog_tag.is_some() {
720 return Err(argument::Error::TooManyArguments(
721 "`syslog-tag` already given".to_owned(),
722 ));
723 }
724 syslog::set_proc_name(value.unwrap());
725 cfg.syslog_tag = Some(value.unwrap().to_owned());
726 }
Daniel Verkamp4b62cd92019-11-08 13:09:27 -0800727 "root" | "rwroot" | "disk" | "rwdisk" => {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800728 let param = value.unwrap();
729 let mut components = param.split(',');
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700730 let read_only = !name.starts_with("rw");
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800731 let disk_path =
732 PathBuf::from(
733 components
734 .next()
735 .ok_or_else(|| argument::Error::InvalidValue {
736 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800737 expected: String::from("missing disk path"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800738 })?,
739 );
Zach Reiznerefe95782017-08-26 18:05:48 -0700740 if !disk_path.exists() {
741 return Err(argument::Error::InvalidValue {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800742 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800743 expected: String::from("this disk path does not exist"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700744 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700745 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700746 if name.ends_with("root") {
Daniel Verkampaac28132018-10-15 14:58:48 -0700747 if cfg.disks.len() >= 26 {
Zach Reizner55a9e502018-10-03 10:22:32 -0700748 return Err(argument::Error::TooManyArguments(
749 "ran out of letters for to assign to root disk".to_owned(),
750 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700751 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700752 cfg.params.push(format!(
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700753 "root=/dev/vd{} {}",
754 char::from(b'a' + cfg.disks.len() as u8),
755 if read_only { "ro" } else { "rw" }
Zach Reizner55a9e502018-10-03 10:22:32 -0700756 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700757 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800758
759 let mut disk = DiskOption {
Zach Reizner55a9e502018-10-03 10:22:32 -0700760 path: disk_path,
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700761 read_only,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800762 sparse: true,
Daniel Verkamp27672232019-12-06 17:26:55 +1100763 block_size: 512,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800764 };
765
766 for opt in components {
767 let mut o = opt.splitn(2, '=');
768 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
769 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800770 expected: String::from("disk options must not be empty"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800771 })?;
772 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
773 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800774 expected: String::from("disk options must be of the form `kind=value`"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800775 })?;
776
777 match kind {
778 "sparse" => {
779 let sparse = value.parse().map_err(|_| argument::Error::InvalidValue {
780 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800781 expected: String::from("`sparse` must be a boolean"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800782 })?;
783 disk.sparse = sparse;
784 }
Daniel Verkamp27672232019-12-06 17:26:55 +1100785 "block_size" => {
786 let block_size =
787 value.parse().map_err(|_| argument::Error::InvalidValue {
788 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800789 expected: String::from("`block_size` must be an integer"),
Daniel Verkamp27672232019-12-06 17:26:55 +1100790 })?;
791 disk.block_size = block_size;
792 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800793 _ => {
794 return Err(argument::Error::InvalidValue {
795 value: kind.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800796 expected: String::from("unrecognized disk option"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800797 });
798 }
799 }
800 }
801
802 cfg.disks.push(disk);
Zach Reiznerefe95782017-08-26 18:05:48 -0700803 }
Jakub Starona3411ea2019-04-24 10:55:25 -0700804 "pmem-device" | "rw-pmem-device" => {
805 let disk_path = PathBuf::from(value.unwrap());
806 if !disk_path.exists() {
807 return Err(argument::Error::InvalidValue {
808 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800809 expected: String::from("this disk path does not exist"),
Jakub Starona3411ea2019-04-24 10:55:25 -0700810 });
811 }
812
813 cfg.pmem_devices.push(DiskOption {
814 path: disk_path,
815 read_only: !name.starts_with("rw"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800816 sparse: false,
Michael Hoyle6b196952020-08-02 20:09:41 -0700817 block_size: base::pagesize() as u32,
Jakub Starona3411ea2019-04-24 10:55:25 -0700818 });
819 }
Kansho Nishida282115b2019-12-18 13:13:14 +0900820 "pstore" => {
821 if cfg.pstore.is_some() {
822 return Err(argument::Error::TooManyArguments(
823 "`pstore` already given".to_owned(),
824 ));
825 }
826
827 let value = value.unwrap();
828 let components: Vec<&str> = value.split(',').collect();
829 if components.len() != 2 {
830 return Err(argument::Error::InvalidValue {
831 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800832 expected: String::from(
833 "pstore must have exactly 2 components: path=<path>,size=<size>",
834 ),
Kansho Nishida282115b2019-12-18 13:13:14 +0900835 });
836 }
837 cfg.pstore = Some(Pstore {
838 path: {
839 if components[0].len() <= 5 || !components[0].starts_with("path=") {
840 return Err(argument::Error::InvalidValue {
841 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800842 expected: String::from("pstore path must follow with `path=`"),
Kansho Nishida282115b2019-12-18 13:13:14 +0900843 });
844 };
845 PathBuf::from(&components[0][5..])
846 },
847 size: {
848 if components[1].len() <= 5 || !components[1].starts_with("size=") {
849 return Err(argument::Error::InvalidValue {
850 value: components[1].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800851 expected: String::from("pstore size must follow with `size=`"),
Kansho Nishida282115b2019-12-18 13:13:14 +0900852 });
853 };
854 components[1][5..]
855 .parse()
856 .map_err(|_| argument::Error::InvalidValue {
857 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800858 expected: String::from("pstore size must be an integer"),
Kansho Nishida282115b2019-12-18 13:13:14 +0900859 })?
860 },
861 });
862 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700863 "host_ip" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700864 if cfg.host_ip.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700865 return Err(argument::Error::TooManyArguments(
866 "`host_ip` already given".to_owned(),
867 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700868 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700869 cfg.host_ip =
Zach Reizner55a9e502018-10-03 10:22:32 -0700870 Some(
871 value
872 .unwrap()
873 .parse()
874 .map_err(|_| argument::Error::InvalidValue {
875 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800876 expected: String::from("`host_ip` needs to be in the form \"x.x.x.x\""),
Zach Reizner55a9e502018-10-03 10:22:32 -0700877 })?,
878 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700879 }
880 "netmask" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700881 if cfg.netmask.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700882 return Err(argument::Error::TooManyArguments(
883 "`netmask` already given".to_owned(),
884 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700885 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700886 cfg.netmask =
Zach Reizner55a9e502018-10-03 10:22:32 -0700887 Some(
888 value
889 .unwrap()
890 .parse()
891 .map_err(|_| argument::Error::InvalidValue {
892 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800893 expected: String::from("`netmask` needs to be in the form \"x.x.x.x\""),
Zach Reizner55a9e502018-10-03 10:22:32 -0700894 })?,
895 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700896 }
897 "mac" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700898 if cfg.mac_address.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700899 return Err(argument::Error::TooManyArguments(
900 "`mac` already given".to_owned(),
901 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700902 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700903 cfg.mac_address =
Zach Reizner55a9e502018-10-03 10:22:32 -0700904 Some(
905 value
906 .unwrap()
907 .parse()
908 .map_err(|_| argument::Error::InvalidValue {
909 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800910 expected: String::from(
911 "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
912 ),
Zach Reizner55a9e502018-10-03 10:22:32 -0700913 })?,
914 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700915 }
Xiong Zhang773c7072020-03-20 10:39:55 +0800916 "net-vq-pairs" => {
917 if cfg.net_vq_pairs.is_some() {
918 return Err(argument::Error::TooManyArguments(
919 "`net-vq-pairs` already given".to_owned(),
920 ));
921 }
922 cfg.net_vq_pairs =
923 Some(
924 value
925 .unwrap()
926 .parse()
927 .map_err(|_| argument::Error::InvalidValue {
928 value: value.unwrap().to_owned(),
929 expected: String::from(
930 "this value for `net-vq-pairs` needs to be integer",
931 ),
932 })?,
933 )
934 }
935
Stephen Barber28a5a612017-10-20 17:15:30 -0700936 "wayland-sock" => {
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900937 let mut components = value.unwrap().split(',');
938 let path =
939 PathBuf::from(
940 components
941 .next()
942 .ok_or_else(|| argument::Error::InvalidValue {
943 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800944 expected: String::from("missing socket path"),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900945 })?,
946 );
947 let mut name = "";
948 for c in components {
949 let mut kv = c.splitn(2, '=');
950 let (kind, value) = match (kv.next(), kv.next()) {
951 (Some(kind), Some(value)) => (kind, value),
952 _ => {
953 return Err(argument::Error::InvalidValue {
954 value: c.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800955 expected: String::from("option must be of the form `kind=value`"),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900956 })
957 }
958 };
959 match kind {
960 "name" => name = value,
961 _ => {
962 return Err(argument::Error::InvalidValue {
963 value: kind.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800964 expected: String::from("unrecognized option"),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900965 })
966 }
967 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700968 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900969 if cfg.wayland_socket_paths.contains_key(name) {
970 return Err(argument::Error::TooManyArguments(format!(
971 "wayland socket name already used: '{}'",
972 name
973 )));
Stephen Barber28a5a612017-10-20 17:15:30 -0700974 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900975 cfg.wayland_socket_paths.insert(name.to_string(), path);
Stephen Barber28a5a612017-10-20 17:15:30 -0700976 }
David Reveman52ba4e52018-04-22 21:42:09 -0400977 #[cfg(feature = "wl-dmabuf")]
Daniel Verkampaac28132018-10-15 14:58:48 -0700978 "wayland-dmabuf" => cfg.wayland_dmabuf = true,
Zach Reizner0f2cfb02019-06-19 17:46:03 -0700979 "x-display" => {
980 if cfg.x_display.is_some() {
981 return Err(argument::Error::TooManyArguments(
982 "`x-display` already given".to_owned(),
983 ));
984 }
985 cfg.x_display = Some(value.unwrap().to_owned());
986 }
Zach Reizner65b98f12019-11-22 17:34:58 -0800987 "display-window-keyboard" => {
988 cfg.display_window_keyboard = true;
989 }
990 "display-window-mouse" => {
991 cfg.display_window_mouse = true;
992 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700993 "socket" => {
994 if cfg.socket_path.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700995 return Err(argument::Error::TooManyArguments(
996 "`socket` already given".to_owned(),
997 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700998 }
999 let mut socket_path = PathBuf::from(value.unwrap());
1000 if socket_path.is_dir() {
1001 socket_path.push(format!("crosvm-{}.sock", getpid()));
1002 }
1003 if socket_path.exists() {
1004 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -07001005 value: socket_path.to_string_lossy().into_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001006 expected: String::from("this socket path already exists"),
Zach Reizner55a9e502018-10-03 10:22:32 -07001007 });
Zach Reiznerefe95782017-08-26 18:05:48 -07001008 }
1009 cfg.socket_path = Some(socket_path);
1010 }
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001011 "disable-sandbox" => {
Lepton Wu9105e9f2019-03-14 11:38:31 -07001012 cfg.sandbox = false;
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001013 }
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -07001014 "cid" => {
Daniel Verkampaac28132018-10-15 14:58:48 -07001015 if cfg.cid.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -07001016 return Err(argument::Error::TooManyArguments(
1017 "`cid` alread given".to_owned(),
1018 ));
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -07001019 }
Daniel Verkampaac28132018-10-15 14:58:48 -07001020 cfg.cid = Some(
1021 value
1022 .unwrap()
1023 .parse()
1024 .map_err(|_| argument::Error::InvalidValue {
1025 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001026 expected: String::from("this value for `cid` must be an unsigned integer"),
Daniel Verkampaac28132018-10-15 14:58:48 -07001027 })?,
1028 );
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -07001029 }
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001030 "shared-dir" => {
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001031 // This is formatted as multiple fields, each separated by ":". The first 2 fields are
1032 // fixed (src:tag). The rest may appear in any order:
1033 //
1034 // * type=TYPE - must be one of "p9" or "fs" (default: p9)
1035 // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
1036 // (default: "0 <current euid> 1")
1037 // * gidmap=GIDMAP - a gid map in the same format as uidmap
1038 // (default: "0 <current egid> 1")
1039 // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
1040 // and directory contents should be considered valid (default: 5)
1041 // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
1042 // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001043 let param = value.unwrap();
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001044 let mut components = param.split(':');
Zach Reizner55a9e502018-10-03 10:22:32 -07001045 let src =
1046 PathBuf::from(
1047 components
1048 .next()
1049 .ok_or_else(|| argument::Error::InvalidValue {
1050 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001051 expected: String::from("missing source path for `shared-dir`"),
Zach Reizner55a9e502018-10-03 10:22:32 -07001052 })?,
1053 );
1054 let tag = components
1055 .next()
1056 .ok_or_else(|| argument::Error::InvalidValue {
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001057 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001058 expected: String::from("missing tag for `shared-dir`"),
David Tolnay2bac1e72018-12-12 14:33:42 -08001059 })?
1060 .to_owned();
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001061
1062 if !src.is_dir() {
1063 return Err(argument::Error::InvalidValue {
1064 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001065 expected: String::from("source path for `shared-dir` must be a directory"),
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001066 });
1067 }
1068
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001069 let mut shared_dir = SharedDir {
1070 src,
1071 tag,
1072 ..Default::default()
1073 };
1074 for opt in components {
1075 let mut o = opt.splitn(2, '=');
1076 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
1077 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001078 expected: String::from("`shared-dir` options must not be empty"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001079 })?;
1080 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
1081 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001082 expected: String::from("`shared-dir` options must be of the form `kind=value`"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001083 })?;
1084
1085 match kind {
1086 "type" => {
1087 shared_dir.kind =
1088 value.parse().map_err(|_| argument::Error::InvalidValue {
1089 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001090 expected: String::from("`type` must be one of `fs` or `9p`"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001091 })?
1092 }
1093 "uidmap" => shared_dir.uid_map = value.into(),
1094 "gidmap" => shared_dir.gid_map = value.into(),
1095 "timeout" => {
1096 let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
1097 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001098 expected: String::from("`timeout` must be an integer"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001099 })?;
1100
1101 let dur = Duration::from_secs(seconds);
1102 shared_dir.cfg.entry_timeout = dur.clone();
1103 shared_dir.cfg.attr_timeout = dur;
1104 }
1105 "cache" => {
1106 let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
1107 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001108 expected: String::from(
1109 "`cache` must be one of `never`, `always`, or `auto`",
1110 ),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001111 })?;
1112 shared_dir.cfg.cache_policy = policy;
1113 }
1114 "writeback" => {
1115 let writeback =
1116 value.parse().map_err(|_| argument::Error::InvalidValue {
1117 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001118 expected: String::from("`writeback` must be a boolean"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001119 })?;
1120 shared_dir.cfg.writeback = writeback;
1121 }
Chirantan Ekboted994e512020-06-12 18:46:52 +09001122 "rewrite-security-xattrs" => {
1123 let rewrite_security_xattrs =
1124 value.parse().map_err(|_| argument::Error::InvalidValue {
1125 value: value.to_owned(),
1126 expected: String::from(
1127 "`rewrite-security-xattrs` must be a boolean",
1128 ),
1129 })?;
1130 shared_dir.cfg.rewrite_security_xattrs = rewrite_security_xattrs;
1131 }
Chirantan Ekbote09357c82020-09-10 20:08:28 +09001132 "ascii_casefold" => {
1133 let ascii_casefold =
1134 value.parse().map_err(|_| argument::Error::InvalidValue {
1135 value: value.to_owned(),
1136 expected: String::from("`ascii_casefold` must be a boolean"),
1137 })?;
1138 shared_dir.cfg.ascii_casefold = ascii_casefold;
1139 }
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001140 _ => {
1141 return Err(argument::Error::InvalidValue {
1142 value: kind.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001143 expected: String::from("unrecognized option for `shared-dir`"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001144 })
1145 }
1146 }
1147 }
1148 cfg.shared_dirs.push(shared_dir);
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001149 }
Dylan Reide026ef02017-10-02 19:03:52 -07001150 "seccomp-policy-dir" => {
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001151 // `value` is Some because we are in this match so it's safe to unwrap.
Daniel Verkampaac28132018-10-15 14:58:48 -07001152 cfg.seccomp_policy_dir = PathBuf::from(value.unwrap());
Zach Reizner55a9e502018-10-03 10:22:32 -07001153 }
Zach Reizner44863792019-06-26 14:22:08 -07001154 "seccomp-log-failures" => {
Matt Delco45caf912019-11-13 08:11:09 -08001155 // A side-effect of this flag is to force the use of .policy files
1156 // instead of .bpf files (.bpf files are expected and assumed to be
1157 // compiled to fail an unpermitted action with "trap").
1158 // Normally crosvm will first attempt to use a .bpf file, and if
1159 // not present it will then try to use a .policy file. It's up
1160 // to the build to decide which of these files is present for
1161 // crosvm to use (for CrOS the build will use .bpf files for
1162 // x64 builds and .policy files for arm/arm64 builds).
1163 //
1164 // This flag will likely work as expected for builds that use
1165 // .policy files. For builds that only use .bpf files the initial
1166 // result when using this flag is likely to be a file-not-found
1167 // error (since the .policy files are not present).
1168 // For .bpf builds you can either 1) manually add the .policy files,
1169 // or 2) do not use this command-line parameter and instead
1170 // temporarily change the build by passing "log" rather than
1171 // "trap" as the "--default-action" to compile_seccomp_policy.py.
Zach Reizner44863792019-06-26 14:22:08 -07001172 cfg.seccomp_log_failures = true;
1173 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001174 "plugin" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001175 if cfg.executable_path.is_some() {
1176 return Err(argument::Error::TooManyArguments(format!(
1177 "A VM executable was already specified: {:?}",
1178 cfg.executable_path
1179 )));
Zach Reizner8864cb02018-01-16 17:59:03 -08001180 }
Zach Reiznercc30d582018-01-23 21:16:42 -08001181 let plugin = PathBuf::from(value.unwrap().to_owned());
1182 if plugin.is_relative() {
1183 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -07001184 value: plugin.to_string_lossy().into_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001185 expected: String::from("the plugin path must be an absolute path"),
Zach Reizner55a9e502018-10-03 10:22:32 -07001186 });
Zach Reiznercc30d582018-01-23 21:16:42 -08001187 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001188 cfg.executable_path = Some(Executable::Plugin(plugin));
Zach Reizner55a9e502018-10-03 10:22:32 -07001189 }
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001190 "plugin-root" => {
1191 cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned()));
Zach Reizner55a9e502018-10-03 10:22:32 -07001192 }
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001193 "plugin-mount" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -08001194 let mount = parse_plugin_mount_option(value.unwrap())?;
1195 cfg.plugin_mounts.push(mount);
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001196 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001197 "plugin-mount-file" => {
1198 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
1199 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001200 expected: String::from("unable to open `plugin-mount-file` file"),
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001201 })?;
1202 let reader = BufReader::new(file);
1203 for l in reader.lines() {
1204 let line = l.unwrap();
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001205 let trimmed_line = line.splitn(2, '#').next().unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001206 if !trimmed_line.is_empty() {
1207 let mount = parse_plugin_mount_option(trimmed_line)?;
1208 cfg.plugin_mounts.push(mount);
1209 }
1210 }
1211 }
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001212 "plugin-gid-map" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -08001213 let map = parse_plugin_gid_map_option(value.unwrap())?;
1214 cfg.plugin_gid_maps.push(map);
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001215 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001216 "plugin-gid-map-file" => {
1217 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
1218 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001219 expected: String::from("unable to open `plugin-gid-map-file` file"),
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001220 })?;
1221 let reader = BufReader::new(file);
1222 for l in reader.lines() {
1223 let line = l.unwrap();
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001224 let trimmed_line = line.splitn(2, '#').next().unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001225 if !trimmed_line.is_empty() {
1226 let map = parse_plugin_gid_map_option(trimmed_line)?;
1227 cfg.plugin_gid_maps.push(map);
1228 }
1229 }
1230 }
Daniel Verkampaac28132018-10-15 14:58:48 -07001231 "vhost-net" => cfg.vhost_net = true,
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001232 "tap-fd" => {
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001233 cfg.tap_fd.push(
1234 value
1235 .unwrap()
1236 .parse()
1237 .map_err(|_| argument::Error::InvalidValue {
1238 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001239 expected: String::from(
1240 "this value for `tap-fd` must be an unsigned integer",
1241 ),
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001242 })?,
1243 );
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001244 }
Jason Macnakcc7070b2019-11-06 14:48:12 -08001245 #[cfg(feature = "gpu")]
Zach Reizner3a8100a2017-09-13 19:15:43 -07001246 "gpu" => {
Jason Macnakcc7070b2019-11-06 14:48:12 -08001247 let params = parse_gpu_options(value)?;
1248 cfg.gpu_parameters = Some(params);
Zach Reizner3a8100a2017-09-13 19:15:43 -07001249 }
David Tolnay43f8e212019-02-13 17:28:16 -08001250 "software-tpm" => {
1251 cfg.software_tpm = true;
1252 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001253 "single-touch" => {
1254 if cfg.virtio_single_touch.is_some() {
1255 return Err(argument::Error::TooManyArguments(
1256 "`single-touch` already given".to_owned(),
1257 ));
1258 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001259 let mut it = value.unwrap().split(':');
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001260
1261 let mut single_touch_spec =
1262 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
1263 if let Some(width) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001264 single_touch_spec.set_width(width.trim().parse().unwrap());
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001265 }
1266 if let Some(height) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001267 single_touch_spec.set_height(height.trim().parse().unwrap());
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001268 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001269 cfg.virtio_single_touch = Some(single_touch_spec);
1270 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001271 "trackpad" => {
1272 if cfg.virtio_trackpad.is_some() {
1273 return Err(argument::Error::TooManyArguments(
1274 "`trackpad` already given".to_owned(),
1275 ));
1276 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001277 let mut it = value.unwrap().split(':');
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001278
1279 let mut trackpad_spec =
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001280 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001281 if let Some(width) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001282 trackpad_spec.set_width(width.trim().parse().unwrap());
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001283 }
1284 if let Some(height) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001285 trackpad_spec.set_height(height.trim().parse().unwrap());
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001286 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001287 cfg.virtio_trackpad = Some(trackpad_spec);
1288 }
1289 "mouse" => {
1290 if cfg.virtio_mouse.is_some() {
1291 return Err(argument::Error::TooManyArguments(
1292 "`mouse` already given".to_owned(),
1293 ));
1294 }
1295 cfg.virtio_mouse = Some(PathBuf::from(value.unwrap().to_owned()));
1296 }
1297 "keyboard" => {
1298 if cfg.virtio_keyboard.is_some() {
1299 return Err(argument::Error::TooManyArguments(
1300 "`keyboard` already given".to_owned(),
1301 ));
1302 }
1303 cfg.virtio_keyboard = Some(PathBuf::from(value.unwrap().to_owned()));
1304 }
1305 "evdev" => {
1306 let dev_path = PathBuf::from(value.unwrap());
1307 if !dev_path.exists() {
1308 return Err(argument::Error::InvalidValue {
1309 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001310 expected: String::from("this input device path does not exist"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001311 });
1312 }
1313 cfg.virtio_input_evdevs.push(dev_path);
1314 }
Miriam Zimmerman26ac9282019-01-29 21:21:48 -08001315 "split-irqchip" => {
1316 cfg.split_irqchip = true;
1317 }
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001318 "initrd" => {
1319 cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
1320 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001321 "bios" => {
1322 if cfg.executable_path.is_some() {
1323 return Err(argument::Error::TooManyArguments(format!(
1324 "A VM executable was already specified: {:?}",
1325 cfg.executable_path
1326 )));
1327 }
1328 cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
1329 }
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001330 "vfio" => {
1331 let vfio_path = PathBuf::from(value.unwrap());
1332 if !vfio_path.exists() {
1333 return Err(argument::Error::InvalidValue {
1334 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001335 expected: String::from("the vfio path does not exist"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001336 });
1337 }
1338 if !vfio_path.is_dir() {
1339 return Err(argument::Error::InvalidValue {
1340 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001341 expected: String::from("the vfio path should be directory"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001342 });
1343 }
1344
Xiong Zhang8bb4faa2019-11-12 10:06:13 +08001345 cfg.vfio.push(vfio_path);
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001346 }
Keiichi Watanabe57df6a02019-12-06 22:24:40 +09001347 "video-decoder" => {
1348 cfg.video_dec = true;
1349 }
1350 "video-encoder" => {
1351 cfg.video_enc = true;
1352 }
Tomasz Jeznach42644642020-05-20 23:27:59 -07001353 "acpi-table" => {
1354 let acpi_table = PathBuf::from(value.unwrap());
1355 if !acpi_table.exists() {
1356 return Err(argument::Error::InvalidValue {
1357 value: value.unwrap().to_owned(),
1358 expected: String::from("the acpi-table path does not exist"),
1359 });
1360 }
1361 if !acpi_table.is_file() {
1362 return Err(argument::Error::InvalidValue {
1363 value: value.unwrap().to_owned(),
1364 expected: String::from("the acpi-table path should be a file"),
1365 });
1366 }
1367
1368 cfg.acpi_tables.push(acpi_table);
1369 }
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001370
Zach Reiznerefe95782017-08-26 18:05:48 -07001371 "help" => return Err(argument::Error::PrintHelp),
1372 _ => unreachable!(),
1373 }
1374 Ok(())
1375}
1376
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001377fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Error> {
1378 if cfg.executable_path.is_none() {
1379 return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
1380 }
1381 if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
1382 if cfg.host_ip.is_none() {
1383 return Err(argument::Error::ExpectedArgument(
1384 "`host_ip` missing from network config".to_owned(),
1385 ));
1386 }
1387 if cfg.netmask.is_none() {
1388 return Err(argument::Error::ExpectedArgument(
1389 "`netmask` missing from network config".to_owned(),
1390 ));
1391 }
1392 if cfg.mac_address.is_none() {
1393 return Err(argument::Error::ExpectedArgument(
1394 "`mac` missing from network config".to_owned(),
1395 ));
1396 }
1397 }
1398 if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
1399 return Err(argument::Error::ExpectedArgument(
1400 "`plugin-root` requires `plugin`".to_owned(),
1401 ));
1402 }
1403 #[cfg(feature = "gpu")]
1404 {
1405 if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() {
1406 let (width, height) = (gpu_parameters.display_width, gpu_parameters.display_height);
1407 if let Some(virtio_single_touch) = cfg.virtio_single_touch.as_mut() {
1408 virtio_single_touch.set_default_size(width, height);
1409 }
1410 }
1411 }
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001412 set_default_serial_parameters(&mut cfg.serial_parameters);
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001413 Ok(())
1414}
1415
Dylan Reidbfba9932018-02-05 15:51:59 -08001416fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reiznerefe95782017-08-26 18:05:48 -07001417 let arguments =
1418 &[Argument::positional("KERNEL", "bzImage of kernel to run"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -08001419 Argument::value("android-fstab", "PATH", "Path to Android fstab"),
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001420 Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001421 Argument::short_value('p',
1422 "params",
1423 "PARAMS",
Zach Reiznerbb678712018-01-30 18:13:04 -08001424 "Extra kernel or plugin command line arguments. Can be given more than once."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001425 Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Daniel Verkampc677fb42020-09-08 13:47:49 -07001426 Argument::value("cpu-affinity", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on (e.g. 0,1-3,5)
1427 or colon-separated list of assignments of guest to host CPU assignments (e.g. 0=0:1=1:2=2) (default: no mask)"),
Kansho Nishidaab205af2020-08-13 18:17:50 +09001428 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)"),
Zach Reiznerefe95782017-08-26 18:05:48 -07001429 Argument::short_value('m',
1430 "mem",
1431 "N",
1432 "Amount of guest memory in MiB. (default: 256)"),
1433 Argument::short_value('r',
1434 "root",
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001435 "PATH[,key=value[,key=value[,...]]",
1436 "Path to a root disk image followed by optional comma-separated options.
1437 Like `--disk` but adds appropriate kernel command line option.
1438 See --disk for valid options."),
1439 Argument::value("rwroot", "PATH[,key=value[,key=value[,...]]", "Path to a writable root disk image followed by optional comma-separated options.
1440 See --disk for valid options."),
1441 Argument::short_value('d', "disk", "PATH[,key=value[,key=value[,...]]", "Path to a disk image followed by optional comma-separated options.
1442 Valid keys:
Daniel Verkamp27672232019-12-06 17:26:55 +11001443 sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
1444 block_size=BYTES - Set the reported block size of the disk (default: 512)"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001445 Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
1446 See --disk for valid options."),
Jakub Starona3411ea2019-04-24 10:55:25 -07001447 Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
1448 Argument::value("pmem-device", "PATH", "Path to a disk image."),
Kansho Nishida282115b2019-12-18 13:13:14 +09001449 Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file follewed by size."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001450 Argument::value("host_ip",
1451 "IP",
1452 "IP address to assign to host tap interface."),
1453 Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
1454 Argument::value("mac", "MAC", "MAC address for VM."),
Xiong Zhang773c7072020-03-20 10:39:55 +08001455 Argument::value("net-vq-pairs", "N", "virtio net virtual queue paris. (default: 1)"),
Andrew Scull1590e6f2020-03-18 18:00:47 +00001456 #[cfg(feature = "audio")]
Judy Hsiaod5c1e962020-02-04 12:30:01 +08001457 Argument::value("ac97",
1458 "[backend=BACKEND,capture=true,capture_effect=EFFECT]",
1459 "Comma separated key=value pairs for setting up Ac97 devices. Can be given more than once .
1460 Possible key values:
1461 backend=(null, cras) - Where to route the audio device. If not provided, backend will default to null.
1462 `null` for /dev/null, and cras for CRAS server.
1463 capture - Enable audio capture
1464 capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec."),
Trent Begin17ccaad2019-04-17 13:51:25 -06001465 Argument::value("serial",
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001466 "type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]",
Jason Macnakcc7070b2019-11-06 14:48:12 -08001467 "Comma separated key=value pairs for setting up serial devices. Can be given more than once.
Trent Begin17ccaad2019-04-17 13:51:25 -06001468 Possible key values:
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001469 type=(stdout,syslog,sink,file) - Where to route the serial device
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001470 hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
Trent Begin923bab02019-06-17 13:48:06 -06001471 num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001472 path=PATH - The path to the file to write to when type=file
Iliyan Malchev2c1417b2020-04-14 09:40:41 -07001473 input=PATH - The path to the file to read from when not stdin
Trent Begin17ccaad2019-04-17 13:51:25 -06001474 console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001475 earlycon - Use this serial device as the early console. Can only be given once.
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001476 stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided.
Trent Begin17ccaad2019-04-17 13:51:25 -06001477 "),
1478 Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
Zach Reizner0f2cfb02019-06-19 17:46:03 -07001479 Argument::value("x-display", "DISPLAY", "X11 display name to use."),
Zach Reizner65b98f12019-11-22 17:34:58 -08001480 Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
1481 Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +09001482 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."),
David Reveman52ba4e52018-04-22 21:42:09 -04001483 #[cfg(feature = "wl-dmabuf")]
1484 Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001485 Argument::short_value('s',
1486 "socket",
1487 "PATH",
1488 "Path to put the control socket. If PATH is a directory, a name will be generated."),
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001489 Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001490 Argument::value("cid", "CID", "Context ID for virtual sockets."),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001491 Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE]",
1492 "Colon-separated options for configuring a directory to be shared with the VM.
1493The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
1494The remaining fields are key=value pairs that may appear in any order. Valid keys are:
1495type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
1496uidmap=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).
1497gidmap=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).
1498cache=(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.
1499timeout=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.
1500writeback=BOOL - Indicates whether the VM can use 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.
1501"),
Dylan Reide026ef02017-10-02 19:03:52 -07001502 Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
Zach Reizner44863792019-06-26 14:22:08 -07001503 Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
Zach Reizner8864cb02018-01-16 17:59:03 -08001504 #[cfg(feature = "plugin")]
Zach Reiznercc30d582018-01-23 21:16:42 -08001505 Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
Daniel Verkampbd1a0842019-01-08 15:50:34 -08001506 #[cfg(feature = "plugin")]
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001507 Argument::value("plugin-root", "PATH", "Absolute path to a directory that will become root filesystem for the plugin process."),
Daniel Verkampbd1a0842019-01-08 15:50:34 -08001508 #[cfg(feature = "plugin")]
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001509 Argument::value("plugin-mount", "PATH:PATH:BOOL", "Path to be mounted into the plugin's root filesystem. Can be given more than once."),
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001510 #[cfg(feature = "plugin")]
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001511 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."),
1512 #[cfg(feature = "plugin")]
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001513 Argument::value("plugin-gid-map", "GID:GID:INT", "Supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001514 #[cfg(feature = "plugin")]
1515 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."),
Rob Bradford8f002f52018-02-19 16:31:11 +00001516 Argument::flag("vhost-net", "Use vhost for networking."),
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001517 Argument::value("tap-fd",
1518 "fd",
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001519 "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
Zach Reizner3a8100a2017-09-13 19:15:43 -07001520 #[cfg(feature = "gpu")]
Jason Macnakcc7070b2019-11-06 14:48:12 -08001521 Argument::flag_or_value("gpu",
1522 "[width=INT,height=INT]",
1523 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
1524 Possible key values:
Lingfeng Yangddbe8b72020-01-30 10:00:36 -08001525 backend=(2d|3d|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
Jason Macnakcc7070b2019-11-06 14:48:12 -08001526 width=INT - The width of the virtual display connected to the virtio-gpu.
1527 height=INT - The height of the virtual display connected to the virtio-gpu.
Jason Macnakbf195582019-11-20 16:25:49 -08001528 egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering.
1529 glx[=true|=false] - If the virtio-gpu backend should use a GLX context for rendering.
1530 surfaceless[=true|=false] - If the virtio-gpu backend should use a surfaceless context for rendering.
Kaiyi Li6404e452020-07-07 19:12:03 -07001531 syncfd[=true|=false] - If the gfxstream backend should support EGL_ANDROID_native_fence_sync
Kaiyi Li6c52a2e2020-07-07 19:28:32 -07001532 vulkan[=true|=false] - If the gfxstream backend should support vulkan
Jason Macnakcc7070b2019-11-06 14:48:12 -08001533 "),
David Tolnay43f8e212019-02-13 17:28:16 -08001534 #[cfg(feature = "tpm")]
1535 Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001536 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"),
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001537 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)."),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001538 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)."),
1539 Argument::value("mouse", "PATH", "Path to a socket from where to read mouse input events and write status updates to."),
1540 Argument::value("keyboard", "PATH", "Path to a socket from where to read keyboard input events and write status updates to."),
Miriam Zimmerman26ac9282019-01-29 21:21:48 -08001541 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1542 Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001543 Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001544 Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
Keiichi Watanabe57df6a02019-12-06 22:24:40 +09001545 #[cfg(feature = "video-decoder")]
1546 Argument::flag("video-decoder", "(EXPERIMENTAL) enable virtio-video decoder device"),
1547 #[cfg(feature = "video-encoder")]
1548 Argument::flag("video-encoder", "(EXPERIMENTAL) enable virtio-video encoder device"),
Tomasz Jeznach42644642020-05-20 23:27:59 -07001549 Argument::value("acpi-table", "PATH", "Path to user provided ACPI table"),
Zach Reiznerefe95782017-08-26 18:05:48 -07001550 Argument::short_flag('h', "help", "Print help message.")];
1551
1552 let mut cfg = Config::default();
Zach Reizner55a9e502018-10-03 10:22:32 -07001553 let match_res = set_arguments(args, &arguments[..], |name, value| {
1554 set_argument(&mut cfg, name, value)
David Tolnay2bac1e72018-12-12 14:33:42 -08001555 })
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001556 .and_then(|_| validate_arguments(&mut cfg));
Zach Reiznerefe95782017-08-26 18:05:48 -07001557
1558 match match_res {
Zach Reizner8864cb02018-01-16 17:59:03 -08001559 #[cfg(feature = "plugin")]
Zach Reizner267f2c82019-07-31 17:07:27 -07001560 Ok(()) if executable_is_plugin(&cfg.executable_path) => {
1561 match crosvm::plugin::run_config(cfg) {
1562 Ok(_) => {
1563 info!("crosvm and plugin have exited normally");
1564 Ok(())
1565 }
1566 Err(e) => {
1567 error!("{}", e);
1568 Err(())
1569 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001570 }
Zach Reizner267f2c82019-07-31 17:07:27 -07001571 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001572 Ok(()) => match linux::run_config(cfg) {
1573 Ok(_) => {
1574 info!("crosvm has exited normally");
1575 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001576 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001577 Err(e) => {
1578 error!("{}", e);
1579 Err(())
1580 }
1581 },
Dylan Reidbfba9932018-02-05 15:51:59 -08001582 Err(argument::Error::PrintHelp) => {
1583 print_help("crosvm run", "KERNEL", &arguments[..]);
1584 Ok(())
1585 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001586 Err(e) => {
Dmitry Torokhov470b1e72020-01-15 12:46:49 -08001587 error!("{}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001588 Err(())
Zach Reizner8864cb02018-01-16 17:59:03 -08001589 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001590 }
1591}
1592
Jingkui Wang100e6e42019-03-08 20:41:57 -08001593fn handle_request(
1594 request: &VmRequest,
1595 args: std::env::Args,
1596) -> std::result::Result<VmResponse, ()> {
1597 let mut return_result = Err(());
Zach Reiznerefe95782017-08-26 18:05:48 -07001598 for socket_path in args {
Zach Reiznera60744b2019-02-13 17:33:32 -08001599 match UnixSeqpacket::connect(&socket_path) {
Zach Reiznerefe95782017-08-26 18:05:48 -07001600 Ok(s) => {
Jakub Starone7c59052019-04-09 12:31:14 -07001601 let socket: VmControlRequestSocket = MsgSocket::new(s);
Zach Reizner78986322019-02-21 20:43:21 -08001602 if let Err(e) = socket.send(request) {
Zach Reizner55a9e502018-10-03 10:22:32 -07001603 error!(
David Tolnayb4bd00f2019-02-12 17:51:26 -08001604 "failed to send request to socket at '{}': {}",
Zach Reizner55a9e502018-10-03 10:22:32 -07001605 socket_path, e
1606 );
Zach Reizner78986322019-02-21 20:43:21 -08001607 return_result = Err(());
1608 continue;
Zach Reiznerefe95782017-08-26 18:05:48 -07001609 }
Zach Reizner78986322019-02-21 20:43:21 -08001610 match socket.recv() {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001611 Ok(response) => return_result = Ok(response),
Zach Reizner78986322019-02-21 20:43:21 -08001612 Err(e) => {
1613 error!(
1614 "failed to send request to socket at2 '{}': {}",
1615 socket_path, e
1616 );
1617 return_result = Err(());
1618 continue;
1619 }
Dylan Reidd4432042017-12-06 18:20:09 -08001620 }
1621 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001622 Err(e) => {
1623 error!("failed to connect to socket at '{}': {}", socket_path, e);
1624 return_result = Err(());
1625 }
Dylan Reidd4432042017-12-06 18:20:09 -08001626 }
1627 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001628
1629 return_result
Dylan Reidd4432042017-12-06 18:20:09 -08001630}
Zach Reiznerefe95782017-08-26 18:05:48 -07001631
Jingkui Wang100e6e42019-03-08 20:41:57 -08001632fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
1633 let response = handle_request(request, args)?;
1634 info!("request response was {}", response);
1635 Ok(())
1636}
1637
Zach Reizner78986322019-02-21 20:43:21 -08001638fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1639 if args.len() == 0 {
1640 print_help("crosvm stop", "VM_SOCKET...", &[]);
1641 println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001642 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001643 }
1644 vms_request(&VmRequest::Exit, args)
1645}
1646
1647fn suspend_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1648 if args.len() == 0 {
1649 print_help("crosvm suspend", "VM_SOCKET...", &[]);
1650 println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001651 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001652 }
1653 vms_request(&VmRequest::Suspend, args)
1654}
1655
1656fn resume_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1657 if args.len() == 0 {
1658 print_help("crosvm resume", "VM_SOCKET...", &[]);
1659 println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001660 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001661 }
1662 vms_request(&VmRequest::Resume, args)
1663}
1664
1665fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
1666 if args.len() < 2 {
1667 print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
1668 println!("Set the ballon size of the crosvm instance to `SIZE` bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001669 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001670 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001671 let num_bytes = match args.next().unwrap().parse::<u64>() {
Zach Reizner78986322019-02-21 20:43:21 -08001672 Ok(n) => n,
1673 Err(_) => {
1674 error!("Failed to parse number of bytes");
1675 return Err(());
1676 }
1677 };
1678
Jakub Staron1f828d72019-04-11 12:49:29 -07001679 let command = BalloonControlCommand::Adjust { num_bytes };
1680 vms_request(&VmRequest::BalloonCommand(command), args)
Zach Reizner78986322019-02-21 20:43:21 -08001681}
1682
Charles William Dicked22f6b2020-04-08 11:05:24 +09001683fn balloon_stats(args: std::env::Args) -> std::result::Result<(), ()> {
1684 if args.len() != 1 {
1685 print_help("crosvm balloon_stats", "VM_SOCKET", &[]);
1686 println!("Prints virtio balloon statistics for a `VM_SOCKET`.");
1687 return Err(());
1688 }
1689 let command = BalloonControlCommand::Stats {};
1690 let request = &VmRequest::BalloonCommand(command);
1691 let response = handle_request(request, args)?;
1692 println!("{}", response);
1693 Ok(())
1694}
1695
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001696fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> {
1697 let arguments = [
1698 Argument::positional("PATH", "where to create the qcow2 image"),
1699 Argument::positional("[SIZE]", "the expanded size of the image"),
1700 Argument::value(
1701 "backing_file",
1702 "path/to/file",
1703 " the file to back the image",
1704 ),
1705 ];
1706 let mut positional_index = 0;
1707 let mut file_path = String::from("");
1708 let mut size: Option<u64> = None;
1709 let mut backing_file: Option<String> = None;
1710 set_arguments(args, &arguments[..], |name, value| {
1711 match (name, positional_index) {
1712 ("", 0) => {
1713 // NAME
1714 positional_index += 1;
1715 file_path = value.unwrap().to_owned();
1716 }
1717 ("", 1) => {
1718 // [SIZE]
1719 positional_index += 1;
1720 size = Some(value.unwrap().parse::<u64>().map_err(|_| {
1721 argument::Error::InvalidValue {
1722 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001723 expected: String::from("SIZE should be a nonnegative integer"),
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001724 }
1725 })?);
1726 }
1727 ("", _) => {
1728 return Err(argument::Error::TooManyArguments(
1729 "Expected at most 2 positional arguments".to_owned(),
1730 ));
1731 }
1732 ("backing_file", _) => {
1733 backing_file = value.map(|x| x.to_owned());
1734 }
1735 _ => unreachable!(),
1736 };
1737 Ok(())
1738 })
1739 .map_err(|e| {
1740 error!("Unable to parse command line arguments: {}", e);
1741 })?;
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001742 if file_path.is_empty() || !(size.is_some() ^ backing_file.is_some()) {
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001743 print_help("crosvm create_qcow2", "PATH [SIZE]", &arguments);
1744 println!(
1745 "Create a new QCOW2 image at `PATH` of either the specified `SIZE` in bytes or
1746with a '--backing_file'."
1747 );
Jianxun Zhang56497d22019-03-04 14:38:24 -08001748 return Err(());
Dylan Reid2dcb6322018-07-13 10:42:48 -07001749 }
Dylan Reid2dcb6322018-07-13 10:42:48 -07001750
1751 let file = OpenOptions::new()
1752 .create(true)
1753 .read(true)
1754 .write(true)
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001755 .truncate(true)
Dylan Reid2dcb6322018-07-13 10:42:48 -07001756 .open(&file_path)
1757 .map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001758 error!("Failed opening qcow file at '{}': {}", file_path, e);
Dylan Reid2dcb6322018-07-13 10:42:48 -07001759 })?;
1760
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001761 match (size, backing_file) {
1762 (Some(size), None) => QcowFile::new(file, size).map_err(|e| {
1763 error!("Failed to create qcow file at '{}': {}", file_path, e);
1764 })?,
1765 (None, Some(backing_file)) => {
1766 QcowFile::new_from_backing(file, &backing_file).map_err(|e| {
1767 error!("Failed to create qcow file at '{}': {}", file_path, e);
1768 })?
1769 }
1770 _ => unreachable!(),
1771 };
Dylan Reid2dcb6322018-07-13 10:42:48 -07001772 Ok(())
1773}
1774
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001775fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
1776 if args.len() < 2 {
1777 print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
1778 println!("Manage attached virtual disk devices.");
1779 println!("Subcommands:");
1780 println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001781 return Err(());
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001782 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001783 let subcommand: &str = &args.next().unwrap();
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001784
1785 let request = match subcommand {
1786 "resize" => {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001787 let disk_index = match args.next().unwrap().parse::<usize>() {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001788 Ok(n) => n,
1789 Err(_) => {
1790 error!("Failed to parse disk index");
1791 return Err(());
1792 }
1793 };
1794
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001795 let new_size = match args.next().unwrap().parse::<u64>() {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001796 Ok(n) => n,
1797 Err(_) => {
1798 error!("Failed to parse disk size");
1799 return Err(());
1800 }
1801 };
1802
Jakub Staronecf81e02019-04-11 11:43:39 -07001803 VmRequest::DiskCommand {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001804 disk_index,
Jakub Staronecf81e02019-04-11 11:43:39 -07001805 command: DiskControlCommand::Resize { new_size },
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001806 }
1807 }
1808 _ => {
1809 error!("Unknown disk subcommand '{}'", subcommand);
1810 return Err(());
1811 }
1812 };
1813
Zach Reizner78986322019-02-21 20:43:21 -08001814 vms_request(&request, args)
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001815}
1816
Jingkui Wang100e6e42019-03-08 20:41:57 -08001817enum ModifyUsbError {
1818 ArgMissing(&'static str),
1819 ArgParse(&'static str, String),
1820 ArgParseInt(&'static str, String, ParseIntError),
Michael Hoyle6b196952020-08-02 20:09:41 -07001821 FailedFdValidate(base::Error),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001822 PathDoesNotExist(PathBuf),
1823 SocketFailed,
1824 UnexpectedResponse(VmResponse),
1825 UnknownCommand(String),
1826 UsbControl(UsbControlResult),
1827}
1828
1829impl fmt::Display for ModifyUsbError {
1830 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1831 use self::ModifyUsbError::*;
1832
1833 match self {
1834 ArgMissing(a) => write!(f, "argument missing: {}", a),
1835 ArgParse(name, value) => {
1836 write!(f, "failed to parse argument {} value `{}`", name, value)
1837 }
1838 ArgParseInt(name, value, e) => write!(
1839 f,
1840 "failed to parse integer argument {} value `{}`: {}",
1841 name, value, e
1842 ),
1843 FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
1844 PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
1845 SocketFailed => write!(f, "socket failed"),
1846 UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
1847 UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
1848 UsbControl(e) => write!(f, "{}", e),
1849 }
1850 }
1851}
1852
1853type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
1854
1855fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
1856 debug!("parse_bus_id_addr: {}", v);
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001857 let mut ids = v.split(':');
Jingkui Wang100e6e42019-03-08 20:41:57 -08001858 match (ids.next(), ids.next(), ids.next(), ids.next()) {
1859 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
1860 let bus_id = bus_id
1861 .parse::<u8>()
1862 .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
1863 let addr = addr
1864 .parse::<u8>()
1865 .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
1866 let vid = u16::from_str_radix(&vid, 16)
1867 .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
1868 let pid = u16::from_str_radix(&pid, 16)
1869 .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
1870 Ok((bus_id, addr, vid, pid))
1871 }
1872 _ => Err(ModifyUsbError::ArgParse(
1873 "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
1874 v.to_owned(),
1875 )),
1876 }
1877}
1878
1879fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> {
1880 if !path.exists() {
1881 return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
1882 }
1883 let raw_fd = path
1884 .file_name()
1885 .and_then(|fd_osstr| fd_osstr.to_str())
1886 .map_or(
1887 Err(ModifyUsbError::ArgParse(
1888 "USB_DEVICE_PATH",
1889 path.to_string_lossy().into_owned(),
1890 )),
1891 |fd_str| {
1892 fd_str.parse::<libc::c_int>().map_err(|e| {
1893 ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
1894 })
1895 },
1896 )?;
David Tolnay5fb3f512019-04-12 19:22:33 -07001897 validate_raw_fd(raw_fd).map_err(ModifyUsbError::FailedFdValidate)
Jingkui Wang100e6e42019-03-08 20:41:57 -08001898}
1899
1900fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1901 let val = args
1902 .next()
1903 .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
1904 let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
1905 let dev_path = PathBuf::from(
1906 args.next()
1907 .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
1908 );
1909 let usb_file: Option<File> = if dev_path == Path::new("-") {
1910 None
1911 } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
1912 // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
1913 // Safe because we will validate |raw_fd|.
1914 Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) })
1915 } else {
1916 Some(
1917 OpenOptions::new()
1918 .read(true)
1919 .write(true)
1920 .open(&dev_path)
1921 .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?,
1922 )
1923 };
1924
1925 let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
1926 bus,
1927 addr,
1928 vid,
1929 pid,
David Tolnay5fb3f512019-04-12 19:22:33 -07001930 fd: usb_file.map(MaybeOwnedFd::Owned),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001931 });
1932 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1933 match response {
1934 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1935 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1936 }
1937}
1938
1939fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1940 let port: u8 = args
1941 .next()
1942 .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
1943 p.parse::<u8>()
1944 .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
1945 })?;
1946 let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
1947 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1948 match response {
1949 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1950 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1951 }
1952}
1953
Zach Reizneraff94ca2019-03-18 20:58:31 -07001954fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1955 let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
1956 for (index, port) in ports.iter_mut().enumerate() {
1957 *port = index as u8
1958 }
1959 let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
Jingkui Wang100e6e42019-03-08 20:41:57 -08001960 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1961 match response {
1962 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1963 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1964 }
1965}
1966
1967fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reizneraff94ca2019-03-18 20:58:31 -07001968 if args.len() < 2 {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001969 print_help("crosvm usb",
Zach Reizneraff94ca2019-03-18 20:58:31 -07001970 "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
Jingkui Wang100e6e42019-03-08 20:41:57 -08001971 return Err(());
1972 }
1973
1974 // This unwrap will not panic because of the above length check.
1975 let command = args.next().unwrap();
1976 let result = match command.as_ref() {
1977 "attach" => usb_attach(args),
1978 "detach" => usb_detach(args),
1979 "list" => usb_list(args),
1980 other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
1981 };
1982 match result {
1983 Ok(response) => {
1984 println!("{}", response);
1985 Ok(())
1986 }
1987 Err(e) => {
1988 println!("error {}", e);
1989 Err(())
1990 }
1991 }
1992}
1993
Zach Reiznerefe95782017-08-26 18:05:48 -07001994fn print_usage() {
1995 print_help("crosvm", "[stop|run]", &[]);
1996 println!("Commands:");
1997 println!(" stop - Stops crosvm instances via their control sockets.");
1998 println!(" run - Start a new crosvm instance.");
Dylan Reid2dcb6322018-07-13 10:42:48 -07001999 println!(" create_qcow2 - Create a new qcow2 disk image file.");
Jingkui Wang100e6e42019-03-08 20:41:57 -08002000 println!(" disk - Manage attached virtual disk devices.");
2001 println!(" usb - Manage attached virtual USB devices.");
Yi Sun54305cd2020-01-04 00:19:37 +08002002 println!(" version - Show package version.");
2003}
2004
2005fn pkg_version() -> std::result::Result<(), ()> {
2006 const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
2007 const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
2008
2009 print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
2010 match PKG_VERSION {
2011 Some(v) => println!("-{}", v),
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09002012 None => println!(),
Yi Sun54305cd2020-01-04 00:19:37 +08002013 }
2014 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07002015}
2016
Dylan Reidbfba9932018-02-05 15:51:59 -08002017fn crosvm_main() -> std::result::Result<(), ()> {
Stephen Barber56fbf092017-06-29 16:12:14 -07002018 if let Err(e) = syslog::init() {
David Tolnayb4bd00f2019-02-12 17:51:26 -08002019 println!("failed to initialize syslog: {}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08002020 return Err(());
Stephen Barber56fbf092017-06-29 16:12:14 -07002021 }
Zach Reizner639d9672017-05-01 17:57:18 -07002022
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08002023 panic_hook::set_panic_hook();
2024
Zach Reiznerefe95782017-08-26 18:05:48 -07002025 let mut args = std::env::args();
2026 if args.next().is_none() {
2027 error!("expected executable name");
Dylan Reidbfba9932018-02-05 15:51:59 -08002028 return Err(());
Zach Reizner639d9672017-05-01 17:57:18 -07002029 }
Zach Reiznerefe95782017-08-26 18:05:48 -07002030
Zach Reizner8864cb02018-01-16 17:59:03 -08002031 // Past this point, usage of exit is in danger of leaking zombie processes.
Dylan Reidbfba9932018-02-05 15:51:59 -08002032 let ret = match args.next().as_ref().map(|a| a.as_ref()) {
2033 None => {
2034 print_usage();
2035 Ok(())
2036 }
Zach Reizner55a9e502018-10-03 10:22:32 -07002037 Some("stop") => stop_vms(args),
Zach Reizner6a8fdd92019-01-16 14:38:41 -08002038 Some("suspend") => suspend_vms(args),
2039 Some("resume") => resume_vms(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07002040 Some("run") => run_vm(args),
2041 Some("balloon") => balloon_vms(args),
Charles William Dicked22f6b2020-04-08 11:05:24 +09002042 Some("balloon_stats") => balloon_stats(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07002043 Some("create_qcow2") => create_qcow2(args),
Daniel Verkamp92f73d72018-12-04 13:17:46 -08002044 Some("disk") => disk_cmd(args),
Jingkui Wang100e6e42019-03-08 20:41:57 -08002045 Some("usb") => modify_usb(args),
Yi Sun54305cd2020-01-04 00:19:37 +08002046 Some("version") => pkg_version(),
Zach Reiznerefe95782017-08-26 18:05:48 -07002047 Some(c) => {
2048 println!("invalid subcommand: {:?}", c);
2049 print_usage();
Dylan Reidbfba9932018-02-05 15:51:59 -08002050 Err(())
Zach Reiznerefe95782017-08-26 18:05:48 -07002051 }
Dylan Reidbfba9932018-02-05 15:51:59 -08002052 };
Zach Reiznerefe95782017-08-26 18:05:48 -07002053
2054 // Reap exit status from any child device processes. At this point, all devices should have been
2055 // dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
2056 // take some time for the processes to shut down.
2057 if !wait_all_children() {
2058 // We gave them a chance, and it's too late.
2059 warn!("not all child processes have exited; sending SIGKILL");
2060 if let Err(e) = kill_process_group() {
2061 // We're now at the mercy of the OS to clean up after us.
David Tolnayb4bd00f2019-02-12 17:51:26 -08002062 warn!("unable to kill all child processes: {}", e);
Zach Reiznerefe95782017-08-26 18:05:48 -07002063 }
2064 }
2065
2066 // WARNING: Any code added after this point is not guaranteed to run
2067 // since we may forcibly kill this process (and its children) above.
Dylan Reidbfba9932018-02-05 15:51:59 -08002068 ret
2069}
2070
2071fn main() {
2072 std::process::exit(if crosvm_main().is_ok() { 0 } else { 1 });
Zach Reizner639d9672017-05-01 17:57:18 -07002073}
Daniel Verkamp107edb32019-04-05 09:58:48 -07002074
2075#[cfg(test)]
2076mod tests {
2077 use super::*;
Kaiyi Libccb4eb2020-02-06 17:53:11 -08002078 use crosvm::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH};
Daniel Verkamp107edb32019-04-05 09:58:48 -07002079
2080 #[test]
2081 fn parse_cpu_set_single() {
2082 assert_eq!(parse_cpu_set("123").expect("parse failed"), vec![123]);
2083 }
2084
2085 #[test]
2086 fn parse_cpu_set_list() {
2087 assert_eq!(
2088 parse_cpu_set("0,1,2,3").expect("parse failed"),
2089 vec![0, 1, 2, 3]
2090 );
2091 }
2092
2093 #[test]
2094 fn parse_cpu_set_range() {
2095 assert_eq!(
2096 parse_cpu_set("0-3").expect("parse failed"),
2097 vec![0, 1, 2, 3]
2098 );
2099 }
2100
2101 #[test]
2102 fn parse_cpu_set_list_of_ranges() {
2103 assert_eq!(
2104 parse_cpu_set("3-4,7-9,18").expect("parse failed"),
2105 vec![3, 4, 7, 8, 9, 18]
2106 );
2107 }
2108
2109 #[test]
2110 fn parse_cpu_set_repeated() {
2111 // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
2112 assert_eq!(parse_cpu_set("1,1,1").expect("parse failed"), vec![1, 1, 1]);
2113 }
2114
2115 #[test]
2116 fn parse_cpu_set_negative() {
2117 // Negative CPU numbers are not allowed.
2118 parse_cpu_set("-3").expect_err("parse should have failed");
2119 }
2120
2121 #[test]
2122 fn parse_cpu_set_reverse_range() {
2123 // Ranges must be from low to high.
2124 parse_cpu_set("5-2").expect_err("parse should have failed");
2125 }
2126
2127 #[test]
2128 fn parse_cpu_set_open_range() {
2129 parse_cpu_set("3-").expect_err("parse should have failed");
2130 }
2131
2132 #[test]
2133 fn parse_cpu_set_extra_comma() {
2134 parse_cpu_set("0,1,2,").expect_err("parse should have failed");
2135 }
Trent Begin17ccaad2019-04-17 13:51:25 -06002136
Daniel Verkampc677fb42020-09-08 13:47:49 -07002137 #[test]
2138 fn parse_cpu_affinity_global() {
2139 assert_eq!(
2140 parse_cpu_affinity("0,5-7,9").expect("parse failed"),
2141 VcpuAffinity::Global(vec![0, 5, 6, 7, 9]),
2142 );
2143 }
2144
2145 #[test]
2146 fn parse_cpu_affinity_per_vcpu_one_to_one() {
2147 let mut expected_map = BTreeMap::new();
2148 expected_map.insert(0, vec![0]);
2149 expected_map.insert(1, vec![1]);
2150 expected_map.insert(2, vec![2]);
2151 expected_map.insert(3, vec![3]);
2152 assert_eq!(
2153 parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
2154 VcpuAffinity::PerVcpu(expected_map),
2155 );
2156 }
2157
2158 #[test]
2159 fn parse_cpu_affinity_per_vcpu_sets() {
2160 let mut expected_map = BTreeMap::new();
2161 expected_map.insert(0, vec![0, 1, 2]);
2162 expected_map.insert(1, vec![3, 4, 5]);
2163 expected_map.insert(2, vec![6, 7, 8]);
2164 assert_eq!(
2165 parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
2166 VcpuAffinity::PerVcpu(expected_map),
2167 );
2168 }
2169
Andrew Scull1590e6f2020-03-18 18:00:47 +00002170 #[cfg(feature = "audio")]
Trent Begin17ccaad2019-04-17 13:51:25 -06002171 #[test]
Judy Hsiaod5c1e962020-02-04 12:30:01 +08002172 fn parse_ac97_vaild() {
2173 parse_ac97_options("backend=cras").expect("parse should have succeded");
2174 }
2175
Andrew Scull1590e6f2020-03-18 18:00:47 +00002176 #[cfg(feature = "audio")]
Judy Hsiaod5c1e962020-02-04 12:30:01 +08002177 #[test]
2178 fn parse_ac97_null_vaild() {
2179 parse_ac97_options("backend=null").expect("parse should have succeded");
2180 }
2181
Andrew Scull1590e6f2020-03-18 18:00:47 +00002182 #[cfg(feature = "audio")]
Judy Hsiaod5c1e962020-02-04 12:30:01 +08002183 #[test]
Judy Hsiaob4b94c72020-09-07 15:56:00 +08002184 fn parse_ac97_capture_vaild() {
2185 parse_ac97_options("backend=cras,capture=true").expect("parse should have succeded");
Judy Hsiaod5c1e962020-02-04 12:30:01 +08002186 }
2187
2188 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06002189 fn parse_serial_vaild() {
Jorge E. Moreira1e262302019-08-01 14:40:03 -07002190 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
2191 .expect("parse should have succeded");
Trent Begin17ccaad2019-04-17 13:51:25 -06002192 }
2193
2194 #[test]
Trent Begin923bab02019-06-17 13:48:06 -06002195 fn parse_serial_valid_no_num() {
2196 parse_serial_options("type=syslog").expect("parse should have succeded");
2197 }
2198
2199 #[test]
Nicholas Hollingumc76c2da2020-09-18 15:53:16 +10002200 fn parse_serial_equals_in_value() {
2201 let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
2202 .expect("parse should have succeded");
2203 assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
2204 }
2205
2206 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06002207 fn parse_serial_invalid_type() {
2208 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
2209 }
2210
2211 #[test]
2212 fn parse_serial_invalid_num_upper() {
2213 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
2214 }
2215
2216 #[test]
2217 fn parse_serial_invalid_num_lower() {
2218 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
2219 }
2220
2221 #[test]
2222 fn parse_serial_invalid_num_string() {
2223 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
2224 }
2225
2226 #[test]
2227 fn parse_serial_invalid_option() {
2228 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
2229 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -07002230
2231 #[test]
2232 fn parse_serial_invalid_two_stdin() {
2233 let mut config = Config::default();
2234 set_argument(&mut config, "serial", Some("num=1,type=stdout,stdin=true"))
2235 .expect("should parse the first serial argument");
2236 set_argument(&mut config, "serial", Some("num=2,type=stdout,stdin=true"))
2237 .expect_err("should fail to parse a second serial port connected to stdin");
2238 }
Dmitry Torokhov458bb642019-12-13 11:47:52 -08002239
2240 #[test]
2241 fn parse_plugin_mount_valid() {
2242 let mut config = Config::default();
2243 set_argument(
2244 &mut config,
2245 "plugin-mount",
2246 Some("/dev/null:/dev/zero:true"),
2247 )
2248 .expect("parse should succeed");
2249 assert_eq!(config.plugin_mounts[0].src, PathBuf::from("/dev/null"));
2250 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/zero"));
2251 assert_eq!(config.plugin_mounts[0].writable, true);
2252 }
2253
2254 #[test]
2255 fn parse_plugin_mount_valid_shorthand() {
2256 let mut config = Config::default();
2257 set_argument(&mut config, "plugin-mount", Some("/dev/null")).expect("parse should succeed");
2258 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/null"));
2259 assert_eq!(config.plugin_mounts[0].writable, false);
2260 set_argument(&mut config, "plugin-mount", Some("/dev/null:/dev/zero"))
2261 .expect("parse should succeed");
2262 assert_eq!(config.plugin_mounts[1].dst, PathBuf::from("/dev/zero"));
2263 assert_eq!(config.plugin_mounts[1].writable, false);
2264 set_argument(&mut config, "plugin-mount", Some("/dev/null::true"))
2265 .expect("parse should succeed");
2266 assert_eq!(config.plugin_mounts[2].dst, PathBuf::from("/dev/null"));
2267 assert_eq!(config.plugin_mounts[2].writable, true);
2268 }
2269
2270 #[test]
2271 fn parse_plugin_mount_invalid() {
2272 let mut config = Config::default();
2273 set_argument(&mut config, "plugin-mount", Some("")).expect_err("parse should fail");
2274 set_argument(
2275 &mut config,
2276 "plugin-mount",
2277 Some("/dev/null:/dev/null:true:false"),
2278 )
2279 .expect_err("parse should fail because too many arguments");
2280 set_argument(&mut config, "plugin-mount", Some("null:/dev/null:true"))
2281 .expect_err("parse should fail because source is not absolute");
2282 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:true"))
2283 .expect_err("parse should fail because source is not absolute");
2284 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:blah"))
2285 .expect_err("parse should fail because flag is not boolean");
2286 }
2287
2288 #[test]
2289 fn parse_plugin_gid_map_valid() {
2290 let mut config = Config::default();
2291 set_argument(&mut config, "plugin-gid-map", Some("1:2:3")).expect("parse should succeed");
2292 assert_eq!(config.plugin_gid_maps[0].inner, 1);
2293 assert_eq!(config.plugin_gid_maps[0].outer, 2);
2294 assert_eq!(config.plugin_gid_maps[0].count, 3);
2295 }
2296
2297 #[test]
2298 fn parse_plugin_gid_map_valid_shorthand() {
2299 let mut config = Config::default();
2300 set_argument(&mut config, "plugin-gid-map", Some("1")).expect("parse should succeed");
2301 assert_eq!(config.plugin_gid_maps[0].inner, 1);
2302 assert_eq!(config.plugin_gid_maps[0].outer, 1);
2303 assert_eq!(config.plugin_gid_maps[0].count, 1);
2304 set_argument(&mut config, "plugin-gid-map", Some("1:2")).expect("parse should succeed");
2305 assert_eq!(config.plugin_gid_maps[1].inner, 1);
2306 assert_eq!(config.plugin_gid_maps[1].outer, 2);
2307 assert_eq!(config.plugin_gid_maps[1].count, 1);
2308 set_argument(&mut config, "plugin-gid-map", Some("1::3")).expect("parse should succeed");
2309 assert_eq!(config.plugin_gid_maps[2].inner, 1);
2310 assert_eq!(config.plugin_gid_maps[2].outer, 1);
2311 assert_eq!(config.plugin_gid_maps[2].count, 3);
2312 }
2313
2314 #[test]
2315 fn parse_plugin_gid_map_invalid() {
2316 let mut config = Config::default();
2317 set_argument(&mut config, "plugin-gid-map", Some("")).expect_err("parse should fail");
2318 set_argument(&mut config, "plugin-gid-map", Some("1:2:3:4"))
2319 .expect_err("parse should fail because too many arguments");
2320 set_argument(&mut config, "plugin-gid-map", Some("blah:2:3"))
2321 .expect_err("parse should fail because inner is not a number");
2322 set_argument(&mut config, "plugin-gid-map", Some("1:blah:3"))
2323 .expect_err("parse should fail because outer is not a number");
2324 set_argument(&mut config, "plugin-gid-map", Some("1:2:blah"))
2325 .expect_err("parse should fail because count is not a number");
2326 }
Kaiyi Libccb4eb2020-02-06 17:53:11 -08002327
2328 #[test]
2329 fn single_touch_spec_and_track_pad_spec_default_size() {
2330 let mut config = Config::default();
2331 config
2332 .executable_path
2333 .replace(Executable::Kernel(PathBuf::from("kernel")));
2334 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
2335 set_argument(&mut config, "trackpad", Some("/dev/single-touch-test")).unwrap();
2336 validate_arguments(&mut config).unwrap();
2337 assert_eq!(
2338 config.virtio_single_touch.unwrap().get_size(),
2339 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
2340 );
2341 assert_eq!(
2342 config.virtio_trackpad.unwrap().get_size(),
2343 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
2344 );
2345 }
2346
2347 #[cfg(feature = "gpu")]
2348 #[test]
2349 fn single_touch_spec_default_size_from_gpu() {
2350 let width = 12345u32;
2351 let height = 54321u32;
2352 let mut config = Config::default();
2353 config
2354 .executable_path
2355 .replace(Executable::Kernel(PathBuf::from("kernel")));
2356 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
2357 set_argument(
2358 &mut config,
2359 "gpu",
2360 Some(&format!("width={},height={}", width, height)),
2361 )
2362 .unwrap();
2363 validate_arguments(&mut config).unwrap();
2364 assert_eq!(
2365 config.virtio_single_touch.unwrap().get_size(),
2366 (width, height)
2367 );
2368 }
2369
2370 #[test]
2371 fn single_touch_spec_and_track_pad_spec_with_size() {
2372 let width = 12345u32;
2373 let height = 54321u32;
2374 let mut config = Config::default();
2375 config
2376 .executable_path
2377 .replace(Executable::Kernel(PathBuf::from("kernel")));
2378 set_argument(
2379 &mut config,
2380 "single-touch",
2381 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
2382 )
2383 .unwrap();
2384 set_argument(
2385 &mut config,
2386 "trackpad",
2387 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
2388 )
2389 .unwrap();
2390 validate_arguments(&mut config).unwrap();
2391 assert_eq!(
2392 config.virtio_single_touch.unwrap().get_size(),
2393 (width, height)
2394 );
2395 assert_eq!(config.virtio_trackpad.unwrap().get_size(), (width, height));
2396 }
2397
2398 #[cfg(feature = "gpu")]
2399 #[test]
2400 fn single_touch_spec_with_size_independent_from_gpu() {
2401 let touch_width = 12345u32;
2402 let touch_height = 54321u32;
2403 let display_width = 1234u32;
2404 let display_height = 5432u32;
2405 let mut config = Config::default();
2406 config
2407 .executable_path
2408 .replace(Executable::Kernel(PathBuf::from("kernel")));
2409 set_argument(
2410 &mut config,
2411 "single-touch",
2412 Some(&format!(
2413 "/dev/single-touch-test:{}:{}",
2414 touch_width, touch_height
2415 )),
2416 )
2417 .unwrap();
2418 set_argument(
2419 &mut config,
2420 "gpu",
2421 Some(&format!(
2422 "width={},height={}",
2423 display_width, display_height
2424 )),
2425 )
2426 .unwrap();
2427 validate_arguments(&mut config).unwrap();
2428 assert_eq!(
2429 config.virtio_single_touch.unwrap().get_size(),
2430 (touch_width, touch_height)
2431 );
2432 }
Kaiyi Lidd348a42020-07-13 11:49:46 -07002433
2434 #[cfg(all(feature = "gpu", feature = "gfxstream"))]
2435 #[test]
2436 fn parse_gpu_options_gfxstream_with_vulkan_specified() {
2437 assert!(
2438 parse_gpu_options(Some("backend=gfxstream,vulkan=true"))
2439 .unwrap()
2440 .gfxstream_support_vulkan
2441 );
2442 assert!(
2443 parse_gpu_options(Some("vulkan=true,backend=gfxstream"))
2444 .unwrap()
2445 .gfxstream_support_vulkan
2446 );
2447 assert!(
2448 !parse_gpu_options(Some("backend=gfxstream,vulkan=false"))
2449 .unwrap()
2450 .gfxstream_support_vulkan
2451 );
2452 assert!(
2453 !parse_gpu_options(Some("vulkan=false,backend=gfxstream"))
2454 .unwrap()
2455 .gfxstream_support_vulkan
2456 );
2457 assert!(parse_gpu_options(Some("backend=gfxstream,vulkan=invalid_value")).is_err());
2458 assert!(parse_gpu_options(Some("vulkan=invalid_value,backend=gfxstream")).is_err());
2459 }
2460
2461 #[cfg(all(feature = "gpu", feature = "gfxstream"))]
2462 #[test]
2463 fn parse_gpu_options_not_gfxstream_with_vulkan_specified() {
2464 assert!(parse_gpu_options(Some("backend=3d,vulkan=true")).is_err());
2465 assert!(parse_gpu_options(Some("vulkan=true,backend=3d")).is_err());
2466 }
2467
2468 #[cfg(all(feature = "gpu", feature = "gfxstream"))]
2469 #[test]
2470 fn parse_gpu_options_gfxstream_with_syncfd_specified() {
2471 assert!(
2472 parse_gpu_options(Some("backend=gfxstream,syncfd=true"))
2473 .unwrap()
2474 .gfxstream_use_syncfd
2475 );
2476 assert!(
2477 parse_gpu_options(Some("syncfd=true,backend=gfxstream"))
2478 .unwrap()
2479 .gfxstream_use_syncfd
2480 );
2481 assert!(
2482 !parse_gpu_options(Some("backend=gfxstream,syncfd=false"))
2483 .unwrap()
2484 .gfxstream_use_syncfd
2485 );
2486 assert!(
2487 !parse_gpu_options(Some("syncfd=false,backend=gfxstream"))
2488 .unwrap()
2489 .gfxstream_use_syncfd
2490 );
2491 assert!(parse_gpu_options(Some("backend=gfxstream,syncfd=invalid_value")).is_err());
2492 assert!(parse_gpu_options(Some("syncfd=invalid_value,backend=gfxstream")).is_err());
2493 }
2494
2495 #[cfg(all(feature = "gpu", feature = "gfxstream"))]
2496 #[test]
2497 fn parse_gpu_options_not_gfxstream_with_syncfd_specified() {
2498 assert!(parse_gpu_options(Some("backend=3d,syncfd=true")).is_err());
2499 assert!(parse_gpu_options(Some("syncfd=true,backend=3d")).is_err());
2500 }
Daniel Verkamp107edb32019-04-05 09:58:48 -07002501}