#![cfg(any(target_os = "android", target_os = "linux"))]
use std::path::Path;
use fixture::vhost_user::CmdType;
use fixture::vhost_user::Config as VuConfig;
use fixture::vhost_user::VhostUserBackend;
use fixture::vm::Config;
use fixture::vm::TestVm;
use tempfile::NamedTempFile;
use tempfile::TempDir;
fn copy_file(mut vm: TestVm, tag: &str, dir: TempDir) {
const ORIGINAL_FILE_NAME: &str = "original.txt";
const NEW_FILE_NAME: &str = "new.txt";
const TEST_DATA: &str = "virtiofs works!";
let orig_file = dir.path().join(ORIGINAL_FILE_NAME);
std::fs::write(orig_file, TEST_DATA).unwrap();
vm.exec_in_guest(&format!(
"mount -t virtiofs {tag} /mnt && cp /mnt/{ORIGINAL_FILE_NAME} /mnt/{NEW_FILE_NAME} && sync",
))
.unwrap();
let new_file = dir.path().join(NEW_FILE_NAME);
let contents = std::fs::read(new_file).unwrap();
assert_eq!(TEST_DATA.as_bytes(), &contents);
}
fn mount_rw(mut vm: TestVm, tag: &str, dir: TempDir) {
const READ_FILE_NAME: &str = "read_test.txt";
const WRITE_FILE_NAME: &str = "write_test.txt";
const TEST_DATA: &str = "hello world";
let read_test_file = dir.path().join(READ_FILE_NAME);
let write_test_file = dir.path().join(WRITE_FILE_NAME);
std::fs::write(read_test_file, TEST_DATA).unwrap();
assert_eq!(
vm.exec_in_guest(&format!(
"mount -t virtiofs {tag} /mnt && cat /mnt/read_test.txt"
))
.unwrap()
.stdout
.trim(),
TEST_DATA
);
const IN_FS_WRITE_FILE_PATH: &str = "/mnt/write_test.txt";
let _ = vm.exec_in_guest(&format!("echo -n {TEST_DATA} > {IN_FS_WRITE_FILE_PATH}"));
let read_contents = std::fs::read(write_test_file).unwrap();
assert_eq!(TEST_DATA.as_bytes(), &read_contents);
}
#[test]
fn fs_copy_file() {
let tag = "mtdtest";
let temp_dir = tempfile::tempdir().unwrap();
let config = Config::new().extra_args(vec![
"--shared-dir".to_string(),
format!(
"{}:{tag}:type=fs:cache=auto",
temp_dir.path().to_str().unwrap()
),
]);
let vm = TestVm::new(config).unwrap();
copy_file(vm, tag, temp_dir)
}
#[test]
fn fs_mount_rw() {
let tag = "mtdtest";
let temp_dir = tempfile::tempdir().unwrap();
let config = Config::new().extra_args(vec![
"--shared-dir".to_string(),
format!(
"{}:{tag}:type=fs:cache=auto",
temp_dir.path().to_str().unwrap()
),
]);
let vm = TestVm::new(config).unwrap();
mount_rw(vm, tag, temp_dir)
}
#[test]
fn file_ugid() {
const FILE_NAME: &str = "user_file.txt";
let uid = base::geteuid();
let gid = base::getegid();
let mapped_uid: u32 = rand::random();
let mapped_gid: u32 = rand::random();
let uid_map: String = format!("{mapped_uid} {uid} 1");
let gid_map = format!("{mapped_gid} {gid} 1");
let temp_dir = tempfile::tempdir().unwrap();
let orig_file = temp_dir.path().join(FILE_NAME);
std::fs::write(orig_file, "").unwrap();
let tag = "mtdtest";
let config = Config::new().extra_args(vec![
"--shared-dir".to_string(),
format!(
"{}:{tag}:type=fs:uidmap={}:gidmap={}:uid={}:gid={}",
temp_dir.path().to_str().unwrap(),
uid_map,
gid_map,
mapped_uid,
mapped_gid
),
]);
let mut vm = TestVm::new(config).unwrap();
vm.exec_in_guest(&format!("mount -t virtiofs {tag} /mnt"))
.unwrap();
let output = vm
.exec_in_guest(&format!("stat /mnt/{FILE_NAME}",))
.unwrap();
assert!(output.stdout.contains(&format!("Uid: ({mapped_uid}/")));
assert!(output.stdout.contains(&format!("Gid: ({mapped_gid}/")));
}
pub fn create_vu_fs_config(socket: &Path, shared_dir: &Path, tag: &str) -> VuConfig {
let uid = base::geteuid();
let gid = base::getegid();
let socket_path = socket.to_str().unwrap();
let shared_dir_path = shared_dir.to_str().unwrap();
println!("socket={socket_path}, tag={tag}, shared_dir={shared_dir_path}");
VuConfig::new(CmdType::Device, "vhost-user-fs").extra_args(vec![
"fs".to_string(),
format!("--socket-path={socket_path}"),
format!("--shared-dir={shared_dir_path}"),
format!("--tag={tag}"),
format!("--uid-map=0 {uid} 1"),
format!("--gid-map=0 {gid} 1"),
])
}
#[test]
fn vhost_user_fs_copy_file() {
let socket = NamedTempFile::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let config = Config::new();
let tag = "mtdtest";
let vu_config = create_vu_fs_config(socket.path(), temp_dir.path(), tag);
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
let config = config.with_vhost_user("fs", socket.path());
let vm = TestVm::new(config).unwrap();
copy_file(vm, tag, temp_dir);
}
#[test]
fn vhost_user_fs_mount_rw() {
let socket = NamedTempFile::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let config = Config::new();
let tag = "mtdtest";
let vu_config = create_vu_fs_config(socket.path(), temp_dir.path(), tag);
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
let config = config.with_vhost_user("fs", socket.path());
let vm = TestVm::new(config).unwrap();
mount_rw(vm, tag, temp_dir);
}
fn copy_file_validate_ugid_mapping(
mut vm: TestVm,
tag: &str,
dir: TempDir,
mapped_uid: u32,
mapped_gid: u32,
) {
use std::os::linux::fs::MetadataExt;
const ORIGINAL_FILE_NAME: &str = "original.txt";
const NEW_FILE_NAME: &str = "new.txt";
const TEST_DATA: &str = "Hello world!";
let orig_file = dir.path().join(ORIGINAL_FILE_NAME);
std::fs::write(orig_file, TEST_DATA).unwrap();
vm.exec_in_guest(&format!(
"mount -t virtiofs {tag} /mnt && cp /mnt/{ORIGINAL_FILE_NAME} /mnt/{NEW_FILE_NAME} && sync",
))
.unwrap();
let output = vm
.exec_in_guest(&format!("stat /mnt/{ORIGINAL_FILE_NAME}",))
.unwrap();
assert!(output.stdout.contains(&format!("Uid: ({mapped_uid}/")));
assert!(output.stdout.contains(&format!("Gid: ({mapped_gid}/")));
let new_file = dir.path().join(NEW_FILE_NAME);
let output_stat = std::fs::metadata(new_file.clone());
assert_eq!(
output_stat
.as_ref()
.expect("stat of new_file failed")
.st_uid(),
base::geteuid()
);
assert_eq!(
output_stat
.as_ref()
.expect("stat of new_file failed")
.st_gid(),
base::getegid()
);
let contents = std::fs::read(new_file).unwrap();
assert_eq!(TEST_DATA.as_bytes(), &contents);
}
pub fn create_ugid_map_config(
socket: &Path,
shared_dir: &Path,
tag: &str,
mapped_uid: u32,
mapped_gid: u32,
) -> VuConfig {
let socket_path = socket.to_str().unwrap();
let shared_dir_path = shared_dir.to_str().unwrap();
let uid = base::geteuid();
let gid = base::getegid();
let ugid_map_value = format!("{mapped_uid} {mapped_gid} {uid} {gid} 7 /",);
let cfg_arg = format!("writeback=true,ugid_map='{ugid_map_value}'");
println!("socket={socket_path}, tag={tag}, shared_dir={shared_dir_path}");
VuConfig::new(CmdType::Device, "vhost-user-fs").extra_args(vec![
"fs".to_string(),
format!("--socket-path={socket_path}"),
format!("--shared-dir={shared_dir_path}"),
format!("--tag={tag}"),
format!("--cfg={cfg_arg}"),
format!("--disable-sandbox"),
format!("--skip-pivot-root=true"),
])
}
#[test]
fn vhost_user_fs_without_sandbox_and_pivot_root() {
let socket = NamedTempFile::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let config = Config::new();
let tag = "android";
let mapped_uid = 123456;
let mapped_gid = 12345;
let vu_config =
create_ugid_map_config(socket.path(), temp_dir.path(), tag, mapped_uid, mapped_gid);
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
let config = config.with_vhost_user("fs", socket.path());
let vm = TestVm::new(config).unwrap();
copy_file_validate_ugid_mapping(vm, tag, temp_dir, mapped_uid, mapped_gid);
}