#![cfg(any(target_os = "android", target_os = "linux"))]
use std::ffi::CString;
use std::fs::read_to_string;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
use base::error;
use base::EventToken;
use base::WaitContext;
use base::WorkerThread;
use fixture::utils::create_vu_console_multiport_config;
use fixture::vhost_user::VhostUserBackend;
use fixture::vm::Config as VmConfig;
use fixture::vm::TestVm;
use tempfile::NamedTempFile;
use tempfile::TempDir;
fn run_vhost_user_console_multiport_test_portname(config: VmConfig) -> anyhow::Result<()> {
let socket = NamedTempFile::new().unwrap();
let temp_dir = TempDir::new()?;
let file_path = vec![
(temp_dir.path().join("vconsole0.out"), PathBuf::new()),
(temp_dir.path().join("vconsole1.out"), PathBuf::new()),
];
let vu_config = create_vu_console_multiport_config(socket.path(), file_path.clone());
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
let config = config
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
.with_vhost_user("console", socket.path());
let mut vm = TestVm::new(config).unwrap();
vm.exec_in_guest("mount -t sysfs sysfs /sys")?;
let result = vm
.exec_in_guest("ls /sys/class/virtio-ports/")
.expect("No virtio-ports dir");
let mut portlist: Vec<&str> = result.stdout.trim_end().split('\n').collect();
portlist.remove(0);
for (i, port) in portlist.into_iter().enumerate() {
let portname = vm
.exec_in_guest(format!("cat /sys/class/virtio-ports/{port}/name").as_str())
.expect("Failed to read portname")
.stdout;
assert_eq!(portname.trim_end(), format!("port{i}").as_str());
}
Ok(())
}
#[test]
fn vhost_user_console_portname_check() -> anyhow::Result<()> {
let config = VmConfig::new();
run_vhost_user_console_multiport_test_portname(config)?;
Ok(())
}
fn run_vhost_user_console_multiport_test_output(config: VmConfig) -> anyhow::Result<()> {
let socket = NamedTempFile::new().unwrap();
let temp_dir = TempDir::new()?;
let file_path = vec![
(temp_dir.path().join("vconsole0.out"), PathBuf::new()),
(temp_dir.path().join("vconsole1.out"), PathBuf::new()),
];
let vu_config = create_vu_console_multiport_config(socket.path(), file_path.clone());
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
let config = config
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
.with_vhost_user("console", socket.path());
let mut vm = TestVm::new(config).unwrap();
vm.exec_in_guest("mount -t sysfs sysfs /sys")?;
let result = vm
.exec_in_guest("ls /sys/class/virtio-ports/")
.expect("No virtio-ports dir");
let mut portlist: Vec<&str> = result.stdout.trim_end().split('\n').collect();
portlist.remove(0);
for (i, port) in portlist.into_iter().enumerate() {
vm.exec_in_guest(format!("echo \"hello {port}\" > /dev/{port}").as_str())
.expect("Failed to echo data to port");
let data = read_to_string(&file_path[i].0).expect("vu-console: read output failed");
assert_eq!(data.trim(), format!("hello {port}").as_str());
}
Ok(())
}
#[test]
fn vhost_user_console_check_output() -> anyhow::Result<()> {
let config = VmConfig::new();
run_vhost_user_console_multiport_test_output(config)?;
Ok(())
}
fn generate_workthread_to_monitor_fifo(
idx: usize,
infile: PathBuf,
outfile: PathBuf,
) -> WorkerThread<()> {
#[derive(EventToken)]
enum Token {
InputDataAvailable,
Kill,
}
let cpath_in = CString::new(infile.to_str().unwrap()).unwrap();
let cpath_out = CString::new(outfile.to_str().unwrap()).unwrap();
unsafe {
libc::mkfifo(cpath_in.as_ptr(), 0o777);
libc::mkfifo(cpath_out.as_ptr(), 0o777);
}
WorkerThread::start(format!("monitor_vconsole{idx}"), move |kill_event| {
let mut tx = OpenOptions::new().write(true).open(outfile).unwrap();
let mut rx = OpenOptions::new().read(true).open(infile).unwrap();
let mut msg = vec![0; 256];
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
(&rx, Token::InputDataAvailable),
(&kill_event, Token::Kill),
]) {
Ok(wait_ctx) => wait_ctx,
Err(e) => {
error!("failed creating WaitContext: {}", e);
return;
}
};
'monitor_loop: loop {
let wait_events = match wait_ctx.wait() {
Ok(wait_events) => wait_events,
Err(e) => {
error!("failed polling for events: {}", e);
break;
}
};
for wait_event in wait_events.iter().filter(|e| e.is_readable) {
match wait_event.token {
Token::InputDataAvailable => {
let bytes = rx.read(&mut msg).expect("Failed to read from port");
if bytes > 0 {
if tx.write_all(&msg.to_ascii_uppercase()[..bytes]).is_err() {
break 'monitor_loop;
}
}
}
Token::Kill => break 'monitor_loop,
}
}
}
})
}
fn run_vhost_user_console_multiport_test_input(config: VmConfig) -> anyhow::Result<()> {
let socket = NamedTempFile::new().unwrap();
let temp_dir = TempDir::new()?;
let mut file_path = vec![];
for idx in 0..2 {
let fifo_name_out = format!("vconsole{idx}.out");
let fifo_name_in = format!("vconsole{idx}.in");
file_path.push((
temp_dir.path().join(fifo_name_out),
temp_dir.path().join(fifo_name_in),
));
}
let mut thread_vec = vec![];
for idx in 0..2 {
thread_vec.push(generate_workthread_to_monitor_fifo(
idx,
(*file_path.get(idx).unwrap().0).to_path_buf(),
(*file_path.get(idx).unwrap().1).to_path_buf(),
));
}
let vu_config = create_vu_console_multiport_config(socket.path(), file_path.clone());
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
let config = config
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
.with_vhost_user("console", socket.path());
let mut vm = TestVm::new(config).unwrap();
vm.exec_in_guest("mount -t sysfs sysfs /sys")?;
let result = vm
.exec_in_guest("ls /sys/class/virtio-ports/")
.expect("No virtio-ports dir");
let mut portlist: Vec<&str> = result.stdout.trim_end().split('\n').collect();
portlist.remove(0);
let file_fd = 5;
for port in portlist.into_iter() {
let result = vm
.exec_in_guest(
format!(
"exec {file_fd}<>/dev/{port} && echo \"hello {port}\" >&{file_fd} && head -1 <&{file_fd}"
)
.as_str(),
)
.expect("Failed to echo data to port")
.stdout;
vm.exec_in_guest(format!("exec {file_fd}>&-").as_str())
.expect("Failed to close device fd");
assert_eq!(
result.trim_end(),
format!("hello {port}").to_uppercase().as_str()
);
}
for handler in thread_vec.into_iter() {
handler.stop();
}
Ok(())
}
#[test]
fn vhost_user_console_check_input() -> anyhow::Result<()> {
let config = VmConfig::new();
run_vhost_user_console_multiport_test_input(config)?;
Ok(())
}