blob: e053082aeca03bb8829ac14294a8c7f8c214c348 [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")]
25use devices::virtio::gpu::{GpuParameters, DEFAULT_GPU_PARAMS};
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> {
117 let mut gpu_params = DEFAULT_GPU_PARAMS;
118
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 {
Jason Macnakbf195582019-11-20 16:25:49 -0800127 "egl" => match v {
128 "true" | "" => {
129 gpu_params.renderer_use_egl = true;
130 }
131 "false" => {
132 gpu_params.renderer_use_egl = false;
133 }
134 _ => {
135 return Err(argument::Error::InvalidValue {
136 value: v.to_string(),
137 expected: "gpu parameter 'egl' should be a boolean",
138 });
139 }
140 },
141 "gles" => match v {
142 "true" | "" => {
143 gpu_params.renderer_use_gles = true;
144 }
145 "false" => {
146 gpu_params.renderer_use_gles = false;
147 }
148 _ => {
149 return Err(argument::Error::InvalidValue {
150 value: v.to_string(),
151 expected: "gpu parameter 'gles' should be a boolean",
152 });
153 }
154 },
155 "glx" => match v {
156 "true" | "" => {
157 gpu_params.renderer_use_glx = true;
158 }
159 "false" => {
160 gpu_params.renderer_use_glx = false;
161 }
162 _ => {
163 return Err(argument::Error::InvalidValue {
164 value: v.to_string(),
165 expected: "gpu parameter 'glx' should be a boolean",
166 });
167 }
168 },
169 "surfaceless" => match v {
170 "true" | "" => {
171 gpu_params.renderer_use_surfaceless = true;
172 }
173 "false" => {
174 gpu_params.renderer_use_surfaceless = false;
175 }
176 _ => {
177 return Err(argument::Error::InvalidValue {
178 value: v.to_string(),
179 expected: "gpu parameter 'surfaceless' should be a boolean",
180 });
181 }
182 },
Jason Macnakcc7070b2019-11-06 14:48:12 -0800183 "width" => {
184 gpu_params.display_width =
185 v.parse::<u32>()
186 .map_err(|_| argument::Error::InvalidValue {
187 value: v.to_string(),
188 expected: "gpu parameter 'width' must be a valid integer",
189 })?;
190 }
191 "height" => {
192 gpu_params.display_height =
193 v.parse::<u32>()
194 .map_err(|_| argument::Error::InvalidValue {
195 value: v.to_string(),
196 expected: "gpu parameter 'height' must be a valid integer",
197 })?;
198 }
199 "" => {}
200 _ => {
201 return Err(argument::Error::UnknownArgument(format!(
202 "gpu parameter {}",
203 k
204 )));
205 }
206 }
207 }
208 }
209
210 Ok(gpu_params)
211}
212
Trent Begin17ccaad2019-04-17 13:51:25 -0600213fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
214 let mut serial_setting = SerialParameters {
215 type_: SerialType::Sink,
216 path: None,
Trent Begin923bab02019-06-17 13:48:06 -0600217 num: 1,
Trent Begin17ccaad2019-04-17 13:51:25 -0600218 console: false,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700219 stdin: false,
Trent Begin17ccaad2019-04-17 13:51:25 -0600220 };
221
222 let opts = s
223 .split(",")
224 .map(|frag| frag.split("="))
225 .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
226
227 for (k, v) in opts {
228 match k {
229 "type" => {
230 serial_setting.type_ = v
231 .parse::<SerialType>()
232 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
233 }
234 "num" => {
235 let num = v.parse::<u8>().map_err(|e| {
236 argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
237 })?;
238 if num < 1 || num > 4 {
239 return Err(argument::Error::InvalidValue {
240 value: num.to_string(),
241 expected: "Serial port num must be between 1 - 4",
242 });
243 }
244 serial_setting.num = num;
245 }
246 "console" => {
247 serial_setting.console = v.parse::<bool>().map_err(|e| {
248 argument::Error::Syntax(format!(
249 "serial device console is not parseable: {}",
250 e
251 ))
252 })?
253 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700254 "stdin" => {
255 serial_setting.stdin = v.parse::<bool>().map_err(|e| {
256 argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e))
257 })?
258 }
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700259 "path" => serial_setting.path = Some(PathBuf::from(v)),
Trent Begin17ccaad2019-04-17 13:51:25 -0600260 _ => {
261 return Err(argument::Error::UnknownArgument(format!(
262 "serial parameter {}",
263 k
264 )));
265 }
266 }
267 }
268
269 Ok(serial_setting)
270}
271
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800272fn parse_plugin_mount_option(value: &str) -> argument::Result<BindMount> {
273 let components: Vec<&str> = value.split(":").collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800274 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800275 return Err(argument::Error::InvalidValue {
276 value: value.to_owned(),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800277 expected: "`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800278 });
279 }
280
281 let src = PathBuf::from(components[0]);
282 if src.is_relative() {
283 return Err(argument::Error::InvalidValue {
284 value: components[0].to_owned(),
285 expected: "the source path for `plugin-mount` must be absolute",
286 });
287 }
288 if !src.exists() {
289 return Err(argument::Error::InvalidValue {
290 value: components[0].to_owned(),
291 expected: "the source path for `plugin-mount` does not exist",
292 });
293 }
294
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800295 let dst = PathBuf::from(match components.get(1) {
296 None | Some(&"") => components[0],
297 Some(path) => path,
298 });
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800299 if dst.is_relative() {
300 return Err(argument::Error::InvalidValue {
301 value: components[1].to_owned(),
302 expected: "the destination path for `plugin-mount` must be absolute",
303 });
304 }
305
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800306 let writable: bool = match components.get(2) {
307 None => false,
308 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800309 value: components[2].to_owned(),
310 expected: "the <writable> component for `plugin-mount` is not valid bool",
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800311 })?,
312 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800313
314 Ok(BindMount { src, dst, writable })
315}
316
317fn parse_plugin_gid_map_option(value: &str) -> argument::Result<GidMap> {
318 let components: Vec<&str> = value.split(":").collect();
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800319 if components.is_empty() || components.len() > 3 || components[0].is_empty() {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800320 return Err(argument::Error::InvalidValue {
321 value: value.to_owned(),
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800322 expected:
323 "`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800324 });
325 }
326
327 let inner: libc::gid_t = components[0]
328 .parse()
329 .map_err(|_| argument::Error::InvalidValue {
330 value: components[0].to_owned(),
331 expected: "the <inner> component for `plugin-gid-map` is not valid gid",
332 })?;
333
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800334 let outer: libc::gid_t = match components.get(1) {
335 None | Some(&"") => inner,
336 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800337 value: components[1].to_owned(),
338 expected: "the <outer> component for `plugin-gid-map` is not valid gid",
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800339 })?,
340 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800341
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800342 let count: u32 = match components.get(2) {
343 None => 1,
344 Some(s) => s.parse().map_err(|_| argument::Error::InvalidValue {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800345 value: components[2].to_owned(),
346 expected: "the <count> component for `plugin-gid-map` is not valid number",
Dmitry Torokhov458bb642019-12-13 11:47:52 -0800347 })?,
348 };
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800349
350 Ok(GidMap {
351 inner,
352 outer,
353 count,
354 })
355}
356
Zach Reiznerefe95782017-08-26 18:05:48 -0700357fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
358 match name {
359 "" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700360 if cfg.executable_path.is_some() {
361 return Err(argument::Error::TooManyArguments(format!(
362 "A VM executable was already specified: {:?}",
363 cfg.executable_path
364 )));
Zach Reiznerefe95782017-08-26 18:05:48 -0700365 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700366 let kernel_path = PathBuf::from(value.unwrap());
367 if !kernel_path.exists() {
368 return Err(argument::Error::InvalidValue {
369 value: value.unwrap().to_owned(),
370 expected: "this kernel path does not exist",
371 });
372 }
373 cfg.executable_path = Some(Executable::Kernel(kernel_path));
Zach Reiznerefe95782017-08-26 18:05:48 -0700374 }
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800375 "android-fstab" => {
376 if cfg.android_fstab.is_some()
377 && !cfg.android_fstab.as_ref().unwrap().as_os_str().is_empty()
378 {
379 return Err(argument::Error::TooManyArguments(
380 "expected exactly one android fstab path".to_owned(),
381 ));
382 } else {
383 let android_fstab = PathBuf::from(value.unwrap());
384 if !android_fstab.exists() {
385 return Err(argument::Error::InvalidValue {
386 value: value.unwrap().to_owned(),
387 expected: "this android fstab path does not exist",
388 });
389 }
390 cfg.android_fstab = Some(android_fstab);
391 }
392 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700393 "params" => {
Zach Reiznerbb678712018-01-30 18:13:04 -0800394 cfg.params.push(value.unwrap().to_owned());
Zach Reiznerefe95782017-08-26 18:05:48 -0700395 }
396 "cpus" => {
397 if cfg.vcpu_count.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700398 return Err(argument::Error::TooManyArguments(
399 "`cpus` already given".to_owned(),
400 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700401 }
402 cfg.vcpu_count =
Zach Reizner55a9e502018-10-03 10:22:32 -0700403 Some(
404 value
405 .unwrap()
406 .parse()
407 .map_err(|_| argument::Error::InvalidValue {
408 value: value.unwrap().to_owned(),
409 expected: "this value for `cpus` needs to be integer",
410 })?,
411 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700412 }
Daniel Verkamp107edb32019-04-05 09:58:48 -0700413 "cpu-affinity" => {
414 if cfg.vcpu_affinity.len() != 0 {
415 return Err(argument::Error::TooManyArguments(
416 "`cpu-affinity` already given".to_owned(),
417 ));
418 }
419 cfg.vcpu_affinity = parse_cpu_set(value.unwrap())?;
420 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700421 "mem" => {
422 if cfg.memory.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700423 return Err(argument::Error::TooManyArguments(
424 "`mem` already given".to_owned(),
425 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700426 }
427 cfg.memory =
Zach Reizner55a9e502018-10-03 10:22:32 -0700428 Some(
429 value
430 .unwrap()
431 .parse()
432 .map_err(|_| argument::Error::InvalidValue {
433 value: value.unwrap().to_owned(),
434 expected: "this value for `mem` needs to be integer",
435 })?,
436 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700437 }
paulhsiaf052cfe2019-01-22 15:22:25 +0800438 "cras-audio" => {
439 cfg.cras_audio = true;
440 }
paulhsia580d4182019-05-24 16:53:55 +0800441 "cras-capture" => {
442 cfg.cras_capture = true;
443 }
Dylan Reid3082e8e2019-01-07 10:33:48 -0800444 "null-audio" => {
445 cfg.null_audio = true;
446 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600447 "serial" => {
448 let serial_params = parse_serial_options(value.unwrap())?;
449 let num = serial_params.num;
450 if cfg.serial_parameters.contains_key(&num) {
451 return Err(argument::Error::TooManyArguments(format!(
452 "serial num {}",
453 num
454 )));
455 }
456
457 if serial_params.console {
Jakub Staronb6515a92019-06-05 15:18:25 -0700458 for params in cfg.serial_parameters.values() {
Trent Begin17ccaad2019-04-17 13:51:25 -0600459 if params.console {
460 return Err(argument::Error::TooManyArguments(format!(
461 "serial device {} already set as console",
462 params.num
463 )));
464 }
465 }
466 }
467
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700468 if serial_params.stdin {
469 if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) {
470 return Err(argument::Error::TooManyArguments(format!(
471 "serial device {} already connected to standard input",
472 previous_stdin.num
473 )));
474 }
475 }
476
Trent Begin17ccaad2019-04-17 13:51:25 -0600477 cfg.serial_parameters.insert(num, serial_params);
478 }
479 "syslog-tag" => {
480 if cfg.syslog_tag.is_some() {
481 return Err(argument::Error::TooManyArguments(
482 "`syslog-tag` already given".to_owned(),
483 ));
484 }
485 syslog::set_proc_name(value.unwrap());
486 cfg.syslog_tag = Some(value.unwrap().to_owned());
487 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700488 "root" | "rwroot" | "disk" | "rwdisk" | "qcow" | "rwqcow" => {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800489 let param = value.unwrap();
490 let mut components = param.split(',');
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700491 let read_only = !name.starts_with("rw");
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800492 let disk_path =
493 PathBuf::from(
494 components
495 .next()
496 .ok_or_else(|| argument::Error::InvalidValue {
497 value: param.to_owned(),
498 expected: "missing disk path",
499 })?,
500 );
Zach Reiznerefe95782017-08-26 18:05:48 -0700501 if !disk_path.exists() {
502 return Err(argument::Error::InvalidValue {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800503 value: param.to_owned(),
Zach Reizner55a9e502018-10-03 10:22:32 -0700504 expected: "this disk path does not exist",
505 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700506 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700507 if name.ends_with("root") {
Daniel Verkampaac28132018-10-15 14:58:48 -0700508 if cfg.disks.len() >= 26 {
Zach Reizner55a9e502018-10-03 10:22:32 -0700509 return Err(argument::Error::TooManyArguments(
510 "ran out of letters for to assign to root disk".to_owned(),
511 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700512 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700513 cfg.params.push(format!(
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700514 "root=/dev/vd{} {}",
515 char::from(b'a' + cfg.disks.len() as u8),
516 if read_only { "ro" } else { "rw" }
Zach Reizner55a9e502018-10-03 10:22:32 -0700517 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700518 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800519
520 let mut disk = DiskOption {
Zach Reizner55a9e502018-10-03 10:22:32 -0700521 path: disk_path,
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700522 read_only,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800523 sparse: true,
Daniel Verkamp27672232019-12-06 17:26:55 +1100524 block_size: 512,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800525 };
526
527 for opt in components {
528 let mut o = opt.splitn(2, '=');
529 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
530 value: opt.to_owned(),
531 expected: "disk options must not be empty",
532 })?;
533 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
534 value: opt.to_owned(),
535 expected: "disk options must be of the form `kind=value`",
536 })?;
537
538 match kind {
539 "sparse" => {
540 let sparse = value.parse().map_err(|_| argument::Error::InvalidValue {
541 value: value.to_owned(),
542 expected: "`sparse` must be a boolean",
543 })?;
544 disk.sparse = sparse;
545 }
Daniel Verkamp27672232019-12-06 17:26:55 +1100546 "block_size" => {
547 let block_size =
548 value.parse().map_err(|_| argument::Error::InvalidValue {
549 value: value.to_owned(),
550 expected: "`block_size` must be an integer",
551 })?;
552 disk.block_size = block_size;
553 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800554 _ => {
555 return Err(argument::Error::InvalidValue {
556 value: kind.to_owned(),
557 expected: "unrecognized disk option",
558 });
559 }
560 }
561 }
562
563 cfg.disks.push(disk);
Zach Reiznerefe95782017-08-26 18:05:48 -0700564 }
Jakub Starona3411ea2019-04-24 10:55:25 -0700565 "pmem-device" | "rw-pmem-device" => {
566 let disk_path = PathBuf::from(value.unwrap());
567 if !disk_path.exists() {
568 return Err(argument::Error::InvalidValue {
569 value: value.unwrap().to_owned(),
570 expected: "this disk path does not exist",
571 });
572 }
573
574 cfg.pmem_devices.push(DiskOption {
575 path: disk_path,
576 read_only: !name.starts_with("rw"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800577 sparse: false,
Daniel Verkamp27672232019-12-06 17:26:55 +1100578 block_size: sys_util::pagesize() as u32,
Jakub Starona3411ea2019-04-24 10:55:25 -0700579 });
580 }
Kansho Nishida282115b2019-12-18 13:13:14 +0900581 "pstore" => {
582 if cfg.pstore.is_some() {
583 return Err(argument::Error::TooManyArguments(
584 "`pstore` already given".to_owned(),
585 ));
586 }
587
588 let value = value.unwrap();
589 let components: Vec<&str> = value.split(',').collect();
590 if components.len() != 2 {
591 return Err(argument::Error::InvalidValue {
592 value: value.to_owned(),
593 expected: "pstore must have exactly 2 components: path=<path>,size=<size>",
594 });
595 }
596 cfg.pstore = Some(Pstore {
597 path: {
598 if components[0].len() <= 5 || !components[0].starts_with("path=") {
599 return Err(argument::Error::InvalidValue {
600 value: components[0].to_owned(),
601 expected: "pstore path must follow with `path=`",
602 });
603 };
604 PathBuf::from(&components[0][5..])
605 },
606 size: {
607 if components[1].len() <= 5 || !components[1].starts_with("size=") {
608 return Err(argument::Error::InvalidValue {
609 value: components[1].to_owned(),
610 expected: "pstore size must follow with `size=`",
611 });
612 };
613 components[1][5..]
614 .parse()
615 .map_err(|_| argument::Error::InvalidValue {
616 value: value.to_owned(),
617 expected: "pstore size must be an integer",
618 })?
619 },
620 });
621 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700622 "host_ip" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700623 if cfg.host_ip.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700624 return Err(argument::Error::TooManyArguments(
625 "`host_ip` already given".to_owned(),
626 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700627 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700628 cfg.host_ip =
Zach Reizner55a9e502018-10-03 10:22:32 -0700629 Some(
630 value
631 .unwrap()
632 .parse()
633 .map_err(|_| argument::Error::InvalidValue {
634 value: value.unwrap().to_owned(),
635 expected: "`host_ip` needs to be in the form \"x.x.x.x\"",
636 })?,
637 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700638 }
639 "netmask" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700640 if cfg.netmask.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700641 return Err(argument::Error::TooManyArguments(
642 "`netmask` already given".to_owned(),
643 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700644 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700645 cfg.netmask =
Zach Reizner55a9e502018-10-03 10:22:32 -0700646 Some(
647 value
648 .unwrap()
649 .parse()
650 .map_err(|_| argument::Error::InvalidValue {
651 value: value.unwrap().to_owned(),
652 expected: "`netmask` needs to be in the form \"x.x.x.x\"",
653 })?,
654 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700655 }
656 "mac" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700657 if cfg.mac_address.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700658 return Err(argument::Error::TooManyArguments(
659 "`mac` already given".to_owned(),
660 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700661 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700662 cfg.mac_address =
Zach Reizner55a9e502018-10-03 10:22:32 -0700663 Some(
664 value
665 .unwrap()
666 .parse()
667 .map_err(|_| argument::Error::InvalidValue {
668 value: value.unwrap().to_owned(),
669 expected: "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
670 })?,
671 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700672 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700673 "wayland-sock" => {
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900674 let mut components = value.unwrap().split(',');
675 let path =
676 PathBuf::from(
677 components
678 .next()
679 .ok_or_else(|| argument::Error::InvalidValue {
680 value: value.unwrap().to_owned(),
681 expected: "missing socket path",
682 })?,
683 );
684 let mut name = "";
685 for c in components {
686 let mut kv = c.splitn(2, '=');
687 let (kind, value) = match (kv.next(), kv.next()) {
688 (Some(kind), Some(value)) => (kind, value),
689 _ => {
690 return Err(argument::Error::InvalidValue {
691 value: c.to_owned(),
692 expected: "option must be of the form `kind=value`",
693 })
694 }
695 };
696 match kind {
697 "name" => name = value,
698 _ => {
699 return Err(argument::Error::InvalidValue {
700 value: kind.to_owned(),
701 expected: "unrecognized option",
702 })
703 }
704 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700705 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900706 if cfg.wayland_socket_paths.contains_key(name) {
707 return Err(argument::Error::TooManyArguments(format!(
708 "wayland socket name already used: '{}'",
709 name
710 )));
Stephen Barber28a5a612017-10-20 17:15:30 -0700711 }
Ryo Hashimoto0b788de2019-12-10 17:14:13 +0900712 cfg.wayland_socket_paths.insert(name.to_string(), path);
Stephen Barber28a5a612017-10-20 17:15:30 -0700713 }
David Reveman52ba4e52018-04-22 21:42:09 -0400714 #[cfg(feature = "wl-dmabuf")]
Daniel Verkampaac28132018-10-15 14:58:48 -0700715 "wayland-dmabuf" => cfg.wayland_dmabuf = true,
Zach Reizner0f2cfb02019-06-19 17:46:03 -0700716 "x-display" => {
717 if cfg.x_display.is_some() {
718 return Err(argument::Error::TooManyArguments(
719 "`x-display` already given".to_owned(),
720 ));
721 }
722 cfg.x_display = Some(value.unwrap().to_owned());
723 }
Zach Reizner65b98f12019-11-22 17:34:58 -0800724 "display-window-keyboard" => {
725 cfg.display_window_keyboard = true;
726 }
727 "display-window-mouse" => {
728 cfg.display_window_mouse = true;
729 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700730 "socket" => {
731 if cfg.socket_path.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700732 return Err(argument::Error::TooManyArguments(
733 "`socket` already given".to_owned(),
734 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700735 }
736 let mut socket_path = PathBuf::from(value.unwrap());
737 if socket_path.is_dir() {
738 socket_path.push(format!("crosvm-{}.sock", getpid()));
739 }
740 if socket_path.exists() {
741 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700742 value: socket_path.to_string_lossy().into_owned(),
743 expected: "this socket path already exists",
744 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700745 }
746 cfg.socket_path = Some(socket_path);
747 }
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700748 "disable-sandbox" => {
Lepton Wu9105e9f2019-03-14 11:38:31 -0700749 cfg.sandbox = false;
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700750 }
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700751 "cid" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700752 if cfg.cid.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700753 return Err(argument::Error::TooManyArguments(
754 "`cid` alread given".to_owned(),
755 ));
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700756 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700757 cfg.cid = Some(
758 value
759 .unwrap()
760 .parse()
761 .map_err(|_| argument::Error::InvalidValue {
762 value: value.unwrap().to_owned(),
763 expected: "this value for `cid` must be an unsigned integer",
764 })?,
765 );
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700766 }
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700767 "shared-dir" => {
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900768 // This is formatted as multiple fields, each separated by ":". The first 2 fields are
769 // fixed (src:tag). The rest may appear in any order:
770 //
771 // * type=TYPE - must be one of "p9" or "fs" (default: p9)
772 // * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
773 // (default: "0 <current euid> 1")
774 // * gidmap=GIDMAP - a gid map in the same format as uidmap
775 // (default: "0 <current egid> 1")
776 // * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
777 // and directory contents should be considered valid (default: 5)
778 // * cache=CACHE - one of "never", "always", or "auto" (default: auto)
779 // * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700780 let param = value.unwrap();
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900781 let mut components = param.split(':');
Zach Reizner55a9e502018-10-03 10:22:32 -0700782 let src =
783 PathBuf::from(
784 components
785 .next()
786 .ok_or_else(|| argument::Error::InvalidValue {
787 value: param.to_owned(),
788 expected: "missing source path for `shared-dir`",
789 })?,
790 );
791 let tag = components
792 .next()
793 .ok_or_else(|| argument::Error::InvalidValue {
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700794 value: param.to_owned(),
795 expected: "missing tag for `shared-dir`",
David Tolnay2bac1e72018-12-12 14:33:42 -0800796 })?
797 .to_owned();
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700798
799 if !src.is_dir() {
800 return Err(argument::Error::InvalidValue {
801 value: param.to_owned(),
802 expected: "source path for `shared-dir` must be a directory",
803 });
804 }
805
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +0900806 let mut shared_dir = SharedDir {
807 src,
808 tag,
809 ..Default::default()
810 };
811 for opt in components {
812 let mut o = opt.splitn(2, '=');
813 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
814 value: opt.to_owned(),
815 expected: "`shared-dir` options must not be empty",
816 })?;
817 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
818 value: opt.to_owned(),
819 expected: "`shared-dir` options must be of the form `kind=value`",
820 })?;
821
822 match kind {
823 "type" => {
824 shared_dir.kind =
825 value.parse().map_err(|_| argument::Error::InvalidValue {
826 value: value.to_owned(),
827 expected: "`type` must be one of `fs` or `9p`",
828 })?
829 }
830 "uidmap" => shared_dir.uid_map = value.into(),
831 "gidmap" => shared_dir.gid_map = value.into(),
832 "timeout" => {
833 let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
834 value: value.to_owned(),
835 expected: "`timeout` must be an integer",
836 })?;
837
838 let dur = Duration::from_secs(seconds);
839 shared_dir.cfg.entry_timeout = dur.clone();
840 shared_dir.cfg.attr_timeout = dur;
841 }
842 "cache" => {
843 let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
844 value: value.to_owned(),
845 expected: "`cache` must be one of `never`, `always`, or `auto`",
846 })?;
847 shared_dir.cfg.cache_policy = policy;
848 }
849 "writeback" => {
850 let writeback =
851 value.parse().map_err(|_| argument::Error::InvalidValue {
852 value: value.to_owned(),
853 expected: "`writeback` must be a boolean",
854 })?;
855 shared_dir.cfg.writeback = writeback;
856 }
857 _ => {
858 return Err(argument::Error::InvalidValue {
859 value: kind.to_owned(),
860 expected: "unrecognized option for `shared-dir`",
861 })
862 }
863 }
864 }
865 cfg.shared_dirs.push(shared_dir);
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700866 }
Dylan Reide026ef02017-10-02 19:03:52 -0700867 "seccomp-policy-dir" => {
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700868 // `value` is Some because we are in this match so it's safe to unwrap.
Daniel Verkampaac28132018-10-15 14:58:48 -0700869 cfg.seccomp_policy_dir = PathBuf::from(value.unwrap());
Zach Reizner55a9e502018-10-03 10:22:32 -0700870 }
Zach Reizner44863792019-06-26 14:22:08 -0700871 "seccomp-log-failures" => {
Matt Delco45caf912019-11-13 08:11:09 -0800872 // A side-effect of this flag is to force the use of .policy files
873 // instead of .bpf files (.bpf files are expected and assumed to be
874 // compiled to fail an unpermitted action with "trap").
875 // Normally crosvm will first attempt to use a .bpf file, and if
876 // not present it will then try to use a .policy file. It's up
877 // to the build to decide which of these files is present for
878 // crosvm to use (for CrOS the build will use .bpf files for
879 // x64 builds and .policy files for arm/arm64 builds).
880 //
881 // This flag will likely work as expected for builds that use
882 // .policy files. For builds that only use .bpf files the initial
883 // result when using this flag is likely to be a file-not-found
884 // error (since the .policy files are not present).
885 // For .bpf builds you can either 1) manually add the .policy files,
886 // or 2) do not use this command-line parameter and instead
887 // temporarily change the build by passing "log" rather than
888 // "trap" as the "--default-action" to compile_seccomp_policy.py.
Zach Reizner44863792019-06-26 14:22:08 -0700889 cfg.seccomp_log_failures = true;
890 }
Zach Reizner8864cb02018-01-16 17:59:03 -0800891 "plugin" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700892 if cfg.executable_path.is_some() {
893 return Err(argument::Error::TooManyArguments(format!(
894 "A VM executable was already specified: {:?}",
895 cfg.executable_path
896 )));
Zach Reizner8864cb02018-01-16 17:59:03 -0800897 }
Zach Reiznercc30d582018-01-23 21:16:42 -0800898 let plugin = PathBuf::from(value.unwrap().to_owned());
899 if plugin.is_relative() {
900 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700901 value: plugin.to_string_lossy().into_owned(),
902 expected: "the plugin path must be an absolute path",
903 });
Zach Reiznercc30d582018-01-23 21:16:42 -0800904 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700905 cfg.executable_path = Some(Executable::Plugin(plugin));
Zach Reizner55a9e502018-10-03 10:22:32 -0700906 }
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -0700907 "plugin-root" => {
908 cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned()));
Zach Reizner55a9e502018-10-03 10:22:32 -0700909 }
Chirantan Ekboted41d7262018-11-16 16:37:45 -0800910 "plugin-mount" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800911 let mount = parse_plugin_mount_option(value.unwrap())?;
912 cfg.plugin_mounts.push(mount);
Chirantan Ekboted41d7262018-11-16 16:37:45 -0800913 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800914 "plugin-mount-file" => {
915 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
916 value: value.unwrap().to_owned(),
917 expected: "unable to open `plugin-mount-file` file",
918 })?;
919 let reader = BufReader::new(file);
920 for l in reader.lines() {
921 let line = l.unwrap();
Dmitry Torokhovd65265c2019-12-11 13:36:08 -0800922 let trimmed_line = line.splitn(2, '#').nth(0).unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800923 if !trimmed_line.is_empty() {
924 let mount = parse_plugin_mount_option(trimmed_line)?;
925 cfg.plugin_mounts.push(mount);
926 }
927 }
928 }
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -0800929 "plugin-gid-map" => {
Dmitry Torokhovc689fd92019-12-11 13:36:08 -0800930 let map = parse_plugin_gid_map_option(value.unwrap())?;
931 cfg.plugin_gid_maps.push(map);
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -0800932 }
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800933 "plugin-gid-map-file" => {
934 let file = File::open(value.unwrap()).map_err(|_| argument::Error::InvalidValue {
935 value: value.unwrap().to_owned(),
936 expected: "unable to open `plugin-gid-map-file` file",
937 })?;
938 let reader = BufReader::new(file);
939 for l in reader.lines() {
940 let line = l.unwrap();
Dmitry Torokhovd65265c2019-12-11 13:36:08 -0800941 let trimmed_line = line.splitn(2, '#').nth(0).unwrap().trim();
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -0800942 if !trimmed_line.is_empty() {
943 let map = parse_plugin_gid_map_option(trimmed_line)?;
944 cfg.plugin_gid_maps.push(map);
945 }
946 }
947 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700948 "vhost-net" => cfg.vhost_net = true,
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700949 "tap-fd" => {
Jorge E. Moreirab7952802019-02-12 16:43:05 -0800950 cfg.tap_fd.push(
951 value
952 .unwrap()
953 .parse()
954 .map_err(|_| argument::Error::InvalidValue {
955 value: value.unwrap().to_owned(),
956 expected: "this value for `tap-fd` must be an unsigned integer",
957 })?,
958 );
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700959 }
Jason Macnakcc7070b2019-11-06 14:48:12 -0800960 #[cfg(feature = "gpu")]
Zach Reizner3a8100a2017-09-13 19:15:43 -0700961 "gpu" => {
Jason Macnakcc7070b2019-11-06 14:48:12 -0800962 let params = parse_gpu_options(value)?;
963 cfg.gpu_parameters = Some(params);
Zach Reizner3a8100a2017-09-13 19:15:43 -0700964 }
David Tolnay43f8e212019-02-13 17:28:16 -0800965 "software-tpm" => {
966 cfg.software_tpm = true;
967 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -0800968 "single-touch" => {
969 if cfg.virtio_single_touch.is_some() {
970 return Err(argument::Error::TooManyArguments(
971 "`single-touch` already given".to_owned(),
972 ));
973 }
974 let mut it = value.unwrap().split(":");
975
976 let mut single_touch_spec =
977 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
978 if let Some(width) = it.next() {
979 single_touch_spec.width = width.trim().parse().unwrap();
980 }
981 if let Some(height) = it.next() {
982 single_touch_spec.height = height.trim().parse().unwrap();
983 }
984
985 cfg.virtio_single_touch = Some(single_touch_spec);
986 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -0800987 "trackpad" => {
988 if cfg.virtio_trackpad.is_some() {
989 return Err(argument::Error::TooManyArguments(
990 "`trackpad` already given".to_owned(),
991 ));
992 }
993 let mut it = value.unwrap().split(":");
994
995 let mut trackpad_spec =
Jorge E. Moreira99d3f082019-03-07 10:59:54 -0800996 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
Jorge E. Moreiradffec502019-01-14 18:44:49 -0800997 if let Some(width) = it.next() {
998 trackpad_spec.width = width.trim().parse().unwrap();
999 }
1000 if let Some(height) = it.next() {
1001 trackpad_spec.height = height.trim().parse().unwrap();
1002 }
1003
1004 cfg.virtio_trackpad = Some(trackpad_spec);
1005 }
1006 "mouse" => {
1007 if cfg.virtio_mouse.is_some() {
1008 return Err(argument::Error::TooManyArguments(
1009 "`mouse` already given".to_owned(),
1010 ));
1011 }
1012 cfg.virtio_mouse = Some(PathBuf::from(value.unwrap().to_owned()));
1013 }
1014 "keyboard" => {
1015 if cfg.virtio_keyboard.is_some() {
1016 return Err(argument::Error::TooManyArguments(
1017 "`keyboard` already given".to_owned(),
1018 ));
1019 }
1020 cfg.virtio_keyboard = Some(PathBuf::from(value.unwrap().to_owned()));
1021 }
1022 "evdev" => {
1023 let dev_path = PathBuf::from(value.unwrap());
1024 if !dev_path.exists() {
1025 return Err(argument::Error::InvalidValue {
1026 value: value.unwrap().to_owned(),
1027 expected: "this input device path does not exist",
1028 });
1029 }
1030 cfg.virtio_input_evdevs.push(dev_path);
1031 }
Miriam Zimmerman26ac9282019-01-29 21:21:48 -08001032 "split-irqchip" => {
1033 cfg.split_irqchip = true;
1034 }
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001035 "initrd" => {
1036 cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
1037 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001038 "bios" => {
1039 if cfg.executable_path.is_some() {
1040 return Err(argument::Error::TooManyArguments(format!(
1041 "A VM executable was already specified: {:?}",
1042 cfg.executable_path
1043 )));
1044 }
1045 cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
1046 }
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001047 "vfio" => {
1048 let vfio_path = PathBuf::from(value.unwrap());
1049 if !vfio_path.exists() {
1050 return Err(argument::Error::InvalidValue {
1051 value: value.unwrap().to_owned(),
1052 expected: "the vfio path does not exist",
1053 });
1054 }
1055 if !vfio_path.is_dir() {
1056 return Err(argument::Error::InvalidValue {
1057 value: value.unwrap().to_owned(),
1058 expected: "the vfio path should be directory",
1059 });
1060 }
1061
1062 cfg.vfio = Some(vfio_path);
1063 }
1064
Zach Reiznerefe95782017-08-26 18:05:48 -07001065 "help" => return Err(argument::Error::PrintHelp),
1066 _ => unreachable!(),
1067 }
1068 Ok(())
1069}
1070
Dylan Reidbfba9932018-02-05 15:51:59 -08001071fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reiznerefe95782017-08-26 18:05:48 -07001072 let arguments =
1073 &[Argument::positional("KERNEL", "bzImage of kernel to run"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -08001074 Argument::value("android-fstab", "PATH", "Path to Android fstab"),
Daniel Verkampe403f5c2018-12-11 16:29:26 -08001075 Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001076 Argument::short_value('p',
1077 "params",
1078 "PARAMS",
Zach Reiznerbb678712018-01-30 18:13:04 -08001079 "Extra kernel or plugin command line arguments. Can be given more than once."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001080 Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Daniel Verkamp107edb32019-04-05 09:58:48 -07001081 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 -07001082 Argument::short_value('m',
1083 "mem",
1084 "N",
1085 "Amount of guest memory in MiB. (default: 256)"),
1086 Argument::short_value('r',
1087 "root",
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001088 "PATH[,key=value[,key=value[,...]]",
1089 "Path to a root disk image followed by optional comma-separated options.
1090 Like `--disk` but adds appropriate kernel command line option.
1091 See --disk for valid options."),
1092 Argument::value("rwroot", "PATH[,key=value[,key=value[,...]]", "Path to a writable root disk image followed by optional comma-separated options.
1093 See --disk for valid options."),
1094 Argument::short_value('d', "disk", "PATH[,key=value[,key=value[,...]]", "Path to a disk image followed by optional comma-separated options.
1095 Valid keys:
Daniel Verkamp27672232019-12-06 17:26:55 +11001096 sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
1097 block_size=BYTES - Set the reported block size of the disk (default: 512)"),
Daniel Verkampf02fdd12018-10-10 17:25:14 -07001098 Argument::value("qcow", "PATH", "Path to a qcow2 disk image. (Deprecated; use --disk instead.)"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -08001099 Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
1100 See --disk for valid options."),
Daniel Verkampf02fdd12018-10-10 17:25:14 -07001101 Argument::value("rwqcow", "PATH", "Path to a writable qcow2 disk image. (Deprecated; use --rwdisk instead.)"),
Jakub Starona3411ea2019-04-24 10:55:25 -07001102 Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
1103 Argument::value("pmem-device", "PATH", "Path to a disk image."),
Kansho Nishida282115b2019-12-18 13:13:14 +09001104 Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file follewed by size."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001105 Argument::value("host_ip",
1106 "IP",
1107 "IP address to assign to host tap interface."),
1108 Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
1109 Argument::value("mac", "MAC", "MAC address for VM."),
paulhsiaf052cfe2019-01-22 15:22:25 +08001110 Argument::flag("cras-audio", "Add an audio device to the VM that plays samples through CRAS server"),
paulhsia580d4182019-05-24 16:53:55 +08001111 Argument::flag("cras-capture", "Enable capturing audio from CRAS server to the cras-audio device"),
Dylan Reid3082e8e2019-01-07 10:33:48 -08001112 Argument::flag("null-audio", "Add an audio device to the VM that plays samples to /dev/null"),
Trent Begin17ccaad2019-04-17 13:51:25 -06001113 Argument::value("serial",
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001114 "type=TYPE,[num=NUM,path=PATH,console,stdin]",
Jason Macnakcc7070b2019-11-06 14:48:12 -08001115 "Comma separated key=value pairs for setting up serial devices. Can be given more than once.
Trent Begin17ccaad2019-04-17 13:51:25 -06001116 Possible key values:
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001117 type=(stdout,syslog,sink,file) - Where to route the serial device
Trent Begin923bab02019-06-17 13:48:06 -06001118 num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -07001119 path=PATH - The path to the file to write to when type=file
Trent Begin17ccaad2019-04-17 13:51:25 -06001120 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 -07001121 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 -06001122 "),
1123 Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
Zach Reizner0f2cfb02019-06-19 17:46:03 -07001124 Argument::value("x-display", "DISPLAY", "X11 display name to use."),
Zach Reizner65b98f12019-11-22 17:34:58 -08001125 Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
1126 Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
Ryo Hashimoto0b788de2019-12-10 17:14:13 +09001127 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 -04001128 #[cfg(feature = "wl-dmabuf")]
1129 Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
Zach Reiznerefe95782017-08-26 18:05:48 -07001130 Argument::short_value('s',
1131 "socket",
1132 "PATH",
1133 "Path to put the control socket. If PATH is a directory, a name will be generated."),
Dylan Reidd0c9adc2017-10-02 19:04:50 -07001134 Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
Chirantan Ekboteebd56812018-04-16 19:32:04 -07001135 Argument::value("cid", "CID", "Context ID for virtual sockets."),
Chirantan Ekbotebd4723b2019-07-17 10:50:30 +09001136 Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE]",
1137 "Colon-separated options for configuring a directory to be shared with the VM.
1138The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
1139The remaining fields are key=value pairs that may appear in any order. Valid keys are:
1140type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
1141uidmap=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).
1142gidmap=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).
1143cache=(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.
1144timeout=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.
1145writeback=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.
1146"),
Dylan Reide026ef02017-10-02 19:03:52 -07001147 Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
Zach Reizner44863792019-06-26 14:22:08 -07001148 Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
Zach Reizner8864cb02018-01-16 17:59:03 -08001149 #[cfg(feature = "plugin")]
Zach Reiznercc30d582018-01-23 21:16:42 -08001150 Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
Daniel Verkampbd1a0842019-01-08 15:50:34 -08001151 #[cfg(feature = "plugin")]
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001152 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 -08001153 #[cfg(feature = "plugin")]
Chirantan Ekboted41d7262018-11-16 16:37:45 -08001154 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 -08001155 #[cfg(feature = "plugin")]
Dmitry Torokhov0f4c5ff2019-12-11 13:36:08 -08001156 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."),
1157 #[cfg(feature = "plugin")]
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -08001158 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 -08001159 #[cfg(feature = "plugin")]
1160 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 +00001161 Argument::flag("vhost-net", "Use vhost for networking."),
Chirantan Ekbote5f787212018-05-31 15:31:31 -07001162 Argument::value("tap-fd",
1163 "fd",
Jorge E. Moreirab7952802019-02-12 16:43:05 -08001164 "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 -07001165 #[cfg(feature = "gpu")]
Jason Macnakcc7070b2019-11-06 14:48:12 -08001166 Argument::flag_or_value("gpu",
1167 "[width=INT,height=INT]",
1168 "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
1169 Possible key values:
1170 width=INT - The width of the virtual display connected to the virtio-gpu.
1171 height=INT - The height of the virtual display connected to the virtio-gpu.
Jason Macnakbf195582019-11-20 16:25:49 -08001172 egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering.
1173 glx[=true|=false] - If the virtio-gpu backend should use a GLX context for rendering.
1174 surfaceless[=true|=false] - If the virtio-gpu backend should use a surfaceless context for rendering.
Jason Macnakcc7070b2019-11-06 14:48:12 -08001175 "),
David Tolnay43f8e212019-02-13 17:28:16 -08001176 #[cfg(feature = "tpm")]
1177 Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -08001178 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 -08001179 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 -08001180 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)."),
1181 Argument::value("mouse", "PATH", "Path to a socket from where to read mouse input events and write status updates to."),
1182 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 -08001183 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1184 Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001185 Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +08001186 Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
Zach Reiznerefe95782017-08-26 18:05:48 -07001187 Argument::short_flag('h', "help", "Print help message.")];
1188
1189 let mut cfg = Config::default();
Zach Reizner55a9e502018-10-03 10:22:32 -07001190 let match_res = set_arguments(args, &arguments[..], |name, value| {
1191 set_argument(&mut cfg, name, value)
David Tolnay2bac1e72018-12-12 14:33:42 -08001192 })
1193 .and_then(|_| {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001194 if cfg.executable_path.is_none() {
Zach Reiznerefe95782017-08-26 18:05:48 -07001195 return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
1196 }
Daniel Verkampaac28132018-10-15 14:58:48 -07001197 if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
1198 if cfg.host_ip.is_none() {
Zach Reizner55a9e502018-10-03 10:22:32 -07001199 return Err(argument::Error::ExpectedArgument(
1200 "`host_ip` missing from network config".to_owned(),
1201 ));
Zach Reiznerefe95782017-08-26 18:05:48 -07001202 }
Daniel Verkampaac28132018-10-15 14:58:48 -07001203 if cfg.netmask.is_none() {
Zach Reizner55a9e502018-10-03 10:22:32 -07001204 return Err(argument::Error::ExpectedArgument(
1205 "`netmask` missing from network config".to_owned(),
1206 ));
Zach Reiznerefe95782017-08-26 18:05:48 -07001207 }
Daniel Verkampaac28132018-10-15 14:58:48 -07001208 if cfg.mac_address.is_none() {
Zach Reizner55a9e502018-10-03 10:22:32 -07001209 return Err(argument::Error::ExpectedArgument(
1210 "`mac` missing from network config".to_owned(),
1211 ));
Zach Reiznerefe95782017-08-26 18:05:48 -07001212 }
1213 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -07001214 if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
Zach Reizner55a9e502018-10-03 10:22:32 -07001215 return Err(argument::Error::ExpectedArgument(
1216 "`plugin-root` requires `plugin`".to_owned(),
1217 ));
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -07001218 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001219 Ok(())
1220 });
1221
1222 match match_res {
Zach Reizner8864cb02018-01-16 17:59:03 -08001223 #[cfg(feature = "plugin")]
Zach Reizner267f2c82019-07-31 17:07:27 -07001224 Ok(()) if executable_is_plugin(&cfg.executable_path) => {
1225 match crosvm::plugin::run_config(cfg) {
1226 Ok(_) => {
1227 info!("crosvm and plugin have exited normally");
1228 Ok(())
1229 }
1230 Err(e) => {
1231 error!("{}", e);
1232 Err(())
1233 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001234 }
Zach Reizner267f2c82019-07-31 17:07:27 -07001235 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001236 Ok(()) => match linux::run_config(cfg) {
1237 Ok(_) => {
1238 info!("crosvm has exited normally");
1239 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001240 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001241 Err(e) => {
1242 error!("{}", e);
1243 Err(())
1244 }
1245 },
Dylan Reidbfba9932018-02-05 15:51:59 -08001246 Err(argument::Error::PrintHelp) => {
1247 print_help("crosvm run", "KERNEL", &arguments[..]);
1248 Ok(())
1249 }
Zach Reizner8864cb02018-01-16 17:59:03 -08001250 Err(e) => {
Dmitry Torokhov470b1e72020-01-15 12:46:49 -08001251 error!("{}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001252 Err(())
Zach Reizner8864cb02018-01-16 17:59:03 -08001253 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001254 }
1255}
1256
Jingkui Wang100e6e42019-03-08 20:41:57 -08001257fn handle_request(
1258 request: &VmRequest,
1259 args: std::env::Args,
1260) -> std::result::Result<VmResponse, ()> {
1261 let mut return_result = Err(());
Zach Reiznerefe95782017-08-26 18:05:48 -07001262 for socket_path in args {
Zach Reiznera60744b2019-02-13 17:33:32 -08001263 match UnixSeqpacket::connect(&socket_path) {
Zach Reiznerefe95782017-08-26 18:05:48 -07001264 Ok(s) => {
Jakub Starone7c59052019-04-09 12:31:14 -07001265 let socket: VmControlRequestSocket = MsgSocket::new(s);
Zach Reizner78986322019-02-21 20:43:21 -08001266 if let Err(e) = socket.send(request) {
Zach Reizner55a9e502018-10-03 10:22:32 -07001267 error!(
David Tolnayb4bd00f2019-02-12 17:51:26 -08001268 "failed to send request to socket at '{}': {}",
Zach Reizner55a9e502018-10-03 10:22:32 -07001269 socket_path, e
1270 );
Zach Reizner78986322019-02-21 20:43:21 -08001271 return_result = Err(());
1272 continue;
Zach Reiznerefe95782017-08-26 18:05:48 -07001273 }
Zach Reizner78986322019-02-21 20:43:21 -08001274 match socket.recv() {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001275 Ok(response) => return_result = Ok(response),
Zach Reizner78986322019-02-21 20:43:21 -08001276 Err(e) => {
1277 error!(
1278 "failed to send request to socket at2 '{}': {}",
1279 socket_path, e
1280 );
1281 return_result = Err(());
1282 continue;
1283 }
Dylan Reidd4432042017-12-06 18:20:09 -08001284 }
1285 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001286 Err(e) => {
1287 error!("failed to connect to socket at '{}': {}", socket_path, e);
1288 return_result = Err(());
1289 }
Dylan Reidd4432042017-12-06 18:20:09 -08001290 }
1291 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001292
1293 return_result
Dylan Reidd4432042017-12-06 18:20:09 -08001294}
Zach Reiznerefe95782017-08-26 18:05:48 -07001295
Jingkui Wang100e6e42019-03-08 20:41:57 -08001296fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
1297 let response = handle_request(request, args)?;
1298 info!("request response was {}", response);
1299 Ok(())
1300}
1301
Zach Reizner78986322019-02-21 20:43:21 -08001302fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1303 if args.len() == 0 {
1304 print_help("crosvm stop", "VM_SOCKET...", &[]);
1305 println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001306 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001307 }
1308 vms_request(&VmRequest::Exit, args)
1309}
1310
1311fn suspend_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1312 if args.len() == 0 {
1313 print_help("crosvm suspend", "VM_SOCKET...", &[]);
1314 println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001315 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001316 }
1317 vms_request(&VmRequest::Suspend, args)
1318}
1319
1320fn resume_vms(args: std::env::Args) -> std::result::Result<(), ()> {
1321 if args.len() == 0 {
1322 print_help("crosvm resume", "VM_SOCKET...", &[]);
1323 println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001324 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001325 }
1326 vms_request(&VmRequest::Resume, args)
1327}
1328
1329fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
1330 if args.len() < 2 {
1331 print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
1332 println!("Set the ballon size of the crosvm instance to `SIZE` bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001333 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -08001334 }
1335 let num_bytes = match args.nth(0).unwrap().parse::<u64>() {
1336 Ok(n) => n,
1337 Err(_) => {
1338 error!("Failed to parse number of bytes");
1339 return Err(());
1340 }
1341 };
1342
Jakub Staron1f828d72019-04-11 12:49:29 -07001343 let command = BalloonControlCommand::Adjust { num_bytes };
1344 vms_request(&VmRequest::BalloonCommand(command), args)
Zach Reizner78986322019-02-21 20:43:21 -08001345}
1346
Dylan Reid2dcb6322018-07-13 10:42:48 -07001347fn create_qcow2(mut args: std::env::Args) -> std::result::Result<(), ()> {
1348 if args.len() != 2 {
1349 print_help("crosvm create_qcow2", "PATH SIZE", &[]);
Dylan Reid940259c2018-07-20 14:22:33 -07001350 println!("Create a new QCOW2 image at `PATH` of the specified `SIZE` in bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001351 return Err(());
Dylan Reid2dcb6322018-07-13 10:42:48 -07001352 }
1353 let file_path = args.nth(0).unwrap();
1354 let size: u64 = match args.nth(0).unwrap().parse::<u64>() {
1355 Ok(n) => n,
1356 Err(_) => {
1357 error!("Failed to parse size of the disk.");
1358 return Err(());
Zach Reizner55a9e502018-10-03 10:22:32 -07001359 }
Dylan Reid2dcb6322018-07-13 10:42:48 -07001360 };
1361
1362 let file = OpenOptions::new()
1363 .create(true)
1364 .read(true)
1365 .write(true)
1366 .open(&file_path)
1367 .map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001368 error!("Failed opening qcow file at '{}': {}", file_path, e);
Dylan Reid2dcb6322018-07-13 10:42:48 -07001369 })?;
1370
Zach Reizner55a9e502018-10-03 10:22:32 -07001371 QcowFile::new(file, size).map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001372 error!("Failed to create qcow file at '{}': {}", file_path, e);
Zach Reizner55a9e502018-10-03 10:22:32 -07001373 })?;
Dylan Reid2dcb6322018-07-13 10:42:48 -07001374
1375 Ok(())
1376}
1377
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001378fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
1379 if args.len() < 2 {
1380 print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
1381 println!("Manage attached virtual disk devices.");
1382 println!("Subcommands:");
1383 println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001384 return Err(());
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001385 }
1386 let subcommand: &str = &args.nth(0).unwrap();
1387
1388 let request = match subcommand {
1389 "resize" => {
1390 let disk_index = match args.nth(0).unwrap().parse::<usize>() {
1391 Ok(n) => n,
1392 Err(_) => {
1393 error!("Failed to parse disk index");
1394 return Err(());
1395 }
1396 };
1397
1398 let new_size = match args.nth(0).unwrap().parse::<u64>() {
1399 Ok(n) => n,
1400 Err(_) => {
1401 error!("Failed to parse disk size");
1402 return Err(());
1403 }
1404 };
1405
Jakub Staronecf81e02019-04-11 11:43:39 -07001406 VmRequest::DiskCommand {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001407 disk_index,
Jakub Staronecf81e02019-04-11 11:43:39 -07001408 command: DiskControlCommand::Resize { new_size },
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001409 }
1410 }
1411 _ => {
1412 error!("Unknown disk subcommand '{}'", subcommand);
1413 return Err(());
1414 }
1415 };
1416
Zach Reizner78986322019-02-21 20:43:21 -08001417 vms_request(&request, args)
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001418}
1419
Jingkui Wang100e6e42019-03-08 20:41:57 -08001420enum ModifyUsbError {
1421 ArgMissing(&'static str),
1422 ArgParse(&'static str, String),
1423 ArgParseInt(&'static str, String, ParseIntError),
1424 FailedFdValidate(sys_util::Error),
1425 PathDoesNotExist(PathBuf),
1426 SocketFailed,
1427 UnexpectedResponse(VmResponse),
1428 UnknownCommand(String),
1429 UsbControl(UsbControlResult),
1430}
1431
1432impl fmt::Display for ModifyUsbError {
1433 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1434 use self::ModifyUsbError::*;
1435
1436 match self {
1437 ArgMissing(a) => write!(f, "argument missing: {}", a),
1438 ArgParse(name, value) => {
1439 write!(f, "failed to parse argument {} value `{}`", name, value)
1440 }
1441 ArgParseInt(name, value, e) => write!(
1442 f,
1443 "failed to parse integer argument {} value `{}`: {}",
1444 name, value, e
1445 ),
1446 FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
1447 PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
1448 SocketFailed => write!(f, "socket failed"),
1449 UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
1450 UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
1451 UsbControl(e) => write!(f, "{}", e),
1452 }
1453 }
1454}
1455
1456type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
1457
1458fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
1459 debug!("parse_bus_id_addr: {}", v);
1460 let mut ids = v.split(":");
1461 match (ids.next(), ids.next(), ids.next(), ids.next()) {
1462 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
1463 let bus_id = bus_id
1464 .parse::<u8>()
1465 .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
1466 let addr = addr
1467 .parse::<u8>()
1468 .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
1469 let vid = u16::from_str_radix(&vid, 16)
1470 .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
1471 let pid = u16::from_str_radix(&pid, 16)
1472 .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
1473 Ok((bus_id, addr, vid, pid))
1474 }
1475 _ => Err(ModifyUsbError::ArgParse(
1476 "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
1477 v.to_owned(),
1478 )),
1479 }
1480}
1481
1482fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> {
1483 if !path.exists() {
1484 return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
1485 }
1486 let raw_fd = path
1487 .file_name()
1488 .and_then(|fd_osstr| fd_osstr.to_str())
1489 .map_or(
1490 Err(ModifyUsbError::ArgParse(
1491 "USB_DEVICE_PATH",
1492 path.to_string_lossy().into_owned(),
1493 )),
1494 |fd_str| {
1495 fd_str.parse::<libc::c_int>().map_err(|e| {
1496 ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
1497 })
1498 },
1499 )?;
David Tolnay5fb3f512019-04-12 19:22:33 -07001500 validate_raw_fd(raw_fd).map_err(ModifyUsbError::FailedFdValidate)
Jingkui Wang100e6e42019-03-08 20:41:57 -08001501}
1502
1503fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1504 let val = args
1505 .next()
1506 .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
1507 let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
1508 let dev_path = PathBuf::from(
1509 args.next()
1510 .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
1511 );
1512 let usb_file: Option<File> = if dev_path == Path::new("-") {
1513 None
1514 } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
1515 // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
1516 // Safe because we will validate |raw_fd|.
1517 Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) })
1518 } else {
1519 Some(
1520 OpenOptions::new()
1521 .read(true)
1522 .write(true)
1523 .open(&dev_path)
1524 .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?,
1525 )
1526 };
1527
1528 let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
1529 bus,
1530 addr,
1531 vid,
1532 pid,
David Tolnay5fb3f512019-04-12 19:22:33 -07001533 fd: usb_file.map(MaybeOwnedFd::Owned),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001534 });
1535 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1536 match response {
1537 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1538 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1539 }
1540}
1541
1542fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1543 let port: u8 = args
1544 .next()
1545 .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
1546 p.parse::<u8>()
1547 .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
1548 })?;
1549 let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
1550 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1551 match response {
1552 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1553 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1554 }
1555}
1556
Zach Reizneraff94ca2019-03-18 20:58:31 -07001557fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1558 let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
1559 for (index, port) in ports.iter_mut().enumerate() {
1560 *port = index as u8
1561 }
1562 let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
Jingkui Wang100e6e42019-03-08 20:41:57 -08001563 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1564 match response {
1565 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1566 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1567 }
1568}
1569
1570fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reizneraff94ca2019-03-18 20:58:31 -07001571 if args.len() < 2 {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001572 print_help("crosvm usb",
Zach Reizneraff94ca2019-03-18 20:58:31 -07001573 "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
Jingkui Wang100e6e42019-03-08 20:41:57 -08001574 return Err(());
1575 }
1576
1577 // This unwrap will not panic because of the above length check.
1578 let command = args.next().unwrap();
1579 let result = match command.as_ref() {
1580 "attach" => usb_attach(args),
1581 "detach" => usb_detach(args),
1582 "list" => usb_list(args),
1583 other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
1584 };
1585 match result {
1586 Ok(response) => {
1587 println!("{}", response);
1588 Ok(())
1589 }
1590 Err(e) => {
1591 println!("error {}", e);
1592 Err(())
1593 }
1594 }
1595}
1596
Zach Reiznerefe95782017-08-26 18:05:48 -07001597fn print_usage() {
1598 print_help("crosvm", "[stop|run]", &[]);
1599 println!("Commands:");
1600 println!(" stop - Stops crosvm instances via their control sockets.");
1601 println!(" run - Start a new crosvm instance.");
Dylan Reid2dcb6322018-07-13 10:42:48 -07001602 println!(" create_qcow2 - Create a new qcow2 disk image file.");
Jingkui Wang100e6e42019-03-08 20:41:57 -08001603 println!(" disk - Manage attached virtual disk devices.");
1604 println!(" usb - Manage attached virtual USB devices.");
Yi Sun54305cd2020-01-04 00:19:37 +08001605 println!(" version - Show package version.");
1606}
1607
1608fn pkg_version() -> std::result::Result<(), ()> {
1609 const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
1610 const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
1611
1612 print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
1613 match PKG_VERSION {
1614 Some(v) => println!("-{}", v),
1615 None => println!(""),
1616 }
1617 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001618}
1619
Dylan Reidbfba9932018-02-05 15:51:59 -08001620fn crosvm_main() -> std::result::Result<(), ()> {
Stephen Barber56fbf092017-06-29 16:12:14 -07001621 if let Err(e) = syslog::init() {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001622 println!("failed to initialize syslog: {}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001623 return Err(());
Stephen Barber56fbf092017-06-29 16:12:14 -07001624 }
Zach Reizner639d9672017-05-01 17:57:18 -07001625
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08001626 panic_hook::set_panic_hook();
1627
Zach Reiznerefe95782017-08-26 18:05:48 -07001628 let mut args = std::env::args();
1629 if args.next().is_none() {
1630 error!("expected executable name");
Dylan Reidbfba9932018-02-05 15:51:59 -08001631 return Err(());
Zach Reizner639d9672017-05-01 17:57:18 -07001632 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001633
Zach Reizner8864cb02018-01-16 17:59:03 -08001634 // Past this point, usage of exit is in danger of leaking zombie processes.
Dylan Reidbfba9932018-02-05 15:51:59 -08001635 let ret = match args.next().as_ref().map(|a| a.as_ref()) {
1636 None => {
1637 print_usage();
1638 Ok(())
1639 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001640 Some("stop") => stop_vms(args),
Zach Reizner6a8fdd92019-01-16 14:38:41 -08001641 Some("suspend") => suspend_vms(args),
1642 Some("resume") => resume_vms(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07001643 Some("run") => run_vm(args),
1644 Some("balloon") => balloon_vms(args),
1645 Some("create_qcow2") => create_qcow2(args),
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001646 Some("disk") => disk_cmd(args),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001647 Some("usb") => modify_usb(args),
Yi Sun54305cd2020-01-04 00:19:37 +08001648 Some("version") => pkg_version(),
Zach Reiznerefe95782017-08-26 18:05:48 -07001649 Some(c) => {
1650 println!("invalid subcommand: {:?}", c);
1651 print_usage();
Dylan Reidbfba9932018-02-05 15:51:59 -08001652 Err(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001653 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001654 };
Zach Reiznerefe95782017-08-26 18:05:48 -07001655
1656 // Reap exit status from any child device processes. At this point, all devices should have been
1657 // dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
1658 // take some time for the processes to shut down.
1659 if !wait_all_children() {
1660 // We gave them a chance, and it's too late.
1661 warn!("not all child processes have exited; sending SIGKILL");
1662 if let Err(e) = kill_process_group() {
1663 // We're now at the mercy of the OS to clean up after us.
David Tolnayb4bd00f2019-02-12 17:51:26 -08001664 warn!("unable to kill all child processes: {}", e);
Zach Reiznerefe95782017-08-26 18:05:48 -07001665 }
1666 }
1667
1668 // WARNING: Any code added after this point is not guaranteed to run
1669 // since we may forcibly kill this process (and its children) above.
Dylan Reidbfba9932018-02-05 15:51:59 -08001670 ret
1671}
1672
1673fn main() {
1674 std::process::exit(if crosvm_main().is_ok() { 0 } else { 1 });
Zach Reizner639d9672017-05-01 17:57:18 -07001675}
Daniel Verkamp107edb32019-04-05 09:58:48 -07001676
1677#[cfg(test)]
1678mod tests {
1679 use super::*;
1680
1681 #[test]
1682 fn parse_cpu_set_single() {
1683 assert_eq!(parse_cpu_set("123").expect("parse failed"), vec![123]);
1684 }
1685
1686 #[test]
1687 fn parse_cpu_set_list() {
1688 assert_eq!(
1689 parse_cpu_set("0,1,2,3").expect("parse failed"),
1690 vec![0, 1, 2, 3]
1691 );
1692 }
1693
1694 #[test]
1695 fn parse_cpu_set_range() {
1696 assert_eq!(
1697 parse_cpu_set("0-3").expect("parse failed"),
1698 vec![0, 1, 2, 3]
1699 );
1700 }
1701
1702 #[test]
1703 fn parse_cpu_set_list_of_ranges() {
1704 assert_eq!(
1705 parse_cpu_set("3-4,7-9,18").expect("parse failed"),
1706 vec![3, 4, 7, 8, 9, 18]
1707 );
1708 }
1709
1710 #[test]
1711 fn parse_cpu_set_repeated() {
1712 // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
1713 assert_eq!(parse_cpu_set("1,1,1").expect("parse failed"), vec![1, 1, 1]);
1714 }
1715
1716 #[test]
1717 fn parse_cpu_set_negative() {
1718 // Negative CPU numbers are not allowed.
1719 parse_cpu_set("-3").expect_err("parse should have failed");
1720 }
1721
1722 #[test]
1723 fn parse_cpu_set_reverse_range() {
1724 // Ranges must be from low to high.
1725 parse_cpu_set("5-2").expect_err("parse should have failed");
1726 }
1727
1728 #[test]
1729 fn parse_cpu_set_open_range() {
1730 parse_cpu_set("3-").expect_err("parse should have failed");
1731 }
1732
1733 #[test]
1734 fn parse_cpu_set_extra_comma() {
1735 parse_cpu_set("0,1,2,").expect_err("parse should have failed");
1736 }
Trent Begin17ccaad2019-04-17 13:51:25 -06001737
1738 #[test]
1739 fn parse_serial_vaild() {
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001740 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1741 .expect("parse should have succeded");
Trent Begin17ccaad2019-04-17 13:51:25 -06001742 }
1743
1744 #[test]
Trent Begin923bab02019-06-17 13:48:06 -06001745 fn parse_serial_valid_no_num() {
1746 parse_serial_options("type=syslog").expect("parse should have succeded");
1747 }
1748
1749 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06001750 fn parse_serial_invalid_type() {
1751 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1752 }
1753
1754 #[test]
1755 fn parse_serial_invalid_num_upper() {
1756 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1757 }
1758
1759 #[test]
1760 fn parse_serial_invalid_num_lower() {
1761 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1762 }
1763
1764 #[test]
1765 fn parse_serial_invalid_num_string() {
1766 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1767 }
1768
1769 #[test]
1770 fn parse_serial_invalid_option() {
1771 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1772 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001773
1774 #[test]
1775 fn parse_serial_invalid_two_stdin() {
1776 let mut config = Config::default();
1777 set_argument(&mut config, "serial", Some("num=1,type=stdout,stdin=true"))
1778 .expect("should parse the first serial argument");
1779 set_argument(&mut config, "serial", Some("num=2,type=stdout,stdin=true"))
1780 .expect_err("should fail to parse a second serial port connected to stdin");
1781 }
Dmitry Torokhov458bb642019-12-13 11:47:52 -08001782
1783 #[test]
1784 fn parse_plugin_mount_valid() {
1785 let mut config = Config::default();
1786 set_argument(
1787 &mut config,
1788 "plugin-mount",
1789 Some("/dev/null:/dev/zero:true"),
1790 )
1791 .expect("parse should succeed");
1792 assert_eq!(config.plugin_mounts[0].src, PathBuf::from("/dev/null"));
1793 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/zero"));
1794 assert_eq!(config.plugin_mounts[0].writable, true);
1795 }
1796
1797 #[test]
1798 fn parse_plugin_mount_valid_shorthand() {
1799 let mut config = Config::default();
1800 set_argument(&mut config, "plugin-mount", Some("/dev/null")).expect("parse should succeed");
1801 assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/null"));
1802 assert_eq!(config.plugin_mounts[0].writable, false);
1803 set_argument(&mut config, "plugin-mount", Some("/dev/null:/dev/zero"))
1804 .expect("parse should succeed");
1805 assert_eq!(config.plugin_mounts[1].dst, PathBuf::from("/dev/zero"));
1806 assert_eq!(config.plugin_mounts[1].writable, false);
1807 set_argument(&mut config, "plugin-mount", Some("/dev/null::true"))
1808 .expect("parse should succeed");
1809 assert_eq!(config.plugin_mounts[2].dst, PathBuf::from("/dev/null"));
1810 assert_eq!(config.plugin_mounts[2].writable, true);
1811 }
1812
1813 #[test]
1814 fn parse_plugin_mount_invalid() {
1815 let mut config = Config::default();
1816 set_argument(&mut config, "plugin-mount", Some("")).expect_err("parse should fail");
1817 set_argument(
1818 &mut config,
1819 "plugin-mount",
1820 Some("/dev/null:/dev/null:true:false"),
1821 )
1822 .expect_err("parse should fail because too many arguments");
1823 set_argument(&mut config, "plugin-mount", Some("null:/dev/null:true"))
1824 .expect_err("parse should fail because source is not absolute");
1825 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:true"))
1826 .expect_err("parse should fail because source is not absolute");
1827 set_argument(&mut config, "plugin-mount", Some("/dev/null:null:blah"))
1828 .expect_err("parse should fail because flag is not boolean");
1829 }
1830
1831 #[test]
1832 fn parse_plugin_gid_map_valid() {
1833 let mut config = Config::default();
1834 set_argument(&mut config, "plugin-gid-map", Some("1:2:3")).expect("parse should succeed");
1835 assert_eq!(config.plugin_gid_maps[0].inner, 1);
1836 assert_eq!(config.plugin_gid_maps[0].outer, 2);
1837 assert_eq!(config.plugin_gid_maps[0].count, 3);
1838 }
1839
1840 #[test]
1841 fn parse_plugin_gid_map_valid_shorthand() {
1842 let mut config = Config::default();
1843 set_argument(&mut config, "plugin-gid-map", Some("1")).expect("parse should succeed");
1844 assert_eq!(config.plugin_gid_maps[0].inner, 1);
1845 assert_eq!(config.plugin_gid_maps[0].outer, 1);
1846 assert_eq!(config.plugin_gid_maps[0].count, 1);
1847 set_argument(&mut config, "plugin-gid-map", Some("1:2")).expect("parse should succeed");
1848 assert_eq!(config.plugin_gid_maps[1].inner, 1);
1849 assert_eq!(config.plugin_gid_maps[1].outer, 2);
1850 assert_eq!(config.plugin_gid_maps[1].count, 1);
1851 set_argument(&mut config, "plugin-gid-map", Some("1::3")).expect("parse should succeed");
1852 assert_eq!(config.plugin_gid_maps[2].inner, 1);
1853 assert_eq!(config.plugin_gid_maps[2].outer, 1);
1854 assert_eq!(config.plugin_gid_maps[2].count, 3);
1855 }
1856
1857 #[test]
1858 fn parse_plugin_gid_map_invalid() {
1859 let mut config = Config::default();
1860 set_argument(&mut config, "plugin-gid-map", Some("")).expect_err("parse should fail");
1861 set_argument(&mut config, "plugin-gid-map", Some("1:2:3:4"))
1862 .expect_err("parse should fail because too many arguments");
1863 set_argument(&mut config, "plugin-gid-map", Some("blah:2:3"))
1864 .expect_err("parse should fail because inner is not a number");
1865 set_argument(&mut config, "plugin-gid-map", Some("1:blah:3"))
1866 .expect_err("parse should fail because outer is not a number");
1867 set_argument(&mut config, "plugin-gid-map", Some("1:2:blah"))
1868 .expect_err("parse should fail because count is not a number");
1869 }
Daniel Verkamp107edb32019-04-05 09:58:48 -07001870}