Skip to content

Commit

Permalink
refactor: add and impl file trait
Browse files Browse the repository at this point in the history
  • Loading branch information
skanehira committed Nov 25, 2023
1 parent ee9ad37 commit 82875b3
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 73 deletions.
123 changes: 70 additions & 53 deletions src/wasi/wasi_snapshot_preview1/file.rs
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)
}
}
43 changes: 43 additions & 0 deletions src/wasi/wasi_snapshot_preview1/file_table.rs
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);
}
}
3 changes: 3 additions & 0 deletions src/wasi/wasi_snapshot_preview1/mod.rs
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::*;
96 changes: 76 additions & 20 deletions src/wasi/wasi_snapshot_preview1/preview1.rs
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::*;
Expand Down Expand Up @@ -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 }
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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(())
Expand All @@ -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();
Expand All @@ -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(())
Expand Down
Loading

0 comments on commit 82875b3

Please sign in to comment.