Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/e2e_tests/fixture/src/sys/linux.rs
5394 views
1
// Copyright 2022 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
use std::ffi::CString;
6
use std::fs::File;
7
use std::fs::OpenOptions;
8
use std::io;
9
use std::io::BufReader;
10
use std::os::unix::fs::OpenOptionsExt;
11
use std::path::Path;
12
use std::path::PathBuf;
13
use std::process::Child;
14
use std::process::Command;
15
use std::process::Stdio;
16
use std::sync::Arc;
17
use std::sync::Mutex;
18
use std::time::Duration;
19
use std::time::Instant;
20
21
use anyhow::anyhow;
22
use anyhow::Context;
23
use anyhow::Result;
24
use delegate::wire_format::DelegateMessage;
25
use libc::O_DIRECT;
26
use serde_json::StreamDeserializer;
27
use tempfile::TempDir;
28
29
use crate::utils::find_crosvm_binary;
30
use crate::utils::run_with_status_check;
31
use crate::vm::local_path_from_url;
32
use crate::vm::Config;
33
34
const FROM_GUEST_PIPE: &str = "from_guest";
35
const TO_GUEST_PIPE: &str = "to_guest";
36
const CONTROL_PIPE: &str = "control";
37
38
/// Timeout for communicating with the VM. If we do not hear back, panic so we
39
/// do not block the tests.
40
const VM_COMMUNICATION_TIMEOUT: Duration = Duration::from_secs(10);
41
42
pub(crate) type SerialArgs = Path;
43
44
/// Returns the name of crosvm binary.
45
pub fn binary_name() -> &'static str {
46
"crosvm"
47
}
48
49
/// Safe wrapper for libc::mkfifo
50
pub(crate) fn mkfifo(path: &Path) -> io::Result<()> {
51
let cpath = CString::new(path.to_str().unwrap()).unwrap();
52
// SAFETY: no mutable pointer passed to function and the return value is checked.
53
let result = unsafe { libc::mkfifo(cpath.as_ptr(), 0o777) };
54
if result == 0 {
55
Ok(())
56
} else {
57
Err(io::Error::last_os_error())
58
}
59
}
60
61
pub struct TestVmSys {
62
/// Maintain ownership of test_dir until the vm is destroyed.
63
#[allow(dead_code)]
64
pub test_dir: TempDir,
65
pub from_guest_reader: Arc<
66
Mutex<
67
StreamDeserializer<
68
'static,
69
serde_json::de::IoRead<BufReader<std::fs::File>>,
70
DelegateMessage,
71
>,
72
>,
73
>,
74
pub to_guest: Arc<Mutex<File>>,
75
pub control_socket_path: PathBuf,
76
pub process: Option<Child>, // Use `Option` to allow taking the ownership in `Drop::drop()`.
77
}
78
79
impl TestVmSys {
80
// Check if the test file system is a known compatible one. Needs to support features
81
// like O_DIRECT.
82
pub fn check_rootfs_file(rootfs_path: &Path) {
83
if let Err(e) = OpenOptions::new()
84
.custom_flags(O_DIRECT)
85
.write(false)
86
.read(true)
87
.open(rootfs_path)
88
{
89
panic!("File open with O_DIRECT expected to work but did not: {e}");
90
}
91
}
92
93
// Adds 2 serial devices:
94
// - ttyS0: Console device which prints kernel log / debug output of the delegate binary.
95
// - ttyS1: Serial device attached to the named pipes.
96
fn configure_serial_devices(
97
command: &mut Command,
98
stdout_hardware_type: &str,
99
from_guest_pipe: &Path,
100
to_guest_pipe: &Path,
101
) {
102
let stdout_serial_option = format!("type=stdout,hardware={stdout_hardware_type},console");
103
command.args(["--serial", &stdout_serial_option]);
104
105
// Setup channel for communication with the delegate.
106
let serial_params = format!(
107
"type=file,path={},input={},num=2",
108
from_guest_pipe.display(),
109
to_guest_pipe.display()
110
);
111
command.args(["--serial", &serial_params]);
112
}
113
114
/// Configures the VM rootfs to load from the guest_under_test assets.
115
fn configure_rootfs(command: &mut Command, o_direct: bool, rw: bool, path: &Path) {
116
let rootfs_and_option = format!(
117
"{}{}{},root",
118
path.as_os_str().to_str().unwrap(),
119
if o_direct { ",direct=true" } else { "" },
120
if rw { "" } else { ",ro" }
121
);
122
command
123
.args(["--block", &rootfs_and_option])
124
.args(["--params", "init=/bin/delegate"]);
125
}
126
127
pub fn new_generic<F>(f: F, cfg: Config, sudo: bool) -> Result<TestVmSys>
128
where
129
F: FnOnce(&mut Command, &Path, &Config) -> Result<()>,
130
{
131
// Create two named pipes to communicate with the guest.
132
let test_dir = TempDir::new()?;
133
let from_guest_pipe = test_dir.path().join(FROM_GUEST_PIPE);
134
let to_guest_pipe = test_dir.path().join(TO_GUEST_PIPE);
135
mkfifo(&from_guest_pipe)?;
136
mkfifo(&to_guest_pipe)?;
137
138
let control_socket_path = test_dir.path().join(CONTROL_PIPE);
139
140
let mut command = match &cfg.wrapper_cmd {
141
Some(cmd) => {
142
let wrapper_splitted =
143
shlex::split(cmd).context("Failed to parse wrapper command")?;
144
let mut command_tmp = if sudo {
145
let mut command = Command::new("sudo");
146
command.arg(&wrapper_splitted[0]);
147
command
148
} else {
149
Command::new(&wrapper_splitted[0])
150
};
151
152
command_tmp.args(&wrapper_splitted[1..]);
153
command_tmp.arg(find_crosvm_binary());
154
command_tmp
155
}
156
None => {
157
if sudo {
158
let mut command = Command::new("sudo");
159
command.arg(find_crosvm_binary());
160
command
161
} else {
162
Command::new(find_crosvm_binary())
163
}
164
}
165
};
166
167
command.env("RUST_BACKTRACE", "full");
168
169
if let Some(log_file_name) = &cfg.log_file {
170
let log_file_stdout = File::create(log_file_name)?;
171
let log_file_stderr = log_file_stdout.try_clone()?;
172
command.stdout(Stdio::from(log_file_stdout));
173
command.stderr(Stdio::from(log_file_stderr));
174
}
175
176
command.args(["--log-level", cfg.log_level.as_str()]);
177
command.args(["run"]);
178
179
f(&mut command, test_dir.path(), &cfg)?;
180
181
command.args(&cfg.extra_args);
182
183
println!("$ {command:?}");
184
let mut process = command.spawn()?;
185
186
// Open pipes. Apply timeout to `to_guest` and `from_guest` since it will block until crosvm
187
// opens the other end.
188
let start = Instant::now();
189
let run_result = run_with_status_check(
190
move || (File::create(to_guest_pipe), File::open(from_guest_pipe)),
191
Duration::from_millis(200),
192
|| {
193
if start.elapsed() > VM_COMMUNICATION_TIMEOUT {
194
return false;
195
}
196
if let Some(wait_result) = process.try_wait().unwrap() {
197
println!("crosvm unexpectedly exited: {wait_result:?}");
198
false
199
} else {
200
true
201
}
202
},
203
);
204
205
let (to_guest, from_guest) = match run_result {
206
Ok((to_guest, from_guest)) => (
207
to_guest.context("Cannot open to_guest pipe")?,
208
from_guest.context("Cannot open from_guest pipe")?,
209
),
210
Err(error) => {
211
// Kill the crosvm process if we cannot connect in time.
212
process.kill().unwrap();
213
process.wait().unwrap();
214
panic!("Cannot connect to VM: {error}");
215
}
216
};
217
218
Ok(TestVmSys {
219
test_dir,
220
from_guest_reader: Arc::new(Mutex::new(
221
serde_json::Deserializer::from_reader(BufReader::new(from_guest)).into_iter(),
222
)),
223
to_guest: Arc::new(Mutex::new(to_guest)),
224
control_socket_path,
225
process: Some(process),
226
})
227
}
228
229
// Generates a config file from cfg and appends the command to use the config file.
230
pub fn append_config_args(command: &mut Command, test_dir: &Path, cfg: &Config) -> Result<()> {
231
TestVmSys::configure_serial_devices(
232
command,
233
&cfg.console_hardware,
234
&test_dir.join(FROM_GUEST_PIPE),
235
&test_dir.join(TO_GUEST_PIPE),
236
);
237
command.args(["--socket", test_dir.join(CONTROL_PIPE).to_str().unwrap()]);
238
239
if let Some(rootfs_url) = &cfg.rootfs_url {
240
if cfg.rootfs_rw {
241
std::fs::copy(
242
match cfg.rootfs_compressed {
243
true => local_path_from_url(rootfs_url).with_extension("raw"),
244
false => local_path_from_url(rootfs_url),
245
},
246
test_dir.join("rw_rootfs.img"),
247
)
248
.unwrap();
249
TestVmSys::configure_rootfs(
250
command,
251
cfg.o_direct,
252
true,
253
&test_dir.join("rw_rootfs.img"),
254
);
255
} else if cfg.rootfs_compressed {
256
TestVmSys::configure_rootfs(
257
command,
258
cfg.o_direct,
259
false,
260
&local_path_from_url(rootfs_url).with_extension("raw"),
261
);
262
} else {
263
TestVmSys::configure_rootfs(
264
command,
265
cfg.o_direct,
266
false,
267
&local_path_from_url(rootfs_url),
268
);
269
}
270
};
271
272
// Set initrd if being requested
273
if let Some(initrd_url) = &cfg.initrd_url {
274
command.arg("--initrd");
275
command.arg(local_path_from_url(initrd_url));
276
}
277
278
// Set kernel as the last argument.
279
command.arg(local_path_from_url(&cfg.kernel_url));
280
Ok(())
281
}
282
283
pub fn crosvm_command(
284
&self,
285
command: &str,
286
mut args: Vec<String>,
287
sudo: bool,
288
) -> Result<Vec<u8>> {
289
args.push(self.control_socket_path.to_str().unwrap().to_string());
290
291
println!("$ crosvm {} {:?}", command, &args.join(" "));
292
293
let mut cmd = if sudo {
294
let mut cmd = Command::new("sudo");
295
cmd.arg(find_crosvm_binary());
296
cmd
297
} else {
298
Command::new(find_crosvm_binary())
299
};
300
301
cmd.arg(command).args(args);
302
303
let output = cmd.output()?;
304
if !output.status.success() {
305
Err(anyhow!("Command failed with exit code {}", output.status))
306
} else {
307
Ok(output.stdout)
308
}
309
}
310
}
311
312