-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
291 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,86 @@ | ||
use anyhow::Result; | ||
use std::fs; | ||
use std::io::{Cursor, Read, Seek, Write}; | ||
use std::os::fd::FromRawFd; | ||
use std::sync::{Arc, Mutex}; | ||
use std::io::{Read, Seek, Write}; | ||
|
||
pub trait ReadWrite: Read + Write + Seek {} | ||
pub trait ReadWrite: Read + Write + Seek + Send + Sync + 'static {} | ||
|
||
impl<IO: Read + Write + Send + Seek> ReadWrite for IO {} | ||
impl<IO: Read + Write + Seek + Send + Sync + 'static> ReadWrite for IO {} | ||
|
||
pub struct File(Box<dyn ReadWrite>); | ||
|
||
impl File { | ||
pub fn from_buffer(buffer: Vec<u8>) -> Self { | ||
File(Box::new(Cursor::new(buffer))) | ||
} | ||
|
||
pub fn from_raw_fd(fd: u32) -> Self { | ||
let file = unsafe { fs::File::from_raw_fd(fd as i32) }; | ||
File(Box::new(file)) | ||
} | ||
|
||
pub fn write(&mut self, data: &[u8]) -> Result<usize> { | ||
let written = self.0.write(data)?; | ||
Ok(written) | ||
} | ||
#[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
pub enum FdFlags { | ||
Append = 0b1, | ||
Dsync = 0b10, | ||
Nonblock = 0b1000, | ||
Rsync = 0b10000, | ||
Sync = 0b100000, | ||
} | ||
|
||
pub fn read(&mut self, data: &mut [u8]) -> Result<usize> { | ||
Ok(self.0.read(data)?) | ||
} | ||
#[derive(Debug, Clone)] | ||
pub enum FileCaps { | ||
DataSync = 0b1, | ||
Read = 0b10, | ||
Seek = 0b100, | ||
FdstatSetFlags = 0b1000, | ||
Sync = 0b10000, | ||
Tell = 0b100000, | ||
Write = 0b1000000, | ||
Advise = 0b10000000, | ||
Allocate = 0b100000000, | ||
FilestatGet = 0b1000000000, | ||
FilestatSetSize = 0b10000000000, | ||
FilestatSetTimes = 0b100000000000, | ||
PollReadwrite = 0b1000000000000, | ||
} | ||
|
||
pub fn seek(&mut self, pos: u64) -> Result<u64> { | ||
Ok(self.0.seek(std::io::SeekFrom::Start(pos))?) | ||
} | ||
#[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
pub enum FileType { | ||
Unknown = 0, | ||
BlockDevice = 1, | ||
CharacterDevice = 2, | ||
Directory = 3, | ||
RegularFile = 4, | ||
SocketDgram = 5, | ||
SocketStream = 6, | ||
SymbolicLink = 7, | ||
Pipe = 8, | ||
} | ||
|
||
pub fn read_string(&mut self) -> Result<String> { | ||
let mut buf = String::new(); | ||
self.0.read_to_string(&mut buf)?; | ||
Ok(buf) | ||
} | ||
pub trait File: Send + Sync { | ||
fn write(&mut self, data: &[u8]) -> Result<usize>; | ||
fn read(&mut self, data: &mut [u8]) -> Result<usize>; | ||
fn seek(&mut self, pos: u64) -> Result<u64>; | ||
fn filetype(&self) -> Result<FileType>; | ||
fn fdflags(&self) -> Result<FdFlags>; | ||
fn read_string(&mut self) -> Result<String>; | ||
} | ||
|
||
pub struct FileTable(Vec<Arc<Mutex<File>>>); | ||
#[derive(Debug, Clone)] | ||
pub struct FdStat { | ||
pub filetype: FileType, | ||
pub caps: FileCaps, | ||
pub flags: FdFlags, | ||
} | ||
|
||
impl Default for FileTable { | ||
fn default() -> Self { | ||
Self(vec![ | ||
Arc::new(Mutex::new(File::from_raw_fd(0))), // stdin | ||
Arc::new(Mutex::new(File::from_raw_fd(1))), // stdout | ||
Arc::new(Mutex::new(File::from_raw_fd(2))), // stderr | ||
]) | ||
} | ||
pub struct FileEntry { | ||
caps: FileCaps, | ||
file: Box<dyn File>, | ||
} | ||
|
||
impl FileTable { | ||
pub fn with_io(files: Vec<Arc<Mutex<File>>>) -> Self { | ||
let mut file_table = FileTable(vec![]); | ||
for file in files { | ||
file_table.add(file); | ||
} | ||
file_table | ||
impl FileEntry { | ||
pub fn new(file: Box<dyn File>, caps: FileCaps) -> Self { | ||
Self { caps, file } | ||
} | ||
pub fn get(&self, idx: usize) -> Option<&Arc<Mutex<File>>> { | ||
self.0.get(idx) | ||
|
||
pub fn get_fdstat(&self) -> Result<FdStat> { | ||
Ok(FdStat { | ||
filetype: self.file.filetype()?, | ||
caps: self.caps.clone(), | ||
flags: self.file.fdflags()?, | ||
}) | ||
} | ||
pub fn add(&mut self, file: Arc<Mutex<File>>) { | ||
self.0.push(file); | ||
|
||
pub fn capbable(&mut self, _cap: FileCaps) -> Result<&mut Box<dyn File>> { | ||
// TODO: check capabilites | ||
let file = &mut self.file; | ||
Ok(file) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
use super::{ | ||
file::{FileCaps, FileEntry}, | ||
wasi_file::WasiFile, | ||
}; | ||
use std::sync::{Arc, Mutex}; | ||
|
||
pub struct FileTable(Vec<Arc<Mutex<FileEntry>>>); | ||
|
||
impl Default for FileTable { | ||
fn default() -> Self { | ||
Self(vec![ | ||
// stdin | ||
Arc::new(Mutex::new(FileEntry::new( | ||
Box::new(WasiFile::from_raw_fd(0)), | ||
FileCaps::Sync, | ||
))), | ||
// stdout | ||
Arc::new(Mutex::new(FileEntry::new( | ||
Box::new(WasiFile::from_raw_fd(1)), | ||
FileCaps::Sync, | ||
))), | ||
// stderr | ||
Arc::new(Mutex::new(FileEntry::new( | ||
Box::new(WasiFile::from_raw_fd(2)), | ||
FileCaps::Sync, | ||
))), | ||
]) | ||
} | ||
} | ||
|
||
impl FileTable { | ||
pub fn with_io(files: Vec<Arc<Mutex<FileEntry>>>) -> Self { | ||
FileTable(files) | ||
} | ||
|
||
pub fn get(&self, idx: usize) -> Option<&Arc<Mutex<FileEntry>>> { | ||
self.0.get(idx) | ||
} | ||
|
||
pub fn add(&mut self, file: Arc<Mutex<FileEntry>>) { | ||
self.0.push(file); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
pub mod file; | ||
pub mod file_table; | ||
pub mod preview1; | ||
pub mod types; | ||
pub mod virtual_file; | ||
pub mod wasi_file; | ||
|
||
pub use preview1::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
use super::file::{File, FileTable}; | ||
use super::{file::FileEntry, file_table::FileTable}; | ||
use crate::{ | ||
binary::instruction::MemoryArg, memory_load, memory_write, module::ExternalFuncInst, Importer, | ||
Store, Value, | ||
binary::instruction::MemoryArg, memory_load, memory_write, module::ExternalFuncInst, | ||
wasi::file::FileCaps, Importer, Store, Value, | ||
}; | ||
use anyhow::{Context as _, Result}; | ||
use rand::prelude::*; | ||
|
@@ -34,14 +34,15 @@ impl Importer for WasiSnapshotPreview1 { | |
"args_get" => self.args_get(store, args), | ||
"args_sizes_get" => self.args_sizes_get(store, args), | ||
"random_get" => self.random_get(store, args), | ||
"fd_fdstat_get" => self.fd_fdstat_get(store, args), | ||
_ => todo!(), | ||
}?; | ||
Ok(Some(value)) | ||
} | ||
} | ||
|
||
impl WasiSnapshotPreview1 { | ||
pub fn with_io(files: Vec<Arc<Mutex<File>>>) -> Self { | ||
pub fn with_io(files: Vec<Arc<Mutex<FileEntry>>>) -> Self { | ||
let file_table = FileTable::with_io(files); | ||
Self { file_table } | ||
} | ||
|
@@ -120,7 +121,10 @@ impl WasiSnapshotPreview1 { | |
.file_table | ||
.get(fd) | ||
.with_context(|| format!("cannot get file with fd: {}", fd))?; | ||
|
||
let file = Arc::clone(file); | ||
let mut file = file.lock().expect("cannot lock file"); | ||
let file = file.capbable(FileCaps::Read)?; | ||
|
||
let mut nread = 0; | ||
for _ in 0..iovs_len { | ||
|
@@ -133,10 +137,7 @@ impl WasiSnapshotPreview1 { | |
let offset = offset as usize; | ||
let end = offset + len as usize; | ||
|
||
nread += file | ||
.lock() | ||
.expect("cannot get file lock") | ||
.read(&mut memory.data[offset..end])?; | ||
nread += file.read(&mut memory.data[offset..end])?; | ||
} | ||
|
||
memory_write!(memory, 0, 4, nread_offset, nread); | ||
|
@@ -162,6 +163,10 @@ impl WasiSnapshotPreview1 { | |
.get(fd) | ||
.with_context(|| format!("cannot get file with fd: {}", fd))?; | ||
let file = Arc::clone(file); | ||
|
||
let mut file = file.lock().expect("cannot lock file"); | ||
let file = file.capbable(FileCaps::Write)?; | ||
|
||
let mut written = 0; | ||
|
||
for _ in 0..iovs_len { | ||
|
@@ -175,7 +180,7 @@ impl WasiSnapshotPreview1 { | |
let end = offset + len as usize; | ||
let buf = &memory.data[offset..end]; | ||
|
||
written += file.lock().expect("cannot get file lock").write(buf)?; | ||
written += file.write(buf)?; | ||
} | ||
|
||
memory_write!(memory, 0, 4, rp, written); | ||
|
@@ -252,12 +257,43 @@ impl WasiSnapshotPreview1 { | |
|
||
Ok(0.into()) | ||
} | ||
|
||
fn fd_fdstat_get(&self, store: Rc<RefCell<Store>>, args: Vec<Value>) -> Result<Value> { | ||
let args: Vec<i32> = args.into_iter().map(Into::into).collect(); | ||
let (fd, offset) = (args[0] as usize, args[1] as usize); | ||
|
||
let store = store.borrow(); | ||
let memory = store.memory.get(0).with_context(|| "not found memory")?; | ||
let mut memory = memory.borrow_mut(); | ||
|
||
let file = self | ||
.file_table | ||
.get(fd) | ||
.with_context(|| format!("cannot get file with fd: {}", fd))?; | ||
let file = file.lock().expect("cannot lock file"); | ||
let stat = file.get_fdstat()?; | ||
|
||
// ref: https://deno.land/[email protected]/wasi/snapshot_preview1.ts?source=#L673 | ||
memory.write_bytes(offset, get_memory(&stat.filetype))?; | ||
memory.write_bytes(offset + 2, get_memory(&stat.flags))?; | ||
|
||
Ok(0.into()) | ||
} | ||
} | ||
|
||
fn get_memory<T>(input: &T) -> &[u8] { | ||
unsafe { std::slice::from_raw_parts(input as *const _ as *const u8, std::mem::size_of::<T>()) } | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::sync::Mutex; | ||
|
||
use super::*; | ||
use crate::Runtime; | ||
use crate::{ | ||
wasi::{file::FileEntry, wasi_snapshot_preview1::virtual_file::VirtualFile}, | ||
Runtime, | ||
}; | ||
use pretty_assertions::assert_eq; | ||
|
||
#[test] | ||
|
@@ -290,10 +326,16 @@ mod tests { | |
"#; | ||
let wasm = wat::parse_str(code)?; | ||
|
||
let stdin = Arc::new(Mutex::new(File::from_buffer(vec![]))); | ||
let stdout = Arc::new(Mutex::new(File::from_buffer(vec![]))); | ||
let stdin = Arc::new(Mutex::new(FileEntry::new( | ||
Box::<VirtualFile>::default(), | ||
FileCaps::Sync, | ||
))); | ||
let stdout = Arc::new(Mutex::new(FileEntry::new( | ||
Box::<VirtualFile>::default(), | ||
FileCaps::Sync, | ||
))); | ||
|
||
let wasi = WasiSnapshotPreview1::with_io(vec![stdin, Arc::clone(&stdout)]); | ||
let wasi = WasiSnapshotPreview1::with_io(vec![stdin, stdout.clone()]); | ||
let mut runtime = Runtime::from_bytes(wasm.as_slice(), Some(Box::new(wasi)))?; | ||
|
||
let result: i32 = runtime | ||
|
@@ -303,6 +345,7 @@ mod tests { | |
assert_eq!(result, 0); | ||
|
||
let mut stdout = stdout.lock().expect("cannot lock stdout"); | ||
let stdout = stdout.capbable(FileCaps::Seek)?; | ||
stdout.seek(0)?; // NOTE: need to reset cursor for reading | ||
assert_eq!(stdout.read_string()?, "Hello, World!\n"); | ||
Ok(()) | ||
|
@@ -312,15 +355,22 @@ mod tests { | |
fn test_args_get() -> Result<()> { | ||
let wasm = wat::parse_file("examples/args_get.wasm")?; | ||
|
||
let stdin = Arc::new(Mutex::new(File::from_buffer(vec![]))); | ||
let stdout = Arc::new(Mutex::new(File::from_buffer(vec![]))); | ||
let stdin = Arc::new(Mutex::new(FileEntry::new( | ||
Box::<VirtualFile>::default(), | ||
FileCaps::Sync, | ||
))); | ||
let stdout = Arc::new(Mutex::new(FileEntry::new( | ||
Box::<VirtualFile>::default(), | ||
FileCaps::Sync, | ||
))); | ||
|
||
let wasi = WasiSnapshotPreview1::with_io(vec![stdin, Arc::clone(&stdout)]); | ||
let wasi = WasiSnapshotPreview1::with_io(vec![stdin, stdout.clone()]); | ||
let mut runtime = Runtime::from_bytes(wasm.as_slice(), Some(Box::new(wasi)))?; | ||
|
||
runtime.call("_start".into(), vec![])?; | ||
|
||
let mut stdout = stdout.lock().expect("cannot lock stdout"); | ||
let stdout = stdout.capbable(FileCaps::Read)?; | ||
stdout.seek(0)?; | ||
let result: Vec<String> = serde_json::from_str(&stdout.read_string()?)?; | ||
let arg = std::env::args().take(1).next().unwrap(); | ||
|
@@ -332,17 +382,23 @@ mod tests { | |
fn test_fd_read() -> Result<()> { | ||
let wasm = wat::parse_file("examples/fd_read.wasm")?; | ||
|
||
let stdin = Arc::new(Mutex::new(File::from_buffer( | ||
"hello world".as_bytes().to_vec(), | ||
let stdin = Arc::new(Mutex::new(FileEntry::new( | ||
Box::new(VirtualFile::new(b"hello world")), | ||
FileCaps::Sync, | ||
))); | ||
|
||
let stdout = Arc::new(Mutex::new(FileEntry::new( | ||
Box::<VirtualFile>::default(), | ||
FileCaps::Sync, | ||
))); | ||
let stdout = Arc::new(Mutex::new(File::from_buffer(vec![]))); | ||
|
||
let wasi = WasiSnapshotPreview1::with_io(vec![Arc::clone(&stdin), Arc::clone(&stdout)]); | ||
let wasi = WasiSnapshotPreview1::with_io(vec![stdin.clone(), stdout.clone()]); | ||
let mut runtime = Runtime::from_bytes(wasm.as_slice(), Some(Box::new(wasi)))?; | ||
|
||
runtime.call("_start".into(), vec![])?; | ||
|
||
let mut stdout = stdout.lock().expect("cannot lock stdout"); | ||
let stdout = stdout.capbable(FileCaps::Read)?; | ||
stdout.seek(0)?; | ||
assert_eq!(stdout.read_string()?, "input: got: hello world\n"); | ||
Ok(()) | ||
|
Oops, something went wrong.