From 82875b378d7bf1d5c8f8736ca1cbb204b2c38abb Mon Sep 17 00:00:00 2001 From: skanehira Date: Sat, 25 Nov 2023 12:49:08 +0900 Subject: [PATCH] refactor: add and impl file trait --- src/wasi/wasi_snapshot_preview1/file.rs | 123 ++++++++++-------- src/wasi/wasi_snapshot_preview1/file_table.rs | 43 ++++++ src/wasi/wasi_snapshot_preview1/mod.rs | 3 + src/wasi/wasi_snapshot_preview1/preview1.rs | 96 +++++++++++--- .../wasi_snapshot_preview1/virtual_file.rs | 46 +++++++ src/wasi/wasi_snapshot_preview1/wasi_file.rs | 53 ++++++++ 6 files changed, 291 insertions(+), 73 deletions(-) create mode 100644 src/wasi/wasi_snapshot_preview1/file_table.rs create mode 100644 src/wasi/wasi_snapshot_preview1/virtual_file.rs create mode 100644 src/wasi/wasi_snapshot_preview1/wasi_file.rs diff --git a/src/wasi/wasi_snapshot_preview1/file.rs b/src/wasi/wasi_snapshot_preview1/file.rs index bdd6b1d..79c00f0 100644 --- a/src/wasi/wasi_snapshot_preview1/file.rs +++ b/src/wasi/wasi_snapshot_preview1/file.rs @@ -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 ReadWrite for IO {} +impl ReadWrite for IO {} -pub struct File(Box); - -impl File { - pub fn from_buffer(buffer: Vec) -> 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 { - 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 { - 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 { - 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 { - 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; + fn read(&mut self, data: &mut [u8]) -> Result; + fn seek(&mut self, pos: u64) -> Result; + fn filetype(&self) -> Result; + fn fdflags(&self) -> Result; + fn read_string(&mut self) -> Result; } -pub struct FileTable(Vec>>); +#[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, } -impl FileTable { - pub fn with_io(files: Vec>>) -> Self { - let mut file_table = FileTable(vec![]); - for file in files { - file_table.add(file); - } - file_table +impl FileEntry { + pub fn new(file: Box, caps: FileCaps) -> Self { + Self { caps, file } } - pub fn get(&self, idx: usize) -> Option<&Arc>> { - self.0.get(idx) + + pub fn get_fdstat(&self) -> Result { + Ok(FdStat { + filetype: self.file.filetype()?, + caps: self.caps.clone(), + flags: self.file.fdflags()?, + }) } - pub fn add(&mut self, file: Arc>) { - self.0.push(file); + + pub fn capbable(&mut self, _cap: FileCaps) -> Result<&mut Box> { + // TODO: check capabilites + let file = &mut self.file; + Ok(file) } } diff --git a/src/wasi/wasi_snapshot_preview1/file_table.rs b/src/wasi/wasi_snapshot_preview1/file_table.rs new file mode 100644 index 0000000..d5a2742 --- /dev/null +++ b/src/wasi/wasi_snapshot_preview1/file_table.rs @@ -0,0 +1,43 @@ +use super::{ + file::{FileCaps, FileEntry}, + wasi_file::WasiFile, +}; +use std::sync::{Arc, Mutex}; + +pub struct FileTable(Vec>>); + +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>>) -> Self { + FileTable(files) + } + + pub fn get(&self, idx: usize) -> Option<&Arc>> { + self.0.get(idx) + } + + pub fn add(&mut self, file: Arc>) { + self.0.push(file); + } +} diff --git a/src/wasi/wasi_snapshot_preview1/mod.rs b/src/wasi/wasi_snapshot_preview1/mod.rs index 50c729a..635a7cb 100644 --- a/src/wasi/wasi_snapshot_preview1/mod.rs +++ b/src/wasi/wasi_snapshot_preview1/mod.rs @@ -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::*; diff --git a/src/wasi/wasi_snapshot_preview1/preview1.rs b/src/wasi/wasi_snapshot_preview1/preview1.rs index 1578220..a39a635 100644 --- a/src/wasi/wasi_snapshot_preview1/preview1.rs +++ b/src/wasi/wasi_snapshot_preview1/preview1.rs @@ -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,6 +34,7 @@ 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)) @@ -41,7 +42,7 @@ impl Importer for WasiSnapshotPreview1 { } impl WasiSnapshotPreview1 { - pub fn with_io(files: Vec>>) -> Self { + pub fn with_io(files: Vec>>) -> 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>, args: Vec) -> Result { + let args: Vec = 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/std@0.206.0/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(input: &T) -> &[u8] { + unsafe { std::slice::from_raw_parts(input as *const _ as *const u8, std::mem::size_of::()) } } #[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::::default(), + FileCaps::Sync, + ))); + let stdout = Arc::new(Mutex::new(FileEntry::new( + Box::::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::::default(), + FileCaps::Sync, + ))); + let stdout = Arc::new(Mutex::new(FileEntry::new( + Box::::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 = 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::::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(()) diff --git a/src/wasi/wasi_snapshot_preview1/virtual_file.rs b/src/wasi/wasi_snapshot_preview1/virtual_file.rs new file mode 100644 index 0000000..64643a2 --- /dev/null +++ b/src/wasi/wasi_snapshot_preview1/virtual_file.rs @@ -0,0 +1,46 @@ +use super::file::{FdFlags, File, FileType, ReadWrite}; +use anyhow::Result; +use std::io::Cursor; + +pub struct VirtualFile(Box); + +impl File for VirtualFile { + fn write(&mut self, data: &[u8]) -> Result { + let written = self.0.write(data)?; + Ok(written) + } + + fn read(&mut self, data: &mut [u8]) -> Result { + Ok(self.0.read(data)?) + } + + fn seek(&mut self, pos: u64) -> Result { + Ok(self.0.seek(std::io::SeekFrom::Start(pos))?) + } + + fn read_string(&mut self) -> Result { + let mut buf = String::new(); + self.0.read_to_string(&mut buf)?; + Ok(buf) + } + + fn filetype(&self) -> Result { + Ok(FileType::RegularFile) + } + + fn fdflags(&self) -> Result { + Ok(FdFlags::Append) + } +} + +impl Default for VirtualFile { + fn default() -> Self { + Self(Box::new(Cursor::new(vec![]))) + } +} + +impl VirtualFile { + pub fn new(data: &[u8]) -> Self { + Self(Box::new(Cursor::new(data.to_vec()))) + } +} diff --git a/src/wasi/wasi_snapshot_preview1/wasi_file.rs b/src/wasi/wasi_snapshot_preview1/wasi_file.rs new file mode 100644 index 0000000..a7e9f32 --- /dev/null +++ b/src/wasi/wasi_snapshot_preview1/wasi_file.rs @@ -0,0 +1,53 @@ +use super::file::{FdFlags, File, FileType}; +use anyhow::Result; +use std::{io::prelude::*, os::fd::FromRawFd}; + +pub struct WasiFile(std::fs::File); + +impl File for WasiFile { + fn write(&mut self, data: &[u8]) -> Result { + let written = self.0.write(data)?; + Ok(written) + } + + fn read(&mut self, data: &mut [u8]) -> Result { + Ok(self.0.read(data)?) + } + + fn seek(&mut self, pos: u64) -> Result { + Ok(self.0.seek(std::io::SeekFrom::Start(pos))?) + } + + fn read_string(&mut self) -> Result { + let mut buf = String::new(); + self.0.read_to_string(&mut buf)?; + Ok(buf) + } + + fn filetype(&self) -> Result { + // FIXME: this is not correct + let m = self.0.metadata()?; + let filetype = if m.is_file() { + FileType::RegularFile + } else if m.is_dir() { + FileType::Directory + } else if m.is_symlink() { + FileType::SymbolicLink + } else { + FileType::Unknown + }; + Ok(filetype) + } + + fn fdflags(&self) -> Result { + // TODO: implement fdflags + Ok(FdFlags::Append) + } +} + +impl WasiFile { + pub fn from_raw_fd(fd: u32) -> Self { + let file = unsafe { std::fs::File::from_raw_fd(fd as i32) }; + Self(file) + } +}