blob: 557c630256e61133197d22df28296a543cbcc348 [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
5//! Runs a virtual machine under KVM
6
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08007pub mod panic_hook;
Zach Reizner29ad3c72017-08-04 15:12:58 -07008
Judy Hsiaod5c1e962020-02-04 12:30:01 +08009use std::default::Default;
Jingkui Wang100e6e42019-03-08 20:41:57 -080010use std::fmt;
11use std::fs::{File, OpenOptions};
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -080012use std::io::{BufRead, BufReader};
Jingkui Wang100e6e42019-03-08 20:41:57 -080013use std::num::ParseIntError;
14use std::os::unix::io::{FromRawFd, RawFd};
15use std::path::{Path, PathBuf};
Zach Reizner639d9672017-05-01 17:57:18 -070016use std::string::String;
Zach Reizner39aa26b2017-12-12 18:03:23 -080017use std::thread::sleep;
Stephen Barber56fbf092017-06-29 16:12:14 -070018use std::time::Duration;
Zach Reizner639d9672017-05-01 17:57:18 -070019
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -070020use arch::{set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType};
Judy Hsiaod5c1e962020-02-04 12:30:01 +080021use audio_streams::StreamEffect;
Zach Reizner267f2c82019-07-31 17:07:27 -070022use crosvm::{
23 argument::{self, print_help, set_arguments, Argument},
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +090024 linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
Zach Reizner267f2c82019-07-31 17:07:27 -070025};
Jason Macnakcc7070b2019-11-06 14:48:12 -080026#[cfg(feature = "gpu")]
Kaiyi Libccb4eb2020-02-06 17:53:11 -080027use devices::virtio::gpu::{GpuMode, GpuParameters};
Daniel Verkampfbd61222020-02-14 16:46:36 -080028use devices::{Ac97Backend, Ac97Parameters};
Daniel Verkampf2eecc42019-12-17 17:04:58 -080029use disk::QcowFile;
David Tolnayfe3ef7d2019-03-08 15:57:49 -080030use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
Jingkui Wang100e6e42019-03-08 20:41:57 -080031use sys_util::{
David Tolnay633426a2019-04-12 12:18:35 -070032 debug, error, getpid, info, kill_process_group, net::UnixSeqpacket, reap_child, syslog,
33 validate_raw_fd, warn,
Jingkui Wang100e6e42019-03-08 20:41:57 -080034};
Jakub Starone7c59052019-04-09 12:31:14 -070035use vm_control::{
Jakub Staron1f828d72019-04-11 12:49:29 -070036 BalloonControlCommand, DiskControlCommand, MaybeOwnedFd, UsbControlCommand, UsbControlResult,
Zach Reizneraff94ca2019-03-18 20:58:31 -070037 VmControlRequestSocket, VmRequest, VmResponse, USB_CONTROL_MAX_PORTS,
Jakub Starone7c59052019-04-09 12:31:14 -070038};
Zach Reizner639d9672017-05-01 17:57:18 -070039
Cody Schuffelen6d1ab502019-05-21 12:12:38 -070040fn executable_is_plugin(executable: &Option<Executable>) -> bool {
41 match executable {
42 Some(Executable::Plugin(_)) => true,
43 _ => false,
44 }
45}
46
Stephen Barbera00753b2017-07-18 13:57:26 -070047// Wait for all children to exit. Return true if they have all exited, false
48// otherwise.
49fn wait_all_children() -> bool {
Stephen Barber49dd2e22018-10-29 18:29:58 -070050 const CHILD_WAIT_MAX_ITER: isize = 100;
Stephen Barbera00753b2017-07-18 13:57:26 -070051 const CHILD_WAIT_MS: u64 = 10;
52 for _ in 0..CHILD_WAIT_MAX_ITER {
Stephen Barbera00753b2017-07-18 13:57:26 -070053 loop {
Zach Reizner56158c82017-08-24 13:50:14 -070054 match reap_child() {
55 Ok(0) => break,
56 // We expect ECHILD which indicates that there were no children left.
57 Err(e) if e.errno() == libc::ECHILD => return true,
58 Err(e) => {
David Tolnayb4bd00f2019-02-12 17:51:26 -080059 warn!("error while waiting for children: {}", e);
Zach Reizner56158c82017-08-24 13:50:14 -070060 return false;
Stephen Barbera00753b2017-07-18 13:57:26 -070061 }
Zach Reizner56158c82017-08-24 13:50:14 -070062 // We reaped one child, so continue reaping.
Zach Reizner55a9e502018-10-03 10:22:32 -070063 _ => {}
Stephen Barbera00753b2017-07-18 13:57:26 -070064 }
65 }
Zach Reizner56158c82017-08-24 13:50:14 -070066 // There's no timeout option for waitpid which reap_child calls internally, so our only
67 // recourse is to sleep while waiting for the children to exit.
Stephen Barbera00753b2017-07-18 13:57:26 -070068 sleep(Duration::from_millis(CHILD_WAIT_MS));
69 }
70
71 // If we've made it to this point, not all of the children have exited.
David Tolnay5bbbf612018-12-01 17:49:30 -080072 false
Stephen Barbera00753b2017-07-18 13:57:26 -070073}
74
Daniel Verkamp107edb32019-04-05 09:58:48 -070075/// Parse a comma-separated list of CPU numbers and ranges and convert it to a Vec of CPU numbers.
76fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
77 let mut cpuset = Vec::new();
78 for part in s.split(',') {
79 let range: Vec<&str> = part.split('-').collect();
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +090080 if range.is_empty() || range.len() > 2 {
Daniel Verkamp107edb32019-04-05 09:58:48 -070081 return Err(argument::Error::InvalidValue {
82 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +080083 expected: String::from("invalid list syntax"),
Daniel Verkamp107edb32019-04-05 09:58:48 -070084 });
85 }
86 let first_cpu: usize = range[0]
87 .parse()
88 .map_err(|_| argument::Error::InvalidValue {
89 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +080090 expected: String::from("CPU index must be a non-negative integer"),
Daniel Verkamp107edb32019-04-05 09:58:48 -070091 })?;
92 let last_cpu: usize = if range.len() == 2 {
93 range[1]
94 .parse()
95 .map_err(|_| argument::Error::InvalidValue {
96 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +080097 expected: String::from("CPU index must be a non-negative integer"),
Daniel Verkamp107edb32019-04-05 09:58:48 -070098 })?
99 } else {
100 first_cpu
101 };
102
103 if last_cpu < first_cpu {
104 return Err(argument::Error::InvalidValue {
105 value: part.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800106 expected: String::from("CPU ranges must be from low to high"),
Daniel Verkamp107edb32019-04-05 09:58:48 -0700107 });
108 }
109
110 for cpu in first_cpu..=last_cpu {
111 cpuset.push(cpu);
112 }
113 }
114 Ok(cpuset)
115}
116
Jason Macnakcc7070b2019-11-06 14:48:12 -0800117#[cfg(feature = "gpu")]
118fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
Kaiyi Libccb4eb2020-02-06 17:53:11 -0800119 let mut gpu_params: GpuParameters = Default::default();
Jason Macnakcc7070b2019-11-06 14:48:12 -0800120
121 if let Some(s) = s {
122 let opts = s
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900123 .split(',')
124 .map(|frag| frag.split('='))
Jason Macnakcc7070b2019-11-06 14:48:12 -0800125 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
126
127 for (k, v) in opts {
128 match k {
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800129 // Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
130 // times if the user specifies several modes (--gpu=2d,3d,gfxstream)
Jason Macnak327fc242020-01-10 12:45:36 -0800131 "2d" | "2D" => {
132 gpu_params.mode = GpuMode::Mode2D;
133 }
134 "3d" | "3D" => {
135 gpu_params.mode = GpuMode::Mode3D;
136 }
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800137 #[cfg(feature = "gfxstream")]
138 "gfxstream" => {
139 gpu_params.mode = GpuMode::ModeGfxStream;
140 }
141 // Preferred: Specifying --gpu,backend=<mode>
142 "backend" => match v {
143 "2d" | "2D" => {
144 gpu_params.mode = GpuMode::Mode2D;
145 }
146 "3d" | "3D" => {
147 gpu_params.mode = GpuMode::Mode3D;
148 }
149 #[cfg(feature = "gfxstream")]
150 "gfxstream" => {
151 gpu_params.mode = GpuMode::ModeGfxStream;
152 }
153 _ => {
154 return Err(argument::Error::InvalidValue {
155 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800156 expected: String::from(
157 "gpu parameter 'backend' should be one of (2d|3d|gfxstream)",
158 ),
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800159 });
160 }
161 },
Jason Macnakbf195582019-11-20 16:25:49 -0800162 "egl" => match v {
163 "true" | "" => {
164 gpu_params.renderer_use_egl = true;
165 }
166 "false" => {
167 gpu_params.renderer_use_egl = false;
168 }
169 _ => {
170 return Err(argument::Error::InvalidValue {
171 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800172 expected: String::from("gpu parameter 'egl' should be a boolean"),
Jason Macnakbf195582019-11-20 16:25:49 -0800173 });
174 }
175 },
176 "gles" => match v {
177 "true" | "" => {
178 gpu_params.renderer_use_gles = true;
179 }
180 "false" => {
181 gpu_params.renderer_use_gles = false;
182 }
183 _ => {
184 return Err(argument::Error::InvalidValue {
185 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800186 expected: String::from("gpu parameter 'gles' should be a boolean"),
Jason Macnakbf195582019-11-20 16:25:49 -0800187 });
188 }
189 },
190 "glx" => match v {
191 "true" | "" => {
192 gpu_params.renderer_use_glx = true;
193 }
194 "false" => {
195 gpu_params.renderer_use_glx = false;
196 }
197 _ => {
198 return Err(argument::Error::InvalidValue {
199 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800200 expected: String::from("gpu parameter 'glx' should be a boolean"),
Jason Macnakbf195582019-11-20 16:25:49 -0800201 });
202 }
203 },
204 "surfaceless" => match v {
205 "true" | "" => {
206 gpu_params.renderer_use_surfaceless = true;
207 }
208 "false" => {
209 gpu_params.renderer_use_surfaceless = false;
210 }
211 _ => {
212 return Err(argument::Error::InvalidValue {
213 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800214 expected: String::from(
215 "gpu parameter 'surfaceless' should be a boolean",
216 ),
Jason Macnakbf195582019-11-20 16:25:49 -0800217 });
218 }
219 },
Jason Macnakcc7070b2019-11-06 14:48:12 -0800220 "width" => {
221 gpu_params.display_width =
222 v.parse::<u32>()
223 .map_err(|_| argument::Error::InvalidValue {
224 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800225 expected: String::from(
226 "gpu parameter 'width' must be a valid integer",
227 ),
Jason Macnakcc7070b2019-11-06 14:48:12 -0800228 })?;
229 }
230 "height" => {
231 gpu_params.display_height =
232 v.parse::<u32>()
233 .map_err(|_| argument::Error::InvalidValue {
234 value: v.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800235 expected: String::from(
236 "gpu parameter 'height' must be a valid integer",
237 ),
Jason Macnakcc7070b2019-11-06 14:48:12 -0800238 })?;
239 }
240 "" => {}
241 _ => {
242 return Err(argument::Error::UnknownArgument(format!(
243 "gpu parameter {}",
244 k
245 )));
246 }
247 }
248 }
249 }
250
251 Ok(gpu_params)
252}
253
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800254fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
255 let mut ac97_params: Ac97Parameters = Default::default();
256
257 let opts = s
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900258 .split(',')
259 .map(|frag| frag.split('='))
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800260 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
261
262 for (k, v) in opts {
263 match k {
264 "backend" => {
265 ac97_params.backend =
266 v.parse::<Ac97Backend>()
267 .map_err(|e| argument::Error::InvalidValue {
268 value: v.to_string(),
269 expected: e.to_string(),
270 })?;
271 }
272 "capture" => {
273 ac97_params.capture = v.parse::<bool>().map_err(|e| {
274 argument::Error::Syntax(format!("invalid capture option: {}", e))
275 })?;
276 }
277 "capture_effects" => {
278 ac97_params.capture_effects = v
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900279 .split('|')
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800280 .map(|val| {
281 val.parse::<StreamEffect>()
282 .map_err(|e| argument::Error::InvalidValue {
283 value: val.to_string(),
284 expected: e.to_string(),
285 })
286 })
287 .collect::<argument::Result<Vec<_>>>()?;
288 }
289 _ => {
290 return Err(argument::Error::UnknownArgument(format!(
291 "unknown ac97 parameter {}",
292 k
293 )));
294 }
295 }
296 }
297
298 Ok(ac97_params)
299}
300
Trent Begin17ccaad2019-04-17 13:51:25 -0600301fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
302 let mut serial_setting = SerialParameters {
303 type_: SerialType::Sink,
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700304 hardware: SerialHardware::Serial,
Trent Begin17ccaad2019-04-17 13:51:25 -0600305 path: None,
Iliyan Malchev2c1417b2020-04-14 09:40:41 -0700306 input: None,
Trent Begin923bab02019-06-17 13:48:06 -0600307 num: 1,
Trent Begin17ccaad2019-04-17 13:51:25 -0600308 console: false,
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700309 earlycon: false,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700310 stdin: false,
Trent Begin17ccaad2019-04-17 13:51:25 -0600311 };
312
313 let opts = s
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900314 .split(',')
315 .map(|frag| frag.split('='))
Trent Begin17ccaad2019-04-17 13:51:25 -0600316 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
317
318 for (k, v) in opts {
319 match k {
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700320 "hardware" => {
321 serial_setting.hardware = v
322 .parse::<SerialHardware>()
323 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
324 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600325 "type" => {
326 serial_setting.type_ = v
327 .parse::<SerialType>()
328 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
329 }
330 "num" => {
331 let num = v.parse::<u8>().map_err(|e| {
332 argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
333 })?;
334 if num < 1 || num > 4 {
335 return Err(argument::Error::InvalidValue {
336 value: num.to_string(),
Judy Hsiao59343052020-03-16 15:58:03 +0800337 expected: String::from("Serial port num must be between 1 - 4"),
Trent Begin17ccaad2019-04-17 13:51:25 -0600338 });
339 }
340 serial_setting.num = num;
341 }
342 "console" => {
343 serial_setting.console = v.parse::<bool>().map_err(|e| {
344 argument::Error::Syntax(format!(
345 "serial device console is not parseable: {}",
346 e
347 ))
348 })?
349 }
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700350 "earlycon" => {
351 serial_setting.earlycon = v.parse::<bool>().map_err(|e| {
352 argument::Error::Syntax(format!(
353 "serial device earlycon is not parseable: {}",
354 e,
355 ))
356 })?
357 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700358 "stdin" => {
359 serial_setting.stdin = v.parse::<bool>().map_err(|e| {
360 argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e))
Iliyan Malchev2c1417b2020-04-14 09:40:41 -0700361 })?;
362 if serial_setting.stdin && serial_setting.input.is_some() {
363 return Err(argument::Error::TooManyArguments(
364 "Cannot specify both stdin and input options".to_string(),
365 ));
366 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700367 }
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700368 "path" => serial_setting.path = Some(PathBuf::from(v)),
Iliyan Malchev2c1417b2020-04-14 09:40:41 -0700369 "input" => {
370 if serial_setting.stdin {
371 return Err(argument::Error::TooManyArguments(
372 "Cannot specify both stdin and input options".to_string(),
373 ));
374 }
375 serial_setting.input = Some(PathBuf::from(v));
376 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600377 _ => {
378 return Err(argument::Error::UnknownArgument(format!(
379 "serial parameter {}",
380 k
381 )));
382 }
383 }
384 }
385
386 Ok(serial_setting)
387}
388
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800389fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900390 let components: Vec<&str> = value.split(':').collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800391 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800392 return Err(argument::Error::InvalidValue {
393 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800394 expected: String::from(
395 "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
396 ),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800397 });
398 }
399
400 let src = PathBuf::from(components[0]);
401 if src.is_relative() {
402 return Err(argument::Error::InvalidValue {
403 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800404 expected: String::from("the source path for `plugin-mount` must be absolute"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800405 });
406 }
407 if !src.exists() {
408 return Err(argument::Error::InvalidValue {
409 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800410 expected: String::from("the source path for `plugin-mount` does not exist"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800411 });
412 }
413
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800414 let dst = PathBuf::from(match components.get(1) {
415 None | Some(&"") => components[0],
416 Some(path) => path,
417 });
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800418 if dst.is_relative() {
419 return Err(argument::Error::InvalidValue {
420 value: components[1].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800421 expected: String::from("the destination path for `plugin-mount` must be absolute"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800422 });
423 }
424
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800425 let writable: bool = match components.get(2) {
426 None => false,
427 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800428 value: components[2].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800429 expected: String::from("the <writable> component for `plugin-mount` is not valid bool"),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800430 })?,
431 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800432
433 Ok(BindMount { src, dst, writable })
434}
435
436fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900437 let components: Vec<&str> = value.split(':').collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800438 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800439 return Err(argument::Error::InvalidValue {
440 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800441 expected: String::from(
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800442 "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
Judy Hsiao59343052020-03-16 15:58:03 +0800443 ),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800444 });
445 }
446
447 let inner: libc::gid_t = components[0]
448 .parse()
449 .map_err(|_| argument::Error::InvalidValue {
450 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800451 expected: String::from("the <inner> component for `plugin-gid-map` is not valid gid"),
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800452 })?;
453
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800454 let outer: libc::gid_t = match components.get(1) {
455 None | Some(&"") => inner,
456 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800457 value: components[1].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800458 expected: String::from("the <outer> component for `plugin-gid-map` is not valid gid"),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800459 })?,
460 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800461
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800462 let count: u32 = match components.get(2) {
463 None => 1,
464 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800465 value: components[2].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800466 expected: String::from(
467 "the <count> component for `plugin-gid-map` is not valid number",
468 ),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800469 })?,
470 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800471
472 Ok(GidMap {
473 inner,
474 outer,
475 count,
476 })
477}
478
Zach Reiznerefe95782017-08-26 18:05:48 -0700479fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
480 match name {
481 "" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700482 if cfg.executable_path.is_some() {
483 return Err(argument::Error::TooManyArguments(format!(
484 "A VM executable was already specified: {:?}",
485 cfg.executable_path
486 )));
Zach Reiznerefe95782017-08-26 18:05:48 -0700487 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700488 let kernel_path = PathBuf::from(value.unwrap());
489 if !kernel_path.exists() {
490 return Err(argument::Error::InvalidValue {
491 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800492 expected: String::from("this kernel path does not exist"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700493 });
494 }
495 cfg.executable_path = Some(Executable::Kernel(kernel_path));
Zach Reiznerefe95782017-08-26 18:05:48 -0700496 }
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800497 "android-fstab" => {
498 if cfg.android_fstab.is_some()
499 && !cfg.android_fstab.as_ref().unwrap().as_os_str().is_empty()
500 {
501 return Err(argument::Error::TooManyArguments(
502 "expected exactly one android fstab path".to_owned(),
503 ));
504 } else {
505 let android_fstab = PathBuf::from(value.unwrap());
506 if !android_fstab.exists() {
507 return Err(argument::Error::InvalidValue {
508 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800509 expected: String::from("this android fstab path does not exist"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800510 });
511 }
512 cfg.android_fstab = Some(android_fstab);
513 }
514 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700515 "params" => {
Zach Reiznerbb678712018-01-30 18:13:04 -0800516 cfg.params.push(value.unwrap().to_owned());
Zach Reiznerefe95782017-08-26 18:05:48 -0700517 }
518 "cpus" => {
519 if cfg.vcpu_count.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700520 return Err(argument::Error::TooManyArguments(
521 "`cpus` already given".to_owned(),
522 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700523 }
524 cfg.vcpu_count =
Zach Reizner55a9e502018-10-03 10:22:32 -0700525 Some(
526 value
527 .unwrap()
528 .parse()
529 .map_err(|_| argument::Error::InvalidValue {
530 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800531 expected: String::from("this value for `cpus` needs to be integer"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700532 })?,
533 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700534 }
Daniel Verkamp107edb32019-04-05 09:58:48 -0700535 "cpu-affinity" => {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +0900536 if !cfg.vcpu_affinity.is_empty() {
Daniel Verkamp107edb32019-04-05 09:58:48 -0700537 return Err(argument::Error::TooManyArguments(
538 "`cpu-affinity` already given".to_owned(),
539 ));
540 }
541 cfg.vcpu_affinity = parse_cpu_set(value.unwrap())?;
542 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700543 "mem" => {
544 if cfg.memory.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700545 return Err(argument::Error::TooManyArguments(
546 "`mem` already given".to_owned(),
547 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700548 }
549 cfg.memory =
Zach Reizner55a9e502018-10-03 10:22:32 -0700550 Some(
551 value
552 .unwrap()
553 .parse()
554 .map_err(|_| argument::Error::InvalidValue {
555 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800556 expected: String::from("this value for `mem` needs to be integer"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700557 })?,
558 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700559 }
Judy Hsiaod5c1e962020-02-04 12:30:01 +0800560 "ac97" => {
561 let ac97_params = parse_ac97_options(value.unwrap())?;
562 cfg.ac97_parameters.push(ac97_params);
Dylan Reid3082e8e2019-01-07 10:33:48 -0800563 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600564 "serial" => {
565 let serial_params = parse_serial_options(value.unwrap())?;
566 let num = serial_params.num;
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700567 let key = (serial_params.hardware, num);
568 if cfg.serial_parameters.contains_key(&key) {
Trent Begin17ccaad2019-04-17 13:51:25 -0600569 return Err(argument::Error::TooManyArguments(format!(
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700570 "serial hardware {} num {}",
571 serial_params.hardware, num,
Trent Begin17ccaad2019-04-17 13:51:25 -0600572 )));
573 }
574
575 if serial_params.console {
Jakub Staronb6515a92019-06-05 15:18:25 -0700576 for params in cfg.serial_parameters.values() {
Trent Begin17ccaad2019-04-17 13:51:25 -0600577 if params.console {
578 return Err(argument::Error::TooManyArguments(format!(
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700579 "{} device {} already set as console",
580 params.hardware, params.num,
581 )));
582 }
583 }
584 }
585
586 if serial_params.earlycon {
587 // Only SerialHardware::Serial supports earlycon= currently.
588 match serial_params.hardware {
589 SerialHardware::Serial => {}
590 _ => {
591 return Err(argument::Error::InvalidValue {
592 value: serial_params.hardware.to_string().to_owned(),
593 expected: String::from("earlycon not supported for hardware"),
594 });
595 }
596 }
597 for params in cfg.serial_parameters.values() {
598 if params.earlycon {
599 return Err(argument::Error::TooManyArguments(format!(
600 "{} device {} already set as earlycon",
601 params.hardware, params.num,
Trent Begin17ccaad2019-04-17 13:51:25 -0600602 )));
603 }
604 }
605 }
606
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700607 if serial_params.stdin {
608 if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) {
609 return Err(argument::Error::TooManyArguments(format!(
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700610 "{} device {} already connected to standard input",
611 previous_stdin.hardware, previous_stdin.num,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700612 )));
613 }
614 }
615
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -0700616 cfg.serial_parameters.insert(key, serial_params);
Trent Begin17ccaad2019-04-17 13:51:25 -0600617 }
618 "syslog-tag" => {
619 if cfg.syslog_tag.is_some() {
620 return Err(argument::Error::TooManyArguments(
621 "`syslog-tag` already given".to_owned(),
622 ));
623 }
624 syslog::set_proc_name(value.unwrap());
625 cfg.syslog_tag = Some(value.unwrap().to_owned());
626 }
Daniel Verkamp4b62cd92019-11-08 13:09:27 -0800627 "root" | "rwroot" | "disk" | "rwdisk" => {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800628 let param = value.unwrap();
629 let mut components = param.split(',');
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700630 let read_only = !name.starts_with("rw");
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800631 let disk_path =
632 PathBuf::from(
633 components
634 .next()
635 .ok_or_else(|| argument::Error::InvalidValue {
636 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800637 expected: String::from("missing disk path"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800638 })?,
639 );
Zach Reiznerefe95782017-08-26 18:05:48 -0700640 if !disk_path.exists() {
641 return Err(argument::Error::InvalidValue {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800642 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800643 expected: String::from("this disk path does not exist"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700644 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700645 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700646 if name.ends_with("root") {
Daniel Verkampaac28132018-10-15 14:58:48 -0700647 if cfg.disks.len() >= 26 {
Zach Reizner55a9e502018-10-03 10:22:32 -0700648 return Err(argument::Error::TooManyArguments(
649 "ran out of letters for to assign to root disk".to_owned(),
650 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700651 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700652 cfg.params.push(format!(
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700653 "root=/dev/vd{} {}",
654 char::from(b'a' + cfg.disks.len() as u8),
655 if read_only { "ro" } else { "rw" }
Zach Reizner55a9e502018-10-03 10:22:32 -0700656 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700657 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800658
659 let mut disk = DiskOption {
Zach Reizner55a9e502018-10-03 10:22:32 -0700660 path: disk_path,
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700661 read_only,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800662 sparse: true,
Daniel Verkamp27672232019-12-06 17:26:55 +1100663 block_size: 512,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800664 };
665
666 for opt in components {
667 let mut o = opt.splitn(2, '=');
668 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
669 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800670 expected: String::from("disk options must not be empty"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800671 })?;
672 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
673 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800674 expected: String::from("disk options must be of the form `kind=value`"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800675 })?;
676
677 match kind {
678 "sparse" => {
679 let sparse = value.parse().map_err(|_| argument::Error::InvalidValue {
680 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800681 expected: String::from("`sparse` must be a boolean"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800682 })?;
683 disk.sparse = sparse;
684 }
Daniel Verkamp27672232019-12-06 17:26:55 +1100685 "block_size" => {
686 let block_size =
687 value.parse().map_err(|_| argument::Error::InvalidValue {
688 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800689 expected: String::from("`block_size` must be an integer"),
Daniel Verkamp27672232019-12-06 17:26:55 +1100690 })?;
691 disk.block_size = block_size;
692 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800693 _ => {
694 return Err(argument::Error::InvalidValue {
695 value: kind.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800696 expected: String::from("unrecognized disk option"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800697 });
698 }
699 }
700 }
701
702 cfg.disks.push(disk);
Zach Reiznerefe95782017-08-26 18:05:48 -0700703 }
Jakub Starona3411ea2019-04-24 10:55:25 -0700704 "pmem-device" | "rw-pmem-device" => {
705 let disk_path = PathBuf::from(value.unwrap());
706 if !disk_path.exists() {
707 return Err(argument::Error::InvalidValue {
708 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800709 expected: String::from("this disk path does not exist"),
Jakub Starona3411ea2019-04-24 10:55:25 -0700710 });
711 }
712
713 cfg.pmem_devices.push(DiskOption {
714 path: disk_path,
715 read_only: !name.starts_with("rw"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800716 sparse: false,
Daniel Verkamp27672232019-12-06 17:26:55 +1100717 block_size: sys_util::pagesize() as u32,
Jakub Starona3411ea2019-04-24 10:55:25 -0700718 });
719 }
Kansho Nishida282115b2019-12-18 13:13:14 +0900720 "pstore" => {
721 if cfg.pstore.is_some() {
722 return Err(argument::Error::TooManyArguments(
723 "`pstore` already given".to_owned(),
724 ));
725 }
726
727 let value = value.unwrap();
728 let components: Vec<&str> = value.split(',').collect();
729 if components.len() != 2 {
730 return Err(argument::Error::InvalidValue {
731 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800732 expected: String::from(
733 "pstore must have exactly 2 components: path=<path>,size=<size>",
734 ),
Kansho Nishida282115b2019-12-18 13:13:14 +0900735 });
736 }
737 cfg.pstore = Some(Pstore {
738 path: {
739 if components[0].len() <= 5 || !components[0].starts_with("path=") {
740 return Err(argument::Error::InvalidValue {
741 value: components[0].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800742 expected: String::from("pstore path must follow with `path=`"),
Kansho Nishida282115b2019-12-18 13:13:14 +0900743 });
744 };
745 PathBuf::from(&components[0][5..])
746 },
747 size: {
748 if components[1].len() <= 5 || !components[1].starts_with("size=") {
749 return Err(argument::Error::InvalidValue {
750 value: components[1].to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800751 expected: String::from("pstore size must follow with `size=`"),
Kansho Nishida282115b2019-12-18 13:13:14 +0900752 });
753 };
754 components[1][5..]
755 .parse()
756 .map_err(|_| argument::Error::InvalidValue {
757 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800758 expected: String::from("pstore size must be an integer"),
Kansho Nishida282115b2019-12-18 13:13:14 +0900759 })?
760 },
761 });
762 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700763 "host_ip" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700764 if cfg.host_ip.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700765 return Err(argument::Error::TooManyArguments(
766 "`host_ip` already given".to_owned(),
767 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700768 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700769 cfg.host_ip =
Zach Reizner55a9e502018-10-03 10:22:32 -0700770 Some(
771 value
772 .unwrap()
773 .parse()
774 .map_err(|_| argument::Error::InvalidValue {
775 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800776 expected: String::from("`host_ip` needs to be in the form \"x.x.x.x\""),
Zach Reizner55a9e502018-10-03 10:22:32 -0700777 })?,
778 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700779 }
780 "netmask" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700781 if cfg.netmask.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700782 return Err(argument::Error::TooManyArguments(
783 "`netmask` already given".to_owned(),
784 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700785 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700786 cfg.netmask =
Zach Reizner55a9e502018-10-03 10:22:32 -0700787 Some(
788 value
789 .unwrap()
790 .parse()
791 .map_err(|_| argument::Error::InvalidValue {
792 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800793 expected: String::from("`netmask` needs to be in the form \"x.x.x.x\""),
Zach Reizner55a9e502018-10-03 10:22:32 -0700794 })?,
795 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700796 }
797 "mac" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700798 if cfg.mac_address.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700799 return Err(argument::Error::TooManyArguments(
800 "`mac` already given".to_owned(),
801 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700802 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700803 cfg.mac_address =
Zach Reizner55a9e502018-10-03 10:22:32 -0700804 Some(
805 value
806 .unwrap()
807 .parse()
808 .map_err(|_| argument::Error::InvalidValue {
809 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800810 expected: String::from(
811 "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
812 ),
Zach Reizner55a9e502018-10-03 10:22:32 -0700813 })?,
814 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700815 }
Xiong Zhang773c7072020-03-20 10:39:55 +0800816 "net-vq-pairs" => {
817 if cfg.net_vq_pairs.is_some() {
818 return Err(argument::Error::TooManyArguments(
819 "`net-vq-pairs` already given".to_owned(),
820 ));
821 }
822 cfg.net_vq_pairs =
823 Some(
824 value
825 .unwrap()
826 .parse()
827 .map_err(|_| argument::Error::InvalidValue {
828 value: value.unwrap().to_owned(),
829 expected: String::from(
830 "this value for `net-vq-pairs` needs to be integer",
831 ),
832 })?,
833 )
834 }
835
Stephen Barber28a5a612017-10-20 17:15:30 -0700836 "wayland-sock" => {
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900837 let mut components = value.unwrap().split(',');
838 let path =
839 PathBuf::from(
840 components
841 .next()
842 .ok_or_else(|| argument::Error::InvalidValue {
843 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800844 expected: String::from("missing socket path"),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900845 })?,
846 );
847 let mut name = "";
848 for c in components {
849 let mut kv = c.splitn(2, '=');
850 let (kind, value) = match (kv.next(), kv.next()) {
851 (Some(kind), Some(value)) => (kind, value),
852 _ => {
853 return Err(argument::Error::InvalidValue {
854 value: c.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800855 expected: String::from("option must be of the form `kind=value`"),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900856 })
857 }
858 };
859 match kind {
860 "name" => name = value,
861 _ => {
862 return Err(argument::Error::InvalidValue {
863 value: kind.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800864 expected: String::from("unrecognized option"),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900865 })
866 }
867 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700868 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900869 if cfg.wayland_socket_paths.contains_key(name) {
870 return Err(argument::Error::TooManyArguments(format!(
871 "wayland socket name already used: '{}'",
872 name
873 )));
Stephen Barber28a5a612017-10-20 17:15:30 -0700874 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900875 cfg.wayland_socket_paths.insert(name.to_string(), path);
Stephen Barber28a5a612017-10-20 17:15:30 -0700876 }
David Reveman52ba4e52018-04-22 21:42:09 -0400877 #[cfg(feature = "wl-dmabuf")]
Daniel Verkampaac28132018-10-15 14:58:48 -0700878 "wayland-dmabuf" => cfg.wayland_dmabuf = true,
Zach Reizner0f2cfb02019-06-19 17:46:03 -0700879 "x-display" => {
880 if cfg.x_display.is_some() {
881 return Err(argument::Error::TooManyArguments(
882 "`x-display` already given".to_owned(),
883 ));
884 }
885 cfg.x_display = Some(value.unwrap().to_owned());
886 }
Zach Reizner65b98f12019-11-22 17:34:58 -0800887 "display-window-keyboard" => {
888 cfg.display_window_keyboard = true;
889 }
890 "display-window-mouse" => {
891 cfg.display_window_mouse = true;
892 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700893 "socket" => {
894 if cfg.socket_path.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700895 return Err(argument::Error::TooManyArguments(
896 "`socket` already given".to_owned(),
897 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700898 }
899 let mut socket_path = PathBuf::from(value.unwrap());
900 if socket_path.is_dir() {
901 socket_path.push(format!("crosvm-{}.sock", getpid()));
902 }
903 if socket_path.exists() {
904 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700905 value: socket_path.to_string_lossy().into_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800906 expected: String::from("this socket path already exists"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700907 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700908 }
909 cfg.socket_path = Some(socket_path);
910 }
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700911 "disable-sandbox" => {
Lepton Wu9105e9f2019-03-14 11:38:31 -0700912 cfg.sandbox = false;
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700913 }
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700914 "cid" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700915 if cfg.cid.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700916 return Err(argument::Error::TooManyArguments(
917 "`cid` alread given".to_owned(),
918 ));
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700919 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700920 cfg.cid = Some(
921 value
922 .unwrap()
923 .parse()
924 .map_err(|_| argument::Error::InvalidValue {
925 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800926 expected: String::from("this value for `cid` must be an unsigned integer"),
Daniel Verkampaac28132018-10-15 14:58:48 -0700927 })?,
928 );
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700929 }
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700930 "shared-dir" => {
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900931 // This is formatted as multiple fields, each separated by ":". The first 2 fields are
932 // fixed (src:tag). The rest may appear in any order:
933 //
934 // * type=TYPE - must be one of "p9" or "fs" (default: p9)
935 // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
936 // (default: "0 <current euid> 1")
937 // * gidmap=GIDMAP - a gid map in the same format as uidmap
938 // (default: "0 <current egid> 1")
939 // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
940 // and directory contents should be considered valid (default: 5)
941 // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
942 // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700943 let param = value.unwrap();
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900944 let mut components = param.split(':');
Zach Reizner55a9e502018-10-03 10:22:32 -0700945 let src =
946 PathBuf::from(
947 components
948 .next()
949 .ok_or_else(|| argument::Error::InvalidValue {
950 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800951 expected: String::from("missing source path for `shared-dir`"),
Zach Reizner55a9e502018-10-03 10:22:32 -0700952 })?,
953 );
954 let tag = components
955 .next()
956 .ok_or_else(|| argument::Error::InvalidValue {
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700957 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800958 expected: String::from("missing tag for `shared-dir`"),
David Tolnay2bac1e72018-12-12 14:33:42 -0800959 })?
960 .to_owned();
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700961
962 if !src.is_dir() {
963 return Err(argument::Error::InvalidValue {
964 value: param.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800965 expected: String::from("source path for `shared-dir` must be a directory"),
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700966 });
967 }
968
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900969 let mut shared_dir = SharedDir {
970 src,
971 tag,
972 ..Default::default()
973 };
974 for opt in components {
975 let mut o = opt.splitn(2, '=');
976 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
977 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800978 expected: String::from("`shared-dir` options must not be empty"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900979 })?;
980 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
981 value: opt.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800982 expected: String::from("`shared-dir` options must be of the form `kind=value`"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900983 })?;
984
985 match kind {
986 "type" => {
987 shared_dir.kind =
988 value.parse().map_err(|_| argument::Error::InvalidValue {
989 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800990 expected: String::from("`type` must be one of `fs` or `9p`"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900991 })?
992 }
993 "uidmap" => shared_dir.uid_map = value.into(),
994 "gidmap" => shared_dir.gid_map = value.into(),
995 "timeout" => {
996 let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
997 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +0800998 expected: String::from("`timeout` must be an integer"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900999 })?;
1000
1001 let dur = Duration::from_secs(seconds);
1002 shared_dir.cfg.entry_timeout = dur.clone();
1003 shared_dir.cfg.attr_timeout = dur;
1004 }
1005 "cache" => {
1006 let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
1007 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001008 expected: String::from(
1009 "`cache` must be one of `never`, `always`, or `auto`",
1010 ),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001011 })?;
1012 shared_dir.cfg.cache_policy = policy;
1013 }
1014 "writeback" => {
1015 let writeback =
1016 value.parse().map_err(|_| argument::Error::InvalidValue {
1017 value: value.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001018 expected: String::from("`writeback` must be a boolean"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001019 })?;
1020 shared_dir.cfg.writeback = writeback;
1021 }
1022 _ => {
1023 return Err(argument::Error::InvalidValue {
1024 value: kind.to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001025 expected: String::from("unrecognized option for `shared-dir`"),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001026 })
1027 }
1028 }
1029 }
1030 cfg.shared_dirs.push(shared_dir);
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001031 }
Dylan Reide026ef02017-10-02 19:03:52 -07001032 "seccomp-policy-dir" => {
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001033 // `value` is Some because we are in this match so it's safe to unwrap.
Daniel Verkampaac28132018-10-15 14:58:48 -07001034 cfg.seccomp_policy_dir = PathBuf::from(value.unwrap());
Zach Reizner55a9e502018-10-03 10:22:32 -07001035 }
Zach Reizner44863792019-06-26 14:22:08 -07001036 "seccomp-log-failures" => {
Matt Delco45caf912019-11-13 08:11:09 -08001037 // A side-effect of this flag is to force the use of .policy files
1038 // instead of .bpf files (.bpf files are expected and assumed to be
1039 // compiled to fail an unpermitted action with "trap").
1040 // Normally crosvm will first attempt to use a .bpf file, and if
1041 // not present it will then try to use a .policy file. It's up
1042 // to the build to decide which of these files is present for
1043 // crosvm to use (for CrOS the build will use .bpf files for
1044 // x64 builds and .policy files for arm/arm64 builds).
1045 //
1046 // This flag will likely work as expected for builds that use
1047 // .policy files. For builds that only use .bpf files the initial
1048 // result when using this flag is likely to be a file-not-found
1049 // error (since the .policy files are not present).
1050 // For .bpf builds you can either 1) manually add the .policy files,
1051 // or 2) do not use this command-line parameter and instead
1052 // temporarily change the build by passing "log" rather than
1053 // "trap" as the "--default-action" to compile_seccomp_policy.py.
Zach Reizner44863792019-06-26 14:22:08 -07001054 cfg.seccomp_log_failures = true;
1055 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001056 "plugin" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001057 if cfg.executable_path.is_some() {
1058 return Err(argument::Error::TooManyArguments(format!(
1059 "A VM executable was already specified: {:?}",
1060 cfg.executable_path
1061 )));
Zach Reizner8864cb02018-01-16 17:59:03 -08001062 }
Zach Reiznercc30d582018-01-23 21:16:42 -08001063 let plugin = PathBuf::from(value.unwrap().to_owned());
1064 if plugin.is_relative() {
1065 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -07001066 value: plugin.to_string_lossy().into_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001067 expected: String::from("the plugin path must be an absolute path"),
Zach Reizner55a9e502018-10-03 10:22:32 -07001068 });
Zach Reiznercc30d582018-01-23 21:16:42 -08001069 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001070 cfg.executable_path = Some(Executable::Plugin(plugin));
Zach Reizner55a9e502018-10-03 10:22:32 -07001071 }
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001072 "plugin-root" => {
1073 cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned()));
Zach Reizner55a9e502018-10-03 10:22:32 -07001074 }
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001075 "plugin-mount" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -08001076 let mount = parse_plugin_mount_option(value.unwrap())?;
1077 cfg.plugin_mounts.push(mount);
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001078 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001079 "plugin-mount-file" => {
1080 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
1081 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001082 expected: String::from("unable to open `plugin-mount-file` file"),
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001083 })?;
1084 let reader = BufReader::new(file);
1085 for l in reader.lines() {
1086 let line = l.unwrap();
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001087 let trimmed_line = line.splitn(2, '#').next().unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001088 if !trimmed_line.is_empty() {
1089 let mount = parse_plugin_mount_option(trimmed_line)?;
1090 cfg.plugin_mounts.push(mount);
1091 }
1092 }
1093 }
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001094 "plugin-gid-map" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -08001095 let map = parse_plugin_gid_map_option(value.unwrap())?;
1096 cfg.plugin_gid_maps.push(map);
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001097 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001098 "plugin-gid-map-file" => {
1099 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
1100 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001101 expected: String::from("unable to open `plugin-gid-map-file` file"),
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001102 })?;
1103 let reader = BufReader::new(file);
1104 for l in reader.lines() {
1105 let line = l.unwrap();
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001106 let trimmed_line = line.splitn(2, '#').next().unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001107 if !trimmed_line.is_empty() {
1108 let map = parse_plugin_gid_map_option(trimmed_line)?;
1109 cfg.plugin_gid_maps.push(map);
1110 }
1111 }
1112 }
Daniel Verkampaac28132018-10-15 14:58:48 -07001113 "vhost-net" => cfg.vhost_net = true,
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001114 "tap-fd" => {
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001115 cfg.tap_fd.push(
1116 value
1117 .unwrap()
1118 .parse()
1119 .map_err(|_| argument::Error::InvalidValue {
1120 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001121 expected: String::from(
1122 "this value for `tap-fd` must be an unsigned integer",
1123 ),
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001124 })?,
1125 );
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001126 }
Jason Macnakcc7070b2019-11-06 14:48:12 -08001127 #[cfg(feature = "gpu")]
Zach Reizner3a8100a2017-09-13 19:15:43 -07001128 "gpu" => {
Jason Macnakcc7070b2019-11-06 14:48:12 -08001129 let params = parse_gpu_options(value)?;
1130 cfg.gpu_parameters = Some(params);
Zach Reizner3a8100a2017-09-13 19:15:43 -07001131 }
David Tolnay43f8e212019-02-13 17:28:16 -08001132 "software-tpm" => {
1133 cfg.software_tpm = true;
1134 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001135 "single-touch" => {
1136 if cfg.virtio_single_touch.is_some() {
1137 return Err(argument::Error::TooManyArguments(
1138 "`single-touch` already given".to_owned(),
1139 ));
1140 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001141 let mut it = value.unwrap().split(':');
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001142
1143 let mut single_touch_spec =
1144 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
1145 if let Some(width) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001146 single_touch_spec.set_width(width.trim().parse().unwrap());
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001147 }
1148 if let Some(height) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001149 single_touch_spec.set_height(height.trim().parse().unwrap());
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001150 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001151 cfg.virtio_single_touch = Some(single_touch_spec);
1152 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001153 "trackpad" => {
1154 if cfg.virtio_trackpad.is_some() {
1155 return Err(argument::Error::TooManyArguments(
1156 "`trackpad` already given".to_owned(),
1157 ));
1158 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001159 let mut it = value.unwrap().split(':');
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001160
1161 let mut trackpad_spec =
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001162 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001163 if let Some(width) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001164 trackpad_spec.set_width(width.trim().parse().unwrap());
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001165 }
1166 if let Some(height) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001167 trackpad_spec.set_height(height.trim().parse().unwrap());
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001168 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001169 cfg.virtio_trackpad = Some(trackpad_spec);
1170 }
1171 "mouse" => {
1172 if cfg.virtio_mouse.is_some() {
1173 return Err(argument::Error::TooManyArguments(
1174 "`mouse` already given".to_owned(),
1175 ));
1176 }
1177 cfg.virtio_mouse = Some(PathBuf::from(value.unwrap().to_owned()));
1178 }
1179 "keyboard" => {
1180 if cfg.virtio_keyboard.is_some() {
1181 return Err(argument::Error::TooManyArguments(
1182 "`keyboard` already given".to_owned(),
1183 ));
1184 }
1185 cfg.virtio_keyboard = Some(PathBuf::from(value.unwrap().to_owned()));
1186 }
1187 "evdev" => {
1188 let dev_path = PathBuf::from(value.unwrap());
1189 if !dev_path.exists() {
1190 return Err(argument::Error::InvalidValue {
1191 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001192 expected: String::from("this input device path does not exist"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001193 });
1194 }
1195 cfg.virtio_input_evdevs.push(dev_path);
1196 }
Miriam Zimmerman26ac9282019-01-29 21:21:48 -08001197 "split-irqchip" => {
1198 cfg.split_irqchip = true;
1199 }
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001200 "initrd" => {
1201 cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
1202 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001203 "bios" => {
1204 if cfg.executable_path.is_some() {
1205 return Err(argument::Error::TooManyArguments(format!(
1206 "A VM executable was already specified: {:?}",
1207 cfg.executable_path
1208 )));
1209 }
1210 cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
1211 }
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001212 "vfio" => {
1213 let vfio_path = PathBuf::from(value.unwrap());
1214 if !vfio_path.exists() {
1215 return Err(argument::Error::InvalidValue {
1216 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001217 expected: String::from("the vfio path does not exist"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001218 });
1219 }
1220 if !vfio_path.is_dir() {
1221 return Err(argument::Error::InvalidValue {
1222 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001223 expected: String::from("the vfio path should be directory"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001224 });
1225 }
1226
Xiong Zhang8bb4faa2019-11-12 10:06:13 +08001227 cfg.vfio.push(vfio_path);
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001228 }
1229
Zach Reiznerefe95782017-08-26 18:05:48 -07001230 "help" => return Err(argument::Error::PrintHelp),
1231 _ => unreachable!(),
1232 }
1233 Ok(())
1234}
1235
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001236fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Error> {
1237 if cfg.executable_path.is_none() {
1238 return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
1239 }
1240 if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
1241 if cfg.host_ip.is_none() {
1242 return Err(argument::Error::ExpectedArgument(
1243 "`host_ip` missing from network config".to_owned(),
1244 ));
1245 }
1246 if cfg.netmask.is_none() {
1247 return Err(argument::Error::ExpectedArgument(
1248 "`netmask` missing from network config".to_owned(),
1249 ));
1250 }
1251 if cfg.mac_address.is_none() {
1252 return Err(argument::Error::ExpectedArgument(
1253 "`mac` missing from network config".to_owned(),
1254 ));
1255 }
1256 }
1257 if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
1258 return Err(argument::Error::ExpectedArgument(
1259 "`plugin-root` requires `plugin`".to_owned(),
1260 ));
1261 }
1262 #[cfg(feature = "gpu")]
1263 {
1264 if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() {
1265 let (width, height) = (gpu_parameters.display_width, gpu_parameters.display_height);
1266 if let Some(virtio_single_touch) = cfg.virtio_single_touch.as_mut() {
1267 virtio_single_touch.set_default_size(width, height);
1268 }
1269 }
1270 }
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001271 set_default_serial_parameters(&mut cfg.serial_parameters);
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001272 Ok(())
1273}
1274
Dylan Reidbfba9932018-02-05 15:51:59 -08001275fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reiznerefe95782017-08-26 18:05:48 -07001276 let arguments =
1277 &[Argument::positional("KERNEL", "bzImage of kernel to run"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -08001278 Argument::value("android-fstab", "PATH", "Path to Android fstab"),
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001279 Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001280 Argument::short_value('p',
1281 "params",
1282 "PARAMS",
Zach Reiznerbb678712018-01-30 18:13:04 -08001283 "Extra kernel or plugin command line arguments. Can be given more than once."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001284 Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Daniel Verkamp107edb32019-04-05 09:58:48 -07001285 Argument::value("cpu-affinity", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on. (e.g. 0,1-3,5) (default: no mask)"),
Zach Reiznerefe95782017-08-26 18:05:48 -07001286 Argument::short_value('m',
1287 "mem",
1288 "N",
1289 "Amount of guest memory in MiB. (default: 256)"),
1290 Argument::short_value('r',
1291 "root",
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001292 "PATH[,key=value[,key=value[,...]]",
1293 "Path to a root disk image followed by optional comma-separated options.
1294 Like `--disk` but adds appropriate kernel command line option.
1295 See --disk for valid options."),
1296 Argument::value("rwroot", "PATH[,key=value[,key=value[,...]]", "Path to a writable root disk image followed by optional comma-separated options.
1297 See --disk for valid options."),
1298 Argument::short_value('d', "disk", "PATH[,key=value[,key=value[,...]]", "Path to a disk image followed by optional comma-separated options.
1299 Valid keys:
Daniel Verkamp27672232019-12-06 17:26:55 +11001300 sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
1301 block_size=BYTES - Set the reported block size of the disk (default: 512)"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001302 Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
1303 See --disk for valid options."),
Jakub Starona3411ea2019-04-24 10:55:25 -07001304 Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
1305 Argument::value("pmem-device", "PATH", "Path to a disk image."),
Kansho Nishida282115b2019-12-18 13:13:14 +09001306 Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file follewed by size."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001307 Argument::value("host_ip",
1308 "IP",
1309 "IP address to assign to host tap interface."),
1310 Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
1311 Argument::value("mac", "MAC", "MAC address for VM."),
Xiong Zhang773c7072020-03-20 10:39:55 +08001312 Argument::value("net-vq-pairs", "N", "virtio net virtual queue paris. (default: 1)"),
Judy Hsiaod5c1e962020-02-04 12:30:01 +08001313 Argument::value("ac97",
1314 "[backend=BACKEND,capture=true,capture_effect=EFFECT]",
1315 "Comma separated key=value pairs for setting up Ac97 devices. Can be given more than once .
1316 Possible key values:
1317 backend=(null, cras) - Where to route the audio device. If not provided, backend will default to null.
1318 `null` for /dev/null, and cras for CRAS server.
1319 capture - Enable audio capture
1320 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 -06001321 Argument::value("serial",
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001322 "type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]",
Jason Macnakcc7070b2019-11-06 14:48:12 -08001323 "Comma separated key=value pairs for setting up serial devices. Can be given more than once.
Trent Begin17ccaad2019-04-17 13:51:25 -06001324 Possible key values:
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001325 type=(stdout,syslog,sink,file) - Where to route the serial device
Daniel Verkampa7b6a1c2020-03-09 13:16:46 -07001326 hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
Trent Begin923bab02019-06-17 13:48:06 -06001327 num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001328 path=PATH - The path to the file to write to when type=file
Iliyan Malchev2c1417b2020-04-14 09:40:41 -07001329 input=PATH - The path to the file to read from when not stdin
Trent Begin17ccaad2019-04-17 13:51:25 -06001330 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 -07001331 earlycon - Use this serial device as the early console. Can only be given once.
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001332 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 -06001333 "),
1334 Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
Zach Reizner0f2cfb02019-06-19 17:46:03 -07001335 Argument::value("x-display", "DISPLAY", "X11 display name to use."),
Zach Reizner65b98f12019-11-22 17:34:58 -08001336 Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
1337 Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +09001338 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 -04001339 #[cfg(feature = "wl-dmabuf")]
1340 Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001341 Argument::short_value('s',
1342 "socket",
1343 "PATH",
1344 "Path to put the control socket. If PATH is a directory, a name will be generated."),
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001345 Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001346 Argument::value("cid", "CID", "Context ID for virtual sockets."),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001347 Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE]",
1348 "Colon-separated options for configuring a directory to be shared with the VM.
1349The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
1350The remaining fields are key=value pairs that may appear in any order. Valid keys are:
1351type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
1352uidmap=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).
1353gidmap=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).
1354cache=(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.
1355timeout=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.
1356writeback=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.
1357"),
Dylan Reide026ef02017-10-02 19:03:52 -07001358 Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
Zach Reizner44863792019-06-26 14:22:08 -07001359 Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
Zach Reizner8864cb02018-01-16 17:59:03 -08001360 #[cfg(feature = "plugin")]
Zach Reiznercc30d582018-01-23 21:16:42 -08001361 Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
Daniel Verkampbd1a0842019-01-08 15:50:34 -08001362 #[cfg(feature = "plugin")]
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001363 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 -08001364 #[cfg(feature = "plugin")]
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001365 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 -08001366 #[cfg(feature = "plugin")]
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001367 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."),
1368 #[cfg(feature = "plugin")]
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001369 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 -08001370 #[cfg(feature = "plugin")]
1371 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 +00001372 Argument::flag("vhost-net", "Use vhost for networking."),
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001373 Argument::value("tap-fd",
1374 "fd",
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001375 "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 -07001376 #[cfg(feature = "gpu")]
Jason Macnakcc7070b2019-11-06 14:48:12 -08001377 Argument::flag_or_value("gpu",
1378 "[width=INT,height=INT]",
1379 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
1380 Possible key values:
Lingfeng Yangddbe8b72020-01-30 10:00:36 -08001381 backend=(2d|3d|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
Jason Macnakcc7070b2019-11-06 14:48:12 -08001382 width=INT - The width of the virtual display connected to the virtio-gpu.
1383 height=INT - The height of the virtual display connected to the virtio-gpu.
Jason Macnakbf195582019-11-20 16:25:49 -08001384 egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering.
1385 glx[=true|=false] - If the virtio-gpu backend should use a GLX context for rendering.
1386 surfaceless[=true|=false] - If the virtio-gpu backend should use a surfaceless context for rendering.
Jason Macnakcc7070b2019-11-06 14:48:12 -08001387 "),
David Tolnay43f8e212019-02-13 17:28:16 -08001388 #[cfg(feature = "tpm")]
1389 Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001390 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 -08001391 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 -08001392 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)."),
1393 Argument::value("mouse", "PATH", "Path to a socket from where to read mouse input events and write status updates to."),
1394 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 -08001395 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1396 Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001397 Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001398 Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
Zach Reiznerefe95782017-08-26 18:05:48 -07001399 Argument::short_flag('h', "help", "Print help message.")];
1400
1401 let mut cfg = Config::default();
Zach Reizner55a9e502018-10-03 10:22:32 -07001402 let match_res = set_arguments(args, &arguments[..], |name, value| {
1403 set_argument(&mut cfg, name, value)
David Tolnay2bac1e72018-12-12 14:33:42 -08001404 })
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001405 .and_then(|_| validate_arguments(&mut cfg));
Zach Reiznerefe95782017-08-26 18:05:48 -07001406
1407 match match_res {
Zach Reizner8864cb02018-01-16 17:59:03 -08001408 #[cfg(feature = "plugin")]
Zach Reizner267f2c82019-07-31 17:07:27 -07001409 Ok(()) if executable_is_plugin(&cfg.executable_path) => {
1410 match crosvm::plugin::run_config(cfg) {
1411 Ok(_) => {
1412 info!("crosvm and plugin have exited normally");
1413 Ok(())
1414 }
1415 Err(e) => {
1416 error!("{}", e);
1417 Err(())
1418 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001419 }
Zach Reizner267f2c82019-07-31 17:07:27 -07001420 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001421 Ok(()) => match linux::run_config(cfg) {
1422 Ok(_) => {
1423 info!("crosvm has exited normally");
1424 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001425 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001426 Err(e) => {
1427 error!("{}", e);
1428 Err(())
1429 }
1430 },
Dylan Reidbfba9932018-02-05 15:51:59 -08001431 Err(argument::Error::PrintHelp) => {
1432 print_help("crosvm run", "KERNEL", &arguments[..]);
1433 Ok(())
1434 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001435 Err(e) => {
Dmitry Torokhov470b1e72020-01-15 12:46:49 -08001436 error!("{}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001437 Err(())
Zach Reizner8864cb02018-01-16 17:59:03 -08001438 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001439 }
1440}
1441
Jingkui Wang100e6e42019-03-08 20:41:57 -08001442fn handle_request(
1443 request: &VmRequest,
1444 args: std::env::Args,
1445) -> std::result::Result<VmResponse, ()> {
1446 let mut return_result = Err(());
Zach Reiznerefe95782017-08-26 18:05:48 -07001447 for socket_path in args {
Zach Reiznera60744b2019-02-13 17:33:32 -08001448 match UnixSeqpacket::connect(&socket_path) {
Zach Reiznerefe95782017-08-26 18:05:48 -07001449 Ok(s) => {
Jakub Starone7c59052019-04-09 12:31:14 -07001450 let socket: VmControlRequestSocket = MsgSocket::new(s);
Zach Reizner78986322019-02-21 20:43:21 -08001451 if let Err(e) = socket.send(request) {
Zach Reizner55a9e502018-10-03 10:22:32 -07001452 error!(
David Tolnayb4bd00f2019-02-12 17:51:26 -08001453 "failed to send request to socket at '{}': {}",
Zach Reizner55a9e502018-10-03 10:22:32 -07001454 socket_path, e
1455 );
Zach Reizner78986322019-02-21 20:43:21 -08001456 return_result = Err(());
1457 continue;
Zach Reiznerefe95782017-08-26 18:05:48 -07001458 }
Zach Reizner78986322019-02-21 20:43:21 -08001459 match socket.recv() {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001460 Ok(response) => return_result = Ok(response),
Zach Reizner78986322019-02-21 20:43:21 -08001461 Err(e) => {
1462 error!(
1463 "failed to send request to socket at2 '{}': {}",
1464 socket_path, e
1465 );
1466 return_result = Err(());
1467 continue;
1468 }
Dylan Reidd4432042017-12-06 18:20:09 -08001469 }
1470 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001471 Err(e) => {
1472 error!("failed to connect to socket at '{}': {}", socket_path, e);
1473 return_result = Err(());
1474 }
Dylan Reidd4432042017-12-06 18:20:09 -08001475 }
1476 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001477
1478 return_result
Dylan Reidd4432042017-12-06 18:20:09 -08001479}
Zach Reiznerefe95782017-08-26 18:05:48 -07001480
Jingkui Wang100e6e42019-03-08 20:41:57 -08001481fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
1482 let response = handle_request(request, args)?;
1483 info!("request response was {}", response);
1484 Ok(())
1485}
1486
Zach Reizner78986322019-02-21 20:43:21 -08001487fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1488 if args.len() == 0 {
1489 print_help("crosvm stop", "VM_SOCKET...", &[]);
1490 println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001491 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001492 }
1493 vms_request(&VmRequest::Exit, args)
1494}
1495
1496fn suspend_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1497 if args.len() == 0 {
1498 print_help("crosvm suspend", "VM_SOCKET...", &[]);
1499 println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001500 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001501 }
1502 vms_request(&VmRequest::Suspend, args)
1503}
1504
1505fn resume_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1506 if args.len() == 0 {
1507 print_help("crosvm resume", "VM_SOCKET...", &[]);
1508 println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001509 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001510 }
1511 vms_request(&VmRequest::Resume, args)
1512}
1513
1514fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
1515 if args.len() < 2 {
1516 print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
1517 println!("Set the ballon size of the crosvm instance to `SIZE` bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001518 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001519 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001520 let num_bytes = match args.next().unwrap().parse::<u64>() {
Zach Reizner78986322019-02-21 20:43:21 -08001521 Ok(n) => n,
1522 Err(_) => {
1523 error!("Failed to parse number of bytes");
1524 return Err(());
1525 }
1526 };
1527
Jakub Staron1f828d72019-04-11 12:49:29 -07001528 let command = BalloonControlCommand::Adjust { num_bytes };
1529 vms_request(&VmRequest::BalloonCommand(command), args)
Zach Reizner78986322019-02-21 20:43:21 -08001530}
1531
Charles William Dicked22f6b2020-04-08 11:05:24 +09001532fn balloon_stats(args: std::env::Args) -> std::result::Result<(), ()> {
1533 if args.len() != 1 {
1534 print_help("crosvm balloon_stats", "VM_SOCKET", &[]);
1535 println!("Prints virtio balloon statistics for a `VM_SOCKET`.");
1536 return Err(());
1537 }
1538 let command = BalloonControlCommand::Stats {};
1539 let request = &VmRequest::BalloonCommand(command);
1540 let response = handle_request(request, args)?;
1541 println!("{}", response);
1542 Ok(())
1543}
1544
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001545fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> {
1546 let arguments = [
1547 Argument::positional("PATH", "where to create the qcow2 image"),
1548 Argument::positional("[SIZE]", "the expanded size of the image"),
1549 Argument::value(
1550 "backing_file",
1551 "path/to/file",
1552 " the file to back the image",
1553 ),
1554 ];
1555 let mut positional_index = 0;
1556 let mut file_path = String::from("");
1557 let mut size: Option<u64> = None;
1558 let mut backing_file: Option<String> = None;
1559 set_arguments(args, &arguments[..], |name, value| {
1560 match (name, positional_index) {
1561 ("", 0) => {
1562 // NAME
1563 positional_index += 1;
1564 file_path = value.unwrap().to_owned();
1565 }
1566 ("", 1) => {
1567 // [SIZE]
1568 positional_index += 1;
1569 size = Some(value.unwrap().parse::<u64>().map_err(|_| {
1570 argument::Error::InvalidValue {
1571 value: value.unwrap().to_owned(),
Judy Hsiao59343052020-03-16 15:58:03 +08001572 expected: String::from("SIZE should be a nonnegative integer"),
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001573 }
1574 })?);
1575 }
1576 ("", _) => {
1577 return Err(argument::Error::TooManyArguments(
1578 "Expected at most 2 positional arguments".to_owned(),
1579 ));
1580 }
1581 ("backing_file", _) => {
1582 backing_file = value.map(|x| x.to_owned());
1583 }
1584 _ => unreachable!(),
1585 };
1586 Ok(())
1587 })
1588 .map_err(|e| {
1589 error!("Unable to parse command line arguments: {}", e);
1590 })?;
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001591 if file_path.is_empty() || !(size.is_some() ^ backing_file.is_some()) {
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001592 print_help("crosvm create_qcow2", "PATH [SIZE]", &arguments);
1593 println!(
1594 "Create a new QCOW2 image at `PATH` of either the specified `SIZE` in bytes or
1595with a '--backing_file'."
1596 );
Jianxun Zhang56497d22019-03-04 14:38:24 -08001597 return Err(());
Dylan Reid2dcb6322018-07-13 10:42:48 -07001598 }
Dylan Reid2dcb6322018-07-13 10:42:48 -07001599
1600 let file = OpenOptions::new()
1601 .create(true)
1602 .read(true)
1603 .write(true)
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001604 .truncate(true)
Dylan Reid2dcb6322018-07-13 10:42:48 -07001605 .open(&file_path)
1606 .map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001607 error!("Failed opening qcow file at '{}': {}", file_path, e);
Dylan Reid2dcb6322018-07-13 10:42:48 -07001608 })?;
1609
A. Cody Schuffelen9ca60392019-12-23 18:27:11 -08001610 match (size, backing_file) {
1611 (Some(size), None) => QcowFile::new(file, size).map_err(|e| {
1612 error!("Failed to create qcow file at '{}': {}", file_path, e);
1613 })?,
1614 (None, Some(backing_file)) => {
1615 QcowFile::new_from_backing(file, &backing_file).map_err(|e| {
1616 error!("Failed to create qcow file at '{}': {}", file_path, e);
1617 })?
1618 }
1619 _ => unreachable!(),
1620 };
Dylan Reid2dcb6322018-07-13 10:42:48 -07001621 Ok(())
1622}
1623
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001624fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
1625 if args.len() < 2 {
1626 print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
1627 println!("Manage attached virtual disk devices.");
1628 println!("Subcommands:");
1629 println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001630 return Err(());
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001631 }
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001632 let subcommand: &str = &args.next().unwrap();
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001633
1634 let request = match subcommand {
1635 "resize" => {
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001636 let disk_index = match args.next().unwrap().parse::<usize>() {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001637 Ok(n) => n,
1638 Err(_) => {
1639 error!("Failed to parse disk index");
1640 return Err(());
1641 }
1642 };
1643
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001644 let new_size = match args.next().unwrap().parse::<u64>() {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001645 Ok(n) => n,
1646 Err(_) => {
1647 error!("Failed to parse disk size");
1648 return Err(());
1649 }
1650 };
1651
Jakub Staronecf81e02019-04-11 11:43:39 -07001652 VmRequest::DiskCommand {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001653 disk_index,
Jakub Staronecf81e02019-04-11 11:43:39 -07001654 command: DiskControlCommand::Resize { new_size },
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001655 }
1656 }
1657 _ => {
1658 error!("Unknown disk subcommand '{}'", subcommand);
1659 return Err(());
1660 }
1661 };
1662
Zach Reizner78986322019-02-21 20:43:21 -08001663 vms_request(&request, args)
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001664}
1665
Jingkui Wang100e6e42019-03-08 20:41:57 -08001666enum ModifyUsbError {
1667 ArgMissing(&'static str),
1668 ArgParse(&'static str, String),
1669 ArgParseInt(&'static str, String, ParseIntError),
1670 FailedFdValidate(sys_util::Error),
1671 PathDoesNotExist(PathBuf),
1672 SocketFailed,
1673 UnexpectedResponse(VmResponse),
1674 UnknownCommand(String),
1675 UsbControl(UsbControlResult),
1676}
1677
1678impl fmt::Display for ModifyUsbError {
1679 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1680 use self::ModifyUsbError::*;
1681
1682 match self {
1683 ArgMissing(a) => write!(f, "argument missing: {}", a),
1684 ArgParse(name, value) => {
1685 write!(f, "failed to parse argument {} value `{}`", name, value)
1686 }
1687 ArgParseInt(name, value, e) => write!(
1688 f,
1689 "failed to parse integer argument {} value `{}`: {}",
1690 name, value, e
1691 ),
1692 FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
1693 PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
1694 SocketFailed => write!(f, "socket failed"),
1695 UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
1696 UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
1697 UsbControl(e) => write!(f, "{}", e),
1698 }
1699 }
1700}
1701
1702type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
1703
1704fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
1705 debug!("parse_bus_id_addr: {}", v);
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001706 let mut ids = v.split(':');
Jingkui Wang100e6e42019-03-08 20:41:57 -08001707 match (ids.next(), ids.next(), ids.next(), ids.next()) {
1708 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
1709 let bus_id = bus_id
1710 .parse::<u8>()
1711 .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
1712 let addr = addr
1713 .parse::<u8>()
1714 .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
1715 let vid = u16::from_str_radix(&vid, 16)
1716 .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
1717 let pid = u16::from_str_radix(&pid, 16)
1718 .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
1719 Ok((bus_id, addr, vid, pid))
1720 }
1721 _ => Err(ModifyUsbError::ArgParse(
1722 "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
1723 v.to_owned(),
1724 )),
1725 }
1726}
1727
1728fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> {
1729 if !path.exists() {
1730 return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
1731 }
1732 let raw_fd = path
1733 .file_name()
1734 .and_then(|fd_osstr| fd_osstr.to_str())
1735 .map_or(
1736 Err(ModifyUsbError::ArgParse(
1737 "USB_DEVICE_PATH",
1738 path.to_string_lossy().into_owned(),
1739 )),
1740 |fd_str| {
1741 fd_str.parse::<libc::c_int>().map_err(|e| {
1742 ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
1743 })
1744 },
1745 )?;
David Tolnay5fb3f512019-04-12 19:22:33 -07001746 validate_raw_fd(raw_fd).map_err(ModifyUsbError::FailedFdValidate)
Jingkui Wang100e6e42019-03-08 20:41:57 -08001747}
1748
1749fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1750 let val = args
1751 .next()
1752 .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
1753 let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
1754 let dev_path = PathBuf::from(
1755 args.next()
1756 .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
1757 );
1758 let usb_file: Option<File> = if dev_path == Path::new("-") {
1759 None
1760 } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
1761 // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
1762 // Safe because we will validate |raw_fd|.
1763 Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) })
1764 } else {
1765 Some(
1766 OpenOptions::new()
1767 .read(true)
1768 .write(true)
1769 .open(&dev_path)
1770 .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?,
1771 )
1772 };
1773
1774 let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
1775 bus,
1776 addr,
1777 vid,
1778 pid,
David Tolnay5fb3f512019-04-12 19:22:33 -07001779 fd: usb_file.map(MaybeOwnedFd::Owned),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001780 });
1781 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1782 match response {
1783 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1784 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1785 }
1786}
1787
1788fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1789 let port: u8 = args
1790 .next()
1791 .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
1792 p.parse::<u8>()
1793 .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
1794 })?;
1795 let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
1796 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1797 match response {
1798 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1799 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1800 }
1801}
1802
Zach Reizneraff94ca2019-03-18 20:58:31 -07001803fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1804 let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
1805 for (index, port) in ports.iter_mut().enumerate() {
1806 *port = index as u8
1807 }
1808 let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
Jingkui Wang100e6e42019-03-08 20:41:57 -08001809 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1810 match response {
1811 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1812 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1813 }
1814}
1815
1816fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reizneraff94ca2019-03-18 20:58:31 -07001817 if args.len() < 2 {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001818 print_help("crosvm usb",
Zach Reizneraff94ca2019-03-18 20:58:31 -07001819 "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
Jingkui Wang100e6e42019-03-08 20:41:57 -08001820 return Err(());
1821 }
1822
1823 // This unwrap will not panic because of the above length check.
1824 let command = args.next().unwrap();
1825 let result = match command.as_ref() {
1826 "attach" => usb_attach(args),
1827 "detach" => usb_detach(args),
1828 "list" => usb_list(args),
1829 other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
1830 };
1831 match result {
1832 Ok(response) => {
1833 println!("{}", response);
1834 Ok(())
1835 }
1836 Err(e) => {
1837 println!("error {}", e);
1838 Err(())
1839 }
1840 }
1841}
1842
Zach Reiznerefe95782017-08-26 18:05:48 -07001843fn print_usage() {
1844 print_help("crosvm", "[stop|run]", &[]);
1845 println!("Commands:");
1846 println!(" stop - Stops crosvm instances via their control sockets.");
1847 println!(" run - Start a new crosvm instance.");
Dylan Reid2dcb6322018-07-13 10:42:48 -07001848 println!(" create_qcow2 - Create a new qcow2 disk image file.");
Jingkui Wang100e6e42019-03-08 20:41:57 -08001849 println!(" disk - Manage attached virtual disk devices.");
1850 println!(" usb - Manage attached virtual USB devices.");
Yi Sun54305cd2020-01-04 00:19:37 +08001851 println!(" version - Show package version.");
1852}
1853
1854fn pkg_version() -> std::result::Result<(), ()> {
1855 const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
1856 const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
1857
1858 print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
1859 match PKG_VERSION {
1860 Some(v) => println!("-{}", v),
Keiichi Watanabe275c1ef2020-04-10 21:49:10 +09001861 None => println!(),
Yi Sun54305cd2020-01-04 00:19:37 +08001862 }
1863 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001864}
1865
Dylan Reidbfba9932018-02-05 15:51:59 -08001866fn crosvm_main() -> std::result::Result<(), ()> {
Stephen Barber56fbf092017-06-29 16:12:14 -07001867 if let Err(e) = syslog::init() {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001868 println!("failed to initialize syslog: {}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001869 return Err(());
Stephen Barber56fbf092017-06-29 16:12:14 -07001870 }
Zach Reizner639d9672017-05-01 17:57:18 -07001871
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08001872 panic_hook::set_panic_hook();
1873
Zach Reiznerefe95782017-08-26 18:05:48 -07001874 let mut args = std::env::args();
1875 if args.next().is_none() {
1876 error!("expected executable name");
Dylan Reidbfba9932018-02-05 15:51:59 -08001877 return Err(());
Zach Reizner639d9672017-05-01 17:57:18 -07001878 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001879
Zach Reizner8864cb02018-01-16 17:59:03 -08001880 // Past this point, usage of exit is in danger of leaking zombie processes.
Dylan Reidbfba9932018-02-05 15:51:59 -08001881 let ret = match args.next().as_ref().map(|a| a.as_ref()) {
1882 None => {
1883 print_usage();
1884 Ok(())
1885 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001886 Some("stop") => stop_vms(args),
Zach Reizner6a8fdd92019-01-16 14:38:41 -08001887 Some("suspend") => suspend_vms(args),
1888 Some("resume") => resume_vms(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07001889 Some("run") => run_vm(args),
1890 Some("balloon") => balloon_vms(args),
Charles William Dicked22f6b2020-04-08 11:05:24 +09001891 Some("balloon_stats") => balloon_stats(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07001892 Some("create_qcow2") => create_qcow2(args),
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001893 Some("disk") => disk_cmd(args),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001894 Some("usb") => modify_usb(args),
Yi Sun54305cd2020-01-04 00:19:37 +08001895 Some("version") => pkg_version(),
Zach Reiznerefe95782017-08-26 18:05:48 -07001896 Some(c) => {
1897 println!("invalid subcommand: {:?}", c);
1898 print_usage();
Dylan Reidbfba9932018-02-05 15:51:59 -08001899 Err(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001900 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001901 };
Zach Reiznerefe95782017-08-26 18:05:48 -07001902
1903 // Reap exit status from any child device processes. At this point, all devices should have been
1904 // dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
1905 // take some time for the processes to shut down.
1906 if !wait_all_children() {
1907 // We gave them a chance, and it's too late.
1908 warn!("not all child processes have exited; sending SIGKILL");
1909 if let Err(e) = kill_process_group() {
1910 // We're now at the mercy of the OS to clean up after us.
David Tolnayb4bd00f2019-02-12 17:51:26 -08001911 warn!("unable to kill all child processes: {}", e);
Zach Reiznerefe95782017-08-26 18:05:48 -07001912 }
1913 }
1914
1915 // WARNING: Any code added after this point is not guaranteed to run
1916 // since we may forcibly kill this process (and its children) above.
Dylan Reidbfba9932018-02-05 15:51:59 -08001917 ret
1918}
1919
1920fn main() {
1921 std::process::exit(if crosvm_main().is_ok() { 0 } else { 1 });
Zach Reizner639d9672017-05-01 17:57:18 -07001922}
Daniel Verkamp107edb32019-04-05 09:58:48 -07001923
1924#[cfg(test)]
1925mod tests {
1926 use super::*;
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001927 use crosvm::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH};
Daniel Verkamp107edb32019-04-05 09:58:48 -07001928
1929 #[test]
1930 fn parse_cpu_set_single() {
1931 assert_eq!(parse_cpu_set("123").expect("parse failed"), vec![123]);
1932 }
1933
1934 #[test]
1935 fn parse_cpu_set_list() {
1936 assert_eq!(
1937 parse_cpu_set("0,1,2,3").expect("parse failed"),
1938 vec![0, 1, 2, 3]
1939 );
1940 }
1941
1942 #[test]
1943 fn parse_cpu_set_range() {
1944 assert_eq!(
1945 parse_cpu_set("0-3").expect("parse failed"),
1946 vec![0, 1, 2, 3]
1947 );
1948 }
1949
1950 #[test]
1951 fn parse_cpu_set_list_of_ranges() {
1952 assert_eq!(
1953 parse_cpu_set("3-4,7-9,18").expect("parse failed"),
1954 vec![3, 4, 7, 8, 9, 18]
1955 );
1956 }
1957
1958 #[test]
1959 fn parse_cpu_set_repeated() {
1960 // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
1961 assert_eq!(parse_cpu_set("1,1,1").expect("parse failed"), vec![1, 1, 1]);
1962 }
1963
1964 #[test]
1965 fn parse_cpu_set_negative() {
1966 // Negative CPU numbers are not allowed.
1967 parse_cpu_set("-3").expect_err("parse should have failed");
1968 }
1969
1970 #[test]
1971 fn parse_cpu_set_reverse_range() {
1972 // Ranges must be from low to high.
1973 parse_cpu_set("5-2").expect_err("parse should have failed");
1974 }
1975
1976 #[test]
1977 fn parse_cpu_set_open_range() {
1978 parse_cpu_set("3-").expect_err("parse should have failed");
1979 }
1980
1981 #[test]
1982 fn parse_cpu_set_extra_comma() {
1983 parse_cpu_set("0,1,2,").expect_err("parse should have failed");
1984 }
Trent Begin17ccaad2019-04-17 13:51:25 -06001985
1986 #[test]
Judy Hsiaod5c1e962020-02-04 12:30:01 +08001987 fn parse_ac97_vaild() {
1988 parse_ac97_options("backend=cras").expect("parse should have succeded");
1989 }
1990
1991 #[test]
1992 fn parse_ac97_null_vaild() {
1993 parse_ac97_options("backend=null").expect("parse should have succeded");
1994 }
1995
1996 #[test]
1997 fn parse_ac97_dup_effect_vaild() {
1998 parse_ac97_options("backend=cras,capture=true,capture_effects=aec|aec")
1999 .expect("parse should have succeded");
2000 }
2001
2002 #[test]
2003 fn parse_ac97_effect_invaild() {
2004 parse_ac97_options("backend=cras,capture=true,capture_effects=abc")
2005 .expect_err("parse should have failed");
2006 }
2007
2008 #[test]
2009 fn parse_ac97_effect_vaild() {
2010 parse_ac97_options("backend=cras,capture=true,capture_effects=aec")
2011 .expect("parse should have succeded");
2012 }
2013
2014 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06002015 fn parse_serial_vaild() {
Jorge E. Moreira1e262302019-08-01 14:40:03 -07002016 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
2017 .expect("parse should have succeded");
Trent Begin17ccaad2019-04-17 13:51:25 -06002018 }
2019
2020 #[test]
Trent Begin923bab02019-06-17 13:48:06 -06002021 fn parse_serial_valid_no_num() {
2022 parse_serial_options("type=syslog").expect("parse should have succeded");
2023 }
2024
2025 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06002026 fn parse_serial_invalid_type() {
2027 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
2028 }
2029
2030 #[test]
2031 fn parse_serial_invalid_num_upper() {
2032 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
2033 }
2034
2035 #[test]
2036 fn parse_serial_invalid_num_lower() {
2037 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
2038 }
2039
2040 #[test]
2041 fn parse_serial_invalid_num_string() {
2042 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
2043 }
2044
2045 #[test]
2046 fn parse_serial_invalid_option() {
2047 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
2048 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -07002049
2050 #[test]
2051 fn parse_serial_invalid_two_stdin() {
2052 let mut config = Config::default();
2053 set_argument(&mut config, "serial", Some("num=1,type=stdout,stdin=true"))
2054 .expect("should parse the first serial argument");
2055 set_argument(&mut config, "serial", Some("num=2,type=stdout,stdin=true"))
2056 .expect_err("should fail to parse a second serial port connected to stdin");
2057 }
Dmitry Torokhov458bb642019-12-13 11:47:52 -08002058
2059 #[test]
2060 fn parse_plugin_mount_valid() {
2061 let mut config = Config::default();
2062 set_argument(
2063 &mut config,
2064 "plugin-mount",
2065 Some("/dev/null:/dev/zero:true"),
2066 )
2067 .expect("parse should succeed");
2068 assert_eq!(config.plugin_mounts[0].src, PathBuf::from("/dev/null"));
2069 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/zero"));
2070 assert_eq!(config.plugin_mounts[0].writable, true);
2071 }
2072
2073 #[test]
2074 fn parse_plugin_mount_valid_shorthand() {
2075 let mut config = Config::default();
2076 set_argument(&mut config, "plugin-mount", Some("/dev/null")).expect("parse should succeed");
2077 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/null"));
2078 assert_eq!(config.plugin_mounts[0].writable, false);
2079 set_argument(&mut config, "plugin-mount", Some("/dev/null:/dev/zero"))
2080 .expect("parse should succeed");
2081 assert_eq!(config.plugin_mounts[1].dst, PathBuf::from("/dev/zero"));
2082 assert_eq!(config.plugin_mounts[1].writable, false);
2083 set_argument(&mut config, "plugin-mount", Some("/dev/null::true"))
2084 .expect("parse should succeed");
2085 assert_eq!(config.plugin_mounts[2].dst, PathBuf::from("/dev/null"));
2086 assert_eq!(config.plugin_mounts[2].writable, true);
2087 }
2088
2089 #[test]
2090 fn parse_plugin_mount_invalid() {
2091 let mut config = Config::default();
2092 set_argument(&mut config, "plugin-mount", Some("")).expect_err("parse should fail");
2093 set_argument(
2094 &mut config,
2095 "plugin-mount",
2096 Some("/dev/null:/dev/null:true:false"),
2097 )
2098 .expect_err("parse should fail because too many arguments");
2099 set_argument(&mut config, "plugin-mount", Some("null:/dev/null:true"))
2100 .expect_err("parse should fail because source is not absolute");
2101 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:true"))
2102 .expect_err("parse should fail because source is not absolute");
2103 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:blah"))
2104 .expect_err("parse should fail because flag is not boolean");
2105 }
2106
2107 #[test]
2108 fn parse_plugin_gid_map_valid() {
2109 let mut config = Config::default();
2110 set_argument(&mut config, "plugin-gid-map", Some("1:2:3")).expect("parse should succeed");
2111 assert_eq!(config.plugin_gid_maps[0].inner, 1);
2112 assert_eq!(config.plugin_gid_maps[0].outer, 2);
2113 assert_eq!(config.plugin_gid_maps[0].count, 3);
2114 }
2115
2116 #[test]
2117 fn parse_plugin_gid_map_valid_shorthand() {
2118 let mut config = Config::default();
2119 set_argument(&mut config, "plugin-gid-map", Some("1")).expect("parse should succeed");
2120 assert_eq!(config.plugin_gid_maps[0].inner, 1);
2121 assert_eq!(config.plugin_gid_maps[0].outer, 1);
2122 assert_eq!(config.plugin_gid_maps[0].count, 1);
2123 set_argument(&mut config, "plugin-gid-map", Some("1:2")).expect("parse should succeed");
2124 assert_eq!(config.plugin_gid_maps[1].inner, 1);
2125 assert_eq!(config.plugin_gid_maps[1].outer, 2);
2126 assert_eq!(config.plugin_gid_maps[1].count, 1);
2127 set_argument(&mut config, "plugin-gid-map", Some("1::3")).expect("parse should succeed");
2128 assert_eq!(config.plugin_gid_maps[2].inner, 1);
2129 assert_eq!(config.plugin_gid_maps[2].outer, 1);
2130 assert_eq!(config.plugin_gid_maps[2].count, 3);
2131 }
2132
2133 #[test]
2134 fn parse_plugin_gid_map_invalid() {
2135 let mut config = Config::default();
2136 set_argument(&mut config, "plugin-gid-map", Some("")).expect_err("parse should fail");
2137 set_argument(&mut config, "plugin-gid-map", Some("1:2:3:4"))
2138 .expect_err("parse should fail because too many arguments");
2139 set_argument(&mut config, "plugin-gid-map", Some("blah:2:3"))
2140 .expect_err("parse should fail because inner is not a number");
2141 set_argument(&mut config, "plugin-gid-map", Some("1:blah:3"))
2142 .expect_err("parse should fail because outer is not a number");
2143 set_argument(&mut config, "plugin-gid-map", Some("1:2:blah"))
2144 .expect_err("parse should fail because count is not a number");
2145 }
Kaiyi Libccb4eb2020-02-06 17:53:11 -08002146
2147 #[test]
2148 fn single_touch_spec_and_track_pad_spec_default_size() {
2149 let mut config = Config::default();
2150 config
2151 .executable_path
2152 .replace(Executable::Kernel(PathBuf::from("kernel")));
2153 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
2154 set_argument(&mut config, "trackpad", Some("/dev/single-touch-test")).unwrap();
2155 validate_arguments(&mut config).unwrap();
2156 assert_eq!(
2157 config.virtio_single_touch.unwrap().get_size(),
2158 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
2159 );
2160 assert_eq!(
2161 config.virtio_trackpad.unwrap().get_size(),
2162 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
2163 );
2164 }
2165
2166 #[cfg(feature = "gpu")]
2167 #[test]
2168 fn single_touch_spec_default_size_from_gpu() {
2169 let width = 12345u32;
2170 let height = 54321u32;
2171 let mut config = Config::default();
2172 config
2173 .executable_path
2174 .replace(Executable::Kernel(PathBuf::from("kernel")));
2175 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
2176 set_argument(
2177 &mut config,
2178 "gpu",
2179 Some(&format!("width={},height={}", width, height)),
2180 )
2181 .unwrap();
2182 validate_arguments(&mut config).unwrap();
2183 assert_eq!(
2184 config.virtio_single_touch.unwrap().get_size(),
2185 (width, height)
2186 );
2187 }
2188
2189 #[test]
2190 fn single_touch_spec_and_track_pad_spec_with_size() {
2191 let width = 12345u32;
2192 let height = 54321u32;
2193 let mut config = Config::default();
2194 config
2195 .executable_path
2196 .replace(Executable::Kernel(PathBuf::from("kernel")));
2197 set_argument(
2198 &mut config,
2199 "single-touch",
2200 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
2201 )
2202 .unwrap();
2203 set_argument(
2204 &mut config,
2205 "trackpad",
2206 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
2207 )
2208 .unwrap();
2209 validate_arguments(&mut config).unwrap();
2210 assert_eq!(
2211 config.virtio_single_touch.unwrap().get_size(),
2212 (width, height)
2213 );
2214 assert_eq!(config.virtio_trackpad.unwrap().get_size(), (width, height));
2215 }
2216
2217 #[cfg(feature = "gpu")]
2218 #[test]
2219 fn single_touch_spec_with_size_independent_from_gpu() {
2220 let touch_width = 12345u32;
2221 let touch_height = 54321u32;
2222 let display_width = 1234u32;
2223 let display_height = 5432u32;
2224 let mut config = Config::default();
2225 config
2226 .executable_path
2227 .replace(Executable::Kernel(PathBuf::from("kernel")));
2228 set_argument(
2229 &mut config,
2230 "single-touch",
2231 Some(&format!(
2232 "/dev/single-touch-test:{}:{}",
2233 touch_width, touch_height
2234 )),
2235 )
2236 .unwrap();
2237 set_argument(
2238 &mut config,
2239 "gpu",
2240 Some(&format!(
2241 "width={},height={}",
2242 display_width, display_height
2243 )),
2244 )
2245 .unwrap();
2246 validate_arguments(&mut config).unwrap();
2247 assert_eq!(
2248 config.virtio_single_touch.unwrap().get_size(),
2249 (touch_width, touch_height)
2250 );
2251 }
Daniel Verkamp107edb32019-04-05 09:58:48 -07002252}