Merge branch 'master' of github.com:wasmerio/wasmer into simd

This commit is contained in:
Nick Lewycky
2019-07-19 15:37:36 -07:00
43 changed files with 1198 additions and 599 deletions

View File

@ -6,6 +6,11 @@ Blocks of changes will separated by version increments.
## **[Unreleased]** ## **[Unreleased]**
- [#542](https://github.com/wasmerio/wasmer/pull/542) Add SIMD support to wasmer and implement it in the LLVM backend only. - [#542](https://github.com/wasmerio/wasmer/pull/542) Add SIMD support to wasmer and implement it in the LLVM backend only.
- [#555](https://github.com/wasmerio/wasmer/pull/555) WASI filesystem rewrite. Major improvements
- adds virtual root showing all preopened directories
- improved sandboxing and code-reuse
- symlinks work in a lot more situations
- many various improvements to most syscalls touching the filesystem
## 0.5.6 ## 0.5.6
- [#565](https://github.com/wasmerio/wasmer/pull/565) Update wapm and bump version to 0.5.6 - [#565](https://github.com/wasmerio/wasmer/pull/565) Update wapm and bump version to 0.5.6

View File

@ -8,7 +8,13 @@ generate-emtests:
WASM_EMSCRIPTEN_GENERATE_EMTESTS=1 cargo build -p wasmer-emscripten-tests --release WASM_EMSCRIPTEN_GENERATE_EMTESTS=1 cargo build -p wasmer-emscripten-tests --release
generate-wasitests: generate-wasitests:
WASM_WASI_GENERATE_WASITESTS=1 cargo build -p wasmer-wasi-tests --release -vv WASM_WASI_GENERATE_WASITESTS=1 cargo build -p wasmer-wasi-tests --release -vv \
&& echo "formatting" \
&& cargo fmt
spectests-generate: generate-spectests
emtests-generate: generate-emtests
wasitests-generate: generate-wasitests
generate: generate-spectests generate-emtests generate-wasitests generate: generate-spectests generate-emtests generate-wasitests

View File

@ -126,6 +126,15 @@ impl<T: Copy + ValueType> WasmPtr<T, Array> {
[index as usize..slice_full_len]; [index as usize..slice_full_len];
Some(cell_ptrs) Some(cell_ptrs)
} }
pub fn get_utf8_string<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> {
if self.offset as usize + str_len as usize > memory.size().bytes().0 {
return None;
}
let ptr = unsafe { memory.view::<u8>().as_ptr().add(self.offset as usize) as *const u8 };
let slice: &[u8] = unsafe { std::slice::from_raw_parts(ptr, str_len as usize) };
std::str::from_utf8(slice).ok()
}
} }
unsafe impl<T: Copy, Ty> WasmExternType for WasmPtr<T, Ty> { unsafe impl<T: Copy, Ty> WasmExternType for WasmPtr<T, Ty> {

View File

@ -42,7 +42,7 @@ pub struct Ctx {
pub import_backing: *mut ImportBacking, pub import_backing: *mut ImportBacking,
pub module: *const ModuleInner, pub module: *const ModuleInner,
//// This is intended to be user-supplied, per-instance /// This is intended to be user-supplied, per-instance
/// contextual data. There are currently some issue with it, /// contextual data. There are currently some issue with it,
/// notably that it cannot be set before running the `start` /// notably that it cannot be set before running the `start`
/// function in a WebAssembly module. /// function in a WebAssembly module.

View File

@ -89,7 +89,7 @@ pub fn compile(file: &str, ignores: &HashSet<String>) -> Option<String> {
}; };
let src_code = fs::read_to_string(file).expect("read src file"); let src_code = fs::read_to_string(file).expect("read src file");
let args = extract_args_from_source_file(&src_code).unwrap_or_default(); let args: Args = extract_args_from_source_file(&src_code).unwrap_or_default();
let mapdir_args = { let mapdir_args = {
let mut out_str = String::new(); let mut out_str = String::new();
@ -116,12 +116,25 @@ pub fn compile(file: &str, ignores: &HashSet<String>) -> Option<String> {
out_str out_str
}; };
let dir_args = {
let mut out_str = String::new();
out_str.push_str("vec![");
for entry in args.po_dirs {
out_str.push_str(&format!("\"{}\".to_string(),", entry));
}
out_str.push_str("]");
out_str
};
let contents = format!( let contents = format!(
"#[test]{ignore} "#[test]{ignore}
fn test_{rs_module_name}() {{ fn test_{rs_module_name}() {{
assert_wasi_output!( assert_wasi_output!(
\"../../{module_path}\", \"../../{module_path}\",
\"{rs_module_name}\", \"{rs_module_name}\",
{dir_args},
{mapdir_args}, {mapdir_args},
{envvar_args}, {envvar_args},
\"../../{test_output_path}\" \"../../{test_output_path}\"
@ -132,6 +145,7 @@ fn test_{rs_module_name}() {{
module_path = wasm_out_name, module_path = wasm_out_name,
rs_module_name = rs_module_name, rs_module_name = rs_module_name,
test_output_path = format!("{}.out", normalized_name), test_output_path = format!("{}.out", normalized_name),
dir_args = dir_args,
mapdir_args = mapdir_args, mapdir_args = mapdir_args,
envvar_args = envvar_args envvar_args = envvar_args
); );
@ -192,6 +206,8 @@ fn read_ignore_list() -> HashSet<String> {
struct Args { struct Args {
pub mapdir: Vec<(String, String)>, pub mapdir: Vec<(String, String)>,
pub envvars: Vec<(String, String)>, pub envvars: Vec<(String, String)>,
/// pre-opened directories
pub po_dirs: Vec<String>,
} }
/// Pulls args to the program out of a comment at the top of the file starting with "// Args:" /// Pulls args to the program out of a comment at the top of the file starting with "// Args:"
@ -237,6 +253,9 @@ fn extract_args_from_source_file(source_code: &str) -> Option<Args> {
eprintln!("Parse error in env {} not parsed correctly", &tokenized[1]); eprintln!("Parse error in env {} not parsed correctly", &tokenized[1]);
} }
} }
"dir" => {
args.po_dirs.push(tokenized[1].to_string());
}
e => { e => {
eprintln!("WARN: comment arg: {} is not supported", e); eprintln!("WARN: comment arg: {} is not supported", e);
} }

View File

@ -1,5 +1,5 @@
macro_rules! assert_wasi_output { macro_rules! assert_wasi_output {
($file:expr, $name:expr, $mapdir_args:expr, $envvar_args:expr, $expected:expr) => {{ ($file:expr, $name:expr, $po_dir_args: expr, $mapdir_args:expr, $envvar_args:expr, $expected:expr) => {{
use wasmer_dev_utils::stdio::StdioCapturer; use wasmer_dev_utils::stdio::StdioCapturer;
use wasmer_runtime_core::{backend::Compiler, Func}; use wasmer_runtime_core::{backend::Compiler, Func};
use wasmer_wasi::generate_import_object; use wasmer_wasi::generate_import_object;
@ -33,8 +33,7 @@ macro_rules! assert_wasi_output {
let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler()) let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler())
.expect("WASM can't be compiled"); .expect("WASM can't be compiled");
let import_object = let import_object = generate_import_object(vec![], vec![], $po_dir_args, $mapdir_args);
generate_import_object(vec![], vec![], vec![".".to_string()], $mapdir_args);
let instance = module let instance = module
.instantiate(&import_object) .instantiate(&import_object)

View File

@ -1,9 +1,9 @@
#[test] #[test]
#[ignore]
fn test_create_dir() { fn test_create_dir() {
assert_wasi_output!( assert_wasi_output!(
"../../wasitests/create_dir.wasm", "../../wasitests/create_dir.wasm",
"create_dir", "create_dir",
vec![".".to_string(),],
vec![], vec![],
vec![], vec![],
"../../wasitests/create_dir.out" "../../wasitests/create_dir.out"

View File

@ -4,6 +4,7 @@ fn test_envvar() {
"../../wasitests/envvar.wasm", "../../wasitests/envvar.wasm",
"envvar", "envvar",
vec![], vec![],
vec![],
vec!["DOG=1".to_string(), "CAT=2".to_string(),], vec!["DOG=1".to_string(), "CAT=2".to_string(),],
"../../wasitests/envvar.out" "../../wasitests/envvar.out"
); );

View File

@ -3,6 +3,7 @@ fn test_file_metadata() {
assert_wasi_output!( assert_wasi_output!(
"../../wasitests/file_metadata.wasm", "../../wasitests/file_metadata.wasm",
"file_metadata", "file_metadata",
vec![".".to_string(),],
vec![], vec![],
vec![], vec![],
"../../wasitests/file_metadata.out" "../../wasitests/file_metadata.out"

View File

@ -5,6 +5,7 @@ fn test_fs_sandbox_test() {
"fs_sandbox_test", "fs_sandbox_test",
vec![], vec![],
vec![], vec![],
vec![],
"../../wasitests/fs_sandbox_test.out" "../../wasitests/fs_sandbox_test.out"
); );
} }

View File

@ -3,6 +3,7 @@ fn test_fseek() {
assert_wasi_output!( assert_wasi_output!(
"../../wasitests/fseek.wasm", "../../wasitests/fseek.wasm",
"fseek", "fseek",
vec![],
vec![( vec![(
".".to_string(), ".".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet") ::std::path::PathBuf::from("wasitests/test_fs/hamlet")

View File

@ -5,6 +5,7 @@ fn test_hello() {
"hello", "hello",
vec![], vec![],
vec![], vec![],
vec![],
"../../wasitests/hello.out" "../../wasitests/hello.out"
); );
} }

View File

@ -3,6 +3,7 @@ fn test_mapdir() {
assert_wasi_output!( assert_wasi_output!(
"../../wasitests/mapdir.wasm", "../../wasitests/mapdir.wasm",
"mapdir", "mapdir",
vec![],
vec![( vec![(
".".to_string(), ".".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet") ::std::path::PathBuf::from("wasitests/test_fs/hamlet")

View File

@ -13,3 +13,6 @@ mod fseek;
mod hello; mod hello;
mod mapdir; mod mapdir;
mod quine; mod quine;
mod readlink;
mod wasi_sees_virtual_root;
mod writing;

View File

@ -3,6 +3,7 @@ fn test_quine() {
assert_wasi_output!( assert_wasi_output!(
"../../wasitests/quine.wasm", "../../wasitests/quine.wasm",
"quine", "quine",
vec![".".to_string(),],
vec![], vec![],
vec![], vec![],
"../../wasitests/quine.out" "../../wasitests/quine.out"

View File

@ -0,0 +1,14 @@
#[test]
fn test_readlink() {
assert_wasi_output!(
"../../wasitests/readlink.wasm",
"readlink",
vec![],
vec![(
".".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet")
),],
vec![],
"../../wasitests/readlink.out"
);
}

View File

@ -0,0 +1,24 @@
#[test]
fn test_wasi_sees_virtual_root() {
assert_wasi_output!(
"../../wasitests/wasi_sees_virtual_root.wasm",
"wasi_sees_virtual_root",
vec![],
vec![
(
"act1".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1")
),
(
"act2".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet/act2")
),
(
"act1-again".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1")
),
],
vec![],
"../../wasitests/wasi_sees_virtual_root.out"
);
}

View File

@ -0,0 +1,24 @@
#[test]
fn test_writing() {
assert_wasi_output!(
"../../wasitests/writing.wasm",
"writing",
vec![],
vec![
(
"act1".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1")
),
(
"act2".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet/act2")
),
(
"act1-again".to_string(),
::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1")
),
],
vec![],
"../../wasitests/writing.out"
);
}

View File

@ -1,3 +1,6 @@
// Args:
// dir: .
use std::fs; use std::fs;
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::path::*; use std::path::*;

View File

@ -1,3 +1,3 @@
is dir: false is dir: false
filetype: false true false filetype: false true false
file info: 456 file info: 476

View File

@ -1,3 +1,6 @@
// Args:
// dir: .
use std::fs; use std::fs;
use std::io::Read; use std::io::Read;

View File

@ -1 +1 @@
create_dir

View File

@ -4,3 +4,4 @@
"./act3" "./act3"
"./act4" "./act4"
"./act5" "./act5"
"./bookmarks"

View File

@ -4,8 +4,6 @@
use std::fs; use std::fs;
fn main() { fn main() {
#[cfg(not(target_os = "wasi"))]
let cur_dir = std::env::current_dir().unwrap();
#[cfg(not(target_os = "wasi"))] #[cfg(not(target_os = "wasi"))]
std::env::set_current_dir("wasitests/test_fs/hamlet").unwrap(); std::env::set_current_dir("wasitests/test_fs/hamlet").unwrap();
@ -19,7 +17,4 @@ fn main() {
for p in out { for p in out {
println!("{}", p); println!("{}", p);
} }
// return to the current directory
#[cfg(not(target_os = "wasi"))]
std::env::set_current_dir(cur_dir).unwrap();
} }

View File

@ -1,3 +1,6 @@
// Args:
// dir: .
use std::fs; use std::fs;
use std::io::Read; use std::io::Read;

View File

@ -1,3 +1,6 @@
// Args:
// dir: .
use std::fs; use std::fs;
use std::io::Read; use std::io::Read;

View File

@ -0,0 +1,4 @@
../act1/scene2.txt
SCENE II. A room of state in the castle.
Enter KING CLAUDIUS, QUEEN GERTRUDE, HAMLET, POLONIUS, LAERTES, VOLTIMAND, CORNELI

View File

@ -0,0 +1,21 @@
// Args:
// mapdir: .:wasitests/test_fs/hamlet
use std::io::Read;
fn main() {
#[cfg(not(target_os = "wasi"))]
std::env::set_current_dir("wasitests/test_fs/hamlet").unwrap();
let sym_link_path = "bookmarks/2019-07-16";
let link_path = std::fs::read_link(sym_link_path).unwrap();
println!("{}", link_path.to_string_lossy());
let mut some_contents = std::fs::File::open(sym_link_path).unwrap();
let mut buffer = [0; 128];
assert_eq!(some_contents.read(&mut buffer).unwrap(), 128);
let str_val = std::str::from_utf8(&buffer[..]).unwrap();
println!("{}", str_val);
}

Binary file not shown.

View File

@ -0,0 +1 @@
../act1/scene2.txt

View File

@ -0,0 +1,13 @@
"/act1"
"/act1-again"
"/act2"
"/act1"
"/act1-again"
"/act2"
"/act1"
"/act1-again"
"/act2"
"/act1"
"/act1-again"
"/act2"
ROOT IS SAFE

View File

@ -0,0 +1,59 @@
// Args:
// mapdir: act1:wasitests/test_fs/hamlet/act1
// mapdir: act2:wasitests/test_fs/hamlet/act2
// mapdir: act1-again:wasitests/test_fs/hamlet/act1
use std::fs;
fn main() {
// just cheat in this test because there is no comparison for native
#[cfg(not(target_os = "wasi"))]
let results = {
let start = vec!["\"/act1\"", "\"/act1-again\"", "\"/act2\""];
let mut out = vec![];
for _ in 0..4 {
for path_str in &start {
out.push(path_str.to_string());
}
}
out.push("ROOT IS SAFE".to_string());
out
};
#[cfg(target_os = "wasi")]
let results = {
let mut out = vec![];
let read_dir = fs::read_dir("/").unwrap();
for entry in read_dir {
out.push(format!("{:?}", entry.unwrap().path()))
}
let read_dir = fs::read_dir("act1/..").unwrap();
for entry in read_dir {
out.push(format!("{:?}", entry.unwrap().path()))
}
let read_dir = fs::read_dir("act1/../../..").unwrap();
for entry in read_dir {
out.push(format!("{:?}", entry.unwrap().path()))
}
let read_dir = fs::read_dir("act1/../../act2/../act1/../../../").unwrap();
for entry in read_dir {
out.push(format!("{:?}", entry.unwrap().path()))
}
let f = fs::OpenOptions::new().write(true).open("/abc");
if f.is_ok() {
out.push("ROOT IS NOT SAFE".to_string());
} else {
out.push("ROOT IS SAFE".to_string());
}
out
};
for result in results {
println!("{}", result);
}
}

Binary file not shown.

View File

@ -0,0 +1,2 @@
abcdefghijklmnopqrstuvwxyz
file is gone

View File

@ -0,0 +1,41 @@
// Args:
// mapdir: act1:wasitests/test_fs/hamlet/act1
// mapdir: act2:wasitests/test_fs/hamlet/act2
// mapdir: act1-again:wasitests/test_fs/hamlet/act1
use std::fs;
use std::io::Write;
pub const BYTE_STR: &'static [u8] = b"abcdefghijklmnopqrstuvwxyz";
fn main() {
#[cfg(not(target_os = "wasi"))]
do_logic_on_path(
"wasitests/test_fs/hamlet/act1/abc",
"wasitests/test_fs/hamlet/act1/abc",
);
#[cfg(target_os = "wasi")]
do_logic_on_path("/act1/abc", "act1-again/abc");
}
fn do_logic_on_path(path: &'static str, alt_path: &'static str) {
{
let mut f = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(path)
.unwrap();
f.write_all(BYTE_STR).unwrap();
}
println!("{}", fs::read_to_string(alt_path).unwrap());
fs::remove_file(path).unwrap();
let file_path = std::path::Path::new(path);
if file_path.exists() {
println!("file is here");
} else {
println!("file is gone")
}
}

Binary file not shown.

View File

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

View File

@ -12,7 +12,7 @@ macro_rules! wasi_try {
} }
} }
}}; }};
($expr:expr; $e:expr) => {{ ($expr:expr, $e:expr) => {{
let opt: Option<_> = $expr; let opt: Option<_> = $expr;
wasi_try!(opt.ok_or($e)) wasi_try!(opt.ok_or($e))
}}; }};

View File

@ -75,4 +75,9 @@ impl<T: Copy + ValueType> WasmPtr<T, ptr::Array> {
) -> Result<&'a [Cell<T>], __wasi_errno_t> { ) -> Result<&'a [Cell<T>], __wasi_errno_t> {
self.0.deref(memory, index, length).ok_or(__WASI_EFAULT) self.0.deref(memory, index, length).ok_or(__WASI_EFAULT)
} }
#[inline(always)]
pub fn get_utf8_string<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> {
self.0.get_utf8_string(memory, str_len)
}
} }

View File

@ -3,24 +3,32 @@
// file_like::{FileLike, Metadata}; // file_like::{FileLike, Metadata};
// }; // };
use crate::syscalls::types::*; use crate::syscalls::types::*;
use generational_arena::{Arena, Index as Inode}; use generational_arena::Arena;
pub use generational_arena::Index as Inode;
use hashbrown::hash_map::{Entry, HashMap}; use hashbrown::hash_map::{Entry, HashMap};
use std::{ use std::{
borrow::Borrow,
cell::Cell, cell::Cell,
fs, fs,
io::{self, Read, Seek, Write}, io::{self, Read, Seek, Write},
path::PathBuf, path::{Path, PathBuf},
time::SystemTime, time::SystemTime,
}; };
use wasmer_runtime_core::debug; use wasmer_runtime_core::debug;
pub const MAX_SYMLINKS: usize = 100; /// A completely aribtrary "big enough" number used as the upper limit for
/// the number of symlinks that can be traversed when resolving a path
pub const MAX_SYMLINKS: u32 = 128;
#[derive(Debug)] #[derive(Debug)]
pub enum WasiFile { pub enum WasiFile {
HostFile(fs::File), HostFile(fs::File),
} }
impl WasiFile {
pub fn close(self) {}
}
impl Write for WasiFile { impl Write for WasiFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self { match self {
@ -81,6 +89,7 @@ impl Seek for WasiFile {
} }
} }
/// A file that Wasi knows about that may or may not be open
#[derive(Debug)] #[derive(Debug)]
pub struct InodeVal { pub struct InodeVal {
pub stat: __wasi_filestat_t, pub stat: __wasi_filestat_t,
@ -89,54 +98,14 @@ pub struct InodeVal {
pub kind: Kind, pub kind: Kind,
} }
impl InodeVal {
// TODO: clean this up
pub fn from_file_metadata(
metadata: &std::fs::Metadata,
name: String,
is_preopened: bool,
kind: Kind,
) -> Self {
InodeVal {
stat: __wasi_filestat_t {
st_filetype: if metadata.is_dir() {
__WASI_FILETYPE_DIRECTORY
} else {
__WASI_FILETYPE_REGULAR_FILE
},
st_size: metadata.len(),
st_atim: metadata
.accessed()
.ok()
.and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|duration| duration.as_nanos() as u64)
.unwrap_or(0),
st_ctim: metadata
.created()
.ok()
.and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|duration| duration.as_nanos() as u64)
.unwrap_or(0),
st_mtim: metadata
.modified()
.ok()
.and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|duration| duration.as_nanos() as u64)
.unwrap_or(0),
..__wasi_filestat_t::default()
},
is_preopened,
name,
kind,
}
}
}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Kind { pub enum Kind {
File { File {
handle: WasiFile, /// the open file, if it's open
handle: Option<WasiFile>,
/// the path to the file
path: PathBuf,
}, },
Dir { Dir {
/// Parent directory /// Parent directory
@ -147,8 +116,26 @@ pub enum Kind {
/// The entries of a directory are lazily filled. /// The entries of a directory are lazily filled.
entries: HashMap<String, Inode>, entries: HashMap<String, Inode>,
}, },
/// The same as Dir but without the irrelevant bits
/// The root is immutable after creation; generally the Kind::Root
/// branch of whatever code you're writing will be a simpler version of
/// your Kind::Dir logic
Root {
entries: HashMap<String, Inode>,
},
/// The first two fields are data _about_ the symlink
/// the last field is the data _inside_ the symlink
///
/// `base_po_dir` should never be the root because:
/// - Right now symlinks are not allowed in the immutable root
/// - There is always a closer pre-opened dir to the symlink file (by definition of the root being a collection of preopened dirs)
Symlink { Symlink {
forwarded: Inode, /// The preopened dir that this symlink file is relative to (via `path_to_symlink`)
base_po_dir: __wasi_fd_t,
/// The path to the symlink from the `base_po_dir`
path_to_symlink: PathBuf,
/// the value of the symlink as a relative path
relative_path: PathBuf,
}, },
Buffer { Buffer {
buffer: Vec<u8>, buffer: Vec<u8>,
@ -167,11 +154,12 @@ pub struct Fd {
#[derive(Debug)] #[derive(Debug)]
pub struct WasiFs { pub struct WasiFs {
//pub repo: Repo, //pub repo: Repo,
pub preopen_fds: Vec<u32>,
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>,
pub next_fd: Cell<u32>, pub next_fd: Cell<u32>,
pub inode_counter: Cell<u64>, inode_counter: Cell<u64>,
} }
impl WasiFs { impl WasiFs {
@ -182,12 +170,41 @@ impl WasiFs {
debug!("wasi::fs::inodes"); debug!("wasi::fs::inodes");
let inodes = Arena::new(); let inodes = Arena::new();
let mut wasi_fs = Self { let mut wasi_fs = Self {
preopen_fds: vec![],
name_map: HashMap::new(), name_map: HashMap::new(),
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(1024),
}; };
// create virtual root
let root_inode = {
let all_rights = 0x1FFFFFFF;
// TODO: make this a list of positive rigths instead of negative ones
// root gets all right for now
let root_rights = all_rights
/*& (!__WASI_RIGHT_FD_WRITE)
& (!__WASI_RIGHT_FD_ALLOCATE)
& (!__WASI_RIGHT_PATH_CREATE_DIRECTORY)
& (!__WASI_RIGHT_PATH_CREATE_FILE)
& (!__WASI_RIGHT_PATH_LINK_SOURCE)
& (!__WASI_RIGHT_PATH_RENAME_SOURCE)
& (!__WASI_RIGHT_PATH_RENAME_TARGET)
& (!__WASI_RIGHT_PATH_FILESTAT_SET_SIZE)
& (!__WASI_RIGHT_PATH_FILESTAT_SET_TIMES)
& (!__WASI_RIGHT_FD_FILESTAT_SET_SIZE)
& (!__WASI_RIGHT_FD_FILESTAT_SET_TIMES)
& (!__WASI_RIGHT_PATH_SYMLINK)
& (!__WASI_RIGHT_PATH_UNLINK_FILE)
& (!__WASI_RIGHT_PATH_REMOVE_DIRECTORY)*/;
let inode = wasi_fs.create_virtual_root();
let fd = wasi_fs
.create_fd(root_rights, root_rights, 0, inode)
.expect("Could not create root fd");
wasi_fs.preopen_fds.push(fd);
inode
};
debug!("wasi::fs::preopen_dirs"); debug!("wasi::fs::preopen_dirs");
for dir in preopened_dirs { for dir in preopened_dirs {
debug!("Attempting to preopen {}", &dir); debug!("Attempting to preopen {}", &dir);
@ -197,7 +214,7 @@ impl WasiFs {
let cur_dir_metadata = cur_dir.metadata().expect("Could not find directory"); let cur_dir_metadata = cur_dir.metadata().expect("Could not find directory");
let kind = if cur_dir_metadata.is_dir() { let kind = if cur_dir_metadata.is_dir() {
Kind::Dir { Kind::Dir {
parent: None, parent: Some(root_inode),
path: cur_dir.clone(), path: cur_dir.clone(),
entries: Default::default(), entries: Default::default(),
} }
@ -208,14 +225,22 @@ impl WasiFs {
)); ));
}; };
// TODO: handle nested pats in `file` // TODO: handle nested pats in `file`
let inode_val = let inode = wasi_fs
InodeVal::from_file_metadata(&cur_dir_metadata, dir.clone(), true, kind); .create_inode(kind, true, dir.to_string())
.map_err(|e| {
let inode = wasi_fs.inodes.insert(inode_val); format!(
wasi_fs.inodes[inode].stat.st_ino = wasi_fs.inode_counter.get(); "Failed to create inode for preopened dir: WASI error code: {}",
wasi_fs e
)
})?;
let fd = wasi_fs
.create_fd(default_rights, default_rights, 0, inode) .create_fd(default_rights, default_rights, 0, inode)
.expect("Could not open fd"); .expect("Could not open fd");
if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind {
// todo handle collisions
assert!(entries.insert(dir.to_string(), inode).is_none())
}
wasi_fs.preopen_fds.push(fd);
} }
debug!("wasi::fs::mapped_dirs"); debug!("wasi::fs::mapped_dirs");
for (alias, real_dir) in mapped_dirs { for (alias, real_dir) in mapped_dirs {
@ -227,7 +252,7 @@ impl WasiFs {
.expect("mapped dir not at previously verified location"); .expect("mapped dir not at previously verified location");
let kind = if cur_dir_metadata.is_dir() { let kind = if cur_dir_metadata.is_dir() {
Kind::Dir { Kind::Dir {
parent: None, parent: Some(root_inode),
path: real_dir.clone(), path: real_dir.clone(),
entries: Default::default(), entries: Default::default(),
} }
@ -238,19 +263,34 @@ impl WasiFs {
)); ));
}; };
// TODO: handle nested pats in `file` // TODO: handle nested pats in `file`
let inode_val = let inode = wasi_fs
InodeVal::from_file_metadata(&cur_dir_metadata, alias.clone(), true, kind); .create_inode(kind, true, alias.clone())
.map_err(|e| {
let inode = wasi_fs.inodes.insert(inode_val); format!(
wasi_fs.inodes[inode].stat.st_ino = wasi_fs.inode_counter.get(); "Failed to create inode for preopened dir: WASI error code: {}",
wasi_fs e
)
})?;
let fd = wasi_fs
.create_fd(default_rights, default_rights, 0, inode) .create_fd(default_rights, default_rights, 0, inode)
.expect("Could not open fd"); .expect("Could not open fd");
if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind {
// todo handle collisions
assert!(entries.insert(alias.clone(), inode).is_none());
}
wasi_fs.preopen_fds.push(fd);
} }
debug!("wasi::fs::end"); debug!("wasi::fs::end");
Ok(wasi_fs) Ok(wasi_fs)
} }
fn get_next_inode_index(&mut self) -> u64 {
let next = self.inode_counter.get();
self.inode_counter.set(next + 1);
next
}
#[allow(dead_code)] #[allow(dead_code)]
fn get_inode(&mut self, path: &str) -> Option<Inode> { fn get_inode(&mut self, path: &str) -> Option<Inode> {
Some(match self.name_map.entry(path.to_string()) { Some(match self.name_map.entry(path.to_string()) {
@ -311,6 +351,7 @@ impl WasiFs {
}) })
} }
/*
#[allow(dead_code)] #[allow(dead_code)]
fn filestat_inode( fn filestat_inode(
&self, &self,
@ -318,8 +359,13 @@ impl WasiFs {
flags: __wasi_lookupflags_t, flags: __wasi_lookupflags_t,
) -> Result<__wasi_filestat_t, __wasi_errno_t> { ) -> Result<__wasi_filestat_t, __wasi_errno_t> {
let inode_val = &self.inodes[inode]; let inode_val = &self.inodes[inode];
if let (true, Kind::Symlink { mut forwarded }) = if let (
(flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind) true,
Kind::Symlink {
mut forwarded,
path,
},
) = (flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind)
{ {
// Time to follow the symlink. // Time to follow the symlink.
let mut counter = 0; let mut counter = 0;
@ -355,6 +401,224 @@ impl WasiFs {
self.filestat_inode(inode, flags) self.filestat_inode(inode, flags)
} }
*/
fn get_inode_at_path_inner(
&mut self,
base: __wasi_fd_t,
path: &str,
mut symlink_count: u32,
follow_symlinks: bool,
) -> Result<Inode, __wasi_errno_t> {
if symlink_count > MAX_SYMLINKS {
return Err(__WASI_EMLINK);
}
let base_dir = self.get_fd(base)?;
let path: &Path = Path::new(path);
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: while symlink_count < MAX_SYMLINKS {
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,
..
} => {
match component.as_os_str().to_string_lossy().borrow() {
".." => {
if let Some(p) = parent {
cur_inode = *p;
continue 'path_iter;
} else {
return Err(__WASI_EACCES);
}
}
"." => continue 'path_iter,
_ => (),
}
// used for full resolution of symlinks
let mut loop_for_symlink = false;
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_EINVAL)?;
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() {
let link_value = file.read_link().ok().ok_or(__WASI_EIO)?;
debug!("attempting to decompose path {:?}", link_value);
let (pre_open_dir_fd, relative_path) = if link_value.is_relative() {
self.path_into_pre_open_and_relative_path(&file)?
} else {
unimplemented!("Absolute symlinks are not yet supported");
};
loop_for_symlink = true;
symlink_count += 1;
Kind::Symlink {
base_po_dir: pre_open_dir_fd,
path_to_symlink: relative_path,
relative_path: link_value,
}
} else {
unimplemented!("state::get_inode_at_path unknown file type: not file, directory, or symlink");
};
cur_inode =
self.create_inode(kind, false, file.to_string_lossy().to_string())?;
if loop_for_symlink && follow_symlinks {
continue 'symlink_resolution;
}
}
}
Kind::Root { entries } => {
match component.as_os_str().to_string_lossy().borrow() {
// the root's parent is the root
".." => continue 'path_iter,
// the root's current directory is the root
"." => continue 'path_iter,
_ => (),
}
if let Some(entry) =
entries.get(component.as_os_str().to_string_lossy().as_ref())
{
cur_inode = *entry;
} else {
return Err(__WASI_EINVAL);
}
}
Kind::File { .. } => {
return Err(__WASI_ENOTDIR);
}
Kind::Symlink {
base_po_dir,
path_to_symlink,
relative_path,
} => {
let new_base_dir = *base_po_dir;
// allocate to reborrow mutabily to recur
let new_path = {
/*if let Kind::Root { .. } = self.inodes[base_po_dir].kind {
assert!(false, "symlinks should never be relative to the root");
}*/
let mut base = path_to_symlink.clone();
// remove the symlink file itself from the path, leaving just the path from the base
// to the dir containing the symlink
base.pop();
base.push(relative_path);
base.to_string_lossy().to_string()
};
let symlink_inode = self.get_inode_at_path_inner(
new_base_dir,
&new_path,
symlink_count + 1,
follow_symlinks,
)?;
cur_inode = symlink_inode;
//continue 'symlink_resolution;
}
}
break 'symlink_resolution;
}
}
Ok(cur_inode)
}
fn path_into_pre_open_and_relative_path(
&self,
path: &Path,
) -> Result<(__wasi_fd_t, PathBuf), __wasi_errno_t> {
// for each preopened directory
for po_fd in &self.preopen_fds {
let po_inode = self.fd_map[po_fd].inode;
let po_path = match &self.inodes[po_inode].kind {
Kind::Dir { path, .. } => &**path,
Kind::Root { .. } => Path::new("/"),
_ => unreachable!("Preopened FD that's not a directory or the root"),
};
// stem path based on it
if let Ok(rest) = path.strip_prefix(po_path) {
// if any path meets this criteria
// (verify that all remaining components are not symlinks except for maybe last? (or do the more complex logic of resolving intermediary symlinks))
// return preopened dir and the rest of the path
return Ok((*po_fd, rest.to_owned()));
}
}
Err(__WASI_EINVAL) // this may not make sense
}
/// gets a host file from a base directory and a path
/// this function ensures the fs remains sandboxed
// NOTE: follow symlinks is super weird right now
// even if it's false, it still follows symlinks, just not the last
// symlink so
// This will be resolved when we have tests asserting the correct behavior
pub fn get_inode_at_path(
&mut self,
base: __wasi_fd_t,
path: &str,
follow_symlinks: bool,
) -> Result<Inode, __wasi_errno_t> {
self.get_inode_at_path_inner(base, path, 0, follow_symlinks)
}
/// Returns the parent Dir or Root that the file at a given path is in and the file name
/// stripped off
pub fn get_parent_inode_at_path(
&mut self,
base: __wasi_fd_t,
path: &Path,
follow_symlinks: bool,
) -> Result<(Inode, String), __wasi_errno_t> {
let mut parent_dir = std::path::PathBuf::new();
let mut components = path.components().rev();
let new_entity_name = components
.next()
.ok_or(__WASI_EINVAL)?
.as_os_str()
.to_string_lossy()
.to_string();
for comp in components.rev() {
parent_dir.push(comp);
}
self.get_inode_at_path(base, &parent_dir.to_string_lossy(), follow_symlinks)
.map(|v| (v, new_entity_name))
}
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> { 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)?; let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?;
@ -414,17 +678,39 @@ impl WasiFs {
let inode = &mut self.inodes[fd.inode]; let inode = &mut self.inodes[fd.inode];
match &mut inode.kind { 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 // TODO: verify this behavior
Kind::Dir { .. } => return Err(__WASI_EISDIR), Kind::Dir { .. } => return Err(__WASI_EISDIR),
Kind::Symlink { .. } => unimplemented!(), Kind::Symlink { .. } => unimplemented!(),
Kind::Buffer { .. } => (), Kind::Buffer { .. } => (),
_ => return Err(__WASI_EIO),
} }
} }
} }
Ok(()) Ok(())
} }
/// Creates an inode and inserts it given a Kind and some extra data
pub fn create_inode(
&mut self,
kind: Kind,
is_preopened: bool,
name: String,
) -> Result<Inode, __wasi_errno_t> {
let mut stat = self.get_stat_for_kind(&kind).ok_or(__WASI_EIO)?;
stat.st_ino = self.get_next_inode_index();
Ok(self.inodes.insert(InodeVal {
stat: stat,
is_preopened,
name,
kind,
}))
}
pub fn create_fd( pub fn create_fd(
&mut self, &mut self,
rights: __wasi_rights_t, rights: __wasi_rights_t,
@ -447,11 +733,90 @@ impl WasiFs {
Ok(idx) Ok(idx)
} }
pub fn get_base_path_for_directory(&self, directory: Inode) -> Option<String> { /// This function is unsafe because it's the caller's responsibility to ensure that
if let Kind::Dir { path, .. } = &self.inodes[directory].kind { /// all refences to the given inode have been removed from the filesystem
return Some(path.to_string_lossy().to_string()); ///
} /// returns true if the inode existed and was removed
None pub unsafe fn remove_inode(&mut self, inode: Inode) -> bool {
self.inodes.remove(inode).is_some()
}
fn create_virtual_root(&mut self) -> Inode {
let stat = __wasi_filestat_t {
st_filetype: __WASI_FILETYPE_DIRECTORY,
st_ino: self.get_next_inode_index(),
..__wasi_filestat_t::default()
};
let root_kind = Kind::Root {
entries: HashMap::new(),
};
self.inodes.insert(InodeVal {
stat: stat,
is_preopened: true,
name: "/".to_string(),
kind: root_kind,
})
}
pub fn get_stat_for_kind(&self, kind: &Kind) -> Option<__wasi_filestat_t> {
let md = match kind {
Kind::File { handle, path } => match handle {
Some(WasiFile::HostFile(hf)) => hf.metadata().ok()?,
None => path.metadata().ok()?,
},
Kind::Dir { path, .. } => path.metadata().ok()?,
Kind::Symlink {
base_po_dir,
path_to_symlink,
..
} => {
let base_po_inode = &self.fd_map[base_po_dir].inode;
let base_po_inode_v = &self.inodes[*base_po_inode];
match &base_po_inode_v.kind {
Kind::Root { .. } => {
path_to_symlink.clone().symlink_metadata().ok()?
}
Kind::Dir { path, .. } => {
let mut real_path = path.clone();
// PHASE 1: ignore all possible symlinks in `relative_path`
// TODO: walk the segments of `relative_path` via the entries of the Dir
// use helper function to avoid duplicating this logic (walking this will require
// &self to be &mut sel
// TODO: adjust size of symlink, too
// for all paths adjusted think about this
real_path.push(path_to_symlink);
real_path.symlink_metadata().ok()?
}
// if this triggers, there's a bug in the symlink code
_ => unreachable!("Symlink pointing to something that's not a directory as its base preopened directory"),
}
}
__ => return None,
};
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()
})
} }
} }
@ -474,64 +839,3 @@ pub fn host_file_type_to_wasi_file_type(file_type: fs::FileType) -> __wasi_filet
__WASI_FILETYPE_UNKNOWN __WASI_FILETYPE_UNKNOWN
} }
} }
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()?;
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 {
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()
})
}
_ => None,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
use crate::syscalls::types::*; use crate::syscalls::types::*;
use std::cell::Cell; use std::cell::Cell;
use wasmer_runtime_core::debug;
pub fn platform_clock_res_get( pub fn platform_clock_res_get(
clock_id: __wasi_clockid_t, clock_id: __wasi_clockid_t,