blob: 569935c4542ab6629c0867a55a185d912f94148e [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
Jingkui Wang100e6e42019-03-08 20:41:57 -08009use std::fmt;
10use std::fs::{File, OpenOptions};
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -080011use std::io::{BufRead, BufReader};
Jingkui Wang100e6e42019-03-08 20:41:57 -080012use std::num::ParseIntError;
13use std::os::unix::io::{FromRawFd, RawFd};
14use std::path::{Path, PathBuf};
Zach Reizner639d9672017-05-01 17:57:18 -070015use std::string::String;
Zach Reizner39aa26b2017-12-12 18:03:23 -080016use std::thread::sleep;
Stephen Barber56fbf092017-06-29 16:12:14 -070017use std::time::Duration;
Zach Reizner639d9672017-05-01 17:57:18 -070018
Kansho Nishida282115b2019-12-18 13:13:14 +090019use arch::Pstore;
Zach Reizner267f2c82019-07-31 17:07:27 -070020use crosvm::{
21 argument::{self, print_help, set_arguments, Argument},
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +090022 linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
Zach Reizner267f2c82019-07-31 17:07:27 -070023};
Jason Macnakcc7070b2019-11-06 14:48:12 -080024#[cfg(feature = "gpu")]
Kaiyi Libccb4eb2020-02-06 17:53:11 -080025use devices::virtio::gpu::{GpuMode, GpuParameters};
Trent Begin17ccaad2019-04-17 13:51:25 -060026use devices::{SerialParameters, SerialType};
Daniel Verkampf2eecc42019-12-17 17:04:58 -080027use disk::QcowFile;
David Tolnayfe3ef7d2019-03-08 15:57:49 -080028use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
Jingkui Wang100e6e42019-03-08 20:41:57 -080029use sys_util::{
David Tolnay633426a2019-04-12 12:18:35 -070030 debug, error, getpid, info, kill_process_group, net::UnixSeqpacket, reap_child, syslog,
31 validate_raw_fd, warn,
Jingkui Wang100e6e42019-03-08 20:41:57 -080032};
Jakub Starone7c59052019-04-09 12:31:14 -070033use vm_control::{
Jakub Staron1f828d72019-04-11 12:49:29 -070034 BalloonControlCommand, DiskControlCommand, MaybeOwnedFd, UsbControlCommand, UsbControlResult,
Zach Reizneraff94ca2019-03-18 20:58:31 -070035 VmControlRequestSocket, VmRequest, VmResponse, USB_CONTROL_MAX_PORTS,
Jakub Starone7c59052019-04-09 12:31:14 -070036};
Zach Reizner639d9672017-05-01 17:57:18 -070037
Cody Schuffelen6d1ab502019-05-21 12:12:38 -070038fn executable_is_plugin(executable: &Option<Executable>) -> bool {
39 match executable {
40 Some(Executable::Plugin(_)) => true,
41 _ => false,
42 }
43}
44
Stephen Barbera00753b2017-07-18 13:57:26 -070045// Wait for all children to exit. Return true if they have all exited, false
46// otherwise.
47fn wait_all_children() -> bool {
Stephen Barber49dd2e22018-10-29 18:29:58 -070048 const CHILD_WAIT_MAX_ITER: isize = 100;
Stephen Barbera00753b2017-07-18 13:57:26 -070049 const CHILD_WAIT_MS: u64 = 10;
50 for _ in 0..CHILD_WAIT_MAX_ITER {
Stephen Barbera00753b2017-07-18 13:57:26 -070051 loop {
Zach Reizner56158c82017-08-24 13:50:14 -070052 match reap_child() {
53 Ok(0) => break,
54 // We expect ECHILD which indicates that there were no children left.
55 Err(e) if e.errno() == libc::ECHILD => return true,
56 Err(e) => {
David Tolnayb4bd00f2019-02-12 17:51:26 -080057 warn!("error while waiting for children: {}", e);
Zach Reizner56158c82017-08-24 13:50:14 -070058 return false;
Stephen Barbera00753b2017-07-18 13:57:26 -070059 }
Zach Reizner56158c82017-08-24 13:50:14 -070060 // We reaped one child, so continue reaping.
Zach Reizner55a9e502018-10-03 10:22:32 -070061 _ => {}
Stephen Barbera00753b2017-07-18 13:57:26 -070062 }
63 }
Zach Reizner56158c82017-08-24 13:50:14 -070064 // There's no timeout option for waitpid which reap_child calls internally, so our only
65 // recourse is to sleep while waiting for the children to exit.
Stephen Barbera00753b2017-07-18 13:57:26 -070066 sleep(Duration::from_millis(CHILD_WAIT_MS));
67 }
68
69 // If we've made it to this point, not all of the children have exited.
David Tolnay5bbbf612018-12-01 17:49:30 -080070 false
Stephen Barbera00753b2017-07-18 13:57:26 -070071}
72
Daniel Verkamp107edb32019-04-05 09:58:48 -070073/// Parse a comma-separated list of CPU numbers and ranges and convert it to a Vec of CPU numbers.
74fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
75 let mut cpuset = Vec::new();
76 for part in s.split(',') {
77 let range: Vec<&str> = part.split('-').collect();
78 if range.len() == 0 || range.len() > 2 {
79 return Err(argument::Error::InvalidValue {
80 value: part.to_owned(),
81 expected: "invalid list syntax",
82 });
83 }
84 let first_cpu: usize = range[0]
85 .parse()
86 .map_err(|_| argument::Error::InvalidValue {
87 value: part.to_owned(),
88 expected: "CPU index must be a non-negative integer",
89 })?;
90 let last_cpu: usize = if range.len() == 2 {
91 range[1]
92 .parse()
93 .map_err(|_| argument::Error::InvalidValue {
94 value: part.to_owned(),
95 expected: "CPU index must be a non-negative integer",
96 })?
97 } else {
98 first_cpu
99 };
100
101 if last_cpu < first_cpu {
102 return Err(argument::Error::InvalidValue {
103 value: part.to_owned(),
104 expected: "CPU ranges must be from low to high",
105 });
106 }
107
108 for cpu in first_cpu..=last_cpu {
109 cpuset.push(cpu);
110 }
111 }
112 Ok(cpuset)
113}
114
Jason Macnakcc7070b2019-11-06 14:48:12 -0800115#[cfg(feature = "gpu")]
116fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
Kaiyi Libccb4eb2020-02-06 17:53:11 -0800117 let mut gpu_params: GpuParameters = Default::default();
Jason Macnakcc7070b2019-11-06 14:48:12 -0800118
119 if let Some(s) = s {
120 let opts = s
121 .split(",")
122 .map(|frag| frag.split("="))
123 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
124
125 for (k, v) in opts {
126 match k {
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800127 // Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
128 // times if the user specifies several modes (--gpu=2d,3d,gfxstream)
Jason Macnak327fc242020-01-10 12:45:36 -0800129 "2d" | "2D" => {
130 gpu_params.mode = GpuMode::Mode2D;
131 }
132 "3d" | "3D" => {
133 gpu_params.mode = GpuMode::Mode3D;
134 }
Lingfeng Yangddbe8b72020-01-30 10:00:36 -0800135 #[cfg(feature = "gfxstream")]
136 "gfxstream" => {
137 gpu_params.mode = GpuMode::ModeGfxStream;
138 }
139 // Preferred: Specifying --gpu,backend=<mode>
140 "backend" => match v {
141 "2d" | "2D" => {
142 gpu_params.mode = GpuMode::Mode2D;
143 }
144 "3d" | "3D" => {
145 gpu_params.mode = GpuMode::Mode3D;
146 }
147 #[cfg(feature = "gfxstream")]
148 "gfxstream" => {
149 gpu_params.mode = GpuMode::ModeGfxStream;
150 }
151 _ => {
152 return Err(argument::Error::InvalidValue {
153 value: v.to_string(),
154 expected: "gpu parameter 'backend' should be one of (2d|3d|gfxstream)",
155 });
156 }
157 },
Jason Macnakbf195582019-11-20 16:25:49 -0800158 "egl" => match v {
159 "true" | "" => {
160 gpu_params.renderer_use_egl = true;
161 }
162 "false" => {
163 gpu_params.renderer_use_egl = false;
164 }
165 _ => {
166 return Err(argument::Error::InvalidValue {
167 value: v.to_string(),
168 expected: "gpu parameter 'egl' should be a boolean",
169 });
170 }
171 },
172 "gles" => match v {
173 "true" | "" => {
174 gpu_params.renderer_use_gles = true;
175 }
176 "false" => {
177 gpu_params.renderer_use_gles = false;
178 }
179 _ => {
180 return Err(argument::Error::InvalidValue {
181 value: v.to_string(),
182 expected: "gpu parameter 'gles' should be a boolean",
183 });
184 }
185 },
186 "glx" => match v {
187 "true" | "" => {
188 gpu_params.renderer_use_glx = true;
189 }
190 "false" => {
191 gpu_params.renderer_use_glx = false;
192 }
193 _ => {
194 return Err(argument::Error::InvalidValue {
195 value: v.to_string(),
196 expected: "gpu parameter 'glx' should be a boolean",
197 });
198 }
199 },
200 "surfaceless" => match v {
201 "true" | "" => {
202 gpu_params.renderer_use_surfaceless = true;
203 }
204 "false" => {
205 gpu_params.renderer_use_surfaceless = false;
206 }
207 _ => {
208 return Err(argument::Error::InvalidValue {
209 value: v.to_string(),
210 expected: "gpu parameter 'surfaceless' should be a boolean",
211 });
212 }
213 },
Jason Macnakcc7070b2019-11-06 14:48:12 -0800214 "width" => {
215 gpu_params.display_width =
216 v.parse::<u32>()
217 .map_err(|_| argument::Error::InvalidValue {
218 value: v.to_string(),
219 expected: "gpu parameter 'width' must be a valid integer",
220 })?;
221 }
222 "height" => {
223 gpu_params.display_height =
224 v.parse::<u32>()
225 .map_err(|_| argument::Error::InvalidValue {
226 value: v.to_string(),
227 expected: "gpu parameter 'height' must be a valid integer",
228 })?;
229 }
230 "" => {}
231 _ => {
232 return Err(argument::Error::UnknownArgument(format!(
233 "gpu parameter {}",
234 k
235 )));
236 }
237 }
238 }
239 }
240
241 Ok(gpu_params)
242}
243
Trent Begin17ccaad2019-04-17 13:51:25 -0600244fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
245 let mut serial_setting = SerialParameters {
246 type_: SerialType::Sink,
247 path: None,
Trent Begin923bab02019-06-17 13:48:06 -0600248 num: 1,
Trent Begin17ccaad2019-04-17 13:51:25 -0600249 console: false,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700250 stdin: false,
Trent Begin17ccaad2019-04-17 13:51:25 -0600251 };
252
253 let opts = s
254 .split(",")
255 .map(|frag| frag.split("="))
256 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
257
258 for (k, v) in opts {
259 match k {
260 "type" => {
261 serial_setting.type_ = v
262 .parse::<SerialType>()
263 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
264 }
265 "num" => {
266 let num = v.parse::<u8>().map_err(|e| {
267 argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
268 })?;
269 if num < 1 || num > 4 {
270 return Err(argument::Error::InvalidValue {
271 value: num.to_string(),
272 expected: "Serial port num must be between 1 - 4",
273 });
274 }
275 serial_setting.num = num;
276 }
277 "console" => {
278 serial_setting.console = v.parse::<bool>().map_err(|e| {
279 argument::Error::Syntax(format!(
280 "serial device console is not parseable: {}",
281 e
282 ))
283 })?
284 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700285 "stdin" => {
286 serial_setting.stdin = v.parse::<bool>().map_err(|e| {
287 argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e))
288 })?
289 }
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700290 "path" => serial_setting.path = Some(PathBuf::from(v)),
Trent Begin17ccaad2019-04-17 13:51:25 -0600291 _ => {
292 return Err(argument::Error::UnknownArgument(format!(
293 "serial parameter {}",
294 k
295 )));
296 }
297 }
298 }
299
300 Ok(serial_setting)
301}
302
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800303fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> {
304 let components: Vec<&str> = value.split(":").collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800305 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800306 return Err(argument::Error::InvalidValue {
307 value: value.to_owned(),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800308 expected: "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800309 });
310 }
311
312 let src = PathBuf::from(components[0]);
313 if src.is_relative() {
314 return Err(argument::Error::InvalidValue {
315 value: components[0].to_owned(),
316 expected: "the source path for `plugin-mount` must be absolute",
317 });
318 }
319 if !src.exists() {
320 return Err(argument::Error::InvalidValue {
321 value: components[0].to_owned(),
322 expected: "the source path for `plugin-mount` does not exist",
323 });
324 }
325
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800326 let dst = PathBuf::from(match components.get(1) {
327 None | Some(&"") => components[0],
328 Some(path) => path,
329 });
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800330 if dst.is_relative() {
331 return Err(argument::Error::InvalidValue {
332 value: components[1].to_owned(),
333 expected: "the destination path for `plugin-mount` must be absolute",
334 });
335 }
336
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800337 let writable: bool = match components.get(2) {
338 None => false,
339 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800340 value: components[2].to_owned(),
341 expected: "the <writable> component for `plugin-mount` is not valid bool",
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800342 })?,
343 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800344
345 Ok(BindMount { src, dst, writable })
346}
347
348fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> {
349 let components: Vec<&str> = value.split(":").collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800350 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800351 return Err(argument::Error::InvalidValue {
352 value: value.to_owned(),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800353 expected:
354 "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800355 });
356 }
357
358 let inner: libc::gid_t = components[0]
359 .parse()
360 .map_err(|_| argument::Error::InvalidValue {
361 value: components[0].to_owned(),
362 expected: "the <inner> component for `plugin-gid-map` is not valid gid",
363 })?;
364
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800365 let outer: libc::gid_t = match components.get(1) {
366 None | Some(&"") => inner,
367 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800368 value: components[1].to_owned(),
369 expected: "the <outer> component for `plugin-gid-map` is not valid gid",
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800370 })?,
371 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800372
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800373 let count: u32 = match components.get(2) {
374 None => 1,
375 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800376 value: components[2].to_owned(),
377 expected: "the <count> component for `plugin-gid-map` is not valid number",
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800378 })?,
379 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800380
381 Ok(GidMap {
382 inner,
383 outer,
384 count,
385 })
386}
387
Zach Reiznerefe95782017-08-26 18:05:48 -0700388fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
389 match name {
390 "" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700391 if cfg.executable_path.is_some() {
392 return Err(argument::Error::TooManyArguments(format!(
393 "A VM executable was already specified: {:?}",
394 cfg.executable_path
395 )));
Zach Reiznerefe95782017-08-26 18:05:48 -0700396 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700397 let kernel_path = PathBuf::from(value.unwrap());
398 if !kernel_path.exists() {
399 return Err(argument::Error::InvalidValue {
400 value: value.unwrap().to_owned(),
401 expected: "this kernel path does not exist",
402 });
403 }
404 cfg.executable_path = Some(Executable::Kernel(kernel_path));
Zach Reiznerefe95782017-08-26 18:05:48 -0700405 }
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800406 "android-fstab" => {
407 if cfg.android_fstab.is_some()
408 && !cfg.android_fstab.as_ref().unwrap().as_os_str().is_empty()
409 {
410 return Err(argument::Error::TooManyArguments(
411 "expected exactly one android fstab path".to_owned(),
412 ));
413 } else {
414 let android_fstab = PathBuf::from(value.unwrap());
415 if !android_fstab.exists() {
416 return Err(argument::Error::InvalidValue {
417 value: value.unwrap().to_owned(),
418 expected: "this android fstab path does not exist",
419 });
420 }
421 cfg.android_fstab = Some(android_fstab);
422 }
423 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700424 "params" => {
Zach Reiznerbb678712018-01-30 18:13:04 -0800425 cfg.params.push(value.unwrap().to_owned());
Zach Reiznerefe95782017-08-26 18:05:48 -0700426 }
427 "cpus" => {
428 if cfg.vcpu_count.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700429 return Err(argument::Error::TooManyArguments(
430 "`cpus` already given".to_owned(),
431 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700432 }
433 cfg.vcpu_count =
Zach Reizner55a9e502018-10-03 10:22:32 -0700434 Some(
435 value
436 .unwrap()
437 .parse()
438 .map_err(|_| argument::Error::InvalidValue {
439 value: value.unwrap().to_owned(),
440 expected: "this value for `cpus` needs to be integer",
441 })?,
442 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700443 }
Daniel Verkamp107edb32019-04-05 09:58:48 -0700444 "cpu-affinity" => {
445 if cfg.vcpu_affinity.len() != 0 {
446 return Err(argument::Error::TooManyArguments(
447 "`cpu-affinity` already given".to_owned(),
448 ));
449 }
450 cfg.vcpu_affinity = parse_cpu_set(value.unwrap())?;
451 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700452 "mem" => {
453 if cfg.memory.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700454 return Err(argument::Error::TooManyArguments(
455 "`mem` already given".to_owned(),
456 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700457 }
458 cfg.memory =
Zach Reizner55a9e502018-10-03 10:22:32 -0700459 Some(
460 value
461 .unwrap()
462 .parse()
463 .map_err(|_| argument::Error::InvalidValue {
464 value: value.unwrap().to_owned(),
465 expected: "this value for `mem` needs to be integer",
466 })?,
467 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700468 }
paulhsiaf052cfe2019-01-22 15:22:25 +0800469 "cras-audio" => {
470 cfg.cras_audio = true;
471 }
paulhsia580d4182019-05-24 16:53:55 +0800472 "cras-capture" => {
473 cfg.cras_capture = true;
474 }
Dylan Reid3082e8e2019-01-07 10:33:48 -0800475 "null-audio" => {
476 cfg.null_audio = true;
477 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600478 "serial" => {
479 let serial_params = parse_serial_options(value.unwrap())?;
480 let num = serial_params.num;
481 if cfg.serial_parameters.contains_key(&num) {
482 return Err(argument::Error::TooManyArguments(format!(
483 "serial num {}",
484 num
485 )));
486 }
487
488 if serial_params.console {
Jakub Staronb6515a92019-06-05 15:18:25 -0700489 for params in cfg.serial_parameters.values() {
Trent Begin17ccaad2019-04-17 13:51:25 -0600490 if params.console {
491 return Err(argument::Error::TooManyArguments(format!(
492 "serial device {} already set as console",
493 params.num
494 )));
495 }
496 }
497 }
498
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700499 if serial_params.stdin {
500 if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) {
501 return Err(argument::Error::TooManyArguments(format!(
502 "serial device {} already connected to standard input",
503 previous_stdin.num
504 )));
505 }
506 }
507
Trent Begin17ccaad2019-04-17 13:51:25 -0600508 cfg.serial_parameters.insert(num, serial_params);
509 }
510 "syslog-tag" => {
511 if cfg.syslog_tag.is_some() {
512 return Err(argument::Error::TooManyArguments(
513 "`syslog-tag` already given".to_owned(),
514 ));
515 }
516 syslog::set_proc_name(value.unwrap());
517 cfg.syslog_tag = Some(value.unwrap().to_owned());
518 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700519 "root" | "rwroot" | "disk" | "rwdisk" | "qcow" | "rwqcow" => {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800520 let param = value.unwrap();
521 let mut components = param.split(',');
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700522 let read_only = !name.starts_with("rw");
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800523 let disk_path =
524 PathBuf::from(
525 components
526 .next()
527 .ok_or_else(|| argument::Error::InvalidValue {
528 value: param.to_owned(),
529 expected: "missing disk path",
530 })?,
531 );
Zach Reiznerefe95782017-08-26 18:05:48 -0700532 if !disk_path.exists() {
533 return Err(argument::Error::InvalidValue {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800534 value: param.to_owned(),
Zach Reizner55a9e502018-10-03 10:22:32 -0700535 expected: "this disk path does not exist",
536 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700537 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700538 if name.ends_with("root") {
Daniel Verkampaac28132018-10-15 14:58:48 -0700539 if cfg.disks.len() >= 26 {
Zach Reizner55a9e502018-10-03 10:22:32 -0700540 return Err(argument::Error::TooManyArguments(
541 "ran out of letters for to assign to root disk".to_owned(),
542 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700543 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700544 cfg.params.push(format!(
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700545 "root=/dev/vd{} {}",
546 char::from(b'a' + cfg.disks.len() as u8),
547 if read_only { "ro" } else { "rw" }
Zach Reizner55a9e502018-10-03 10:22:32 -0700548 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700549 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800550
551 let mut disk = DiskOption {
Zach Reizner55a9e502018-10-03 10:22:32 -0700552 path: disk_path,
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700553 read_only,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800554 sparse: true,
Daniel Verkamp27672232019-12-06 17:26:55 +1100555 block_size: 512,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800556 };
557
558 for opt in components {
559 let mut o = opt.splitn(2, '=');
560 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
561 value: opt.to_owned(),
562 expected: "disk options must not be empty",
563 })?;
564 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
565 value: opt.to_owned(),
566 expected: "disk options must be of the form `kind=value`",
567 })?;
568
569 match kind {
570 "sparse" => {
571 let sparse = value.parse().map_err(|_| argument::Error::InvalidValue {
572 value: value.to_owned(),
573 expected: "`sparse` must be a boolean",
574 })?;
575 disk.sparse = sparse;
576 }
Daniel Verkamp27672232019-12-06 17:26:55 +1100577 "block_size" => {
578 let block_size =
579 value.parse().map_err(|_| argument::Error::InvalidValue {
580 value: value.to_owned(),
581 expected: "`block_size` must be an integer",
582 })?;
583 disk.block_size = block_size;
584 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800585 _ => {
586 return Err(argument::Error::InvalidValue {
587 value: kind.to_owned(),
588 expected: "unrecognized disk option",
589 });
590 }
591 }
592 }
593
594 cfg.disks.push(disk);
Zach Reiznerefe95782017-08-26 18:05:48 -0700595 }
Jakub Starona3411ea2019-04-24 10:55:25 -0700596 "pmem-device" | "rw-pmem-device" => {
597 let disk_path = PathBuf::from(value.unwrap());
598 if !disk_path.exists() {
599 return Err(argument::Error::InvalidValue {
600 value: value.unwrap().to_owned(),
601 expected: "this disk path does not exist",
602 });
603 }
604
605 cfg.pmem_devices.push(DiskOption {
606 path: disk_path,
607 read_only: !name.starts_with("rw"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800608 sparse: false,
Daniel Verkamp27672232019-12-06 17:26:55 +1100609 block_size: sys_util::pagesize() as u32,
Jakub Starona3411ea2019-04-24 10:55:25 -0700610 });
611 }
Kansho Nishida282115b2019-12-18 13:13:14 +0900612 "pstore" => {
613 if cfg.pstore.is_some() {
614 return Err(argument::Error::TooManyArguments(
615 "`pstore` already given".to_owned(),
616 ));
617 }
618
619 let value = value.unwrap();
620 let components: Vec<&str> = value.split(',').collect();
621 if components.len() != 2 {
622 return Err(argument::Error::InvalidValue {
623 value: value.to_owned(),
624 expected: "pstore must have exactly 2 components: path=<path>,size=<size>",
625 });
626 }
627 cfg.pstore = Some(Pstore {
628 path: {
629 if components[0].len() <= 5 || !components[0].starts_with("path=") {
630 return Err(argument::Error::InvalidValue {
631 value: components[0].to_owned(),
632 expected: "pstore path must follow with `path=`",
633 });
634 };
635 PathBuf::from(&components[0][5..])
636 },
637 size: {
638 if components[1].len() <= 5 || !components[1].starts_with("size=") {
639 return Err(argument::Error::InvalidValue {
640 value: components[1].to_owned(),
641 expected: "pstore size must follow with `size=`",
642 });
643 };
644 components[1][5..]
645 .parse()
646 .map_err(|_| argument::Error::InvalidValue {
647 value: value.to_owned(),
648 expected: "pstore size must be an integer",
649 })?
650 },
651 });
652 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700653 "host_ip" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700654 if cfg.host_ip.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700655 return Err(argument::Error::TooManyArguments(
656 "`host_ip` already given".to_owned(),
657 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700658 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700659 cfg.host_ip =
Zach Reizner55a9e502018-10-03 10:22:32 -0700660 Some(
661 value
662 .unwrap()
663 .parse()
664 .map_err(|_| argument::Error::InvalidValue {
665 value: value.unwrap().to_owned(),
666 expected: "`host_ip` needs to be in the form \"x.x.x.x\"",
667 })?,
668 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700669 }
670 "netmask" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700671 if cfg.netmask.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700672 return Err(argument::Error::TooManyArguments(
673 "`netmask` already given".to_owned(),
674 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700675 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700676 cfg.netmask =
Zach Reizner55a9e502018-10-03 10:22:32 -0700677 Some(
678 value
679 .unwrap()
680 .parse()
681 .map_err(|_| argument::Error::InvalidValue {
682 value: value.unwrap().to_owned(),
683 expected: "`netmask` needs to be in the form \"x.x.x.x\"",
684 })?,
685 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700686 }
687 "mac" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700688 if cfg.mac_address.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700689 return Err(argument::Error::TooManyArguments(
690 "`mac` already given".to_owned(),
691 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700692 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700693 cfg.mac_address =
Zach Reizner55a9e502018-10-03 10:22:32 -0700694 Some(
695 value
696 .unwrap()
697 .parse()
698 .map_err(|_| argument::Error::InvalidValue {
699 value: value.unwrap().to_owned(),
700 expected: "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
701 })?,
702 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700703 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700704 "wayland-sock" => {
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900705 let mut components = value.unwrap().split(',');
706 let path =
707 PathBuf::from(
708 components
709 .next()
710 .ok_or_else(|| argument::Error::InvalidValue {
711 value: value.unwrap().to_owned(),
712 expected: "missing socket path",
713 })?,
714 );
715 let mut name = "";
716 for c in components {
717 let mut kv = c.splitn(2, '=');
718 let (kind, value) = match (kv.next(), kv.next()) {
719 (Some(kind), Some(value)) => (kind, value),
720 _ => {
721 return Err(argument::Error::InvalidValue {
722 value: c.to_owned(),
723 expected: "option must be of the form `kind=value`",
724 })
725 }
726 };
727 match kind {
728 "name" => name = value,
729 _ => {
730 return Err(argument::Error::InvalidValue {
731 value: kind.to_owned(),
732 expected: "unrecognized option",
733 })
734 }
735 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700736 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900737 if cfg.wayland_socket_paths.contains_key(name) {
738 return Err(argument::Error::TooManyArguments(format!(
739 "wayland socket name already used: '{}'",
740 name
741 )));
Stephen Barber28a5a612017-10-20 17:15:30 -0700742 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900743 cfg.wayland_socket_paths.insert(name.to_string(), path);
Stephen Barber28a5a612017-10-20 17:15:30 -0700744 }
David Reveman52ba4e52018-04-22 21:42:09 -0400745 #[cfg(feature = "wl-dmabuf")]
Daniel Verkampaac28132018-10-15 14:58:48 -0700746 "wayland-dmabuf" => cfg.wayland_dmabuf = true,
Zach Reizner0f2cfb02019-06-19 17:46:03 -0700747 "x-display" => {
748 if cfg.x_display.is_some() {
749 return Err(argument::Error::TooManyArguments(
750 "`x-display` already given".to_owned(),
751 ));
752 }
753 cfg.x_display = Some(value.unwrap().to_owned());
754 }
Zach Reizner65b98f12019-11-22 17:34:58 -0800755 "display-window-keyboard" => {
756 cfg.display_window_keyboard = true;
757 }
758 "display-window-mouse" => {
759 cfg.display_window_mouse = true;
760 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700761 "socket" => {
762 if cfg.socket_path.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700763 return Err(argument::Error::TooManyArguments(
764 "`socket` already given".to_owned(),
765 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700766 }
767 let mut socket_path = PathBuf::from(value.unwrap());
768 if socket_path.is_dir() {
769 socket_path.push(format!("crosvm-{}.sock", getpid()));
770 }
771 if socket_path.exists() {
772 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700773 value: socket_path.to_string_lossy().into_owned(),
774 expected: "this socket path already exists",
775 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700776 }
777 cfg.socket_path = Some(socket_path);
778 }
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700779 "disable-sandbox" => {
Lepton Wu9105e9f2019-03-14 11:38:31 -0700780 cfg.sandbox = false;
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700781 }
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700782 "cid" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700783 if cfg.cid.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700784 return Err(argument::Error::TooManyArguments(
785 "`cid` alread given".to_owned(),
786 ));
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700787 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700788 cfg.cid = Some(
789 value
790 .unwrap()
791 .parse()
792 .map_err(|_| argument::Error::InvalidValue {
793 value: value.unwrap().to_owned(),
794 expected: "this value for `cid` must be an unsigned integer",
795 })?,
796 );
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700797 }
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700798 "shared-dir" => {
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900799 // This is formatted as multiple fields, each separated by ":". The first 2 fields are
800 // fixed (src:tag). The rest may appear in any order:
801 //
802 // * type=TYPE - must be one of "p9" or "fs" (default: p9)
803 // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
804 // (default: "0 <current euid> 1")
805 // * gidmap=GIDMAP - a gid map in the same format as uidmap
806 // (default: "0 <current egid> 1")
807 // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
808 // and directory contents should be considered valid (default: 5)
809 // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
810 // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700811 let param = value.unwrap();
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900812 let mut components = param.split(':');
Zach Reizner55a9e502018-10-03 10:22:32 -0700813 let src =
814 PathBuf::from(
815 components
816 .next()
817 .ok_or_else(|| argument::Error::InvalidValue {
818 value: param.to_owned(),
819 expected: "missing source path for `shared-dir`",
820 })?,
821 );
822 let tag = components
823 .next()
824 .ok_or_else(|| argument::Error::InvalidValue {
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700825 value: param.to_owned(),
826 expected: "missing tag for `shared-dir`",
David Tolnay2bac1e72018-12-12 14:33:42 -0800827 })?
828 .to_owned();
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700829
830 if !src.is_dir() {
831 return Err(argument::Error::InvalidValue {
832 value: param.to_owned(),
833 expected: "source path for `shared-dir` must be a directory",
834 });
835 }
836
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900837 let mut shared_dir = SharedDir {
838 src,
839 tag,
840 ..Default::default()
841 };
842 for opt in components {
843 let mut o = opt.splitn(2, '=');
844 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
845 value: opt.to_owned(),
846 expected: "`shared-dir` options must not be empty",
847 })?;
848 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
849 value: opt.to_owned(),
850 expected: "`shared-dir` options must be of the form `kind=value`",
851 })?;
852
853 match kind {
854 "type" => {
855 shared_dir.kind =
856 value.parse().map_err(|_| argument::Error::InvalidValue {
857 value: value.to_owned(),
858 expected: "`type` must be one of `fs` or `9p`",
859 })?
860 }
861 "uidmap" => shared_dir.uid_map = value.into(),
862 "gidmap" => shared_dir.gid_map = value.into(),
863 "timeout" => {
864 let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
865 value: value.to_owned(),
866 expected: "`timeout` must be an integer",
867 })?;
868
869 let dur = Duration::from_secs(seconds);
870 shared_dir.cfg.entry_timeout = dur.clone();
871 shared_dir.cfg.attr_timeout = dur;
872 }
873 "cache" => {
874 let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
875 value: value.to_owned(),
876 expected: "`cache` must be one of `never`, `always`, or `auto`",
877 })?;
878 shared_dir.cfg.cache_policy = policy;
879 }
880 "writeback" => {
881 let writeback =
882 value.parse().map_err(|_| argument::Error::InvalidValue {
883 value: value.to_owned(),
884 expected: "`writeback` must be a boolean",
885 })?;
886 shared_dir.cfg.writeback = writeback;
887 }
888 _ => {
889 return Err(argument::Error::InvalidValue {
890 value: kind.to_owned(),
891 expected: "unrecognized option for `shared-dir`",
892 })
893 }
894 }
895 }
896 cfg.shared_dirs.push(shared_dir);
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700897 }
Dylan Reide026ef02017-10-02 19:03:52 -0700898 "seccomp-policy-dir" => {
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700899 // `value` is Some because we are in this match so it's safe to unwrap.
Daniel Verkampaac28132018-10-15 14:58:48 -0700900 cfg.seccomp_policy_dir = PathBuf::from(value.unwrap());
Zach Reizner55a9e502018-10-03 10:22:32 -0700901 }
Zach Reizner44863792019-06-26 14:22:08 -0700902 "seccomp-log-failures" => {
Matt Delco45caf912019-11-13 08:11:09 -0800903 // A side-effect of this flag is to force the use of .policy files
904 // instead of .bpf files (.bpf files are expected and assumed to be
905 // compiled to fail an unpermitted action with "trap").
906 // Normally crosvm will first attempt to use a .bpf file, and if
907 // not present it will then try to use a .policy file. It's up
908 // to the build to decide which of these files is present for
909 // crosvm to use (for CrOS the build will use .bpf files for
910 // x64 builds and .policy files for arm/arm64 builds).
911 //
912 // This flag will likely work as expected for builds that use
913 // .policy files. For builds that only use .bpf files the initial
914 // result when using this flag is likely to be a file-not-found
915 // error (since the .policy files are not present).
916 // For .bpf builds you can either 1) manually add the .policy files,
917 // or 2) do not use this command-line parameter and instead
918 // temporarily change the build by passing "log" rather than
919 // "trap" as the "--default-action" to compile_seccomp_policy.py.
Zach Reizner44863792019-06-26 14:22:08 -0700920 cfg.seccomp_log_failures = true;
921 }
Zach Reizner8864cb02018-01-16 17:59:03 -0800922 "plugin" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700923 if cfg.executable_path.is_some() {
924 return Err(argument::Error::TooManyArguments(format!(
925 "A VM executable was already specified: {:?}",
926 cfg.executable_path
927 )));
Zach Reizner8864cb02018-01-16 17:59:03 -0800928 }
Zach Reiznercc30d582018-01-23 21:16:42 -0800929 let plugin = PathBuf::from(value.unwrap().to_owned());
930 if plugin.is_relative() {
931 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700932 value: plugin.to_string_lossy().into_owned(),
933 expected: "the plugin path must be an absolute path",
934 });
Zach Reiznercc30d582018-01-23 21:16:42 -0800935 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700936 cfg.executable_path = Some(Executable::Plugin(plugin));
Zach Reizner55a9e502018-10-03 10:22:32 -0700937 }
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -0700938 "plugin-root" => {
939 cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned()));
Zach Reizner55a9e502018-10-03 10:22:32 -0700940 }
Chirantan Ekboted41d7262018-11-16 16:37:45 -0800941 "plugin-mount" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800942 let mount = parse_plugin_mount_option(value.unwrap())?;
943 cfg.plugin_mounts.push(mount);
Chirantan Ekboted41d7262018-11-16 16:37:45 -0800944 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800945 "plugin-mount-file" => {
946 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
947 value: value.unwrap().to_owned(),
948 expected: "unable to open `plugin-mount-file` file",
949 })?;
950 let reader = BufReader::new(file);
951 for l in reader.lines() {
952 let line = l.unwrap();
Dmitry Torokhovd65265c2019-12-11 13:36:08 -0800953 let trimmed_line = line.splitn(2, '#').nth(0).unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800954 if !trimmed_line.is_empty() {
955 let mount = parse_plugin_mount_option(trimmed_line)?;
956 cfg.plugin_mounts.push(mount);
957 }
958 }
959 }
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -0800960 "plugin-gid-map" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800961 let map = parse_plugin_gid_map_option(value.unwrap())?;
962 cfg.plugin_gid_maps.push(map);
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -0800963 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800964 "plugin-gid-map-file" => {
965 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
966 value: value.unwrap().to_owned(),
967 expected: "unable to open `plugin-gid-map-file` file",
968 })?;
969 let reader = BufReader::new(file);
970 for l in reader.lines() {
971 let line = l.unwrap();
Dmitry Torokhovd65265c2019-12-11 13:36:08 -0800972 let trimmed_line = line.splitn(2, '#').nth(0).unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800973 if !trimmed_line.is_empty() {
974 let map = parse_plugin_gid_map_option(trimmed_line)?;
975 cfg.plugin_gid_maps.push(map);
976 }
977 }
978 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700979 "vhost-net" => cfg.vhost_net = true,
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700980 "tap-fd" => {
Jorge E. Moreirab7952802019-02-12 16:43:05 -0800981 cfg.tap_fd.push(
982 value
983 .unwrap()
984 .parse()
985 .map_err(|_| argument::Error::InvalidValue {
986 value: value.unwrap().to_owned(),
987 expected: "this value for `tap-fd` must be an unsigned integer",
988 })?,
989 );
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700990 }
Jason Macnakcc7070b2019-11-06 14:48:12 -0800991 #[cfg(feature = "gpu")]
Zach Reizner3a8100a2017-09-13 19:15:43 -0700992 "gpu" => {
Jason Macnakcc7070b2019-11-06 14:48:12 -0800993 let params = parse_gpu_options(value)?;
994 cfg.gpu_parameters = Some(params);
Zach Reizner3a8100a2017-09-13 19:15:43 -0700995 }
David Tolnay43f8e212019-02-13 17:28:16 -0800996 "software-tpm" => {
997 cfg.software_tpm = true;
998 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -0800999 "single-touch" => {
1000 if cfg.virtio_single_touch.is_some() {
1001 return Err(argument::Error::TooManyArguments(
1002 "`single-touch` already given".to_owned(),
1003 ));
1004 }
1005 let mut it = value.unwrap().split(":");
1006
1007 let mut single_touch_spec =
1008 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
1009 if let Some(width) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001010 single_touch_spec.set_width(width.trim().parse().unwrap());
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001011 }
1012 if let Some(height) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001013 single_touch_spec.set_height(height.trim().parse().unwrap());
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001014 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001015 cfg.virtio_single_touch = Some(single_touch_spec);
1016 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001017 "trackpad" => {
1018 if cfg.virtio_trackpad.is_some() {
1019 return Err(argument::Error::TooManyArguments(
1020 "`trackpad` already given".to_owned(),
1021 ));
1022 }
1023 let mut it = value.unwrap().split(":");
1024
1025 let mut trackpad_spec =
Jorge E. Moreira99d3f082019-03-07 10:59:54 -08001026 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001027 if let Some(width) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001028 trackpad_spec.set_width(width.trim().parse().unwrap());
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001029 }
1030 if let Some(height) = it.next() {
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001031 trackpad_spec.set_height(height.trim().parse().unwrap());
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001032 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001033 cfg.virtio_trackpad = Some(trackpad_spec);
1034 }
1035 "mouse" => {
1036 if cfg.virtio_mouse.is_some() {
1037 return Err(argument::Error::TooManyArguments(
1038 "`mouse` already given".to_owned(),
1039 ));
1040 }
1041 cfg.virtio_mouse = Some(PathBuf::from(value.unwrap().to_owned()));
1042 }
1043 "keyboard" => {
1044 if cfg.virtio_keyboard.is_some() {
1045 return Err(argument::Error::TooManyArguments(
1046 "`keyboard` already given".to_owned(),
1047 ));
1048 }
1049 cfg.virtio_keyboard = Some(PathBuf::from(value.unwrap().to_owned()));
1050 }
1051 "evdev" => {
1052 let dev_path = PathBuf::from(value.unwrap());
1053 if !dev_path.exists() {
1054 return Err(argument::Error::InvalidValue {
1055 value: value.unwrap().to_owned(),
1056 expected: "this input device path does not exist",
1057 });
1058 }
1059 cfg.virtio_input_evdevs.push(dev_path);
1060 }
Miriam Zimmerman26ac9282019-01-29 21:21:48 -08001061 "split-irqchip" => {
1062 cfg.split_irqchip = true;
1063 }
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001064 "initrd" => {
1065 cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
1066 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001067 "bios" => {
1068 if cfg.executable_path.is_some() {
1069 return Err(argument::Error::TooManyArguments(format!(
1070 "A VM executable was already specified: {:?}",
1071 cfg.executable_path
1072 )));
1073 }
1074 cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
1075 }
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001076 "vfio" => {
1077 let vfio_path = PathBuf::from(value.unwrap());
1078 if !vfio_path.exists() {
1079 return Err(argument::Error::InvalidValue {
1080 value: value.unwrap().to_owned(),
1081 expected: "the vfio path does not exist",
1082 });
1083 }
1084 if !vfio_path.is_dir() {
1085 return Err(argument::Error::InvalidValue {
1086 value: value.unwrap().to_owned(),
1087 expected: "the vfio path should be directory",
1088 });
1089 }
1090
1091 cfg.vfio = Some(vfio_path);
1092 }
1093
Zach Reiznerefe95782017-08-26 18:05:48 -07001094 "help" => return Err(argument::Error::PrintHelp),
1095 _ => unreachable!(),
1096 }
1097 Ok(())
1098}
1099
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001100fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Error> {
1101 if cfg.executable_path.is_none() {
1102 return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
1103 }
1104 if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
1105 if cfg.host_ip.is_none() {
1106 return Err(argument::Error::ExpectedArgument(
1107 "`host_ip` missing from network config".to_owned(),
1108 ));
1109 }
1110 if cfg.netmask.is_none() {
1111 return Err(argument::Error::ExpectedArgument(
1112 "`netmask` missing from network config".to_owned(),
1113 ));
1114 }
1115 if cfg.mac_address.is_none() {
1116 return Err(argument::Error::ExpectedArgument(
1117 "`mac` missing from network config".to_owned(),
1118 ));
1119 }
1120 }
1121 if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
1122 return Err(argument::Error::ExpectedArgument(
1123 "`plugin-root` requires `plugin`".to_owned(),
1124 ));
1125 }
1126 #[cfg(feature = "gpu")]
1127 {
1128 if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() {
1129 let (width, height) = (gpu_parameters.display_width, gpu_parameters.display_height);
1130 if let Some(virtio_single_touch) = cfg.virtio_single_touch.as_mut() {
1131 virtio_single_touch.set_default_size(width, height);
1132 }
1133 }
1134 }
1135 Ok(())
1136}
1137
Dylan Reidbfba9932018-02-05 15:51:59 -08001138fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reiznerefe95782017-08-26 18:05:48 -07001139 let arguments =
1140 &[Argument::positional("KERNEL", "bzImage of kernel to run"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -08001141 Argument::value("android-fstab", "PATH", "Path to Android fstab"),
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001142 Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001143 Argument::short_value('p',
1144 "params",
1145 "PARAMS",
Zach Reiznerbb678712018-01-30 18:13:04 -08001146 "Extra kernel or plugin command line arguments. Can be given more than once."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001147 Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Daniel Verkamp107edb32019-04-05 09:58:48 -07001148 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 -07001149 Argument::short_value('m',
1150 "mem",
1151 "N",
1152 "Amount of guest memory in MiB. (default: 256)"),
1153 Argument::short_value('r',
1154 "root",
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001155 "PATH[,key=value[,key=value[,...]]",
1156 "Path to a root disk image followed by optional comma-separated options.
1157 Like `--disk` but adds appropriate kernel command line option.
1158 See --disk for valid options."),
1159 Argument::value("rwroot", "PATH[,key=value[,key=value[,...]]", "Path to a writable root disk image followed by optional comma-separated options.
1160 See --disk for valid options."),
1161 Argument::short_value('d', "disk", "PATH[,key=value[,key=value[,...]]", "Path to a disk image followed by optional comma-separated options.
1162 Valid keys:
Daniel Verkamp27672232019-12-06 17:26:55 +11001163 sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
1164 block_size=BYTES - Set the reported block size of the disk (default: 512)"),
Daniel Verkampf02fdd12018-10-10 17:25:14 -07001165 Argument::value("qcow", "PATH", "Path to a qcow2 disk image. (Deprecated; use --disk instead.)"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001166 Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
1167 See --disk for valid options."),
Daniel Verkampf02fdd12018-10-10 17:25:14 -07001168 Argument::value("rwqcow", "PATH", "Path to a writable qcow2 disk image. (Deprecated; use --rwdisk instead.)"),
Jakub Starona3411ea2019-04-24 10:55:25 -07001169 Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
1170 Argument::value("pmem-device", "PATH", "Path to a disk image."),
Kansho Nishida282115b2019-12-18 13:13:14 +09001171 Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file follewed by size."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001172 Argument::value("host_ip",
1173 "IP",
1174 "IP address to assign to host tap interface."),
1175 Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
1176 Argument::value("mac", "MAC", "MAC address for VM."),
paulhsiaf052cfe2019-01-22 15:22:25 +08001177 Argument::flag("cras-audio", "Add an audio device to the VM that plays samples through CRAS server"),
paulhsia580d4182019-05-24 16:53:55 +08001178 Argument::flag("cras-capture", "Enable capturing audio from CRAS server to the cras-audio device"),
Dylan Reid3082e8e2019-01-07 10:33:48 -08001179 Argument::flag("null-audio", "Add an audio device to the VM that plays samples to /dev/null"),
Trent Begin17ccaad2019-04-17 13:51:25 -06001180 Argument::value("serial",
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001181 "type=TYPE,[num=NUM,path=PATH,console,stdin]",
Jason Macnakcc7070b2019-11-06 14:48:12 -08001182 "Comma separated key=value pairs for setting up serial devices. Can be given more than once.
Trent Begin17ccaad2019-04-17 13:51:25 -06001183 Possible key values:
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001184 type=(stdout,syslog,sink,file) - Where to route the serial device
Trent Begin923bab02019-06-17 13:48:06 -06001185 num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001186 path=PATH - The path to the file to write to when type=file
Trent Begin17ccaad2019-04-17 13:51:25 -06001187 console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001188 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 -06001189 "),
1190 Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
Zach Reizner0f2cfb02019-06-19 17:46:03 -07001191 Argument::value("x-display", "DISPLAY", "X11 display name to use."),
Zach Reizner65b98f12019-11-22 17:34:58 -08001192 Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
1193 Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +09001194 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 -04001195 #[cfg(feature = "wl-dmabuf")]
1196 Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001197 Argument::short_value('s',
1198 "socket",
1199 "PATH",
1200 "Path to put the control socket. If PATH is a directory, a name will be generated."),
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001201 Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001202 Argument::value("cid", "CID", "Context ID for virtual sockets."),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001203 Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE]",
1204 "Colon-separated options for configuring a directory to be shared with the VM.
1205The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
1206The remaining fields are key=value pairs that may appear in any order. Valid keys are:
1207type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
1208uidmap=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).
1209gidmap=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).
1210cache=(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.
1211timeout=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.
1212writeback=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.
1213"),
Dylan Reide026ef02017-10-02 19:03:52 -07001214 Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
Zach Reizner44863792019-06-26 14:22:08 -07001215 Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
Zach Reizner8864cb02018-01-16 17:59:03 -08001216 #[cfg(feature = "plugin")]
Zach Reiznercc30d582018-01-23 21:16:42 -08001217 Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
Daniel Verkampbd1a0842019-01-08 15:50:34 -08001218 #[cfg(feature = "plugin")]
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001219 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 -08001220 #[cfg(feature = "plugin")]
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001221 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 -08001222 #[cfg(feature = "plugin")]
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001223 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."),
1224 #[cfg(feature = "plugin")]
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001225 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 -08001226 #[cfg(feature = "plugin")]
1227 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 +00001228 Argument::flag("vhost-net", "Use vhost for networking."),
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001229 Argument::value("tap-fd",
1230 "fd",
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001231 "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 -07001232 #[cfg(feature = "gpu")]
Jason Macnakcc7070b2019-11-06 14:48:12 -08001233 Argument::flag_or_value("gpu",
1234 "[width=INT,height=INT]",
1235 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
1236 Possible key values:
Lingfeng Yangddbe8b72020-01-30 10:00:36 -08001237 backend=(2d|3d|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
Jason Macnakcc7070b2019-11-06 14:48:12 -08001238 width=INT - The width of the virtual display connected to the virtio-gpu.
1239 height=INT - The height of the virtual display connected to the virtio-gpu.
Jason Macnakbf195582019-11-20 16:25:49 -08001240 egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering.
1241 glx[=true|=false] - If the virtio-gpu backend should use a GLX context for rendering.
1242 surfaceless[=true|=false] - If the virtio-gpu backend should use a surfaceless context for rendering.
Jason Macnakcc7070b2019-11-06 14:48:12 -08001243 "),
David Tolnay43f8e212019-02-13 17:28:16 -08001244 #[cfg(feature = "tpm")]
1245 Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001246 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 -08001247 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 -08001248 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)."),
1249 Argument::value("mouse", "PATH", "Path to a socket from where to read mouse input events and write status updates to."),
1250 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 -08001251 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1252 Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001253 Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001254 Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
Zach Reiznerefe95782017-08-26 18:05:48 -07001255 Argument::short_flag('h', "help", "Print help message.")];
1256
1257 let mut cfg = Config::default();
Zach Reizner55a9e502018-10-03 10:22:32 -07001258 let match_res = set_arguments(args, &arguments[..], |name, value| {
1259 set_argument(&mut cfg, name, value)
David Tolnay2bac1e72018-12-12 14:33:42 -08001260 })
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001261 .and_then(|_| validate_arguments(&mut cfg));
Zach Reiznerefe95782017-08-26 18:05:48 -07001262
1263 match match_res {
Zach Reizner8864cb02018-01-16 17:59:03 -08001264 #[cfg(feature = "plugin")]
Zach Reizner267f2c82019-07-31 17:07:27 -07001265 Ok(()) if executable_is_plugin(&cfg.executable_path) => {
1266 match crosvm::plugin::run_config(cfg) {
1267 Ok(_) => {
1268 info!("crosvm and plugin have exited normally");
1269 Ok(())
1270 }
1271 Err(e) => {
1272 error!("{}", e);
1273 Err(())
1274 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001275 }
Zach Reizner267f2c82019-07-31 17:07:27 -07001276 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001277 Ok(()) => match linux::run_config(cfg) {
1278 Ok(_) => {
1279 info!("crosvm has exited normally");
1280 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001281 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001282 Err(e) => {
1283 error!("{}", e);
1284 Err(())
1285 }
1286 },
Dylan Reidbfba9932018-02-05 15:51:59 -08001287 Err(argument::Error::PrintHelp) => {
1288 print_help("crosvm run", "KERNEL", &arguments[..]);
1289 Ok(())
1290 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001291 Err(e) => {
Dmitry Torokhov470b1e72020-01-15 12:46:49 -08001292 error!("{}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001293 Err(())
Zach Reizner8864cb02018-01-16 17:59:03 -08001294 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001295 }
1296}
1297
Jingkui Wang100e6e42019-03-08 20:41:57 -08001298fn handle_request(
1299 request: &VmRequest,
1300 args: std::env::Args,
1301) -> std::result::Result<VmResponse, ()> {
1302 let mut return_result = Err(());
Zach Reiznerefe95782017-08-26 18:05:48 -07001303 for socket_path in args {
Zach Reiznera60744b2019-02-13 17:33:32 -08001304 match UnixSeqpacket::connect(&socket_path) {
Zach Reiznerefe95782017-08-26 18:05:48 -07001305 Ok(s) => {
Jakub Starone7c59052019-04-09 12:31:14 -07001306 let socket: VmControlRequestSocket = MsgSocket::new(s);
Zach Reizner78986322019-02-21 20:43:21 -08001307 if let Err(e) = socket.send(request) {
Zach Reizner55a9e502018-10-03 10:22:32 -07001308 error!(
David Tolnayb4bd00f2019-02-12 17:51:26 -08001309 "failed to send request to socket at '{}': {}",
Zach Reizner55a9e502018-10-03 10:22:32 -07001310 socket_path, e
1311 );
Zach Reizner78986322019-02-21 20:43:21 -08001312 return_result = Err(());
1313 continue;
Zach Reiznerefe95782017-08-26 18:05:48 -07001314 }
Zach Reizner78986322019-02-21 20:43:21 -08001315 match socket.recv() {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001316 Ok(response) => return_result = Ok(response),
Zach Reizner78986322019-02-21 20:43:21 -08001317 Err(e) => {
1318 error!(
1319 "failed to send request to socket at2 '{}': {}",
1320 socket_path, e
1321 );
1322 return_result = Err(());
1323 continue;
1324 }
Dylan Reidd4432042017-12-06 18:20:09 -08001325 }
1326 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001327 Err(e) => {
1328 error!("failed to connect to socket at '{}': {}", socket_path, e);
1329 return_result = Err(());
1330 }
Dylan Reidd4432042017-12-06 18:20:09 -08001331 }
1332 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001333
1334 return_result
Dylan Reidd4432042017-12-06 18:20:09 -08001335}
Zach Reiznerefe95782017-08-26 18:05:48 -07001336
Jingkui Wang100e6e42019-03-08 20:41:57 -08001337fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
1338 let response = handle_request(request, args)?;
1339 info!("request response was {}", response);
1340 Ok(())
1341}
1342
Zach Reizner78986322019-02-21 20:43:21 -08001343fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1344 if args.len() == 0 {
1345 print_help("crosvm stop", "VM_SOCKET...", &[]);
1346 println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001347 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001348 }
1349 vms_request(&VmRequest::Exit, args)
1350}
1351
1352fn suspend_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1353 if args.len() == 0 {
1354 print_help("crosvm suspend", "VM_SOCKET...", &[]);
1355 println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001356 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001357 }
1358 vms_request(&VmRequest::Suspend, args)
1359}
1360
1361fn resume_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1362 if args.len() == 0 {
1363 print_help("crosvm resume", "VM_SOCKET...", &[]);
1364 println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001365 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001366 }
1367 vms_request(&VmRequest::Resume, args)
1368}
1369
1370fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
1371 if args.len() < 2 {
1372 print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
1373 println!("Set the ballon size of the crosvm instance to `SIZE` bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001374 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001375 }
1376 let num_bytes = match args.nth(0).unwrap().parse::<u64>() {
1377 Ok(n) => n,
1378 Err(_) => {
1379 error!("Failed to parse number of bytes");
1380 return Err(());
1381 }
1382 };
1383
Jakub Staron1f828d72019-04-11 12:49:29 -07001384 let command = BalloonControlCommand::Adjust { num_bytes };
1385 vms_request(&VmRequest::BalloonCommand(command), args)
Zach Reizner78986322019-02-21 20:43:21 -08001386}
1387
Dylan Reid2dcb6322018-07-13 10:42:48 -07001388fn create_qcow2(mut args: std::env::Args) -> std::result::Result<(), ()> {
1389 if args.len() != 2 {
1390 print_help("crosvm create_qcow2", "PATH SIZE", &[]);
Dylan Reid940259c2018-07-20 14:22:33 -07001391 println!("Create a new QCOW2 image at `PATH` of the specified `SIZE` in bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001392 return Err(());
Dylan Reid2dcb6322018-07-13 10:42:48 -07001393 }
1394 let file_path = args.nth(0).unwrap();
1395 let size: u64 = match args.nth(0).unwrap().parse::<u64>() {
1396 Ok(n) => n,
1397 Err(_) => {
1398 error!("Failed to parse size of the disk.");
1399 return Err(());
Zach Reizner55a9e502018-10-03 10:22:32 -07001400 }
Dylan Reid2dcb6322018-07-13 10:42:48 -07001401 };
1402
1403 let file = OpenOptions::new()
1404 .create(true)
1405 .read(true)
1406 .write(true)
1407 .open(&file_path)
1408 .map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001409 error!("Failed opening qcow file at '{}': {}", file_path, e);
Dylan Reid2dcb6322018-07-13 10:42:48 -07001410 })?;
1411
Zach Reizner55a9e502018-10-03 10:22:32 -07001412 QcowFile::new(file, size).map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001413 error!("Failed to create qcow file at '{}': {}", file_path, e);
Zach Reizner55a9e502018-10-03 10:22:32 -07001414 })?;
Dylan Reid2dcb6322018-07-13 10:42:48 -07001415
1416 Ok(())
1417}
1418
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001419fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
1420 if args.len() < 2 {
1421 print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
1422 println!("Manage attached virtual disk devices.");
1423 println!("Subcommands:");
1424 println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001425 return Err(());
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001426 }
1427 let subcommand: &str = &args.nth(0).unwrap();
1428
1429 let request = match subcommand {
1430 "resize" => {
1431 let disk_index = match args.nth(0).unwrap().parse::<usize>() {
1432 Ok(n) => n,
1433 Err(_) => {
1434 error!("Failed to parse disk index");
1435 return Err(());
1436 }
1437 };
1438
1439 let new_size = match args.nth(0).unwrap().parse::<u64>() {
1440 Ok(n) => n,
1441 Err(_) => {
1442 error!("Failed to parse disk size");
1443 return Err(());
1444 }
1445 };
1446
Jakub Staronecf81e02019-04-11 11:43:39 -07001447 VmRequest::DiskCommand {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001448 disk_index,
Jakub Staronecf81e02019-04-11 11:43:39 -07001449 command: DiskControlCommand::Resize { new_size },
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001450 }
1451 }
1452 _ => {
1453 error!("Unknown disk subcommand '{}'", subcommand);
1454 return Err(());
1455 }
1456 };
1457
Zach Reizner78986322019-02-21 20:43:21 -08001458 vms_request(&request, args)
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001459}
1460
Jingkui Wang100e6e42019-03-08 20:41:57 -08001461enum ModifyUsbError {
1462 ArgMissing(&'static str),
1463 ArgParse(&'static str, String),
1464 ArgParseInt(&'static str, String, ParseIntError),
1465 FailedFdValidate(sys_util::Error),
1466 PathDoesNotExist(PathBuf),
1467 SocketFailed,
1468 UnexpectedResponse(VmResponse),
1469 UnknownCommand(String),
1470 UsbControl(UsbControlResult),
1471}
1472
1473impl fmt::Display for ModifyUsbError {
1474 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1475 use self::ModifyUsbError::*;
1476
1477 match self {
1478 ArgMissing(a) => write!(f, "argument missing: {}", a),
1479 ArgParse(name, value) => {
1480 write!(f, "failed to parse argument {} value `{}`", name, value)
1481 }
1482 ArgParseInt(name, value, e) => write!(
1483 f,
1484 "failed to parse integer argument {} value `{}`: {}",
1485 name, value, e
1486 ),
1487 FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
1488 PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
1489 SocketFailed => write!(f, "socket failed"),
1490 UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
1491 UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
1492 UsbControl(e) => write!(f, "{}", e),
1493 }
1494 }
1495}
1496
1497type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
1498
1499fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
1500 debug!("parse_bus_id_addr: {}", v);
1501 let mut ids = v.split(":");
1502 match (ids.next(), ids.next(), ids.next(), ids.next()) {
1503 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
1504 let bus_id = bus_id
1505 .parse::<u8>()
1506 .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
1507 let addr = addr
1508 .parse::<u8>()
1509 .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
1510 let vid = u16::from_str_radix(&vid, 16)
1511 .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
1512 let pid = u16::from_str_radix(&pid, 16)
1513 .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
1514 Ok((bus_id, addr, vid, pid))
1515 }
1516 _ => Err(ModifyUsbError::ArgParse(
1517 "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
1518 v.to_owned(),
1519 )),
1520 }
1521}
1522
1523fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> {
1524 if !path.exists() {
1525 return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
1526 }
1527 let raw_fd = path
1528 .file_name()
1529 .and_then(|fd_osstr| fd_osstr.to_str())
1530 .map_or(
1531 Err(ModifyUsbError::ArgParse(
1532 "USB_DEVICE_PATH",
1533 path.to_string_lossy().into_owned(),
1534 )),
1535 |fd_str| {
1536 fd_str.parse::<libc::c_int>().map_err(|e| {
1537 ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
1538 })
1539 },
1540 )?;
David Tolnay5fb3f512019-04-12 19:22:33 -07001541 validate_raw_fd(raw_fd).map_err(ModifyUsbError::FailedFdValidate)
Jingkui Wang100e6e42019-03-08 20:41:57 -08001542}
1543
1544fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1545 let val = args
1546 .next()
1547 .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
1548 let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
1549 let dev_path = PathBuf::from(
1550 args.next()
1551 .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
1552 );
1553 let usb_file: Option<File> = if dev_path == Path::new("-") {
1554 None
1555 } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
1556 // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
1557 // Safe because we will validate |raw_fd|.
1558 Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) })
1559 } else {
1560 Some(
1561 OpenOptions::new()
1562 .read(true)
1563 .write(true)
1564 .open(&dev_path)
1565 .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?,
1566 )
1567 };
1568
1569 let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
1570 bus,
1571 addr,
1572 vid,
1573 pid,
David Tolnay5fb3f512019-04-12 19:22:33 -07001574 fd: usb_file.map(MaybeOwnedFd::Owned),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001575 });
1576 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1577 match response {
1578 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1579 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1580 }
1581}
1582
1583fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1584 let port: u8 = args
1585 .next()
1586 .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
1587 p.parse::<u8>()
1588 .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
1589 })?;
1590 let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
1591 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1592 match response {
1593 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1594 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1595 }
1596}
1597
Zach Reizneraff94ca2019-03-18 20:58:31 -07001598fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1599 let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
1600 for (index, port) in ports.iter_mut().enumerate() {
1601 *port = index as u8
1602 }
1603 let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
Jingkui Wang100e6e42019-03-08 20:41:57 -08001604 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1605 match response {
1606 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1607 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1608 }
1609}
1610
1611fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reizneraff94ca2019-03-18 20:58:31 -07001612 if args.len() < 2 {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001613 print_help("crosvm usb",
Zach Reizneraff94ca2019-03-18 20:58:31 -07001614 "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
Jingkui Wang100e6e42019-03-08 20:41:57 -08001615 return Err(());
1616 }
1617
1618 // This unwrap will not panic because of the above length check.
1619 let command = args.next().unwrap();
1620 let result = match command.as_ref() {
1621 "attach" => usb_attach(args),
1622 "detach" => usb_detach(args),
1623 "list" => usb_list(args),
1624 other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
1625 };
1626 match result {
1627 Ok(response) => {
1628 println!("{}", response);
1629 Ok(())
1630 }
1631 Err(e) => {
1632 println!("error {}", e);
1633 Err(())
1634 }
1635 }
1636}
1637
Zach Reiznerefe95782017-08-26 18:05:48 -07001638fn print_usage() {
1639 print_help("crosvm", "[stop|run]", &[]);
1640 println!("Commands:");
1641 println!(" stop - Stops crosvm instances via their control sockets.");
1642 println!(" run - Start a new crosvm instance.");
Dylan Reid2dcb6322018-07-13 10:42:48 -07001643 println!(" create_qcow2 - Create a new qcow2 disk image file.");
Jingkui Wang100e6e42019-03-08 20:41:57 -08001644 println!(" disk - Manage attached virtual disk devices.");
1645 println!(" usb - Manage attached virtual USB devices.");
Yi Sun54305cd2020-01-04 00:19:37 +08001646 println!(" version - Show package version.");
1647}
1648
1649fn pkg_version() -> std::result::Result<(), ()> {
1650 const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
1651 const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
1652
1653 print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
1654 match PKG_VERSION {
1655 Some(v) => println!("-{}", v),
1656 None => println!(""),
1657 }
1658 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001659}
1660
Dylan Reidbfba9932018-02-05 15:51:59 -08001661fn crosvm_main() -> std::result::Result<(), ()> {
Stephen Barber56fbf092017-06-29 16:12:14 -07001662 if let Err(e) = syslog::init() {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001663 println!("failed to initialize syslog: {}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001664 return Err(());
Stephen Barber56fbf092017-06-29 16:12:14 -07001665 }
Zach Reizner639d9672017-05-01 17:57:18 -07001666
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08001667 panic_hook::set_panic_hook();
1668
Zach Reiznerefe95782017-08-26 18:05:48 -07001669 let mut args = std::env::args();
1670 if args.next().is_none() {
1671 error!("expected executable name");
Dylan Reidbfba9932018-02-05 15:51:59 -08001672 return Err(());
Zach Reizner639d9672017-05-01 17:57:18 -07001673 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001674
Zach Reizner8864cb02018-01-16 17:59:03 -08001675 // Past this point, usage of exit is in danger of leaking zombie processes.
Dylan Reidbfba9932018-02-05 15:51:59 -08001676 let ret = match args.next().as_ref().map(|a| a.as_ref()) {
1677 None => {
1678 print_usage();
1679 Ok(())
1680 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001681 Some("stop") => stop_vms(args),
Zach Reizner6a8fdd92019-01-16 14:38:41 -08001682 Some("suspend") => suspend_vms(args),
1683 Some("resume") => resume_vms(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07001684 Some("run") => run_vm(args),
1685 Some("balloon") => balloon_vms(args),
1686 Some("create_qcow2") => create_qcow2(args),
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001687 Some("disk") => disk_cmd(args),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001688 Some("usb") => modify_usb(args),
Yi Sun54305cd2020-01-04 00:19:37 +08001689 Some("version") => pkg_version(),
Zach Reiznerefe95782017-08-26 18:05:48 -07001690 Some(c) => {
1691 println!("invalid subcommand: {:?}", c);
1692 print_usage();
Dylan Reidbfba9932018-02-05 15:51:59 -08001693 Err(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001694 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001695 };
Zach Reiznerefe95782017-08-26 18:05:48 -07001696
1697 // Reap exit status from any child device processes. At this point, all devices should have been
1698 // dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
1699 // take some time for the processes to shut down.
1700 if !wait_all_children() {
1701 // We gave them a chance, and it's too late.
1702 warn!("not all child processes have exited; sending SIGKILL");
1703 if let Err(e) = kill_process_group() {
1704 // We're now at the mercy of the OS to clean up after us.
David Tolnayb4bd00f2019-02-12 17:51:26 -08001705 warn!("unable to kill all child processes: {}", e);
Zach Reiznerefe95782017-08-26 18:05:48 -07001706 }
1707 }
1708
1709 // WARNING: Any code added after this point is not guaranteed to run
1710 // since we may forcibly kill this process (and its children) above.
Dylan Reidbfba9932018-02-05 15:51:59 -08001711 ret
1712}
1713
1714fn main() {
1715 std::process::exit(if crosvm_main().is_ok() { 0 } else { 1 });
Zach Reizner639d9672017-05-01 17:57:18 -07001716}
Daniel Verkamp107edb32019-04-05 09:58:48 -07001717
1718#[cfg(test)]
1719mod tests {
1720 use super::*;
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001721 use crosvm::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH};
Daniel Verkamp107edb32019-04-05 09:58:48 -07001722
1723 #[test]
1724 fn parse_cpu_set_single() {
1725 assert_eq!(parse_cpu_set("123").expect("parse failed"), vec![123]);
1726 }
1727
1728 #[test]
1729 fn parse_cpu_set_list() {
1730 assert_eq!(
1731 parse_cpu_set("0,1,2,3").expect("parse failed"),
1732 vec![0, 1, 2, 3]
1733 );
1734 }
1735
1736 #[test]
1737 fn parse_cpu_set_range() {
1738 assert_eq!(
1739 parse_cpu_set("0-3").expect("parse failed"),
1740 vec![0, 1, 2, 3]
1741 );
1742 }
1743
1744 #[test]
1745 fn parse_cpu_set_list_of_ranges() {
1746 assert_eq!(
1747 parse_cpu_set("3-4,7-9,18").expect("parse failed"),
1748 vec![3, 4, 7, 8, 9, 18]
1749 );
1750 }
1751
1752 #[test]
1753 fn parse_cpu_set_repeated() {
1754 // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
1755 assert_eq!(parse_cpu_set("1,1,1").expect("parse failed"), vec![1, 1, 1]);
1756 }
1757
1758 #[test]
1759 fn parse_cpu_set_negative() {
1760 // Negative CPU numbers are not allowed.
1761 parse_cpu_set("-3").expect_err("parse should have failed");
1762 }
1763
1764 #[test]
1765 fn parse_cpu_set_reverse_range() {
1766 // Ranges must be from low to high.
1767 parse_cpu_set("5-2").expect_err("parse should have failed");
1768 }
1769
1770 #[test]
1771 fn parse_cpu_set_open_range() {
1772 parse_cpu_set("3-").expect_err("parse should have failed");
1773 }
1774
1775 #[test]
1776 fn parse_cpu_set_extra_comma() {
1777 parse_cpu_set("0,1,2,").expect_err("parse should have failed");
1778 }
Trent Begin17ccaad2019-04-17 13:51:25 -06001779
1780 #[test]
1781 fn parse_serial_vaild() {
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001782 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1783 .expect("parse should have succeded");
Trent Begin17ccaad2019-04-17 13:51:25 -06001784 }
1785
1786 #[test]
Trent Begin923bab02019-06-17 13:48:06 -06001787 fn parse_serial_valid_no_num() {
1788 parse_serial_options("type=syslog").expect("parse should have succeded");
1789 }
1790
1791 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06001792 fn parse_serial_invalid_type() {
1793 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1794 }
1795
1796 #[test]
1797 fn parse_serial_invalid_num_upper() {
1798 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1799 }
1800
1801 #[test]
1802 fn parse_serial_invalid_num_lower() {
1803 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1804 }
1805
1806 #[test]
1807 fn parse_serial_invalid_num_string() {
1808 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1809 }
1810
1811 #[test]
1812 fn parse_serial_invalid_option() {
1813 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1814 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001815
1816 #[test]
1817 fn parse_serial_invalid_two_stdin() {
1818 let mut config = Config::default();
1819 set_argument(&mut config, "serial", Some("num=1,type=stdout,stdin=true"))
1820 .expect("should parse the first serial argument");
1821 set_argument(&mut config, "serial", Some("num=2,type=stdout,stdin=true"))
1822 .expect_err("should fail to parse a second serial port connected to stdin");
1823 }
Dmitry Torokhov458bb642019-12-13 11:47:52 -08001824
1825 #[test]
1826 fn parse_plugin_mount_valid() {
1827 let mut config = Config::default();
1828 set_argument(
1829 &mut config,
1830 "plugin-mount",
1831 Some("/dev/null:/dev/zero:true"),
1832 )
1833 .expect("parse should succeed");
1834 assert_eq!(config.plugin_mounts[0].src, PathBuf::from("/dev/null"));
1835 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/zero"));
1836 assert_eq!(config.plugin_mounts[0].writable, true);
1837 }
1838
1839 #[test]
1840 fn parse_plugin_mount_valid_shorthand() {
1841 let mut config = Config::default();
1842 set_argument(&mut config, "plugin-mount", Some("/dev/null")).expect("parse should succeed");
1843 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/null"));
1844 assert_eq!(config.plugin_mounts[0].writable, false);
1845 set_argument(&mut config, "plugin-mount", Some("/dev/null:/dev/zero"))
1846 .expect("parse should succeed");
1847 assert_eq!(config.plugin_mounts[1].dst, PathBuf::from("/dev/zero"));
1848 assert_eq!(config.plugin_mounts[1].writable, false);
1849 set_argument(&mut config, "plugin-mount", Some("/dev/null::true"))
1850 .expect("parse should succeed");
1851 assert_eq!(config.plugin_mounts[2].dst, PathBuf::from("/dev/null"));
1852 assert_eq!(config.plugin_mounts[2].writable, true);
1853 }
1854
1855 #[test]
1856 fn parse_plugin_mount_invalid() {
1857 let mut config = Config::default();
1858 set_argument(&mut config, "plugin-mount", Some("")).expect_err("parse should fail");
1859 set_argument(
1860 &mut config,
1861 "plugin-mount",
1862 Some("/dev/null:/dev/null:true:false"),
1863 )
1864 .expect_err("parse should fail because too many arguments");
1865 set_argument(&mut config, "plugin-mount", Some("null:/dev/null:true"))
1866 .expect_err("parse should fail because source is not absolute");
1867 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:true"))
1868 .expect_err("parse should fail because source is not absolute");
1869 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:blah"))
1870 .expect_err("parse should fail because flag is not boolean");
1871 }
1872
1873 #[test]
1874 fn parse_plugin_gid_map_valid() {
1875 let mut config = Config::default();
1876 set_argument(&mut config, "plugin-gid-map", Some("1:2:3")).expect("parse should succeed");
1877 assert_eq!(config.plugin_gid_maps[0].inner, 1);
1878 assert_eq!(config.plugin_gid_maps[0].outer, 2);
1879 assert_eq!(config.plugin_gid_maps[0].count, 3);
1880 }
1881
1882 #[test]
1883 fn parse_plugin_gid_map_valid_shorthand() {
1884 let mut config = Config::default();
1885 set_argument(&mut config, "plugin-gid-map", Some("1")).expect("parse should succeed");
1886 assert_eq!(config.plugin_gid_maps[0].inner, 1);
1887 assert_eq!(config.plugin_gid_maps[0].outer, 1);
1888 assert_eq!(config.plugin_gid_maps[0].count, 1);
1889 set_argument(&mut config, "plugin-gid-map", Some("1:2")).expect("parse should succeed");
1890 assert_eq!(config.plugin_gid_maps[1].inner, 1);
1891 assert_eq!(config.plugin_gid_maps[1].outer, 2);
1892 assert_eq!(config.plugin_gid_maps[1].count, 1);
1893 set_argument(&mut config, "plugin-gid-map", Some("1::3")).expect("parse should succeed");
1894 assert_eq!(config.plugin_gid_maps[2].inner, 1);
1895 assert_eq!(config.plugin_gid_maps[2].outer, 1);
1896 assert_eq!(config.plugin_gid_maps[2].count, 3);
1897 }
1898
1899 #[test]
1900 fn parse_plugin_gid_map_invalid() {
1901 let mut config = Config::default();
1902 set_argument(&mut config, "plugin-gid-map", Some("")).expect_err("parse should fail");
1903 set_argument(&mut config, "plugin-gid-map", Some("1:2:3:4"))
1904 .expect_err("parse should fail because too many arguments");
1905 set_argument(&mut config, "plugin-gid-map", Some("blah:2:3"))
1906 .expect_err("parse should fail because inner is not a number");
1907 set_argument(&mut config, "plugin-gid-map", Some("1:blah:3"))
1908 .expect_err("parse should fail because outer is not a number");
1909 set_argument(&mut config, "plugin-gid-map", Some("1:2:blah"))
1910 .expect_err("parse should fail because count is not a number");
1911 }
Kaiyi Libccb4eb2020-02-06 17:53:11 -08001912
1913 #[test]
1914 fn single_touch_spec_and_track_pad_spec_default_size() {
1915 let mut config = Config::default();
1916 config
1917 .executable_path
1918 .replace(Executable::Kernel(PathBuf::from("kernel")));
1919 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
1920 set_argument(&mut config, "trackpad", Some("/dev/single-touch-test")).unwrap();
1921 validate_arguments(&mut config).unwrap();
1922 assert_eq!(
1923 config.virtio_single_touch.unwrap().get_size(),
1924 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
1925 );
1926 assert_eq!(
1927 config.virtio_trackpad.unwrap().get_size(),
1928 (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
1929 );
1930 }
1931
1932 #[cfg(feature = "gpu")]
1933 #[test]
1934 fn single_touch_spec_default_size_from_gpu() {
1935 let width = 12345u32;
1936 let height = 54321u32;
1937 let mut config = Config::default();
1938 config
1939 .executable_path
1940 .replace(Executable::Kernel(PathBuf::from("kernel")));
1941 set_argument(&mut config, "single-touch", Some("/dev/single-touch-test")).unwrap();
1942 set_argument(
1943 &mut config,
1944 "gpu",
1945 Some(&format!("width={},height={}", width, height)),
1946 )
1947 .unwrap();
1948 validate_arguments(&mut config).unwrap();
1949 assert_eq!(
1950 config.virtio_single_touch.unwrap().get_size(),
1951 (width, height)
1952 );
1953 }
1954
1955 #[test]
1956 fn single_touch_spec_and_track_pad_spec_with_size() {
1957 let width = 12345u32;
1958 let height = 54321u32;
1959 let mut config = Config::default();
1960 config
1961 .executable_path
1962 .replace(Executable::Kernel(PathBuf::from("kernel")));
1963 set_argument(
1964 &mut config,
1965 "single-touch",
1966 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
1967 )
1968 .unwrap();
1969 set_argument(
1970 &mut config,
1971 "trackpad",
1972 Some(&format!("/dev/single-touch-test:{}:{}", width, height)),
1973 )
1974 .unwrap();
1975 validate_arguments(&mut config).unwrap();
1976 assert_eq!(
1977 config.virtio_single_touch.unwrap().get_size(),
1978 (width, height)
1979 );
1980 assert_eq!(config.virtio_trackpad.unwrap().get_size(), (width, height));
1981 }
1982
1983 #[cfg(feature = "gpu")]
1984 #[test]
1985 fn single_touch_spec_with_size_independent_from_gpu() {
1986 let touch_width = 12345u32;
1987 let touch_height = 54321u32;
1988 let display_width = 1234u32;
1989 let display_height = 5432u32;
1990 let mut config = Config::default();
1991 config
1992 .executable_path
1993 .replace(Executable::Kernel(PathBuf::from("kernel")));
1994 set_argument(
1995 &mut config,
1996 "single-touch",
1997 Some(&format!(
1998 "/dev/single-touch-test:{}:{}",
1999 touch_width, touch_height
2000 )),
2001 )
2002 .unwrap();
2003 set_argument(
2004 &mut config,
2005 "gpu",
2006 Some(&format!(
2007 "width={},height={}",
2008 display_width, display_height
2009 )),
2010 )
2011 .unwrap();
2012 validate_arguments(&mut config).unwrap();
2013 assert_eq!(
2014 config.virtio_single_touch.unwrap().get_size(),
2015 (touch_width, touch_height)
2016 );
2017 }
Daniel Verkamp107edb32019-04-05 09:58:48 -07002018}