Add WIP wasi fs fixes and improvements

This commit is contained in:
Mark McCaskey 2019-04-26 12:22:51 -07:00
parent baae2b3826
commit d5f7e09ce3
2 changed files with 252 additions and 20 deletions

View File

@ -9,10 +9,11 @@ use std::{
cell::Cell,
fs,
io::{self, Read, Seek, Write},
path::{Path, PathBuf},
time::SystemTime,
};
use wasmer_runtime_core::debug;
use zbox::init_env as zbox_init_env;
//use zbox::init_env as zbox_init_env;
pub const MAX_SYMLINKS: usize = 100;
@ -150,6 +151,8 @@ pub enum Kind {
handle: WasiFile,
},
Dir {
// parent directory
parent: Option<Inode>,
handle: WasiFile,
/// The entries of a directory are lazily filled.
entries: HashMap<String, Inode>,
@ -184,9 +187,9 @@ pub struct WasiFs {
impl WasiFs {
pub fn new(preopened_files: &[String]) -> Result<Self, String> {
debug!("wasi::fs::init");
zbox_init_env();
/*zbox_init_env();
debug!("wasi::fs::repo");
/*let repo = RepoOpener::new()
let repo = RepoOpener::new()
.create(true)
.open("mem://wasmer-test-fs", "")
.map_err(|e| e.to_string())?;*/
@ -200,36 +203,197 @@ impl WasiFs {
next_fd: Cell::new(3),
inode_counter: Cell::new(1000),
};
for file in preopened_files {
debug!("Attempting to preopen {}", &file);
let cur_dir = std::env::current_dir().expect("failed to get current directory");
let mut canonical_preopen_dirs: Vec<PathBuf> = preopened_files
.iter()
.map(|pod| fs::canonicalize(pod))
.collect::<Result<Vec<PathBuf>, _>>()
.map_err(|err| format!("Error while preopening files: {}", err))?;
// TODO: more aggressively canonicalize these to remove _all_ ..
// that is, we should pin to specific directories now (.. + symlinks means the directories can change)
let mut canonical_preopen_dirs: Vec<(PathBuf, String)> = canonical_preopen_dirs
.drain(..)
.zip(preopened_files.iter().cloned())
.collect();
canonical_preopen_dirs
.sort_by(|(canonical_a, _), (canonical_b, _)| canonical_a.cmp(canonical_b));
/*let canonical_preopen_dirs: Vec<PathBuf> = canonical_preopen_dirs
.drain(..)
.map(|path| {
if path.starts_with(&cur_dir) {
PathBuf::from(path.strip_prefix(&cur_dir).unwrap())
} else {
path
}
})
.collect();*/
// hack(mark): rather than grouping by least common ancestor, we just assume there is either 0 or 1
// common ancestor and use that (to be fixed once this gets working)
let lca: PathBuf = canonical_preopen_dirs
.iter()
.min_by(|(a, _), (b, _)| a.to_string_lossy().len().cmp(&b.to_string_lossy().len()))
.map(|(pb, _)| pb.clone())
.unwrap_or(PathBuf::from(""));
dbg!(&lca);
dbg!(&canonical_preopen_dirs);
std::env::set_current_dir(&lca);
// set of preopen dirs for parent detection
let mut preopen_dir_map: HashMap<PathBuf, u32> = HashMap::new();
for (canonical_file, raw) in canonical_preopen_dirs.iter().cloned() {
let canonical_file = if canonical_file == lca {
PathBuf::from(".")
} else {
PathBuf::from(canonical_file.strip_prefix(&lca).unwrap_or(&canonical_file))
};
dbg!(&canonical_file);
debug!(
"Attempting to preopen {}",
&canonical_file.to_string_lossy()
);
// TODO: think about this
let default_rights = 0x1FFFFFFF; // all rights
let cur_file: fs::File = fs::OpenOptions::new()
.read(true)
.open(file)
.open(&canonical_file)
.expect("Could not find file");
let cur_file_metadata = cur_file.metadata().unwrap();
let cur_file_metadata = cur_file
.metadata()
.expect("Could not get metadata for file");
// return how deep we had to go to find a parent
let mut depth_seen_and_parent_fd: Option<(usize, u32, &Path)> = None;
for (i, parent) in canonical_file.ancestors().enumerate().skip(1) {
if let Some(parent_fd_idx) = preopen_dir_map.get(parent) {
depth_seen_and_parent_fd = Some((i, *parent_fd_idx, parent));
break;
}
}
// find parent
let parent = if let Some((depth_seen, parent_fd_idx, parent_path)) =
depth_seen_and_parent_fd
{
// depth seen can be computed from the result of strip_prefix
// possibly worth rming depth_seen
if depth_seen > 1 {
let dist_path = canonical_file
.strip_prefix(parent_path)
.expect("Failed to reduce child path by parent path");
let parent_inode = wasi_fs.fd_map[&parent_fd_idx].inode;
let mut cur_parent = parent_inode;
let mut cumulative_path = canonical_file.clone();
for comp in dist_path.components() {
if let Kind::Dir { entries, .. } = &mut wasi_fs.inodes[parent_inode].kind {
if let Some(ent) =
entries.get(&comp.as_os_str().to_string_lossy().to_string())
{
cur_parent = *ent;
} else {
// create it
// TODO: massive refactoring
cumulative_path.push(comp);
let mid_dir_name = comp.as_os_str().to_string_lossy().to_string();
let default_rights = 0x1FFFFFFF; // all rights
let cur_file: fs::File = fs::OpenOptions::new()
.read(true)
.open(&cumulative_path)
.expect("Could not find file");
let mid_dir_metadata = cur_file
.metadata()
.expect("Could not get metadata for intermediate file");
let kind = if mid_dir_metadata.is_dir() {
Kind::Dir {
parent: Some(cur_parent),
handle: WasiFile::HostFile(cur_file),
entries: Default::default(),
}
} else {
// TODO: properly handle symlinks
unreachable!(
"When connecting intermediate directories while preopening directories, found something that's not a directory"
);
};
let inode_val = InodeVal::from_file_metadata(
&mid_dir_metadata,
mid_dir_name.clone(),
true,
kind,
);
let inode = wasi_fs.inodes.insert(inode_val);
let new_inode = wasi_fs.inode_counter.get();
wasi_fs.inodes[inode].stat.st_ino = new_inode;
wasi_fs.inode_counter.replace(new_inode + 1);
// reborrow to insert entry
if let Kind::Dir { entries, .. } =
&mut wasi_fs.inodes[cur_parent].kind
{
entries.insert(mid_dir_name, inode);
cur_parent = inode;
}
let fd = wasi_fs
.create_fd(default_rights, default_rights, 0, inode)
.expect("Could not open fd");
preopen_dir_map.insert(cumulative_path.clone(), fd);
}
} else {
unreachable!(
"Internal error: pre-opened parent directory is not a directory"
);
}
}
Some(cur_parent)
} else {
debug!("Connecting as parent");
Some(wasi_fs.fd_map[&parent_fd_idx].inode)
}
} else {
None
};
let file_name = canonical_file
.file_name()
.map(|file_name| file_name.to_string_lossy().to_string())
// TODO: seriously check this, this looks like a bad bug in the future
.unwrap_or(String::from("."));
let kind = if cur_file_metadata.is_dir() {
Kind::Dir {
parent,
handle: WasiFile::HostFile(cur_file),
entries: Default::default(),
}
} else {
return Err(format!(
"WASI only supports pre-opened directories right now; found \"{}\"",
file
"WASI only supports pre-opened directories; found \"{}\"",
&file_name,
));
};
// TODO: handle nested pats in `file`
let inode_val =
InodeVal::from_file_metadata(&cur_file_metadata, file.clone(), true, kind);
InodeVal::from_file_metadata(&cur_file_metadata, file_name.clone(), true, kind);
let inode = wasi_fs.inodes.insert(inode_val);
wasi_fs.inodes[inode].stat.st_ino = wasi_fs.inode_counter.get();
wasi_fs
let new_inode = wasi_fs.inode_counter.get();
wasi_fs.inodes[inode].stat.st_ino = new_inode;
wasi_fs.inode_counter.replace(new_inode + 1);
let fd = wasi_fs
.create_fd(default_rights, default_rights, 0, inode)
.expect("Could not open fd");
// now connect the parent to the child
if let Some(p) = parent {
if let Kind::Dir { entries, .. } = &mut wasi_fs.inodes[p].kind {
entries.insert(file_name, inode);
} else {
unreachable!("Internal error: pre-opened parent directory is not a directory");
}
}
preopen_dir_map.insert(canonical_file, fd);
}
std::env::set_current_dir(&cur_dir);
debug!("wasi::fs::end");
Ok(wasi_fs)
}
@ -359,7 +523,7 @@ impl WasiFs {
},
fs_flags: fd.flags,
fs_rights_base: fd.rights,
fs_rights_inheriting: fd.rights_inheriting, // TODO(lachlan): Is this right?
fs_rights_inheriting: fd.rights_inheriting,
})
}
@ -429,6 +593,31 @@ impl WasiFs {
);
Ok(idx)
}
pub fn get_base_path_for_directory(&self, directory: Inode) -> Option<String> {
let mut path_segments = vec![];
let mut cur_inode = directory;
loop {
path_segments.push(self.inodes[cur_inode].name.clone());
if let Kind::Dir { parent, .. } = &self.inodes[cur_inode].kind {
if let Some(p_inode) = parent {
cur_inode = *p_inode;
} else {
break;
}
} else {
return None;
}
}
path_segments.reverse();
Some(
path_segments
.iter()
.fold("".to_string(), |a, b| a + "/" + b),
)
}
}
#[derive(Debug)]

View File

@ -1167,7 +1167,7 @@ pub fn path_open(
fs_flags: __wasi_fdflags_t,
fd: WasmPtr<__wasi_fd_t>,
) -> __wasi_errno_t {
debug!("wasi::path_open");
debug!("wasi::path_open: o_flags: {}", o_flags);
let memory = ctx.memory(0);
/* TODO: find actual upper bound on name size (also this is a path, not a name :think-fish:) */
if path_len > 1024 * 1024 {
@ -1196,6 +1196,9 @@ pub fn path_open(
std::str::from_utf8(unsafe { &*(path_cells as *const [_] as *const [u8]) })
.map_err(|_| __WASI_EINVAL)
);
dbg!(path_string);
//let path = wasi_try!(std::fs::canonicalize(path_string).map_err(|_| __WASI_EINVAL));
let path = std::path::PathBuf::from(path_string);
let path_vec = wasi_try!(path
.components()
@ -1213,22 +1216,61 @@ pub fn path_open(
}
let mut cur_dir_inode = working_dir.inode;
let mut cumulative_path = std::path::PathBuf::from(".");
let cur_dir = std::env::current_dir().unwrap();
let base_path =
std::path::PathBuf::from(state.fs.get_base_path_for_directory(cur_dir_inode).unwrap());
// HACK: to make WASI work
// TODO strip LCA instead of this
let mut cumulative_path = if let Ok(new_path) = base_path.strip_prefix(&cur_dir) {
std::path::PathBuf::from(new_path)
} else {
let mut cur = cur_dir.clone();
// TODO: rewrite this when less tired
if let Ok(difference) = cur.strip_prefix(dbg!(&base_path)) {
for seg in dbg!(difference.components()) {
dbg!(seg);
if let Kind::Dir { entries, .. } = &state.fs.inodes[cur_dir_inode].kind {
if let Some(e) = entries.get(&seg.as_os_str().to_string_lossy().to_string()) {
cur_dir_inode = *e;
} else {
return __WASI_EINVAL;
}
}
}
cur_dir.clone()
} else {
std::path::PathBuf::from(".")
}
};
// std::path::PathBuf::from(".");
/*std::path::PathBuf::from(wasi_try!(state
.fs
.get_base_path_for_directory(cur_dir_inode)
.ok_or(__WASI_EINVAL)))*/
dbg!(&cumulative_path);
// traverse path
if path_vec.len() > 1 {
for path_segment in &path_vec[..(path_vec.len() - 1)] {
match &state.fs.inodes[cur_dir_inode].kind {
Kind::Dir { entries, .. } => {
Kind::Dir {
entries, parent, ..
} => {
if let Some(child) = entries.get(path_segment) {
let inode_val = *child;
cur_dir_inode = inode_val;
} else {
// attempt to lazily load or create
if path_segment == ".." {
unimplemented!(
"\"..\" in paths in `path_open` has not been implemented yet"
);
if let Some(p) = parent {
cur_dir_inode = *p;
continue;
} else {
debug!("Attempting to access parent directory when fd {} does not have a parent", dirfd);
return __WASI_ENOTCAPABLE;
}
}
// lazily load
cumulative_path.push(path_segment);
@ -1250,6 +1292,7 @@ pub fn path_open(
let cur_file_metadata = cur_dir.metadata().unwrap();
let kind = if cur_file_metadata.is_dir() {
Kind::Dir {
parent: Some(cur_dir_inode),
handle: WasiFile::HostFile(cur_dir),
entries: Default::default(),
}