blob: d6d82a5e9952e380c4f1a80f64fec799bc419ed3 [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};
Jingkui Wang100e6e42019-03-08 20:41:57 -080011use std::num::ParseIntError;
12use std::os::unix::io::{FromRawFd, RawFd};
13use std::path::{Path, PathBuf};
Zach Reizner639d9672017-05-01 17:57:18 -070014use std::string::String;
Zach Reizner39aa26b2017-12-12 18:03:23 -080015use std::thread::sleep;
Stephen Barber56fbf092017-06-29 16:12:14 -070016use std::time::Duration;
Zach Reizner639d9672017-05-01 17:57:18 -070017
Zach Reizner267f2c82019-07-31 17:07:27 -070018use crosvm::{
19 argument::{self, print_help, set_arguments, Argument},
20 linux, BindMount, Config, DiskOption, Executable, GidMap, TouchDeviceOption,
21};
Trent Begin17ccaad2019-04-17 13:51:25 -060022use devices::{SerialParameters, SerialType};
David Tolnayfe3ef7d2019-03-08 15:57:49 -080023use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
Dylan Reid2dcb6322018-07-13 10:42:48 -070024use qcow::QcowFile;
Jingkui Wang100e6e42019-03-08 20:41:57 -080025use sys_util::{
David Tolnay633426a2019-04-12 12:18:35 -070026 debug, error, getpid, info, kill_process_group, net::UnixSeqpacket, reap_child, syslog,
27 validate_raw_fd, warn,
Jingkui Wang100e6e42019-03-08 20:41:57 -080028};
Jakub Starone7c59052019-04-09 12:31:14 -070029use vm_control::{
Jakub Staron1f828d72019-04-11 12:49:29 -070030 BalloonControlCommand, DiskControlCommand, MaybeOwnedFd, UsbControlCommand, UsbControlResult,
Zach Reizneraff94ca2019-03-18 20:58:31 -070031 VmControlRequestSocket, VmRequest, VmResponse, USB_CONTROL_MAX_PORTS,
Jakub Starone7c59052019-04-09 12:31:14 -070032};
Zach Reizner639d9672017-05-01 17:57:18 -070033
Cody Schuffelen6d1ab502019-05-21 12:12:38 -070034fn executable_is_plugin(executable: &Option<Executable>) -> bool {
35 match executable {
36 Some(Executable::Plugin(_)) => true,
37 _ => false,
38 }
39}
40
Stephen Barbera00753b2017-07-18 13:57:26 -070041// Wait for all children to exit. Return true if they have all exited, false
42// otherwise.
43fn wait_all_children() -> bool {
Stephen Barber49dd2e22018-10-29 18:29:58 -070044 const CHILD_WAIT_MAX_ITER: isize = 100;
Stephen Barbera00753b2017-07-18 13:57:26 -070045 const CHILD_WAIT_MS: u64 = 10;
46 for _ in 0..CHILD_WAIT_MAX_ITER {
Stephen Barbera00753b2017-07-18 13:57:26 -070047 loop {
Zach Reizner56158c82017-08-24 13:50:14 -070048 match reap_child() {
49 Ok(0) => break,
50 // We expect ECHILD which indicates that there were no children left.
51 Err(e) if e.errno() == libc::ECHILD => return true,
52 Err(e) => {
David Tolnayb4bd00f2019-02-12 17:51:26 -080053 warn!("error while waiting for children: {}", e);
Zach Reizner56158c82017-08-24 13:50:14 -070054 return false;
Stephen Barbera00753b2017-07-18 13:57:26 -070055 }
Zach Reizner56158c82017-08-24 13:50:14 -070056 // We reaped one child, so continue reaping.
Zach Reizner55a9e502018-10-03 10:22:32 -070057 _ => {}
Stephen Barbera00753b2017-07-18 13:57:26 -070058 }
59 }
Zach Reizner56158c82017-08-24 13:50:14 -070060 // There's no timeout option for waitpid which reap_child calls internally, so our only
61 // recourse is to sleep while waiting for the children to exit.
Stephen Barbera00753b2017-07-18 13:57:26 -070062 sleep(Duration::from_millis(CHILD_WAIT_MS));
63 }
64
65 // If we've made it to this point, not all of the children have exited.
David Tolnay5bbbf612018-12-01 17:49:30 -080066 false
Stephen Barbera00753b2017-07-18 13:57:26 -070067}
68
Daniel Verkamp107edb32019-04-05 09:58:48 -070069/// Parse a comma-separated list of CPU numbers and ranges and convert it to a Vec of CPU numbers.
70fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
71 let mut cpuset = Vec::new();
72 for part in s.split(',') {
73 let range: Vec<&str> = part.split('-').collect();
74 if range.len() == 0 || range.len() > 2 {
75 return Err(argument::Error::InvalidValue {
76 value: part.to_owned(),
77 expected: "invalid list syntax",
78 });
79 }
80 let first_cpu: usize = range[0]
81 .parse()
82 .map_err(|_| argument::Error::InvalidValue {
83 value: part.to_owned(),
84 expected: "CPU index must be a non-negative integer",
85 })?;
86 let last_cpu: usize = if range.len() == 2 {
87 range[1]
88 .parse()
89 .map_err(|_| argument::Error::InvalidValue {
90 value: part.to_owned(),
91 expected: "CPU index must be a non-negative integer",
92 })?
93 } else {
94 first_cpu
95 };
96
97 if last_cpu < first_cpu {
98 return Err(argument::Error::InvalidValue {
99 value: part.to_owned(),
100 expected: "CPU ranges must be from low to high",
101 });
102 }
103
104 for cpu in first_cpu..=last_cpu {
105 cpuset.push(cpu);
106 }
107 }
108 Ok(cpuset)
109}
110
Trent Begin17ccaad2019-04-17 13:51:25 -0600111fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
112 let mut serial_setting = SerialParameters {
113 type_: SerialType::Sink,
114 path: None,
Trent Begin923bab02019-06-17 13:48:06 -0600115 num: 1,
Trent Begin17ccaad2019-04-17 13:51:25 -0600116 console: false,
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700117 stdin: false,
Trent Begin17ccaad2019-04-17 13:51:25 -0600118 };
119
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 {
127 "type" => {
128 serial_setting.type_ = v
129 .parse::<SerialType>()
130 .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
131 }
132 "num" => {
133 let num = v.parse::<u8>().map_err(|e| {
134 argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
135 })?;
136 if num < 1 || num > 4 {
137 return Err(argument::Error::InvalidValue {
138 value: num.to_string(),
139 expected: "Serial port num must be between 1 - 4",
140 });
141 }
142 serial_setting.num = num;
143 }
144 "console" => {
145 serial_setting.console = v.parse::<bool>().map_err(|e| {
146 argument::Error::Syntax(format!(
147 "serial device console is not parseable: {}",
148 e
149 ))
150 })?
151 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700152 "stdin" => {
153 serial_setting.stdin = v.parse::<bool>().map_err(|e| {
154 argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e))
155 })?
156 }
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700157 "path" => serial_setting.path = Some(PathBuf::from(v)),
Trent Begin17ccaad2019-04-17 13:51:25 -0600158 _ => {
159 return Err(argument::Error::UnknownArgument(format!(
160 "serial parameter {}",
161 k
162 )));
163 }
164 }
165 }
166
167 Ok(serial_setting)
168}
169
Zach Reiznerefe95782017-08-26 18:05:48 -0700170fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
171 match name {
172 "" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700173 if cfg.executable_path.is_some() {
174 return Err(argument::Error::TooManyArguments(format!(
175 "A VM executable was already specified: {:?}",
176 cfg.executable_path
177 )));
Zach Reiznerefe95782017-08-26 18:05:48 -0700178 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700179 let kernel_path = PathBuf::from(value.unwrap());
180 if !kernel_path.exists() {
181 return Err(argument::Error::InvalidValue {
182 value: value.unwrap().to_owned(),
183 expected: "this kernel path does not exist",
184 });
185 }
186 cfg.executable_path = Some(Executable::Kernel(kernel_path));
Zach Reiznerefe95782017-08-26 18:05:48 -0700187 }
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800188 "android-fstab" => {
189 if cfg.android_fstab.is_some()
190 && !cfg.android_fstab.as_ref().unwrap().as_os_str().is_empty()
191 {
192 return Err(argument::Error::TooManyArguments(
193 "expected exactly one android fstab path".to_owned(),
194 ));
195 } else {
196 let android_fstab = PathBuf::from(value.unwrap());
197 if !android_fstab.exists() {
198 return Err(argument::Error::InvalidValue {
199 value: value.unwrap().to_owned(),
200 expected: "this android fstab path does not exist",
201 });
202 }
203 cfg.android_fstab = Some(android_fstab);
204 }
205 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700206 "params" => {
Zach Reiznerbb678712018-01-30 18:13:04 -0800207 cfg.params.push(value.unwrap().to_owned());
Zach Reiznerefe95782017-08-26 18:05:48 -0700208 }
209 "cpus" => {
210 if cfg.vcpu_count.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700211 return Err(argument::Error::TooManyArguments(
212 "`cpus` already given".to_owned(),
213 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700214 }
215 cfg.vcpu_count =
Zach Reizner55a9e502018-10-03 10:22:32 -0700216 Some(
217 value
218 .unwrap()
219 .parse()
220 .map_err(|_| argument::Error::InvalidValue {
221 value: value.unwrap().to_owned(),
222 expected: "this value for `cpus` needs to be integer",
223 })?,
224 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700225 }
Daniel Verkamp107edb32019-04-05 09:58:48 -0700226 "cpu-affinity" => {
227 if cfg.vcpu_affinity.len() != 0 {
228 return Err(argument::Error::TooManyArguments(
229 "`cpu-affinity` already given".to_owned(),
230 ));
231 }
232 cfg.vcpu_affinity = parse_cpu_set(value.unwrap())?;
233 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700234 "mem" => {
235 if cfg.memory.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700236 return Err(argument::Error::TooManyArguments(
237 "`mem` already given".to_owned(),
238 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700239 }
240 cfg.memory =
Zach Reizner55a9e502018-10-03 10:22:32 -0700241 Some(
242 value
243 .unwrap()
244 .parse()
245 .map_err(|_| argument::Error::InvalidValue {
246 value: value.unwrap().to_owned(),
247 expected: "this value for `mem` needs to be integer",
248 })?,
249 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700250 }
paulhsiaf052cfe2019-01-22 15:22:25 +0800251 "cras-audio" => {
252 cfg.cras_audio = true;
253 }
paulhsia580d4182019-05-24 16:53:55 +0800254 "cras-capture" => {
255 cfg.cras_capture = true;
256 }
Dylan Reid3082e8e2019-01-07 10:33:48 -0800257 "null-audio" => {
258 cfg.null_audio = true;
259 }
Trent Begin17ccaad2019-04-17 13:51:25 -0600260 "serial" => {
261 let serial_params = parse_serial_options(value.unwrap())?;
262 let num = serial_params.num;
263 if cfg.serial_parameters.contains_key(&num) {
264 return Err(argument::Error::TooManyArguments(format!(
265 "serial num {}",
266 num
267 )));
268 }
269
270 if serial_params.console {
Jakub Staronb6515a92019-06-05 15:18:25 -0700271 for params in cfg.serial_parameters.values() {
Trent Begin17ccaad2019-04-17 13:51:25 -0600272 if params.console {
273 return Err(argument::Error::TooManyArguments(format!(
274 "serial device {} already set as console",
275 params.num
276 )));
277 }
278 }
279 }
280
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700281 if serial_params.stdin {
282 if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) {
283 return Err(argument::Error::TooManyArguments(format!(
284 "serial device {} already connected to standard input",
285 previous_stdin.num
286 )));
287 }
288 }
289
Trent Begin17ccaad2019-04-17 13:51:25 -0600290 cfg.serial_parameters.insert(num, serial_params);
291 }
292 "syslog-tag" => {
293 if cfg.syslog_tag.is_some() {
294 return Err(argument::Error::TooManyArguments(
295 "`syslog-tag` already given".to_owned(),
296 ));
297 }
298 syslog::set_proc_name(value.unwrap());
299 cfg.syslog_tag = Some(value.unwrap().to_owned());
300 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700301 "root" | "rwroot" | "disk" | "rwdisk" | "qcow" | "rwqcow" => {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800302 let param = value.unwrap();
303 let mut components = param.split(',');
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700304 let read_only = !name.starts_with("rw");
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800305 let disk_path =
306 PathBuf::from(
307 components
308 .next()
309 .ok_or_else(|| argument::Error::InvalidValue {
310 value: param.to_owned(),
311 expected: "missing disk path",
312 })?,
313 );
Zach Reiznerefe95782017-08-26 18:05:48 -0700314 if !disk_path.exists() {
315 return Err(argument::Error::InvalidValue {
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800316 value: param.to_owned(),
Zach Reizner55a9e502018-10-03 10:22:32 -0700317 expected: "this disk path does not exist",
318 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700319 }
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700320 if name.ends_with("root") {
Daniel Verkampaac28132018-10-15 14:58:48 -0700321 if cfg.disks.len() >= 26 {
Zach Reizner55a9e502018-10-03 10:22:32 -0700322 return Err(argument::Error::TooManyArguments(
323 "ran out of letters for to assign to root disk".to_owned(),
324 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700325 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700326 cfg.params.push(format!(
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700327 "root=/dev/vd{} {}",
328 char::from(b'a' + cfg.disks.len() as u8),
329 if read_only { "ro" } else { "rw" }
Zach Reizner55a9e502018-10-03 10:22:32 -0700330 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700331 }
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800332
333 let mut disk = DiskOption {
Zach Reizner55a9e502018-10-03 10:22:32 -0700334 path: disk_path,
Daniel Verkamp6a8cd102019-06-26 15:17:46 -0700335 read_only,
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800336 sparse: true,
337 };
338
339 for opt in components {
340 let mut o = opt.splitn(2, '=');
341 let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
342 value: opt.to_owned(),
343 expected: "disk options must not be empty",
344 })?;
345 let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
346 value: opt.to_owned(),
347 expected: "disk options must be of the form `kind=value`",
348 })?;
349
350 match kind {
351 "sparse" => {
352 let sparse = value.parse().map_err(|_| argument::Error::InvalidValue {
353 value: value.to_owned(),
354 expected: "`sparse` must be a boolean",
355 })?;
356 disk.sparse = sparse;
357 }
358 _ => {
359 return Err(argument::Error::InvalidValue {
360 value: kind.to_owned(),
361 expected: "unrecognized disk option",
362 });
363 }
364 }
365 }
366
367 cfg.disks.push(disk);
Zach Reiznerefe95782017-08-26 18:05:48 -0700368 }
Jakub Starona3411ea2019-04-24 10:55:25 -0700369 "pmem-device" | "rw-pmem-device" => {
370 let disk_path = PathBuf::from(value.unwrap());
371 if !disk_path.exists() {
372 return Err(argument::Error::InvalidValue {
373 value: value.unwrap().to_owned(),
374 expected: "this disk path does not exist",
375 });
376 }
377
378 cfg.pmem_devices.push(DiskOption {
379 path: disk_path,
380 read_only: !name.starts_with("rw"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800381 sparse: false,
Jakub Starona3411ea2019-04-24 10:55:25 -0700382 });
383 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700384 "host_ip" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700385 if cfg.host_ip.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700386 return Err(argument::Error::TooManyArguments(
387 "`host_ip` already given".to_owned(),
388 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700389 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700390 cfg.host_ip =
Zach Reizner55a9e502018-10-03 10:22:32 -0700391 Some(
392 value
393 .unwrap()
394 .parse()
395 .map_err(|_| argument::Error::InvalidValue {
396 value: value.unwrap().to_owned(),
397 expected: "`host_ip` needs to be in the form \"x.x.x.x\"",
398 })?,
399 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700400 }
401 "netmask" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700402 if cfg.netmask.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700403 return Err(argument::Error::TooManyArguments(
404 "`netmask` already given".to_owned(),
405 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700406 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700407 cfg.netmask =
Zach Reizner55a9e502018-10-03 10:22:32 -0700408 Some(
409 value
410 .unwrap()
411 .parse()
412 .map_err(|_| argument::Error::InvalidValue {
413 value: value.unwrap().to_owned(),
414 expected: "`netmask` needs to be in the form \"x.x.x.x\"",
415 })?,
416 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700417 }
418 "mac" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700419 if cfg.mac_address.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700420 return Err(argument::Error::TooManyArguments(
421 "`mac` already given".to_owned(),
422 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700423 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700424 cfg.mac_address =
Zach Reizner55a9e502018-10-03 10:22:32 -0700425 Some(
426 value
427 .unwrap()
428 .parse()
429 .map_err(|_| argument::Error::InvalidValue {
430 value: value.unwrap().to_owned(),
431 expected: "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
432 })?,
433 )
Zach Reiznerefe95782017-08-26 18:05:48 -0700434 }
Stephen Barber28a5a612017-10-20 17:15:30 -0700435 "wayland-sock" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700436 if cfg.wayland_socket_path.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700437 return Err(argument::Error::TooManyArguments(
438 "`wayland-sock` already given".to_owned(),
439 ));
Stephen Barber28a5a612017-10-20 17:15:30 -0700440 }
441 let wayland_socket_path = PathBuf::from(value.unwrap());
442 if !wayland_socket_path.exists() {
443 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700444 value: value.unwrap().to_string(),
445 expected: "Wayland socket does not exist",
446 });
Stephen Barber28a5a612017-10-20 17:15:30 -0700447 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700448 cfg.wayland_socket_path = Some(wayland_socket_path);
Stephen Barber28a5a612017-10-20 17:15:30 -0700449 }
David Reveman52ba4e52018-04-22 21:42:09 -0400450 #[cfg(feature = "wl-dmabuf")]
Daniel Verkampaac28132018-10-15 14:58:48 -0700451 "wayland-dmabuf" => cfg.wayland_dmabuf = true,
Zach Reizner0f2cfb02019-06-19 17:46:03 -0700452 "x-display" => {
453 if cfg.x_display.is_some() {
454 return Err(argument::Error::TooManyArguments(
455 "`x-display` already given".to_owned(),
456 ));
457 }
458 cfg.x_display = Some(value.unwrap().to_owned());
459 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700460 "socket" => {
461 if cfg.socket_path.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700462 return Err(argument::Error::TooManyArguments(
463 "`socket` already given".to_owned(),
464 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700465 }
466 let mut socket_path = PathBuf::from(value.unwrap());
467 if socket_path.is_dir() {
468 socket_path.push(format!("crosvm-{}.sock", getpid()));
469 }
470 if socket_path.exists() {
471 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700472 value: socket_path.to_string_lossy().into_owned(),
473 expected: "this socket path already exists",
474 });
Zach Reiznerefe95782017-08-26 18:05:48 -0700475 }
476 cfg.socket_path = Some(socket_path);
477 }
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700478 "disable-sandbox" => {
Lepton Wu9105e9f2019-03-14 11:38:31 -0700479 cfg.sandbox = false;
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700480 }
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700481 "cid" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700482 if cfg.cid.is_some() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700483 return Err(argument::Error::TooManyArguments(
484 "`cid` alread given".to_owned(),
485 ));
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700486 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700487 cfg.cid = Some(
488 value
489 .unwrap()
490 .parse()
491 .map_err(|_| argument::Error::InvalidValue {
492 value: value.unwrap().to_owned(),
493 expected: "this value for `cid` must be an unsigned integer",
494 })?,
495 );
Chirantan Ekbote88f9cba2017-08-28 09:51:18 -0700496 }
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700497 "shared-dir" => {
498 // Formatted as <src:tag>.
499 let param = value.unwrap();
500 let mut components = param.splitn(2, ':');
Zach Reizner55a9e502018-10-03 10:22:32 -0700501 let src =
502 PathBuf::from(
503 components
504 .next()
505 .ok_or_else(|| argument::Error::InvalidValue {
506 value: param.to_owned(),
507 expected: "missing source path for `shared-dir`",
508 })?,
509 );
510 let tag = components
511 .next()
512 .ok_or_else(|| argument::Error::InvalidValue {
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700513 value: param.to_owned(),
514 expected: "missing tag for `shared-dir`",
David Tolnay2bac1e72018-12-12 14:33:42 -0800515 })?
516 .to_owned();
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700517
518 if !src.is_dir() {
519 return Err(argument::Error::InvalidValue {
520 value: param.to_owned(),
521 expected: "source path for `shared-dir` must be a directory",
522 });
523 }
524
Daniel Verkampaac28132018-10-15 14:58:48 -0700525 cfg.shared_dirs.push((src, tag));
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700526 }
Dylan Reide026ef02017-10-02 19:03:52 -0700527 "seccomp-policy-dir" => {
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700528 // `value` is Some because we are in this match so it's safe to unwrap.
Daniel Verkampaac28132018-10-15 14:58:48 -0700529 cfg.seccomp_policy_dir = PathBuf::from(value.unwrap());
Zach Reizner55a9e502018-10-03 10:22:32 -0700530 }
Zach Reizner44863792019-06-26 14:22:08 -0700531 "seccomp-log-failures" => {
532 cfg.seccomp_log_failures = true;
533 }
Zach Reizner8864cb02018-01-16 17:59:03 -0800534 "plugin" => {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700535 if cfg.executable_path.is_some() {
536 return Err(argument::Error::TooManyArguments(format!(
537 "A VM executable was already specified: {:?}",
538 cfg.executable_path
539 )));
Zach Reizner8864cb02018-01-16 17:59:03 -0800540 }
Zach Reiznercc30d582018-01-23 21:16:42 -0800541 let plugin = PathBuf::from(value.unwrap().to_owned());
542 if plugin.is_relative() {
543 return Err(argument::Error::InvalidValue {
Zach Reizner55a9e502018-10-03 10:22:32 -0700544 value: plugin.to_string_lossy().into_owned(),
545 expected: "the plugin path must be an absolute path",
546 });
Zach Reiznercc30d582018-01-23 21:16:42 -0800547 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700548 cfg.executable_path = Some(Executable::Plugin(plugin));
Zach Reizner55a9e502018-10-03 10:22:32 -0700549 }
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -0700550 "plugin-root" => {
551 cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned()));
Zach Reizner55a9e502018-10-03 10:22:32 -0700552 }
Chirantan Ekboted41d7262018-11-16 16:37:45 -0800553 "plugin-mount" => {
554 let components: Vec<&str> = value.unwrap().split(":").collect();
555 if components.len() != 3 {
556 return Err(argument::Error::InvalidValue {
557 value: value.unwrap().to_owned(),
558 expected:
559 "`plugin-mount` must have exactly 3 components: <src>:<dst>:<writable>",
560 });
561 }
562
563 let src = PathBuf::from(components[0]);
564 if src.is_relative() {
565 return Err(argument::Error::InvalidValue {
566 value: components[0].to_owned(),
567 expected: "the source path for `plugin-mount` must be absolute",
568 });
569 }
570 if !src.exists() {
571 return Err(argument::Error::InvalidValue {
572 value: components[0].to_owned(),
573 expected: "the source path for `plugin-mount` does not exist",
574 });
575 }
576
577 let dst = PathBuf::from(components[1]);
578 if dst.is_relative() {
579 return Err(argument::Error::InvalidValue {
580 value: components[1].to_owned(),
581 expected: "the destination path for `plugin-mount` must be absolute",
582 });
583 }
584
585 let writable: bool =
586 components[2]
587 .parse()
588 .map_err(|_| argument::Error::InvalidValue {
589 value: components[2].to_owned(),
590 expected: "the <writable> component for `plugin-mount` is not valid bool",
591 })?;
592
593 cfg.plugin_mounts.push(BindMount { src, dst, writable });
594 }
Dmitry Torokhov1a6262b2019-03-01 00:34:03 -0800595 "plugin-gid-map" => {
596 let components: Vec<&str> = value.unwrap().split(":").collect();
597 if components.len() != 3 {
598 return Err(argument::Error::InvalidValue {
599 value: value.unwrap().to_owned(),
600 expected:
601 "`plugin-gid-map` must have exactly 3 components: <inner>:<outer>:<count>",
602 });
603 }
604
605 let inner: libc::gid_t =
606 components[0]
607 .parse()
608 .map_err(|_| argument::Error::InvalidValue {
609 value: components[0].to_owned(),
610 expected: "the <inner> component for `plugin-gid-map` is not valid gid",
611 })?;
612
613 let outer: libc::gid_t =
614 components[1]
615 .parse()
616 .map_err(|_| argument::Error::InvalidValue {
617 value: components[1].to_owned(),
618 expected: "the <outer> component for `plugin-gid-map` is not valid gid",
619 })?;
620
621 let count: u32 = components[2]
622 .parse()
623 .map_err(|_| argument::Error::InvalidValue {
624 value: components[2].to_owned(),
625 expected: "the <count> component for `plugin-gid-map` is not valid number",
626 })?;
627
628 cfg.plugin_gid_maps.push(GidMap {
629 inner,
630 outer,
631 count,
632 });
633 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700634 "vhost-net" => cfg.vhost_net = true,
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700635 "tap-fd" => {
Jorge E. Moreirab7952802019-02-12 16:43:05 -0800636 cfg.tap_fd.push(
637 value
638 .unwrap()
639 .parse()
640 .map_err(|_| argument::Error::InvalidValue {
641 value: value.unwrap().to_owned(),
642 expected: "this value for `tap-fd` must be an unsigned integer",
643 })?,
644 );
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700645 }
Zach Reizner3a8100a2017-09-13 19:15:43 -0700646 "gpu" => {
Daniel Verkampaac28132018-10-15 14:58:48 -0700647 cfg.gpu = true;
Zach Reizner3a8100a2017-09-13 19:15:43 -0700648 }
David Tolnay43f8e212019-02-13 17:28:16 -0800649 "software-tpm" => {
650 cfg.software_tpm = true;
651 }
Jorge E. Moreira99d3f082019-03-07 10:59:54 -0800652 "single-touch" => {
653 if cfg.virtio_single_touch.is_some() {
654 return Err(argument::Error::TooManyArguments(
655 "`single-touch` already given".to_owned(),
656 ));
657 }
658 let mut it = value.unwrap().split(":");
659
660 let mut single_touch_spec =
661 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
662 if let Some(width) = it.next() {
663 single_touch_spec.width = width.trim().parse().unwrap();
664 }
665 if let Some(height) = it.next() {
666 single_touch_spec.height = height.trim().parse().unwrap();
667 }
668
669 cfg.virtio_single_touch = Some(single_touch_spec);
670 }
Jorge E. Moreiradffec502019-01-14 18:44:49 -0800671 "trackpad" => {
672 if cfg.virtio_trackpad.is_some() {
673 return Err(argument::Error::TooManyArguments(
674 "`trackpad` already given".to_owned(),
675 ));
676 }
677 let mut it = value.unwrap().split(":");
678
679 let mut trackpad_spec =
Jorge E. Moreira99d3f082019-03-07 10:59:54 -0800680 TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
Jorge E. Moreiradffec502019-01-14 18:44:49 -0800681 if let Some(width) = it.next() {
682 trackpad_spec.width = width.trim().parse().unwrap();
683 }
684 if let Some(height) = it.next() {
685 trackpad_spec.height = height.trim().parse().unwrap();
686 }
687
688 cfg.virtio_trackpad = Some(trackpad_spec);
689 }
690 "mouse" => {
691 if cfg.virtio_mouse.is_some() {
692 return Err(argument::Error::TooManyArguments(
693 "`mouse` already given".to_owned(),
694 ));
695 }
696 cfg.virtio_mouse = Some(PathBuf::from(value.unwrap().to_owned()));
697 }
698 "keyboard" => {
699 if cfg.virtio_keyboard.is_some() {
700 return Err(argument::Error::TooManyArguments(
701 "`keyboard` already given".to_owned(),
702 ));
703 }
704 cfg.virtio_keyboard = Some(PathBuf::from(value.unwrap().to_owned()));
705 }
706 "evdev" => {
707 let dev_path = PathBuf::from(value.unwrap());
708 if !dev_path.exists() {
709 return Err(argument::Error::InvalidValue {
710 value: value.unwrap().to_owned(),
711 expected: "this input device path does not exist",
712 });
713 }
714 cfg.virtio_input_evdevs.push(dev_path);
715 }
Miriam Zimmerman26ac9282019-01-29 21:21:48 -0800716 "split-irqchip" => {
717 cfg.split_irqchip = true;
718 }
Daniel Verkampe403f5c2018-12-11 16:29:26 -0800719 "initrd" => {
720 cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
721 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700722 "bios" => {
723 if cfg.executable_path.is_some() {
724 return Err(argument::Error::TooManyArguments(format!(
725 "A VM executable was already specified: {:?}",
726 cfg.executable_path
727 )));
728 }
729 cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
730 }
Xiong Zhang17b0daf2019-04-23 17:14:50 +0800731 "vfio" => {
732 let vfio_path = PathBuf::from(value.unwrap());
733 if !vfio_path.exists() {
734 return Err(argument::Error::InvalidValue {
735 value: value.unwrap().to_owned(),
736 expected: "the vfio path does not exist",
737 });
738 }
739 if !vfio_path.is_dir() {
740 return Err(argument::Error::InvalidValue {
741 value: value.unwrap().to_owned(),
742 expected: "the vfio path should be directory",
743 });
744 }
745
746 cfg.vfio = Some(vfio_path);
747 }
748
Zach Reiznerefe95782017-08-26 18:05:48 -0700749 "help" => return Err(argument::Error::PrintHelp),
750 _ => unreachable!(),
751 }
752 Ok(())
753}
754
Dylan Reidbfba9932018-02-05 15:51:59 -0800755fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reiznerefe95782017-08-26 18:05:48 -0700756 let arguments =
757 &[Argument::positional("KERNEL", "bzImage of kernel to run"),
Tristan Muntsinger4133b012018-12-21 16:01:56 -0800758 Argument::value("android-fstab", "PATH", "Path to Android fstab"),
Daniel Verkampe403f5c2018-12-11 16:29:26 -0800759 Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
Zach Reiznerefe95782017-08-26 18:05:48 -0700760 Argument::short_value('p',
761 "params",
762 "PARAMS",
Zach Reiznerbb678712018-01-30 18:13:04 -0800763 "Extra kernel or plugin command line arguments. Can be given more than once."),
Zach Reiznerefe95782017-08-26 18:05:48 -0700764 Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Daniel Verkamp107edb32019-04-05 09:58:48 -0700765 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 -0700766 Argument::short_value('m',
767 "mem",
768 "N",
769 "Amount of guest memory in MiB. (default: 256)"),
770 Argument::short_value('r',
771 "root",
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800772 "PATH[,key=value[,key=value[,...]]",
773 "Path to a root disk image followed by optional comma-separated options.
774 Like `--disk` but adds appropriate kernel command line option.
775 See --disk for valid options."),
776 Argument::value("rwroot", "PATH[,key=value[,key=value[,...]]", "Path to a writable root disk image followed by optional comma-separated options.
777 See --disk for valid options."),
778 Argument::short_value('d', "disk", "PATH[,key=value[,key=value[,...]]", "Path to a disk image followed by optional comma-separated options.
779 Valid keys:
780 sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)"),
Daniel Verkampf02fdd12018-10-10 17:25:14 -0700781 Argument::value("qcow", "PATH", "Path to a qcow2 disk image. (Deprecated; use --disk instead.)"),
Daniel Verkampe73c80f2019-11-08 10:11:16 -0800782 Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
783 See --disk for valid options."),
Daniel Verkampf02fdd12018-10-10 17:25:14 -0700784 Argument::value("rwqcow", "PATH", "Path to a writable qcow2 disk image. (Deprecated; use --rwdisk instead.)"),
Jakub Starona3411ea2019-04-24 10:55:25 -0700785 Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
786 Argument::value("pmem-device", "PATH", "Path to a disk image."),
Zach Reiznerefe95782017-08-26 18:05:48 -0700787 Argument::value("host_ip",
788 "IP",
789 "IP address to assign to host tap interface."),
790 Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
791 Argument::value("mac", "MAC", "MAC address for VM."),
paulhsiaf052cfe2019-01-22 15:22:25 +0800792 Argument::flag("cras-audio", "Add an audio device to the VM that plays samples through CRAS server"),
paulhsia580d4182019-05-24 16:53:55 +0800793 Argument::flag("cras-capture", "Enable capturing audio from CRAS server to the cras-audio device"),
Dylan Reid3082e8e2019-01-07 10:33:48 -0800794 Argument::flag("null-audio", "Add an audio device to the VM that plays samples to /dev/null"),
Trent Begin17ccaad2019-04-17 13:51:25 -0600795 Argument::value("serial",
Jorge E. Moreira1e262302019-08-01 14:40:03 -0700796 "type=TYPE,[num=NUM,path=PATH,console,stdin]",
Trent Begin17ccaad2019-04-17 13:51:25 -0600797 "Comma seperated key=value pairs for setting up serial devices. Can be given more than once.
798 Possible key values:
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700799 type=(stdout,syslog,sink,file) - Where to route the serial device
Trent Begin923bab02019-06-17 13:48:06 -0600800 num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
Jorge E. Moreira9c9e0e72019-05-17 13:57:04 -0700801 path=PATH - The path to the file to write to when type=file
Trent Begin17ccaad2019-04-17 13:51:25 -0600802 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 -0700803 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 -0600804 "),
805 Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
Zach Reizner0f2cfb02019-06-19 17:46:03 -0700806 Argument::value("x-display", "DISPLAY", "X11 display name to use."),
Stephen Barber28a5a612017-10-20 17:15:30 -0700807 Argument::value("wayland-sock", "PATH", "Path to the Wayland socket to use."),
David Reveman52ba4e52018-04-22 21:42:09 -0400808 #[cfg(feature = "wl-dmabuf")]
809 Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
Zach Reiznerefe95782017-08-26 18:05:48 -0700810 Argument::short_value('s',
811 "socket",
812 "PATH",
813 "Path to put the control socket. If PATH is a directory, a name will be generated."),
Dylan Reidd0c9adc2017-10-02 19:04:50 -0700814 Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
Chirantan Ekboteebd56812018-04-16 19:32:04 -0700815 Argument::value("cid", "CID", "Context ID for virtual sockets."),
816 Argument::value("shared-dir", "PATH:TAG",
817 "Directory to be shared with a VM as a source:tag pair. Can be given more than once."),
Dylan Reide026ef02017-10-02 19:03:52 -0700818 Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
Zach Reizner44863792019-06-26 14:22:08 -0700819 Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
Zach Reizner8864cb02018-01-16 17:59:03 -0800820 #[cfg(feature = "plugin")]
Zach Reiznercc30d582018-01-23 21:16:42 -0800821 Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
Daniel Verkampbd1a0842019-01-08 15:50:34 -0800822 #[cfg(feature = "plugin")]
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -0700823 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 -0800824 #[cfg(feature = "plugin")]
Chirantan Ekboted41d7262018-11-16 16:37:45 -0800825 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 -0800826 #[cfg(feature = "plugin")]
827 Argument::value("plugin-gid-map", "GID:GID:INT", "Supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
Rob Bradford8f002f52018-02-19 16:31:11 +0000828 Argument::flag("vhost-net", "Use vhost for networking."),
Chirantan Ekbote5f787212018-05-31 15:31:31 -0700829 Argument::value("tap-fd",
830 "fd",
Jorge E. Moreirab7952802019-02-12 16:43:05 -0800831 "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 -0700832 #[cfg(feature = "gpu")]
833 Argument::flag("gpu", "(EXPERIMENTAL) enable virtio-gpu device"),
David Tolnay43f8e212019-02-13 17:28:16 -0800834 #[cfg(feature = "tpm")]
835 Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Jorge E. Moreiradffec502019-01-14 18:44:49 -0800836 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 -0800837 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 -0800838 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)."),
839 Argument::value("mouse", "PATH", "Path to a socket from where to read mouse input events and write status updates to."),
840 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 -0800841 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
842 Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700843 Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
Xiong Zhang17b0daf2019-04-23 17:14:50 +0800844 Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
Zach Reiznerefe95782017-08-26 18:05:48 -0700845 Argument::short_flag('h', "help", "Print help message.")];
846
847 let mut cfg = Config::default();
Zach Reizner55a9e502018-10-03 10:22:32 -0700848 let match_res = set_arguments(args, &arguments[..], |name, value| {
849 set_argument(&mut cfg, name, value)
David Tolnay2bac1e72018-12-12 14:33:42 -0800850 })
851 .and_then(|_| {
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700852 if cfg.executable_path.is_none() {
Zach Reiznerefe95782017-08-26 18:05:48 -0700853 return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
854 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700855 if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
856 if cfg.host_ip.is_none() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700857 return Err(argument::Error::ExpectedArgument(
858 "`host_ip` missing from network config".to_owned(),
859 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700860 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700861 if cfg.netmask.is_none() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700862 return Err(argument::Error::ExpectedArgument(
863 "`netmask` missing from network config".to_owned(),
864 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700865 }
Daniel Verkampaac28132018-10-15 14:58:48 -0700866 if cfg.mac_address.is_none() {
Zach Reizner55a9e502018-10-03 10:22:32 -0700867 return Err(argument::Error::ExpectedArgument(
868 "`mac` missing from network config".to_owned(),
869 ));
Zach Reiznerefe95782017-08-26 18:05:48 -0700870 }
871 }
Cody Schuffelen6d1ab502019-05-21 12:12:38 -0700872 if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
Zach Reizner55a9e502018-10-03 10:22:32 -0700873 return Err(argument::Error::ExpectedArgument(
874 "`plugin-root` requires `plugin`".to_owned(),
875 ));
Dmitry Torokhov0f1770d2018-05-11 10:57:16 -0700876 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700877 Ok(())
878 });
879
880 match match_res {
Zach Reizner8864cb02018-01-16 17:59:03 -0800881 #[cfg(feature = "plugin")]
Zach Reizner267f2c82019-07-31 17:07:27 -0700882 Ok(()) if executable_is_plugin(&cfg.executable_path) => {
883 match crosvm::plugin::run_config(cfg) {
884 Ok(_) => {
885 info!("crosvm and plugin have exited normally");
886 Ok(())
887 }
888 Err(e) => {
889 error!("{}", e);
890 Err(())
891 }
Zach Reizner8864cb02018-01-16 17:59:03 -0800892 }
Zach Reizner267f2c82019-07-31 17:07:27 -0700893 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700894 Ok(()) => match linux::run_config(cfg) {
895 Ok(_) => {
896 info!("crosvm has exited normally");
897 Ok(())
Zach Reiznerefe95782017-08-26 18:05:48 -0700898 }
Zach Reizner55a9e502018-10-03 10:22:32 -0700899 Err(e) => {
900 error!("{}", e);
901 Err(())
902 }
903 },
Dylan Reidbfba9932018-02-05 15:51:59 -0800904 Err(argument::Error::PrintHelp) => {
905 print_help("crosvm run", "KERNEL", &arguments[..]);
906 Ok(())
907 }
Zach Reizner8864cb02018-01-16 17:59:03 -0800908 Err(e) => {
909 println!("{}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -0800910 Err(())
Zach Reizner8864cb02018-01-16 17:59:03 -0800911 }
Zach Reiznerefe95782017-08-26 18:05:48 -0700912 }
913}
914
Jingkui Wang100e6e42019-03-08 20:41:57 -0800915fn handle_request(
916 request: &VmRequest,
917 args: std::env::Args,
918) -> std::result::Result<VmResponse, ()> {
919 let mut return_result = Err(());
Zach Reiznerefe95782017-08-26 18:05:48 -0700920 for socket_path in args {
Zach Reiznera60744b2019-02-13 17:33:32 -0800921 match UnixSeqpacket::connect(&socket_path) {
Zach Reiznerefe95782017-08-26 18:05:48 -0700922 Ok(s) => {
Jakub Starone7c59052019-04-09 12:31:14 -0700923 let socket: VmControlRequestSocket = MsgSocket::new(s);
Zach Reizner78986322019-02-21 20:43:21 -0800924 if let Err(e) = socket.send(request) {
Zach Reizner55a9e502018-10-03 10:22:32 -0700925 error!(
David Tolnayb4bd00f2019-02-12 17:51:26 -0800926 "failed to send request to socket at '{}': {}",
Zach Reizner55a9e502018-10-03 10:22:32 -0700927 socket_path, e
928 );
Zach Reizner78986322019-02-21 20:43:21 -0800929 return_result = Err(());
930 continue;
Zach Reiznerefe95782017-08-26 18:05:48 -0700931 }
Zach Reizner78986322019-02-21 20:43:21 -0800932 match socket.recv() {
Jingkui Wang100e6e42019-03-08 20:41:57 -0800933 Ok(response) => return_result = Ok(response),
Zach Reizner78986322019-02-21 20:43:21 -0800934 Err(e) => {
935 error!(
936 "failed to send request to socket at2 '{}': {}",
937 socket_path, e
938 );
939 return_result = Err(());
940 continue;
941 }
Dylan Reidd4432042017-12-06 18:20:09 -0800942 }
943 }
Dylan Reidbfba9932018-02-05 15:51:59 -0800944 Err(e) => {
945 error!("failed to connect to socket at '{}': {}", socket_path, e);
946 return_result = Err(());
947 }
Dylan Reidd4432042017-12-06 18:20:09 -0800948 }
949 }
Dylan Reidbfba9932018-02-05 15:51:59 -0800950
951 return_result
Dylan Reidd4432042017-12-06 18:20:09 -0800952}
Zach Reiznerefe95782017-08-26 18:05:48 -0700953
Jingkui Wang100e6e42019-03-08 20:41:57 -0800954fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
955 let response = handle_request(request, args)?;
956 info!("request response was {}", response);
957 Ok(())
958}
959
Zach Reizner78986322019-02-21 20:43:21 -0800960fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
961 if args.len() == 0 {
962 print_help("crosvm stop", "VM_SOCKET...", &[]);
963 println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -0800964 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -0800965 }
966 vms_request(&VmRequest::Exit, args)
967}
968
969fn suspend_vms(args: std::env::Args) -> std::result::Result<(), ()> {
970 if args.len() == 0 {
971 print_help("crosvm suspend", "VM_SOCKET...", &[]);
972 println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -0800973 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -0800974 }
975 vms_request(&VmRequest::Suspend, args)
976}
977
978fn resume_vms(args: std::env::Args) -> std::result::Result<(), ()> {
979 if args.len() == 0 {
980 print_help("crosvm resume", "VM_SOCKET...", &[]);
981 println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
Jianxun Zhang56497d22019-03-04 14:38:24 -0800982 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -0800983 }
984 vms_request(&VmRequest::Resume, args)
985}
986
987fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
988 if args.len() < 2 {
989 print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
990 println!("Set the ballon size of the crosvm instance to `SIZE` bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -0800991 return Err(());
Zach Reizner78986322019-02-21 20:43:21 -0800992 }
993 let num_bytes = match args.nth(0).unwrap().parse::<u64>() {
994 Ok(n) => n,
995 Err(_) => {
996 error!("Failed to parse number of bytes");
997 return Err(());
998 }
999 };
1000
Jakub Staron1f828d72019-04-11 12:49:29 -07001001 let command = BalloonControlCommand::Adjust { num_bytes };
1002 vms_request(&VmRequest::BalloonCommand(command), args)
Zach Reizner78986322019-02-21 20:43:21 -08001003}
1004
Dylan Reid2dcb6322018-07-13 10:42:48 -07001005fn create_qcow2(mut args: std::env::Args) -> std::result::Result<(), ()> {
1006 if args.len() != 2 {
1007 print_help("crosvm create_qcow2", "PATH SIZE", &[]);
Dylan Reid940259c2018-07-20 14:22:33 -07001008 println!("Create a new QCOW2 image at `PATH` of the specified `SIZE` in bytes.");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001009 return Err(());
Dylan Reid2dcb6322018-07-13 10:42:48 -07001010 }
1011 let file_path = args.nth(0).unwrap();
1012 let size: u64 = match args.nth(0).unwrap().parse::<u64>() {
1013 Ok(n) => n,
1014 Err(_) => {
1015 error!("Failed to parse size of the disk.");
1016 return Err(());
Zach Reizner55a9e502018-10-03 10:22:32 -07001017 }
Dylan Reid2dcb6322018-07-13 10:42:48 -07001018 };
1019
1020 let file = OpenOptions::new()
1021 .create(true)
1022 .read(true)
1023 .write(true)
1024 .open(&file_path)
1025 .map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001026 error!("Failed opening qcow file at '{}': {}", file_path, e);
Dylan Reid2dcb6322018-07-13 10:42:48 -07001027 })?;
1028
Zach Reizner55a9e502018-10-03 10:22:32 -07001029 QcowFile::new(file, size).map_err(|e| {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001030 error!("Failed to create qcow file at '{}': {}", file_path, e);
Zach Reizner55a9e502018-10-03 10:22:32 -07001031 })?;
Dylan Reid2dcb6322018-07-13 10:42:48 -07001032
1033 Ok(())
1034}
1035
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001036fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
1037 if args.len() < 2 {
1038 print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
1039 println!("Manage attached virtual disk devices.");
1040 println!("Subcommands:");
1041 println!(" resize DISK_INDEX NEW_SIZE VM_SOCKET");
Jianxun Zhang56497d22019-03-04 14:38:24 -08001042 return Err(());
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001043 }
1044 let subcommand: &str = &args.nth(0).unwrap();
1045
1046 let request = match subcommand {
1047 "resize" => {
1048 let disk_index = match args.nth(0).unwrap().parse::<usize>() {
1049 Ok(n) => n,
1050 Err(_) => {
1051 error!("Failed to parse disk index");
1052 return Err(());
1053 }
1054 };
1055
1056 let new_size = match args.nth(0).unwrap().parse::<u64>() {
1057 Ok(n) => n,
1058 Err(_) => {
1059 error!("Failed to parse disk size");
1060 return Err(());
1061 }
1062 };
1063
Jakub Staronecf81e02019-04-11 11:43:39 -07001064 VmRequest::DiskCommand {
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001065 disk_index,
Jakub Staronecf81e02019-04-11 11:43:39 -07001066 command: DiskControlCommand::Resize { new_size },
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001067 }
1068 }
1069 _ => {
1070 error!("Unknown disk subcommand '{}'", subcommand);
1071 return Err(());
1072 }
1073 };
1074
Zach Reizner78986322019-02-21 20:43:21 -08001075 vms_request(&request, args)
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001076}
1077
Jingkui Wang100e6e42019-03-08 20:41:57 -08001078enum ModifyUsbError {
1079 ArgMissing(&'static str),
1080 ArgParse(&'static str, String),
1081 ArgParseInt(&'static str, String, ParseIntError),
1082 FailedFdValidate(sys_util::Error),
1083 PathDoesNotExist(PathBuf),
1084 SocketFailed,
1085 UnexpectedResponse(VmResponse),
1086 UnknownCommand(String),
1087 UsbControl(UsbControlResult),
1088}
1089
1090impl fmt::Display for ModifyUsbError {
1091 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1092 use self::ModifyUsbError::*;
1093
1094 match self {
1095 ArgMissing(a) => write!(f, "argument missing: {}", a),
1096 ArgParse(name, value) => {
1097 write!(f, "failed to parse argument {} value `{}`", name, value)
1098 }
1099 ArgParseInt(name, value, e) => write!(
1100 f,
1101 "failed to parse integer argument {} value `{}`: {}",
1102 name, value, e
1103 ),
1104 FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
1105 PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
1106 SocketFailed => write!(f, "socket failed"),
1107 UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
1108 UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
1109 UsbControl(e) => write!(f, "{}", e),
1110 }
1111 }
1112}
1113
1114type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
1115
1116fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
1117 debug!("parse_bus_id_addr: {}", v);
1118 let mut ids = v.split(":");
1119 match (ids.next(), ids.next(), ids.next(), ids.next()) {
1120 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
1121 let bus_id = bus_id
1122 .parse::<u8>()
1123 .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
1124 let addr = addr
1125 .parse::<u8>()
1126 .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
1127 let vid = u16::from_str_radix(&vid, 16)
1128 .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
1129 let pid = u16::from_str_radix(&pid, 16)
1130 .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
1131 Ok((bus_id, addr, vid, pid))
1132 }
1133 _ => Err(ModifyUsbError::ArgParse(
1134 "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
1135 v.to_owned(),
1136 )),
1137 }
1138}
1139
1140fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> {
1141 if !path.exists() {
1142 return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
1143 }
1144 let raw_fd = path
1145 .file_name()
1146 .and_then(|fd_osstr| fd_osstr.to_str())
1147 .map_or(
1148 Err(ModifyUsbError::ArgParse(
1149 "USB_DEVICE_PATH",
1150 path.to_string_lossy().into_owned(),
1151 )),
1152 |fd_str| {
1153 fd_str.parse::<libc::c_int>().map_err(|e| {
1154 ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
1155 })
1156 },
1157 )?;
David Tolnay5fb3f512019-04-12 19:22:33 -07001158 validate_raw_fd(raw_fd).map_err(ModifyUsbError::FailedFdValidate)
Jingkui Wang100e6e42019-03-08 20:41:57 -08001159}
1160
1161fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1162 let val = args
1163 .next()
1164 .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
1165 let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
1166 let dev_path = PathBuf::from(
1167 args.next()
1168 .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
1169 );
1170 let usb_file: Option<File> = if dev_path == Path::new("-") {
1171 None
1172 } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
1173 // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
1174 // Safe because we will validate |raw_fd|.
1175 Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) })
1176 } else {
1177 Some(
1178 OpenOptions::new()
1179 .read(true)
1180 .write(true)
1181 .open(&dev_path)
1182 .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?,
1183 )
1184 };
1185
1186 let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
1187 bus,
1188 addr,
1189 vid,
1190 pid,
David Tolnay5fb3f512019-04-12 19:22:33 -07001191 fd: usb_file.map(MaybeOwnedFd::Owned),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001192 });
1193 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1194 match response {
1195 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1196 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1197 }
1198}
1199
1200fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1201 let port: u8 = args
1202 .next()
1203 .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
1204 p.parse::<u8>()
1205 .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
1206 })?;
1207 let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
1208 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1209 match response {
1210 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1211 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1212 }
1213}
1214
Zach Reizneraff94ca2019-03-18 20:58:31 -07001215fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
1216 let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
1217 for (index, port) in ports.iter_mut().enumerate() {
1218 *port = index as u8
1219 }
1220 let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
Jingkui Wang100e6e42019-03-08 20:41:57 -08001221 let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
1222 match response {
1223 VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
1224 r => Err(ModifyUsbError::UnexpectedResponse(r)),
1225 }
1226}
1227
1228fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
Zach Reizneraff94ca2019-03-18 20:58:31 -07001229 if args.len() < 2 {
Jingkui Wang100e6e42019-03-08 20:41:57 -08001230 print_help("crosvm usb",
Zach Reizneraff94ca2019-03-18 20:58:31 -07001231 "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
Jingkui Wang100e6e42019-03-08 20:41:57 -08001232 return Err(());
1233 }
1234
1235 // This unwrap will not panic because of the above length check.
1236 let command = args.next().unwrap();
1237 let result = match command.as_ref() {
1238 "attach" => usb_attach(args),
1239 "detach" => usb_detach(args),
1240 "list" => usb_list(args),
1241 other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
1242 };
1243 match result {
1244 Ok(response) => {
1245 println!("{}", response);
1246 Ok(())
1247 }
1248 Err(e) => {
1249 println!("error {}", e);
1250 Err(())
1251 }
1252 }
1253}
1254
Zach Reiznerefe95782017-08-26 18:05:48 -07001255fn print_usage() {
1256 print_help("crosvm", "[stop|run]", &[]);
1257 println!("Commands:");
1258 println!(" stop - Stops crosvm instances via their control sockets.");
1259 println!(" run - Start a new crosvm instance.");
Dylan Reid2dcb6322018-07-13 10:42:48 -07001260 println!(" create_qcow2 - Create a new qcow2 disk image file.");
Jingkui Wang100e6e42019-03-08 20:41:57 -08001261 println!(" disk - Manage attached virtual disk devices.");
1262 println!(" usb - Manage attached virtual USB devices.");
Zach Reiznerefe95782017-08-26 18:05:48 -07001263}
1264
Dylan Reidbfba9932018-02-05 15:51:59 -08001265fn crosvm_main() -> std::result::Result<(), ()> {
Stephen Barber56fbf092017-06-29 16:12:14 -07001266 if let Err(e) = syslog::init() {
David Tolnayb4bd00f2019-02-12 17:51:26 -08001267 println!("failed to initialize syslog: {}", e);
Dylan Reidbfba9932018-02-05 15:51:59 -08001268 return Err(());
Stephen Barber56fbf092017-06-29 16:12:14 -07001269 }
Zach Reizner639d9672017-05-01 17:57:18 -07001270
Zach Reiznerb3fa5c92019-01-28 14:05:23 -08001271 panic_hook::set_panic_hook();
1272
Zach Reiznerefe95782017-08-26 18:05:48 -07001273 let mut args = std::env::args();
1274 if args.next().is_none() {
1275 error!("expected executable name");
Dylan Reidbfba9932018-02-05 15:51:59 -08001276 return Err(());
Zach Reizner639d9672017-05-01 17:57:18 -07001277 }
Zach Reiznerefe95782017-08-26 18:05:48 -07001278
Zach Reizner8864cb02018-01-16 17:59:03 -08001279 // Past this point, usage of exit is in danger of leaking zombie processes.
Dylan Reidbfba9932018-02-05 15:51:59 -08001280 let ret = match args.next().as_ref().map(|a| a.as_ref()) {
1281 None => {
1282 print_usage();
1283 Ok(())
1284 }
Zach Reizner55a9e502018-10-03 10:22:32 -07001285 Some("stop") => stop_vms(args),
Zach Reizner6a8fdd92019-01-16 14:38:41 -08001286 Some("suspend") => suspend_vms(args),
1287 Some("resume") => resume_vms(args),
Zach Reizner55a9e502018-10-03 10:22:32 -07001288 Some("run") => run_vm(args),
1289 Some("balloon") => balloon_vms(args),
1290 Some("create_qcow2") => create_qcow2(args),
Daniel Verkamp92f73d72018-12-04 13:17:46 -08001291 Some("disk") => disk_cmd(args),
Jingkui Wang100e6e42019-03-08 20:41:57 -08001292 Some("usb") => modify_usb(args),
Zach Reiznerefe95782017-08-26 18:05:48 -07001293 Some(c) => {
1294 println!("invalid subcommand: {:?}", c);
1295 print_usage();
Dylan Reidbfba9932018-02-05 15:51:59 -08001296 Err(())
Zach Reiznerefe95782017-08-26 18:05:48 -07001297 }
Dylan Reidbfba9932018-02-05 15:51:59 -08001298 };
Zach Reiznerefe95782017-08-26 18:05:48 -07001299
1300 // Reap exit status from any child device processes. At this point, all devices should have been
1301 // dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
1302 // take some time for the processes to shut down.
1303 if !wait_all_children() {
1304 // We gave them a chance, and it's too late.
1305 warn!("not all child processes have exited; sending SIGKILL");
1306 if let Err(e) = kill_process_group() {
1307 // We're now at the mercy of the OS to clean up after us.
David Tolnayb4bd00f2019-02-12 17:51:26 -08001308 warn!("unable to kill all child processes: {}", e);
Zach Reiznerefe95782017-08-26 18:05:48 -07001309 }
1310 }
1311
1312 // WARNING: Any code added after this point is not guaranteed to run
1313 // since we may forcibly kill this process (and its children) above.
Dylan Reidbfba9932018-02-05 15:51:59 -08001314 ret
1315}
1316
1317fn main() {
1318 std::process::exit(if crosvm_main().is_ok() { 0 } else { 1 });
Zach Reizner639d9672017-05-01 17:57:18 -07001319}
Daniel Verkamp107edb32019-04-05 09:58:48 -07001320
1321#[cfg(test)]
1322mod tests {
1323 use super::*;
1324
1325 #[test]
1326 fn parse_cpu_set_single() {
1327 assert_eq!(parse_cpu_set("123").expect("parse failed"), vec![123]);
1328 }
1329
1330 #[test]
1331 fn parse_cpu_set_list() {
1332 assert_eq!(
1333 parse_cpu_set("0,1,2,3").expect("parse failed"),
1334 vec![0, 1, 2, 3]
1335 );
1336 }
1337
1338 #[test]
1339 fn parse_cpu_set_range() {
1340 assert_eq!(
1341 parse_cpu_set("0-3").expect("parse failed"),
1342 vec![0, 1, 2, 3]
1343 );
1344 }
1345
1346 #[test]
1347 fn parse_cpu_set_list_of_ranges() {
1348 assert_eq!(
1349 parse_cpu_set("3-4,7-9,18").expect("parse failed"),
1350 vec![3, 4, 7, 8, 9, 18]
1351 );
1352 }
1353
1354 #[test]
1355 fn parse_cpu_set_repeated() {
1356 // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
1357 assert_eq!(parse_cpu_set("1,1,1").expect("parse failed"), vec![1, 1, 1]);
1358 }
1359
1360 #[test]
1361 fn parse_cpu_set_negative() {
1362 // Negative CPU numbers are not allowed.
1363 parse_cpu_set("-3").expect_err("parse should have failed");
1364 }
1365
1366 #[test]
1367 fn parse_cpu_set_reverse_range() {
1368 // Ranges must be from low to high.
1369 parse_cpu_set("5-2").expect_err("parse should have failed");
1370 }
1371
1372 #[test]
1373 fn parse_cpu_set_open_range() {
1374 parse_cpu_set("3-").expect_err("parse should have failed");
1375 }
1376
1377 #[test]
1378 fn parse_cpu_set_extra_comma() {
1379 parse_cpu_set("0,1,2,").expect_err("parse should have failed");
1380 }
Trent Begin17ccaad2019-04-17 13:51:25 -06001381
1382 #[test]
1383 fn parse_serial_vaild() {
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001384 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1385 .expect("parse should have succeded");
Trent Begin17ccaad2019-04-17 13:51:25 -06001386 }
1387
1388 #[test]
Trent Begin923bab02019-06-17 13:48:06 -06001389 fn parse_serial_valid_no_num() {
1390 parse_serial_options("type=syslog").expect("parse should have succeded");
1391 }
1392
1393 #[test]
Trent Begin17ccaad2019-04-17 13:51:25 -06001394 fn parse_serial_invalid_type() {
1395 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1396 }
1397
1398 #[test]
1399 fn parse_serial_invalid_num_upper() {
1400 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1401 }
1402
1403 #[test]
1404 fn parse_serial_invalid_num_lower() {
1405 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1406 }
1407
1408 #[test]
1409 fn parse_serial_invalid_num_string() {
1410 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1411 }
1412
1413 #[test]
1414 fn parse_serial_invalid_option() {
1415 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1416 }
Jorge E. Moreira1e262302019-08-01 14:40:03 -07001417
1418 #[test]
1419 fn parse_serial_invalid_two_stdin() {
1420 let mut config = Config::default();
1421 set_argument(&mut config, "serial", Some("num=1,type=stdout,stdin=true"))
1422 .expect("should parse the first serial argument");
1423 set_argument(&mut config, "serial", Some("num=2,type=stdout,stdin=true"))
1424 .expect_err("should fail to parse a second serial port connected to stdin");
1425 }
Daniel Verkamp107edb32019-04-05 09:58:48 -07001426}