use std::{ffi::CString, os::unix::ffi::OsStrExt, path::PathBuf, sync::Arc};
#[cfg(target_os = "linux")]
use libc::{O_DIRECT, O_SYNC};
use tempfile::TempDir;
use uuid::Uuid;
use crate::isolation::{
fd::UhyveFileDescriptorLayer, split_guest_and_host_path, tempdir::create_temp_dir,
};
mod tests;
mod tree;
pub use tree::Leaf as UhyveMapLeaf;
#[cfg(target_os = "linux")]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct UhyveIoMode {
direct: bool,
sync: bool,
}
#[cfg(target_os = "linux")]
impl From<Option<String>> for UhyveIoMode {
fn from(s: Option<String>) -> Self {
let (prefix, flags) = s
.unwrap_or_default()
.to_lowercase()
.split_once("=")
.map(|(prefix, flags)| (prefix.to_string(), flags.to_string()))
.unwrap_or_default();
let flags: Vec<_> = flags.split(',').collect();
match prefix.as_str() {
"host" => {
let direct = flags.contains(&"direct");
let sync = flags.contains(&"sync");
UhyveIoMode { direct, sync }
}
"" => UhyveIoMode {
direct: false,
sync: false,
},
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum HermitImageError {
#[error("The Hermit image tar file is corrupted: {0}")]
CorruptData(#[from] tar_no_std::CorruptDataError),
#[error("A file in the Hermit image doesn't have an UTF-8 file name: {0:?}")]
NonUtf8Filename(Box<tar_no_std::TarFormatString<256>>),
#[error("Unable to create a file from the Hermit image in the directory tree: {0:?}")]
CreateLeaf(String),
}
#[derive(Debug)]
pub struct UhyveFileMap {
root: tree::Directory,
tempdir: TempDir,
pub fdmap: UhyveFileDescriptorLayer,
#[cfg(target_os = "linux")]
iomode: UhyveIoMode,
}
impl UhyveFileMap {
pub fn new(
mappings: &[String],
tempdir: Option<PathBuf>,
#[cfg(target_os = "linux")] iomode: UhyveIoMode,
) -> UhyveFileMap {
let mut fm = UhyveFileMap {
root: tree::Directory::new(),
tempdir: create_temp_dir(tempdir),
fdmap: UhyveFileDescriptorLayer::default(),
#[cfg(target_os = "linux")]
iomode,
};
for i in mappings {
let (guest_path, host_path) = split_guest_and_host_path(i.as_str()).unwrap();
if !tree::create_leaf(
&mut fm.root,
guest_path.as_os_str().as_bytes(),
UhyveMapLeaf::OnHost(host_path),
) {
panic!(
"Error when creating filemap @ guest_path = {guest_path:?}; Are duplicate paths present?"
);
}
}
fm
}
pub fn add_hermit_image(&mut self, tar_bytes: &[u8]) -> Result<(), HermitImageError> {
if tar_bytes.is_empty() {
return Ok(());
}
for i in tar_no_std::TarArchiveRef::new(tar_bytes)?.entries() {
let filename = i.filename();
let filename = filename
.as_str()
.map_err(|_| HermitImageError::NonUtf8Filename(Box::new(filename)))?;
let data: Arc<[u8]> = i.data().to_vec().into();
if !self.create_leaf(filename, UhyveMapLeaf::Virtual(data)) {
return Err(HermitImageError::CreateLeaf(filename.to_string()));
}
}
Ok(())
}
pub fn get_host_path(&self, guest_path: &str) -> Option<UhyveMapLeaf> {
tree::resolve_guest_path(&self.root, guest_path.as_bytes())
}
#[cfg(target_os = "linux")]
pub(crate) fn get_all_host_paths(&self) -> impl Iterator<Item = &std::path::Path> {
tree::get_all_host_paths(&self.root)
}
pub(crate) fn get_all_guest_dirs(&self) -> impl Iterator<Item = String> {
tree::get_all_guest_dirs(&self.root)
}
#[inline]
#[cfg(target_os = "linux")]
pub(crate) fn get_io_mode_flags(&self) -> i32 {
let mut flags: i32 = 0;
if self.iomode.sync {
flags |= O_SYNC;
}
if self.iomode.direct {
flags |= O_DIRECT;
}
flags
}
#[cfg(target_os = "linux")]
pub(crate) fn get_temp_dir(&self) -> &std::path::Path {
self.tempdir.path()
}
pub fn create_leaf(&mut self, guest_path: &str, leaf: UhyveMapLeaf) -> bool {
tree::create_leaf(&mut self.root, guest_path.as_bytes(), leaf)
}
pub fn create_temporary_file(&mut self, guest_path: &str) -> Option<CString> {
let host_path = self.tempdir.path().join(Uuid::new_v4().to_string());
trace!("create_temporary_file (host_path): {host_path:#?}");
let ret = CString::new(host_path.as_os_str().as_bytes()).unwrap();
if self.create_leaf(guest_path, UhyveMapLeaf::OnHost(host_path)) {
Some(ret)
} else {
None
}
}
pub fn unlink(&mut self, guest_path: &str) -> Result<Option<PathBuf>, ()> {
tree::unlink(&mut self.root, guest_path.as_bytes())
}
}