1use std::env;
8use std::io::ErrorKind;
9#[cfg(any(target_os = "android", target_os = "linux"))]
10use std::os::unix::process::ExitStatusExt;
11use std::path::Path;
12use std::path::PathBuf;
13use std::process::Command;
14use std::process::ExitStatus;
15use std::process::Output;
16use std::sync::mpsc::sync_channel;
17use std::sync::mpsc::RecvTimeoutError;
18use std::thread;
19use std::time::Duration;
20use std::time::SystemTime;
21
22use anyhow::bail;
23use anyhow::Result;
24use tempfile::NamedTempFile;
25
26use crate::sys::binary_name;
27use crate::vhost_user::CmdType;
28use crate::vhost_user::Config as VuConfig;
29
30pub const DEFAULT_BLOCK_SIZE: u64 = 1024 * 1024;
31
32pub fn find_crosvm_binary() -> PathBuf {
36 let binary_name = binary_name();
37 let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
40 let bin_crosvm = PathBuf::from(cargo_manifest_dir)
41 .parent()
42 .unwrap()
43 .join("bin")
44 .join(binary_name);
45 if bin_crosvm.exists() {
46 return bin_crosvm;
47 }
48
49 let exe_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf();
52 let parent_dir_crosvm = exe_dir.parent().unwrap().join(binary_name);
53 if parent_dir_crosvm.exists() {
54 return parent_dir_crosvm;
55 }
56
57 panic!(
58 "Cannot find {} either in {} or {}.",
59 binary_name,
60 bin_crosvm.display(),
61 parent_dir_crosvm.display()
62 );
63}
64
65pub fn run_with_timeout<F, U>(closure: F, timeout: Duration) -> Result<U>
71where
72 F: FnOnce() -> U + Send + 'static,
73 U: Send + 'static,
74{
75 run_with_status_check(closure, timeout, || false)
76}
77
78pub fn run_with_status_check<F, U, C>(
85 closure: F,
86 interval: Duration,
87 mut continue_fn: C,
88) -> Result<U>
89where
90 F: FnOnce() -> U + Send + 'static,
91 U: Send + 'static,
92 C: FnMut() -> bool,
93{
94 let (tx, rx) = sync_channel::<()>(1);
95 let handle = thread::spawn(move || {
96 let result = closure();
97 let _ = tx.send(());
99 result
100 });
101 loop {
102 match rx.recv_timeout(interval) {
103 Ok(_) => {
104 return Ok(handle.join().unwrap());
105 }
106 Err(RecvTimeoutError::Timeout) => {
107 if !continue_fn() {
108 bail!("closure timed out");
109 }
110 }
111 Err(RecvTimeoutError::Disconnected) => bail!("closure panicked"),
112 }
113 }
114}
115
116#[derive(Debug)]
117pub enum CommandError {
118 IoError(std::io::Error),
119 ErrorCode(i32),
120 Signal(i32),
121}
122
123pub trait CommandExt {
125 fn output_checked(&mut self) -> std::result::Result<Output, CommandError>;
128
129 fn log(&mut self) -> &mut Self;
131}
132
133impl CommandExt for Command {
134 fn output_checked(&mut self) -> std::result::Result<Output, CommandError> {
135 let output = self.output().map_err(CommandError::IoError)?;
136 if !output.status.success() {
137 if let Some(code) = output.status.code() {
138 return Err(CommandError::ErrorCode(code));
139 } else {
140 #[cfg(any(target_os = "android", target_os = "linux"))]
141 if let Some(signal) = output.status.signal() {
142 return Err(CommandError::Signal(signal));
143 }
144 panic!("No error code and no signal should never happen.");
145 }
146 }
147 Ok(output)
148 }
149
150 fn log(&mut self) -> &mut Self {
151 println!("$ {self:?}");
152 self
153 }
154}
155
156pub trait ChildExt {
158 fn wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>>;
160}
161
162impl ChildExt for std::process::Child {
163 fn wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>> {
164 let start_time = SystemTime::now();
165 while SystemTime::now().duration_since(start_time).unwrap() < timeout {
166 if let Ok(status) = self.try_wait() {
167 return Ok(status);
168 }
169 thread::sleep(Duration::from_millis(10));
170 }
171 Err(std::io::Error::new(
172 ErrorKind::TimedOut,
173 "Timeout while waiting for child",
174 ))
175 }
176}
177
178pub fn retry<F, T, E>(closure: F, retries: usize) -> Result<T, E>
181where
182 F: FnMut() -> Result<T, E>,
183 E: std::fmt::Debug,
184{
185 retry_with_delay(closure, retries, Duration::ZERO)
186}
187
188pub fn retry_with_delay<F, T, E>(mut closure: F, retries: usize, delay: Duration) -> Result<T, E>
192where
193 F: FnMut() -> Result<T, E>,
194 E: std::fmt::Debug,
195{
196 let mut attempts_left = retries + 1;
197 loop {
198 let result = closure();
199 attempts_left -= 1;
200 if result.is_ok() || attempts_left == 0 {
201 break result;
202 } else {
203 println!("Attempt failed: {:?}", result.err());
204 std::thread::sleep(delay);
205 }
206 }
207}
208
209pub fn prepare_disk_img() -> NamedTempFile {
211 let mut disk = NamedTempFile::new().unwrap();
212 disk.as_file_mut().set_len(DEFAULT_BLOCK_SIZE).unwrap();
213
214 let path = env::var("PATH").unwrap();
217 let path = [&path, "/sbin", "/usr/sbin"].join(":");
218
219 Command::new("mkfs.ext4")
221 .arg(disk.path().to_str().unwrap())
222 .env("PATH", path)
223 .output()
224 .expect("failed to execute process");
225 disk
226}
227
228pub fn create_vu_block_config(cmd_type: CmdType, socket: &Path, disk: &Path) -> VuConfig {
229 let socket_path = socket.to_str().unwrap();
230 let disk_path = disk.to_str().unwrap();
231 println!("disk={disk_path}, socket={socket_path}");
232 match cmd_type {
233 CmdType::Device => VuConfig::new(cmd_type, "block").extra_args(vec![
234 "block".to_string(),
235 "--socket-path".to_string(),
236 socket_path.to_string(),
237 "--file".to_string(),
238 disk_path.to_string(),
239 ]),
240 CmdType::Devices => VuConfig::new(cmd_type, "block").extra_args(vec![
241 "--block".to_string(),
242 format!("vhost={},path={}", socket_path, disk_path),
243 ]),
244 }
245}
246
247pub fn create_vu_console_multiport_config(
248 socket: &Path,
249 file_path: Vec<(PathBuf, PathBuf)>,
250) -> VuConfig {
251 let socket_path = socket.to_str().unwrap();
252
253 let mut args = vec![
254 "console".to_string(),
255 "--socket-path".to_string(),
256 socket_path.to_string(),
257 ];
258
259 for (i, (output_file, input_file)) in file_path.iter().enumerate() {
260 args.push("--port".to_string());
261 match input_file.file_name().is_some() {
262 true => {
263 args.push(format!(
264 "type=file,hardware=virtio-console,name=port{},path={},input={}",
265 i,
266 output_file.to_str().unwrap(),
267 input_file.to_str().unwrap(),
268 ));
269 }
270 false => {
271 args.push(format!(
272 "type=file,hardware=virtio-console,name=port{},path={}",
273 i,
274 output_file.to_str().unwrap(),
275 ));
276 }
277 };
278 }
279 VuConfig::new(CmdType::Device, "console").extra_args(args)
280}