wip fs improvements

This commit is contained in:
Mark McCaskey 2019-07-15 17:37:11 -07:00
parent 122963909f
commit dd1ddea37b
3 changed files with 218 additions and 160 deletions

View File

@ -1,7 +1,5 @@
#![deny(unused_imports, unused_variables, unused_unsafe, unreachable_patterns)]
#[macro_use]
extern crate log;
#[cfg(target = "windows")]
extern crate winapi;

View File

@ -9,7 +9,7 @@ use std::{
cell::Cell,
fs,
io::{self, Read, Seek, Write},
path::PathBuf,
path::{Path, PathBuf},
time::SystemTime,
};
use wasmer_runtime_core::debug;
@ -81,6 +81,7 @@ impl Seek for WasiFile {
}
}
/// A file that Wasi knows about that may or may not be open
#[derive(Debug)]
pub struct InodeVal {
pub stat: __wasi_filestat_t,
@ -136,7 +137,10 @@ impl InodeVal {
#[derive(Debug)]
pub enum Kind {
File {
handle: WasiFile,
/// the open file, if it's open
handle: Option<WasiFile>,
/// the path to the file
path: PathBuf,
},
Dir {
/// Parent directory
@ -148,7 +152,9 @@ pub enum Kind {
entries: HashMap<String, Inode>,
},
Symlink {
forwarded: Inode,
forwarded: Option<Inode>,
/// This is required because, at the very least, symlinks can be deleted and we'll need to check that
path: PathBuf,
},
Buffer {
buffer: Vec<u8>,
@ -311,6 +317,7 @@ impl WasiFs {
})
}
/*
#[allow(dead_code)]
fn filestat_inode(
&self,
@ -318,8 +325,13 @@ impl WasiFs {
flags: __wasi_lookupflags_t,
) -> Result<__wasi_filestat_t, __wasi_errno_t> {
let inode_val = &self.inodes[inode];
if let (true, Kind::Symlink { mut forwarded }) =
(flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind)
if let (
true,
Kind::Symlink {
mut forwarded,
path,
},
) = (flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind)
{
// Time to follow the symlink.
let mut counter = 0;
@ -355,6 +367,118 @@ impl WasiFs {
self.filestat_inode(inode, flags)
}
*/
/// gets a host file from a base directory and a path
/// this function ensures the fs remains sandboxed
pub fn get_inode_at_path(
&mut self,
base: __wasi_fd_t,
path: &str,
) -> Result<Inode, __wasi_errno_t> {
let base_dir = self.get_fd(base)?;
let path: &Path = Path::new(path);
let mut symlinks_followed = 0;
let mut cur_inode = base_dir.inode;
// TODO: rights checks
'path_iter: for component in path.components() {
// for each component traverse file structure
// loading inodes as necessary
'symlink_resolution: loop {
match &mut self.inodes[cur_inode].kind {
Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"),
Kind::Dir {
ref mut entries,
ref path,
ref parent,
..
} => {
if component.as_os_str().to_string_lossy() == ".." {
if let Some(p) = parent {
cur_inode = *p;
continue 'path_iter;
} else {
// TODO: be smart here with multiple preopened directories
return Err(__WASI_EACCES);
}
}
if let Some(entry) =
entries.get(component.as_os_str().to_string_lossy().as_ref())
{
cur_inode = *entry;
} else {
let file = {
let mut cd = path.clone();
cd.push(component);
cd
};
// TODO: verify this returns successfully when given a non-symlink
let metadata = file.symlink_metadata().ok().ok_or(__WASI_EEXIST)?;
let file_type = metadata.file_type();
let kind = if file_type.is_dir() {
// load DIR
Kind::Dir {
parent: Some(cur_inode),
path: file.clone(),
entries: Default::default(),
}
} else if file_type.is_file() {
// load file
Kind::File {
handle: None,
path: file.clone(),
}
} else if file_type.is_symlink() {
// use a stack and load symlinks?
// load symlink
Kind::Symlink {
forwarded: None,
path: file.clone(),
}
} else {
unimplemented!("state::get_inode_at_path unknown file type: not file, directory, or symlink");
};
let new_inode = self.inodes.insert(InodeVal {
stat: get_stat_for_kind(&kind).ok_or(__WASI_EIO)?,
is_preopened: false,
name: file.to_string_lossy().to_string(),
kind,
});
*self.inode_counter.get_mut() += 1;
cur_inode = new_inode;
}
}
Kind::File { .. } => {
return Err(__WASI_ENOTDIR);
}
Kind::Symlink { forwarded, path } => {
if symlinks_followed > MAX_SYMLINKS {
return Err(__WASI_EMLINK);
}
if let Some(fwd) = forwarded {
cur_inode = *fwd;
} else {
// load the symlink
let _link = path.read_link().ok().ok_or(__WASI_EIO)?;
}
symlinks_followed += 1;
continue 'symlink_resolution;
}
}
break 'symlink_resolution;
}
}
Ok(cur_inode)
}
pub fn get_fd(&self, fd: __wasi_fd_t) -> Result<&Fd, __wasi_errno_t> {
self.fd_map.get(&fd).ok_or(__WASI_EBADF)
}
pub fn filestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_filestat_t, __wasi_errno_t> {
let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?;
@ -414,11 +538,15 @@ impl WasiFs {
let inode = &mut self.inodes[fd.inode];
match &mut inode.kind {
Kind::File { handle } => handle.flush().map_err(|_| __WASI_EIO)?,
Kind::File {
handle: Some(handle),
..
} => handle.flush().map_err(|_| __WASI_EIO)?,
// TODO: verify this behavior
Kind::Dir { .. } => return Err(__WASI_EISDIR),
Kind::Symlink { .. } => unimplemented!(),
Kind::Buffer { .. } => (),
_ => return Err(__WASI_EIO),
}
}
}
@ -477,35 +605,36 @@ pub fn host_file_type_to_wasi_file_type(file_type: fs::FileType) -> __wasi_filet
pub fn get_stat_for_kind(kind: &Kind) -> Option<__wasi_filestat_t> {
match kind {
Kind::File { handle } => match handle {
WasiFile::HostFile(hf) => {
let md = hf.metadata().ok()?;
Kind::File { handle, path } => {
let md = match handle {
Some(WasiFile::HostFile(hf)) => hf.metadata().ok()?,
None => path.metadata().ok()?,
};
Some(__wasi_filestat_t {
st_filetype: host_file_type_to_wasi_file_type(md.file_type()),
st_size: md.len(),
st_atim: md
.accessed()
.ok()?
.duration_since(SystemTime::UNIX_EPOCH)
.ok()?
.as_nanos() as u64,
st_mtim: md
.modified()
.ok()?
.duration_since(SystemTime::UNIX_EPOCH)
.ok()?
.as_nanos() as u64,
st_ctim: md
.created()
.ok()
.and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|ct| ct.as_nanos() as u64)
.unwrap_or(0),
..__wasi_filestat_t::default()
})
}
},
Some(__wasi_filestat_t {
st_filetype: host_file_type_to_wasi_file_type(md.file_type()),
st_size: md.len(),
st_atim: md
.accessed()
.ok()?
.duration_since(SystemTime::UNIX_EPOCH)
.ok()?
.as_nanos() as u64,
st_mtim: md
.modified()
.ok()?
.duration_since(SystemTime::UNIX_EPOCH)
.ok()?
.as_nanos() as u64,
st_ctim: md
.created()
.ok()
.and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|ct| ct.as_nanos() as u64)
.unwrap_or(0),
..__wasi_filestat_t::default()
})
}
Kind::Dir { path, .. } => {
let md = path.metadata().ok()?;
Some(__wasi_filestat_t {

View File

@ -648,9 +648,13 @@ pub fn fd_pwrite(
let inode = &mut state.fs.inodes[fd_entry.inode];
let bytes_written = match &mut inode.kind {
Kind::File { handle } => {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(write_bytes(handle, memory, iovs_arr_cell))
Kind::File { handle, .. } => {
if let Some(handle) = handle {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(write_bytes(handle, memory, iovs_arr_cell))
} else {
return __WASI_EINVAL;
}
}
Kind::Dir { .. } => {
// TODO: verify
@ -736,9 +740,13 @@ pub fn fd_read(
let inode = &mut state.fs.inodes[fd_entry.inode];
let bytes_read = match &mut inode.kind {
Kind::File { handle } => {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(read_bytes(handle, memory, iovs_arr_cell))
Kind::File { handle, .. } => {
if let Some(handle) = handle {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(read_bytes(handle, memory, iovs_arr_cell))
} else {
return __WASI_EINVAL;
}
}
Kind::Dir { .. } => {
// TODO: verify
@ -1011,10 +1019,13 @@ pub fn fd_write(
let inode = &mut state.fs.inodes[fd_entry.inode];
let bytes_written = match &mut inode.kind {
Kind::File { handle } => {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(write_bytes(handle, memory, iovs_arr_cell))
Kind::File { handle, .. } => {
if let Some(handle) = handle {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(write_bytes(handle, memory, iovs_arr_cell))
} else {
return __WASI_EINVAL;
}
}
Kind::Dir { .. } => {
// TODO: verify
@ -1152,105 +1163,11 @@ pub fn path_filestat_get(
})
.map_err(|_| __WASI_EINVAL));
debug!("=> path: {}", &path_string);
let path = std::path::PathBuf::from(path_string);
let path_vec = path
.components()
.map(|comp| comp.as_os_str().to_string_lossy().to_string())
.collect::<Vec<String>>();
let file_inode = wasi_try!(state.fs.get_inode_at_path(fd, path_string));
let stat = wasi_try!(get_stat_for_kind(&state.fs.inodes[file_inode].kind).ok_or(__WASI_EIO));
let buf_cell = wasi_try!(buf.deref(memory));
if path_vec.is_empty() {
return __WASI_EINVAL;
}
let mut cumulative_path = std::path::PathBuf::from(wasi_try!(state
.fs
.get_base_path_for_directory(root_dir.inode)
.ok_or(__WASI_EIO)));
debug!("=> Path vec: {:?}:", &path_vec);
// find the inode by traversing the path
let mut inode = root_dir.inode;
'outer: for segment in &path_vec[..(path_vec.len() - 1)] {
// loop to traverse symlinks
// TODO: proper cycle detection
let mut sym_count = 0;
loop {
match &state.fs.inodes[inode].kind {
Kind::Dir { entries, .. } => {
cumulative_path.push(&segment);
if let Some(entry) = entries.get(segment) {
debug!("Entry {:?} found", &segment);
inode = entry.clone();
} else {
// lazily load
debug!("Lazily loading entry {:?}", &segment);
let path_metadata =
wasi_try!(cumulative_path.metadata().map_err(|_| __WASI_ENOENT));
if !path_metadata.is_dir() {
// TODO: should this just return invalid arg?
return __WASI_ENOTDIR;
}
let kind = Kind::Dir {
parent: Some(inode),
path: std::path::PathBuf::from(&segment),
entries: Default::default(),
};
let inode_val = InodeVal::from_file_metadata(
&path_metadata,
segment.clone(),
false,
kind,
);
let new_inode = state.fs.inodes.insert(inode_val);
let inode_idx = state.fs.inode_counter.get();
state.fs.inode_counter.replace(inode_idx + 1);
if let Kind::Dir { entries, .. } = &mut state.fs.inodes[inode].kind {
// check that we're not displacing any entries
assert!(entries.insert(segment.clone(), new_inode).is_none());
state.fs.inodes[new_inode].stat.st_ino = state.fs.inode_counter.get();
inode = new_inode;
}
debug!("Directory {:#?} lazily loaded", &cumulative_path);
}
continue 'outer;
}
Kind::Symlink { forwarded } => {
// TODO: updated cumulative path
sym_count += 1;
inode = forwarded.clone();
if sym_count > MAX_SYMLINKS {
return __WASI_ELOOP;
}
}
_ => {
return __WASI_ENOTDIR;
}
}
}
}
let stat = match &state.fs.inodes[inode].kind {
Kind::Dir { path, entries, .. } => {
// read it from internal data structures if we can
let last_segment = path_vec.last().unwrap();
cumulative_path.push(last_segment);
if entries.contains_key(last_segment) {
state.fs.inodes[entries[last_segment]].stat
} else {
// otherwise read it from the host FS
if !cumulative_path.exists() {
return __WASI_ENOENT;
}
let final_path_metadata =
wasi_try!(cumulative_path.metadata().map_err(|_| __WASI_EIO));
wasi_try!(get_stat_for_kind(&state.fs.inodes[inode].kind).ok_or(__WASI_EIO))
}
}
_ => {
return __WASI_ENOTDIR;
}
};
buf_cell.set(stat);
__WASI_ESUCCESS
@ -1562,7 +1479,8 @@ pub fn path_open(
real_open_file
};
Kind::File {
handle: WasiFile::HostFile(real_opened_file),
handle: Some(WasiFile::HostFile(real_opened_file)),
path: file_path,
}
};
@ -1612,24 +1530,37 @@ pub fn path_readlink(
let memory = ctx.memory(0);
let base_dir = wasi_try!(state.fs.fd_map.get(&dir_fd).ok_or(__WASI_EBADF));
if !has_rights(base_dir.rights, __WASI_RIGHT_PATH_READLINK) {
return __WASI_EACCES;
}
let path_str = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL));
let result = wasi_try!(std::fs::read_link(path_str).ok().ok_or(__WASI_EIO));
let result_path_as_str = result.to_string_lossy();
let bytes = result_path_as_str.bytes();
if bytes.len() < buf_len as usize {
return __WASI_EOVERFLOW;
}
let inode = wasi_try!(state.fs.get_inode_at_path(dir_fd, path_str));
let out = wasi_try!(buf.deref(memory, 0, buf_len));
let mut bytes_written = 0;
for b in bytes {
out[bytes_written].set(b);
bytes_written += 1;
}
// should we null terminate this?
if let Kind::Symlink { forwarded, .. } = &state.fs.inodes[inode].kind {
if let Some(fwd) = forwarded {
let resolved_name = &state.fs.inodes[*fwd].name;
let bytes = resolved_name.bytes();
if bytes.len() < buf_len as usize {
return __WASI_EOVERFLOW;
}
let bytes_out = wasi_try!(buf_used.deref(memory));
bytes_out.set(bytes_written as u32);
let out = wasi_try!(buf.deref(memory, 0, buf_len));
let mut bytes_written = 0;
for b in bytes {
out[bytes_written].set(b);
bytes_written += 1;
}
// should we null terminate this?
let bytes_out = wasi_try!(buf_used.deref(memory));
bytes_out.set(bytes_written as u32);
} else {
panic!("do this before shipping");
return __WASI_EINVAL;
}
} else {
return __WASI_EINVAL;
}
__WASI_ESUCCESS
}