use std::env;
use std::fs::OpenOptions;
use std::io::BufReader;
use std::path::Path;
use std::path::PathBuf;
use std::process::Child;
use std::process::Command;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use anyhow::Result;
use base::named_pipes;
use base::PipeConnection;
use delegate::wire_format::DelegateMessage;
use serde_json::StreamDeserializer;
use crate::utils::find_crosvm_binary;
use crate::vm::local_path_from_url;
use crate::vm::Config;
const GUEST_EARLYCON: &str = "guest_earlycon.log";
const GUEST_CONSOLE: &str = "guest_latecon.log";
const HYPERVISOR_LOG: &str = "hypervisor.log";
const SLEEP_TIMEOUT: Duration = Duration::from_millis(500);
const RETRY_COUNT: u16 = 600;
pub struct SerialArgs {
from_guest_pipe: PathBuf,
logs_dir: PathBuf,
}
pub fn binary_name() -> &'static str {
"crosvm.exe"
}
fn generate_pipe_name() -> String {
format!(r"\\.\pipe\test-ipc-pipe-name.rand{}", rand::random::<u64>())
}
fn get_hypervisor() -> String {
env::var("CROSVM_TEST_HYPERVISOR").unwrap_or("whpx".to_string())
}
fn get_irqchip(hypervisor: &str) -> Option<String> {
if hypervisor == "haxm" || hypervisor == "ghaxm" {
Some("userspace".to_string())
} else {
None
}
}
fn get_hypervisor_args() -> Vec<String> {
let hypervisor = get_hypervisor();
let mut args = if let Some(irqchip) = get_irqchip(&hypervisor) {
vec!["--irqchip".to_owned(), irqchip]
} else {
vec![]
};
args.extend_from_slice(&["--hypervisor".to_owned(), hypervisor]);
args
}
fn dump_logs(logs_dir: &str) {
let dir = Path::new(logs_dir);
if dir.is_dir() {
for entry in std::fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if !path.is_dir() {
let data = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("Unable to read file {:?}: {:?}", &path, e));
eprintln!("---------- {:?}", &path);
eprintln!("{}", &data);
eprintln!("---------- {:?}", &path);
}
}
}
}
fn create_client_pipe_helper(from_guest_pipe: &str, logs_dir: &str) -> PipeConnection {
for _ in 0..RETRY_COUNT {
std::thread::sleep(SLEEP_TIMEOUT);
if let Ok(pipe) = named_pipes::create_client_pipe(
from_guest_pipe,
&named_pipes::FramingMode::Byte,
&named_pipes::BlockingMode::Wait,
false,
) {
return pipe;
}
}
dump_logs(logs_dir);
panic!("Failed to open pipe from guest");
}
pub struct TestVmSys {
pub(crate) from_guest_reader: Arc<
Mutex<
StreamDeserializer<
'static,
serde_json::de::IoRead<BufReader<PipeConnection>>,
DelegateMessage,
>,
>,
>,
pub(crate) to_guest: Arc<Mutex<PipeConnection>>,
pub(crate) process: Option<Child>,
}
impl TestVmSys {
pub fn check_rootfs_file(rootfs_path: &Path) {
if let Err(e) = OpenOptions::new().write(false).read(true).open(rootfs_path) {
panic!("File open expected to work but did not: {e}");
}
}
fn configure_serial_devices(
command: &mut Command,
stdout_hardware_type: &str,
from_guest_pipe: &Path,
logs_dir: &Path,
) {
let earlycon_path = Path::new(logs_dir).join(GUEST_EARLYCON);
let earlycon_str = earlycon_path.to_str().unwrap();
command.args([
r"--serial",
&format!("hardware=serial,num=1,type=file,path={earlycon_str},earlycon=true"),
]);
let console_path = Path::new(logs_dir).join(GUEST_CONSOLE);
let console_str = console_path.to_str().unwrap();
command.args([
r"--serial",
&format!(
"hardware={stdout_hardware_type},num=1,type=file,path={console_str},console=true"
),
]);
let serial_params = format!(
"hardware=serial,type=namedpipe,path={},num=2",
from_guest_pipe.display(),
);
command.args(["--serial", &serial_params]);
}
fn configure_rootfs(command: &mut Command, _o_direct: bool, path: &Path) {
let rootfs_and_option = format!(
"{},ro,root,sparse=false",
path.as_os_str().to_str().unwrap(),
);
command.args(["--root", &rootfs_and_option]).args([
"--params",
"init=/bin/delegate noxsaves noxsave nopat nopti tsc=reliable",
]);
}
pub fn new_generic<F>(f: F, cfg: Config, _sudo: bool) -> Result<TestVmSys>
where
F: FnOnce(&mut Command, &SerialArgs, &Config) -> Result<()>,
{
let logs_dir = "emulator_logs";
let mut logs_path = PathBuf::new();
logs_path.push(logs_dir);
std::fs::create_dir_all(logs_dir)?;
let from_guest_path = generate_pipe_name();
let from_guest_pipe = Path::new(&from_guest_path);
let mut command = Command::new(find_crosvm_binary());
command.args(["--log-level", "INFO", "run-mp"]);
f(
&mut command,
&SerialArgs {
from_guest_pipe: from_guest_pipe.to_path_buf(),
logs_dir: logs_path,
},
&cfg,
)?;
let hypervisor_log_path = Path::new(logs_dir).join(HYPERVISOR_LOG);
let hypervisor_log_str = hypervisor_log_path.to_str().unwrap();
command.args([
"--logs-directory",
logs_dir,
"--kernel-log-file",
hypervisor_log_str,
]);
command.args(get_hypervisor_args());
command.args(cfg.extra_args);
println!("Running command: {command:?}");
let process = Some(command.spawn().unwrap());
let to_guest = create_client_pipe_helper(&from_guest_path, logs_dir);
let from_guest_reader = BufReader::new(to_guest.try_clone().unwrap());
Ok(TestVmSys {
from_guest_reader: Arc::new(Mutex::new(
serde_json::Deserializer::from_reader(from_guest_reader).into_iter(),
)),
to_guest: Arc::new(Mutex::new(to_guest)),
process,
})
}
pub fn append_config_args(
command: &mut Command,
serial_args: &SerialArgs,
cfg: &Config,
) -> Result<()> {
TestVmSys::configure_serial_devices(
command,
&cfg.console_hardware,
&serial_args.from_guest_pipe,
&serial_args.logs_dir,
);
if let Some(rootfs_url) = &cfg.rootfs_url {
TestVmSys::configure_rootfs(command, cfg.o_direct, &local_path_from_url(rootfs_url));
};
if let Some(initrd_url) = &cfg.initrd_url {
command.arg("--initrd");
command.arg(local_path_from_url(initrd_url));
}
command.arg(local_path_from_url(&cfg.kernel_url));
Ok(())
}
pub fn crosvm_command(
&mut self,
_command: &str,
mut _args: Vec<String>,
_sudo: bool,
) -> Result<Vec<u8>> {
unimplemented!()
}
}