add preopened fd and fix/improve fs syscalls (WIP)

This commit is contained in:
Mark McCaskey
2019-04-10 18:23:25 -07:00
parent 870faf9838
commit 91af7cf8a8
5 changed files with 262 additions and 53 deletions

View File

@ -17,7 +17,11 @@ pub use self::utils::is_wasi_module;
use wasmer_runtime_core::{func, import::ImportObject, imports}; use wasmer_runtime_core::{func, import::ImportObject, imports};
pub fn generate_import_object(args: Vec<Vec<u8>>, envs: Vec<Vec<u8>>) -> ImportObject { pub fn generate_import_object(
args: Vec<Vec<u8>>,
envs: Vec<Vec<u8>>,
preopened_files: Vec<String>,
) -> ImportObject {
let state_gen = move || { let state_gen = move || {
fn state_destructor(data: *mut c_void) { fn state_destructor(data: *mut c_void) {
unsafe { unsafe {
@ -26,7 +30,7 @@ pub fn generate_import_object(args: Vec<Vec<u8>>, envs: Vec<Vec<u8>>) -> ImportO
} }
let state = Box::new(WasiState { let state = Box::new(WasiState {
fs: WasiFs::new().unwrap(), fs: WasiFs::new(&preopened_files).unwrap(),
args: &args[..], args: &args[..],
envs: &envs[..], envs: &envs[..],
}); });

View File

@ -7,14 +7,89 @@ use generational_arena::{Arena, Index as Inode};
use hashbrown::hash_map::{Entry, HashMap}; use hashbrown::hash_map::{Entry, HashMap};
use std::{ use std::{
cell::Cell, cell::Cell,
io::{self, Write}, fs,
io::{self, Read, Seek, Write},
time::SystemTime, time::SystemTime,
}; };
use wasmer_runtime_core::debug; use wasmer_runtime_core::debug;
use zbox::{init_env as zbox_init_env, File, FileType, OpenOptions, Repo, RepoOpener}; use zbox::{init_env as zbox_init_env, FileType, OpenOptions, Repo, RepoOpener};
pub const MAX_SYMLINKS: usize = 100; pub const MAX_SYMLINKS: usize = 100;
pub enum WasiFile {
ZboxFile(zbox::File),
HostFile(fs::File),
}
impl Write for WasiFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
WasiFile::ZboxFile(zbf) => zbf.write(buf),
WasiFile::HostFile(hf) => hf.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
WasiFile::ZboxFile(zbf) => zbf.flush(),
WasiFile::HostFile(hf) => hf.flush(),
}
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
match self {
WasiFile::ZboxFile(zbf) => zbf.write_all(buf),
WasiFile::HostFile(hf) => hf.write_all(buf),
}
}
fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> {
match self {
WasiFile::ZboxFile(zbf) => zbf.write_fmt(fmt),
WasiFile::HostFile(hf) => hf.write_fmt(fmt),
}
}
}
impl Read for WasiFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
WasiFile::ZboxFile(zbf) => zbf.read(buf),
WasiFile::HostFile(hf) => hf.read(buf),
}
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
match self {
WasiFile::ZboxFile(zbf) => zbf.read_to_end(buf),
WasiFile::HostFile(hf) => hf.read_to_end(buf),
}
}
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
match self {
WasiFile::ZboxFile(zbf) => zbf.read_to_string(buf),
WasiFile::HostFile(hf) => hf.read_to_string(buf),
}
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
match self {
WasiFile::ZboxFile(zbf) => zbf.read_exact(buf),
WasiFile::HostFile(hf) => hf.read_exact(buf),
}
}
}
impl Seek for WasiFile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
match self {
WasiFile::ZboxFile(zbf) => zbf.seek(pos),
WasiFile::HostFile(hf) => hf.seek(pos),
}
}
}
pub struct InodeVal { pub struct InodeVal {
pub stat: __wasi_filestat_t, pub stat: __wasi_filestat_t,
pub is_preopened: bool, pub is_preopened: bool,
@ -25,10 +100,10 @@ pub struct InodeVal {
#[allow(dead_code)] #[allow(dead_code)]
pub enum Kind { pub enum Kind {
File { File {
handle: File, handle: WasiFile,
}, },
Dir { Dir {
handle: File, handle: WasiFile,
/// The entries of a directory are lazily filled. /// The entries of a directory are lazily filled.
entries: HashMap<String, Inode>, entries: HashMap<String, Inode>,
}, },
@ -40,7 +115,7 @@ pub enum Kind {
}, },
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Fd { pub struct Fd {
pub rights: __wasi_rights_t, pub rights: __wasi_rights_t,
pub rights_inheriting: __wasi_rights_t, pub rights_inheriting: __wasi_rights_t,
@ -50,7 +125,7 @@ pub struct Fd {
} }
pub struct WasiFs { pub struct WasiFs {
// pub repo: Repo, pub repo: Repo,
pub name_map: HashMap<String, Inode>, pub name_map: HashMap<String, Inode>,
pub inodes: Arena<InodeVal>, pub inodes: Arena<InodeVal>,
pub fd_map: HashMap<u32, Fd>, pub fd_map: HashMap<u32, Fd>,
@ -59,26 +134,58 @@ pub struct WasiFs {
} }
impl WasiFs { impl WasiFs {
pub fn new() -> Result<Self, String> { pub fn new(preopened_files: &[String]) -> Result<Self, String> {
debug!("wasi::fs::init"); debug!("wasi::fs::init");
zbox_init_env(); zbox_init_env();
debug!("wasi::fs::repo"); debug!("wasi::fs::repo");
// let repo = RepoOpener::new() let repo = RepoOpener::new()
// .create(true) .create(true)
// .open("mem://wasmer-test-fs", "") .open("mem://wasmer-test-fs", "")
// .map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
debug!("wasi::fs::inodes"); debug!("wasi::fs::inodes");
let inodes = Arena::new(); let inodes = Arena::new();
let res = Ok(Self { let mut wasi_fs = Self {
// repo: repo, repo: repo,
name_map: HashMap::new(), name_map: HashMap::new(),
inodes: inodes, inodes: inodes,
fd_map: HashMap::new(), fd_map: HashMap::new(),
next_fd: Cell::new(3), next_fd: Cell::new(3),
inode_counter: Cell::new(1000), inode_counter: Cell::new(1000),
}); };
for file in preopened_files {
debug!("Attempting to preopen {}", &file);
// TODO: think about this
let default_rights = 0x1FFFFFFF;
let cur_file: fs::File = fs::File::open(file).expect("Could not find file");
let kind = if cur_file.metadata().unwrap().is_dir() {
// it seems bad to open every file recursively; can do it lazily though
Kind::Dir {
handle: WasiFile::HostFile(cur_file),
entries: Default::default(),
}
} else {
/*Kind::File {
handle: WasiFile::HostFile(cur_file),
}*/
return Err(format!(
"WASI only supports pre-opened directories right now; found \"{}\"",
file
));
};
let inode_val = InodeVal {
stat: __wasi_filestat_t::default(),
is_preopened: true,
// this is incorrect
name: file.clone(),
kind,
};
let inode = wasi_fs.inodes.insert(inode_val);
wasi_fs
.create_fd(default_rights, default_rights, 0, inode)
.expect("Could not open fd");
}
debug!("wasi::fs::end"); debug!("wasi::fs::end");
res Ok(wasi_fs)
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -195,7 +302,9 @@ impl WasiFs {
pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> { pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> {
let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?;
Ok(__wasi_fdstat_t { debug!("fdstat: {:?}", fd);
dbg!(Ok(__wasi_fdstat_t {
fs_filetype: match self.inodes[fd.inode].kind { fs_filetype: match self.inodes[fd.inode].kind {
Kind::File { .. } => __WASI_FILETYPE_REGULAR_FILE, Kind::File { .. } => __WASI_FILETYPE_REGULAR_FILE,
Kind::Dir { .. } => __WASI_FILETYPE_DIRECTORY, Kind::Dir { .. } => __WASI_FILETYPE_DIRECTORY,
@ -205,12 +314,13 @@ impl WasiFs {
fs_flags: fd.flags, fs_flags: fd.flags,
fs_rights_base: fd.rights, fs_rights_base: fd.rights,
fs_rights_inheriting: fd.rights, // TODO(lachlan): Is this right? fs_rights_inheriting: fd.rights, // TODO(lachlan): Is this right?
}) }))
} }
pub fn prestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_prestat_t, __wasi_errno_t> { pub fn prestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_prestat_t, __wasi_errno_t> {
let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?;
debug!("in prestat_fd {:?}", fd);
let inode_val = &self.inodes[fd.inode]; let inode_val = &self.inodes[fd.inode];
if inode_val.is_preopened { if inode_val.is_preopened {
@ -272,6 +382,19 @@ impl WasiFs {
); );
Ok(idx) Ok(idx)
} }
/*pub fn create_file_at_fd(
&mut self,
parent: __wasi_fd_t,
path: String,
fs_rights_base: __wasi_rights_t,
fs_rights_inheriting: __wasi_rights_t,
fs_flags: fs_flags,
) -> Result<__wasi_fd_t, __wasi_errno_t> {
let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?;
Ok()
}*/
} }
pub struct WasiState<'a> { pub struct WasiState<'a> {

View File

@ -8,11 +8,11 @@ pub mod windows;
use self::types::*; use self::types::*;
use crate::{ use crate::{
ptr::{Array, WasmPtr}, ptr::{Array, WasmPtr},
state::{Fd, Kind, WasiState, MAX_SYMLINKS}, state::{Fd, InodeVal, Kind, WasiFile, WasiState, MAX_SYMLINKS},
}; };
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use std::cell::Cell; use std::cell::Cell;
use std::io::{self, Read, Write}; use std::io::{self, Read, Seek, Write};
use wasmer_runtime_core::{debug, memory::Memory, vm::Ctx}; use wasmer_runtime_core::{debug, memory::Memory, vm::Ctx};
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
@ -335,7 +335,7 @@ pub fn fd_fdstat_get(
let buf = wasi_try!(buf.deref(memory)); let buf = wasi_try!(buf.deref(memory));
buf.set(stat); buf.set(stat);
__WASI_EFAULT __WASI_ESUCCESS
} }
/// ### `fd_fdstat_set_flags()` /// ### `fd_fdstat_set_flags()`
@ -532,27 +532,26 @@ pub fn fd_prestat_dir_name(
fd, path_len fd, path_len
); );
let memory = ctx.memory(0); let memory = ctx.memory(0);
let path_chars = wasi_try!(path.deref(memory, 0, path_len));
if let Ok(path_chars) = path.deref(memory, 0, path_len) { let state = get_wasi_state(ctx);
debug!( let real_fd = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF));
"=> path: {}", let inode_val = &state.fs.inodes[real_fd.inode];
path_chars
.iter() // check inode-val.is_preopened?
.map(|c| c.get() as char)
.collect::<String>() if let Kind::Dir { .. } = inode_val.kind {
); // TODO: verify this: null termination, etc
if true if inode_val.name.len() <= path_len as usize {
/* check if dir */ for (i, c) in inode_val.name.bytes().enumerate() {
{ path_chars[i].set(c);
// get name }
// write name
// if overflow __WASI_EOVERFLOW
__WASI_ESUCCESS __WASI_ESUCCESS
} else { } else {
__WASI_ENOTDIR __WASI_EOVERFLOW
} }
} else { } else {
__WASI_EFAULT __WASI_ENOTDIR
} }
} }
@ -612,7 +611,7 @@ pub fn fd_pwrite(
let bytes_written = match &mut inode.kind { let bytes_written = match &mut inode.kind {
Kind::File { handle } => { Kind::File { handle } => {
// TODO: adjust by offset handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) wasi_try!(write_bytes(handle, memory, iovs_arr_cell))
} }
Kind::Dir { .. } => { Kind::Dir { .. } => {
@ -670,14 +669,18 @@ pub fn fd_read(
for iov in iovs_arr_cell { for iov in iovs_arr_cell {
let iov_inner = iov.get(); let iov_inner = iov.get();
let bytes = iov_inner.buf.deref(memory, 0, iov_inner.buf_len)?; let bytes = iov_inner.buf.deref(memory, 0, dbg!(iov_inner.buf_len))?;
let mut raw_bytes: &mut [u8] = let mut raw_bytes: &mut [u8] =
unsafe { &mut *(bytes as *const [_] as *mut [_] as *mut [u8]) }; unsafe { &mut *(bytes as *const [_] as *mut [_] as *mut [u8]) };
bytes_read += reader.read(raw_bytes).map_err(|_| __WASI_EIO)? as u32; bytes_read += dbg!(reader.read(raw_bytes).map_err(|e| {
dbg!(e);
__WASI_EIO
})? as u32);
} }
Ok(bytes_read) Ok(bytes_read)
} }
debug!("think-fish");
let bytes_read = match fd { let bytes_read = match fd {
0 => { 0 => {
let stdin = io::stdin(); let stdin = io::stdin();
@ -699,7 +702,10 @@ pub fn fd_read(
let inode = &mut state.fs.inodes[fd_entry.inode]; let inode = &mut state.fs.inodes[fd_entry.inode];
let bytes_read = match &mut inode.kind { let bytes_read = match &mut inode.kind {
Kind::File { handle } => wasi_try!(read_bytes(handle, memory, iovs_arr_cell)), Kind::File { handle } => {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(read_bytes(handle, memory, iovs_arr_cell))
}
Kind::Dir { .. } => { Kind::Dir { .. } => {
// TODO: verify // TODO: verify
return __WASI_EISDIR; return __WASI_EISDIR;
@ -716,7 +722,7 @@ pub fn fd_read(
} }
}; };
nread_cell.set(bytes_read); nread_cell.set(dbg!(bytes_read));
__WASI_ESUCCESS __WASI_ESUCCESS
} }
@ -798,7 +804,7 @@ pub fn fd_seek(
whence: __wasi_whence_t, whence: __wasi_whence_t,
newoffset: WasmPtr<__wasi_filesize_t>, newoffset: WasmPtr<__wasi_filesize_t>,
) -> __wasi_errno_t { ) -> __wasi_errno_t {
debug!("wasi::fd_seek: fd={}", fd); debug!("wasi::fd_seek: fd={}, offset={}", fd, offset);
let memory = ctx.memory(0); let memory = ctx.memory(0);
let state = get_wasi_state(ctx); let state = get_wasi_state(ctx);
let new_offset_cell = wasi_try!(newoffset.deref(memory)); let new_offset_cell = wasi_try!(newoffset.deref(memory));
@ -809,9 +815,11 @@ pub fn fd_seek(
return __WASI_EACCES; return __WASI_EACCES;
} }
debug!("Hmml");
// TODO: handle case if fd is a dir? // TODO: handle case if fd is a dir?
match whence { match whence {
__WASI_WHENCE_CUR => fd_entry.offset = (fd_entry.offset as i64 + offset) as u64, __WASI_WHENCE_CUR => fd_entry.offset = (dbg!(fd_entry.offset) as i64 + offset) as u64,
__WASI_WHENCE_END => unimplemented!(), __WASI_WHENCE_END => unimplemented!(),
__WASI_WHENCE_SET => fd_entry.offset = offset as u64, __WASI_WHENCE_SET => fd_entry.offset = offset as u64,
_ => return __WASI_EINVAL, _ => return __WASI_EINVAL,
@ -920,7 +928,11 @@ pub fn fd_write(
let inode = &mut state.fs.inodes[fd_entry.inode]; let inode = &mut state.fs.inodes[fd_entry.inode];
let bytes_written = match &mut inode.kind { let bytes_written = match &mut inode.kind {
Kind::File { handle } => wasi_try!(write_bytes(handle, memory, iovs_arr_cell)), Kind::File { handle } => {
handle.seek(::std::io::SeekFrom::Start(offset as u64));
wasi_try!(write_bytes(handle, memory, iovs_arr_cell))
}
Kind::Dir { .. } => { Kind::Dir { .. } => {
// TODO: verify // TODO: verify
return __WASI_EISDIR; return __WASI_EISDIR;
@ -937,7 +949,7 @@ pub fn fd_write(
} }
}; };
nwritten_cell.set(bytes_written); nwritten_cell.set(dbg!(bytes_written));
__WASI_ESUCCESS __WASI_ESUCCESS
} }
@ -1209,12 +1221,77 @@ pub fn path_open(
// entry does not exist in parent directory // entry does not exist in parent directory
// check to see if we should create it // check to see if we should create it
if o_flags & __WASI_O_CREAT != 0 { if o_flags & __WASI_O_CREAT != 0 {
// REVIEW: the code in this branch was written while very tired
// insert in to directory and set values // insert in to directory and set values
//entries.insert(path_segment[0], ) //entries.insert(path_segment[0], )
unimplemented!()
let real_opened_file = wasi_try!(::std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path_vec[0])
.map_err(|_| __WASI_EIO));
debug!("Creating host file {}", &path_vec[0]);
let new_inode = state.fs.inodes.insert(InodeVal {
stat: __wasi_filestat_t::default(),
is_preopened: false,
name: path_vec[0].clone(),
kind: Kind::File {
handle: WasiFile::HostFile(real_opened_file),
},
});
// reborrow
if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind
{
entries.insert(path_vec[0].clone(), new_inode);
}
let new_fd = wasi_try!(state.fs.create_fd(
fs_rights_base,
fs_rights_inheriting,
fs_flags,
new_inode,
));
new_fd
// TODO: technically this could just not be lazily loaded...
/*wasi_try!(state.fs.create_file_at_fd(
dirfd,
path_vec[0],
fs_rights_base,
fs_rights_inheriting,
fs_flags
));*/
} else { } else {
// no entry and can't create it // attempt to load it
return __WASI_ENOENT; let real_opened_file = wasi_try!(::std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path_vec[0])
.map_err(|_| __WASI_ENOENT));
debug!("Opening host file {}", &path_vec[0]);
let new_inode = state.fs.inodes.insert(InodeVal {
stat: __wasi_filestat_t::default(),
is_preopened: false,
name: path_vec[0].clone(),
kind: Kind::File {
handle: WasiFile::HostFile(real_opened_file),
},
});
// reborrow
if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind
{
entries.insert(path_vec[0].clone(), new_inode);
}
let new_fd = wasi_try!(state.fs.create_fd(
fs_rights_base,
fs_rights_inheriting,
fs_flags,
new_inode,
));
new_fd
} }
} }
} else { } else {

View File

@ -264,7 +264,7 @@ pub type __wasi_filedelta_t = i64;
pub type __wasi_filesize_t = u64; pub type __wasi_filesize_t = u64;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[repr(C)] #[repr(C)]
pub struct __wasi_filestat_t { pub struct __wasi_filestat_t {
pub st_dev: __wasi_device_t, pub st_dev: __wasi_device_t,

View File

@ -66,9 +66,13 @@ struct Run {
#[structopt(name = "--", raw(multiple = "true"))] #[structopt(name = "--", raw(multiple = "true"))]
args: Vec<String>, args: Vec<String>,
/// Emscripten symbol map /// Emscripten symbol ma>p
#[structopt(long = "em-symbol-map", parse(from_os_str))] #[structopt(long = "em-symbol-map", parse(from_os_str), group = "emscripten")]
em_symbol_map: Option<PathBuf>, em_symbol_map: Option<PathBuf>,
/// WASI pre-opened file
#[structopt(long = "pre-open", group = "wasi")]
pre_opened_files: Vec<String>,
} }
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@ -248,6 +252,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
env::vars() env::vars()
.map(|(k, v)| format!("{}={}", k, v).into_bytes()) .map(|(k, v)| format!("{}={}", k, v).into_bytes())
.collect(), .collect(),
options.pre_opened_files.clone(),
), ),
None, None,
) )