Update wasmer-vfs implemenetation

* Add some new file systems
* Add tests
* General cleanup
This commit is contained in:
Felix Schütt
2022-11-16 12:54:03 +01:00
committed by Christoph Herzog
parent 0cf7e0404d
commit dfd7ff41b5
40 changed files with 3000 additions and 1083 deletions

42
Cargo.lock generated
View File

@ -1155,6 +1155,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -4288,7 +4294,7 @@ dependencies = [
"wasmer-types",
"wasmer-vfs",
"wasmer-wasi",
"webc",
"webc 0.1.0",
]
[[package]]
@ -4373,7 +4379,7 @@ dependencies = [
"wasmer-wasi",
"wasmer-wasi-experimental-io-devices",
"wasmer-wast",
"webc",
"webc 3.0.1",
]
[[package]]
@ -4628,6 +4634,9 @@ version = "3.0.0-rc.2"
dependencies = [
"anyhow",
"async-trait",
"derivative",
"filetime",
"fs_extra",
"lazy_static",
"libc",
"serde",
@ -4635,7 +4644,8 @@ dependencies = [
"thiserror",
"tracing",
"typetag",
"webc",
"wasmer-wasi-types",
"webc 0.1.0",
]
[[package]]
@ -4725,7 +4735,7 @@ dependencies = [
"wasmer-vnet",
"wasmer-wasi-local-networking",
"wasmer-wasi-types",
"webc",
"webc 0.1.0",
"webc-vfs",
"weezl",
"winapi",
@ -5033,13 +5043,35 @@ dependencies = [
"walkdir",
]
[[package]]
name = "webc"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef87e7b955d5d1feaa8697ae129f1a9ce8859e151574ad3baceae9413b48d2f0"
dependencies = [
"anyhow",
"base64",
"indexmap",
"leb128",
"lexical-sort",
"memchr",
"path-clean",
"rand 0.8.5",
"serde",
"serde_cbor",
"serde_json",
"sha2",
"url",
"walkdir",
]
[[package]]
name = "webc-vfs"
version = "0.1.0"
dependencies = [
"anyhow",
"wasmer-vfs",
"webc",
"webc 0.1.0",
]
[[package]]

View File

@ -483,6 +483,7 @@ test-packages:
$(CARGO_BINARY) test $(CARGO_TARGET) --manifest-path lib/compiler-cranelift/Cargo.toml --release --no-default-features --features=std
$(CARGO_BINARY) test $(CARGO_TARGET) --manifest-path lib/compiler-singlepass/Cargo.toml --release --no-default-features --features=std
$(CARGO_BINARY) test $(CARGO_TARGET) --manifest-path lib/cli/Cargo.toml $(compiler_features) --release
$(CARGO_BINARY) test $(CARGO_TARGET) --manifest-path lib/vfs/Cargo.toml $(compiler_features) --release
test-js: test-js-api test-js-wasi

View File

@ -73,6 +73,10 @@ fn main() -> anyhow::Result<()> {
for (wasi_filesystem_test_name, wasi_filesystem_kind) in &[
("host_fs", "WasiFileSystemKind::Host"),
("mem_fs", "WasiFileSystemKind::InMemory"),
("tmp_fs", "WasiFileSystemKind::Tmp"),
("passthru_fs", "WasiFileSystemKind::PassthruMemory"),
("union_fs", "WasiFileSystemKind::UnionHostMemory"),
("root_fs", "WasiFileSystemKind::RootFileSystemBuilder"),
] {
with_test_module(wasitests, wasi_filesystem_test_name, |wasitests| {
test_directory(

View File

@ -18,6 +18,5 @@ wasmer-vfs = { path = "../vfs", version = "=3.0.0-rc.2", default-features = fals
wasmer-wasi-types = { path = "../wasi-types/", version = "3.0.0-rc.2" }
[features]
default = ["mem_fs"]
mem_fs = ["wasmer-vfs/mem-fs"]
default = []
host_fs = ["wasmer-vfs/host-fs"]

View File

@ -12,18 +12,22 @@ thiserror = "1"
tracing = { version = "0.1" }
typetag = { version = "0.1", optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
slab = { version = "0.4", optional = true }
# FIXME: use proper dependency
webc = { version = "*", optional = true, path="../../../pirita/crates/webc" }
#webc = { version = "*", optional = true, git = "https://github.com/wasmerio/pirita", branch = "deploy" }
slab = { version = "0.4" }
derivative = "2.2.0"
wasmer-wasi-types = { path = "../wasi-types", version = "3.0.0-rc.2" }
anyhow = { version = "1.0.66", optional = true }
async-trait = { version = "^0.1" }
lazy_static = "1.4"
fs_extra = { version = "1.2.0", optional = true }
filetime = { version = "0.2.18", optional = true }
[features]
default = ["host-fs", "mem-fs", "webc-fs", "static-fs"]
host-fs = ["libc"]
mem-fs = ["slab"]
host-fs = ["libc", "fs_extra", "filetime"]
mem-fs = []
webc-fs = ["webc", "anyhow"]
static-fs = ["webc", "anyhow", "mem-fs"]
enable-serde = [

View File

@ -1,10 +1,13 @@
//! Used for sharing references to the same file across multiple file systems,
//! effectively this is a symbolic link without all the complex path redirection
use crate::FileDescriptor;
use crate::{ClonableVirtualFile, VirtualFile};
use derivative::Derivative;
use std::{
io::{self, *},
sync::{Arc, Mutex},
};
use wasmer_vbus::FileDescriptor;
use wasmer_vfs::{ClonableVirtualFile, VirtualFile};
#[derive(Derivative, Clone)]
#[derivative(Debug)]
@ -27,6 +30,7 @@ impl Seek for ArcFile {
inner.seek(pos)
}
}
impl Write for ArcFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut inner = self.inner.lock().unwrap();
@ -62,23 +66,23 @@ impl VirtualFile for ArcFile {
let inner = self.inner.lock().unwrap();
inner.size()
}
fn set_len(&mut self, new_size: u64) -> wasmer_vfs::Result<()> {
fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
let mut inner = self.inner.lock().unwrap();
inner.set_len(new_size)
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
fn unlink(&mut self) -> crate::Result<()> {
let mut inner = self.inner.lock().unwrap();
inner.unlink()
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
fn bytes_available(&self) -> crate::Result<usize> {
let inner = self.inner.lock().unwrap();
inner.bytes_available()
}
fn bytes_available_read(&self) -> wasmer_vfs::Result<usize> {
fn bytes_available_read(&self) -> crate::Result<usize> {
let inner = self.inner.lock().unwrap();
inner.bytes_available_read()
}
fn bytes_available_write(&self) -> wasmer_vfs::Result<usize> {
fn bytes_available_write(&self) -> crate::Result<usize> {
let inner = self.inner.lock().unwrap();
inner.bytes_available_write()
}

View File

@ -1,9 +1,12 @@
//! Wraps a clonable Arc of a file system - in practice this is useful so you
//! can pass clonable file systems with a Box<dyn FileSystem> to other interfaces
use std::path::Path;
use std::sync::Arc;
#[allow(unused_imports, dead_code)]
use tracing::{debug, error, info, trace, warn};
use wasmer_vfs::*;
use crate::*;
#[derive(Debug)]
pub struct ArcFileSystem {

View File

@ -1,10 +1,11 @@
use crate::{FileSystem, VirtualFile};
use std::path::{Path, PathBuf};
use tracing::*;
use wasmer_vfs::{FileSystem, VirtualFile};
use wasmer_wasi_types::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO};
use super::{NullFile, SpecialFile};
use super::{TmpFileSystem, ZeroFile};
use super::{ZeroFile};
use crate::tmp_fs::TmpFileSystem;
pub struct RootFileSystemBuilder {
default_root_dirs: bool,
@ -16,8 +17,8 @@ pub struct RootFileSystemBuilder {
tty: Option<Box<dyn VirtualFile + Send + Sync>>,
}
impl RootFileSystemBuilder {
pub fn new() -> Self {
impl Default for RootFileSystemBuilder {
fn default() -> Self {
Self {
default_root_dirs: true,
default_dev_files: true,
@ -28,6 +29,12 @@ impl RootFileSystemBuilder {
tty: None,
}
}
}
impl RootFileSystemBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_stdin(mut self, file: Box<dyn VirtualFile + Send + Sync>) -> Self {
self.stdin.replace(file);
@ -57,8 +64,8 @@ impl RootFileSystemBuilder {
pub fn build(self) -> TmpFileSystem {
let tmp = TmpFileSystem::new();
if self.default_root_dirs {
for root_dir in vec!["/.app", "/.private", "/bin", "/dev", "/etc", "/tmp"] {
if let Err(err) = tmp.create_dir(&Path::new(root_dir)) {
for root_dir in &["/.app", "/.private", "/bin", "/dev", "/etc", "/tmp"] {
if let Err(err) = tmp.create_dir(Path::new(root_dir)) {
debug!("failed to create dir [{}] - {}", root_dir, err);
}
}
@ -98,3 +105,71 @@ impl RootFileSystemBuilder {
tmp
}
}
#[test]
fn test_root_file_system() {
let root_fs = RootFileSystemBuilder::new().build();
let mut dev_null = root_fs
.new_open_options()
.read(true)
.write(true)
.open("/dev/null")
.unwrap();
assert_eq!(dev_null.write(b"hello").unwrap(), 5);
let mut buf = Vec::new();
dev_null.read_to_end(&mut buf);
assert!(buf.is_empty());
assert!(dev_null.get_special_fd().is_none());
let mut dev_zero = root_fs
.new_open_options()
.read(true)
.write(true)
.open("/dev/zero")
.unwrap();
assert_eq!(dev_zero.write(b"hello").unwrap(), 5);
let mut buf = vec![1; 10];
dev_zero.read(&mut buf[..]).unwrap();
assert_eq!(buf, vec![0; 10]);
assert!(dev_zero.get_special_fd().is_none());
let mut dev_tty = root_fs
.new_open_options()
.read(true)
.write(true)
.open("/dev/tty")
.unwrap();
assert_eq!(dev_tty.write(b"hello").unwrap(), 5);
let mut buf = Vec::new();
dev_tty.read_to_end(&mut buf);
assert!(buf.is_empty());
assert!(dev_tty.get_special_fd().is_none());
root_fs
.new_open_options()
.read(true)
.open("/bin/wasmer")
.unwrap();
let dev_stdin = root_fs
.new_open_options()
.read(true)
.write(true)
.open("/dev/stdin")
.unwrap();
assert_eq!(dev_stdin.get_special_fd().unwrap(), 0);
let dev_stdout = root_fs
.new_open_options()
.read(true)
.write(true)
.open("/dev/stdout")
.unwrap();
assert_eq!(dev_stdout.get_special_fd().unwrap(), 1);
let dev_stderr = root_fs
.new_open_options()
.read(true)
.write(true)
.open("/dev/stderr")
.unwrap();
assert_eq!(dev_stderr.get_special_fd().unwrap(), 2);
}

View File

@ -0,0 +1,224 @@
//! Do not end up using this but left it anyway - allows one to implement
//! the interface to a file using optionally supplied lambdas - good for
//! capturing variables rather than having to implement ones own
//! VirtualFile implementation.
use crate::FileDescriptor;
use crate::FsError;
use crate::VirtualFile;
use derivative::Derivative;
use std::{
io::{self, *},
sync::{Arc, RwLock},
};
type DelegateSeekFn = Box<dyn Fn(SeekFrom) -> io::Result<u64> + Send + Sync>;
type DelegateWriteFn = Box<dyn Fn(&[u8]) -> io::Result<usize> + Send + Sync>;
type DelegateFlushFn = Box<dyn Fn() -> io::Result<()> + Send + Sync>;
type DelegateReadFn = Box<dyn Fn(&mut [u8]) -> io::Result<usize> + Send + Sync>;
type DelegateSizeFn = Box<dyn Fn() -> u64 + Send + Sync>;
type DelegateSetLenFn = Box<dyn Fn(u64) -> crate::Result<()> + Send + Sync>;
type DelegateUnlinkFn = Box<dyn Fn() -> crate::Result<()> + Send + Sync>;
type DelegateBytesAvailableFn = Box<dyn Fn() -> crate::Result<usize> + Send + Sync>;
#[derive(Default)]
pub struct DelegateFileInner {
seek: Option<DelegateSeekFn>,
write: Option<DelegateWriteFn>,
flush: Option<DelegateFlushFn>,
read: Option<DelegateReadFn>,
size: Option<DelegateSizeFn>,
set_len: Option<DelegateSetLenFn>,
unlink: Option<DelegateUnlinkFn>,
bytes_available: Option<DelegateBytesAvailableFn>,
}
/// Wrapper that forwards calls to `read`, `write`, etc.
/// to custom, user-defined functions - similar to `VirtualFile`
/// itself, except you don't have to create a new struct in order
/// to implement functions
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct DelegateFile {
#[derivative(Debug = "ignore")]
inner: Arc<RwLock<DelegateFileInner>>,
}
impl DelegateFile {
pub fn new() -> Self {
Self::default()
}
pub fn with_seek(
&self,
func: impl Fn(SeekFrom) -> io::Result<u64> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.seek.replace(Box::new(func));
self
}
pub fn with_write(
&self,
func: impl Fn(&[u8]) -> io::Result<usize> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.write.replace(Box::new(func));
self
}
pub fn with_flush(&self, func: impl Fn() -> io::Result<()> + Send + Sync + 'static) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.flush.replace(Box::new(func));
self
}
pub fn with_read(
&self,
func: impl Fn(&mut [u8]) -> io::Result<usize> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.read.replace(Box::new(func));
self
}
pub fn with_size(&self, func: impl Fn() -> u64 + Send + Sync + 'static) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.size.replace(Box::new(func));
self
}
pub fn with_set_len(
&self,
func: impl Fn(u64) -> crate::Result<()> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.set_len.replace(Box::new(func));
self
}
pub fn with_unlink(
&self,
func: impl Fn() -> crate::Result<()> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.unlink.replace(Box::new(func));
self
}
pub fn with_bytes_available(
&self,
func: impl Fn() -> crate::Result<usize> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.bytes_available.replace(Box::new(func));
self
}
}
impl Default for DelegateFile {
fn default() -> Self {
Self {
inner: Arc::new(RwLock::new(DelegateFileInner::default())),
}
}
}
impl Seek for DelegateFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let inner = self.inner.read().unwrap();
let seek = inner.seek.as_ref().ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
"seek function not loaded on DelegateFile",
)
})?;
(seek)(pos)
}
}
impl Write for DelegateFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let inner = self.inner.read().unwrap();
let write = inner.write.as_ref().ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
"write function not loaded on DelegateFile",
)
})?;
(write)(buf)
}
fn flush(&mut self) -> io::Result<()> {
let inner = self.inner.read().unwrap();
let flush = inner.flush.as_ref().ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
"flush function not loaded on DelegateFile",
)
})?;
(flush)()
}
}
impl Read for DelegateFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let inner = self.inner.read().unwrap();
let read = inner.read.as_ref().ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
"read function not loaded on DelegateFile",
)
})?;
(read)(buf)
}
}
impl VirtualFile for DelegateFile {
fn last_accessed(&self) -> u64 {
0
}
fn last_modified(&self) -> u64 {
0
}
fn created_time(&self) -> u64 {
0
}
fn size(&self) -> u64 {
let inner = self.inner.read().unwrap();
inner.size.as_ref().map(|size| size()).unwrap_or(0)
}
fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
let inner = self.inner.read().unwrap();
let set_len = inner.set_len.as_ref().ok_or(FsError::UnknownError)?;
(set_len)(new_size)
}
fn unlink(&mut self) -> crate::Result<()> {
let inner = self.inner.read().unwrap();
let unlink = inner.unlink.as_ref().ok_or(FsError::UnknownError)?;
(unlink)()
}
fn bytes_available(&self) -> crate::Result<usize> {
let inner = self.inner.read().unwrap();
let bytes_available = inner
.bytes_available
.as_ref()
.ok_or(FsError::UnknownError)?;
(bytes_available)()
}
fn get_fd(&self) -> Option<FileDescriptor> {
None
}
}
#[test]
fn test_delegate_file() {
let mut custom_write_buf = vec![0; 17];
let mut file = DelegateFile::new();
file.with_write(|_| Ok(384));
file.with_read(|_| Ok(986));
file.with_seek(|_| Ok(996));
assert_eq!(file.read(custom_write_buf.as_mut_slice()).unwrap(), 986);
assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 996);
assert_eq!(file.write(b"hello").unwrap(), 384);
}

View File

@ -1,8 +1,11 @@
//! When no file system is used by a WebC then this is used as a placeholder -
//! as the name suggests it always returns file not found.
use std::path::Path;
#[allow(unused_imports, dead_code)]
use tracing::{debug, error, info, trace, warn};
use wasmer_vfs::*;
use crate::*;
#[derive(Debug, Default)]
pub struct EmptyFileSystem {}

View File

@ -4,6 +4,7 @@ use crate::{
};
#[cfg(feature = "enable-serde")]
use serde::{de, Deserialize, Serialize};
use std::cmp::Ordering;
use std::convert::TryInto;
use std::fs;
use std::io::{self, Read, Seek, Write};
@ -71,10 +72,19 @@ impl TryInto<RawHandle> for FileDescriptor {
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct FileSystem;
impl FileSystem {
pub fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
if !path.exists() {
return Err(FsError::InvalidInput);
}
fs::canonicalize(path).map_err(Into::into)
}
}
impl crate::FileSystem for FileSystem {
fn read_dir(&self, path: &Path) -> Result<ReadDir> {
let read_dir = fs::read_dir(path)?;
let data = read_dir
let mut data = read_dir
.map(|entry| {
let entry = entry?;
let metadata = entry.metadata()?;
@ -85,22 +95,82 @@ impl crate::FileSystem for FileSystem {
})
.collect::<std::result::Result<Vec<DirEntry>, io::Error>>()
.map_err::<FsError, _>(Into::into)?;
data.sort_by(|a, b| match (a.metadata.as_ref(), b.metadata.as_ref()) {
(Ok(a), Ok(b)) => a.modified.cmp(&b.modified),
_ => Ordering::Equal,
});
Ok(ReadDir::new(data))
}
fn create_dir(&self, path: &Path) -> Result<()> {
if path.parent().is_none() {
return Err(FsError::BaseNotDirectory);
}
fs::create_dir(path).map_err(Into::into)
}
fn remove_dir(&self, path: &Path) -> Result<()> {
if path.parent().is_none() {
return Err(FsError::BaseNotDirectory);
}
// https://github.com/rust-lang/rust/issues/86442
// DirectoryNotEmpty is not implemented consistently
if path.is_dir() && self.read_dir(path).map(|s| !s.is_empty()).unwrap_or(false) {
return Err(FsError::DirectoryNotEmpty);
}
fs::remove_dir(path).map_err(Into::into)
}
fn rename(&self, from: &Path, to: &Path) -> Result<()> {
fs::rename(from, to).map_err(Into::into)
use filetime::{set_file_mtime, FileTime};
if from.parent().is_none() {
return Err(FsError::BaseNotDirectory);
}
if to.parent().is_none() {
return Err(FsError::BaseNotDirectory);
}
if !from.exists() {
return Err(FsError::EntryNotFound);
}
let from_parent = from.parent().unwrap();
let to_parent = to.parent().unwrap();
if !from_parent.exists() {
return Err(FsError::EntryNotFound);
}
if !to_parent.exists() {
return Err(FsError::EntryNotFound);
}
let result = if from_parent != to_parent {
let _ = std::fs::create_dir_all(to_parent);
if from.is_dir() {
fs_extra::move_items(
&[from],
to,
&fs_extra::dir::CopyOptions {
copy_inside: true,
..Default::default()
},
)
.map(|_| ())
.map_err(|_| FsError::UnknownError)?;
let _ = fs_extra::remove_items(&[from]);
Ok(())
} else {
let e: Result<()> = fs::copy(from, to).map(|_| ()).map_err(Into::into);
let _ = e?;
fs::remove_file(from).map(|_| ()).map_err(Into::into)
}
} else {
fs::rename(from, to).map_err(Into::into)
};
let _ = set_file_mtime(to, FileTime::now()).map(|_| ());
result
}
fn remove_file(&self, path: &Path) -> Result<()> {
if path.parent().is_none() {
return Err(FsError::BaseNotDirectory);
}
fs::remove_file(path).map_err(Into::into)
}
@ -792,3 +862,608 @@ impl VirtualFile for Stdin {
Some(0)
}
}
#[cfg(test)]
mod tests {
use crate::host_fs::FileSystem;
use crate::FileSystem as FileSystemTrait;
use crate::FsError;
use std::path::Path;
#[test]
fn test_new_filesystem() {
let fs = FileSystem::default();
assert!(fs.read_dir(Path::new("/")).is_ok(), "hostfs can read root");
std::fs::write("./foo2.txt", b"").unwrap();
assert!(
fs.new_open_options()
.read(true)
.open(Path::new("./foo2.txt"))
.is_ok(),
"created foo2.txt"
);
std::fs::remove_file("./foo2.txt").unwrap();
}
#[test]
fn test_create_dir() {
let fs = FileSystem::default();
assert_eq!(
fs.create_dir(Path::new("/")),
Err(FsError::BaseNotDirectory),
"creating a directory that has no parent",
);
let _ = fs_extra::remove_items(&["./test_create_dir"]);
assert_eq!(
fs.create_dir(Path::new("./test_create_dir")),
Ok(()),
"creating a directory",
);
assert_eq!(
fs.create_dir(Path::new("./test_create_dir/foo")),
Ok(()),
"creating a directory",
);
assert!(
Path::new("./test_create_dir/foo").exists(),
"foo dir exists in host_fs"
);
let cur_dir = read_dir_names(&fs, "./test_create_dir");
if !cur_dir.contains(&"foo".to_string()) {
panic!("cur_dir does not contain foo: {cur_dir:#?}");
}
assert!(
cur_dir.contains(&"foo".to_string()),
"the root is updated and well-defined"
);
assert_eq!(
fs.create_dir(Path::new("./test_create_dir/foo/bar")),
Ok(()),
"creating a sub-directory",
);
assert!(
Path::new("./test_create_dir/foo/bar").exists(),
"foo dir exists in host_fs"
);
let foo_dir = read_dir_names(&fs, "./test_create_dir/foo");
assert!(
foo_dir.contains(&"bar".to_string()),
"the foo directory is updated and well-defined"
);
let bar_dir = read_dir_names(&fs, "./test_create_dir/foo/bar");
assert!(
bar_dir.is_empty(),
"the foo directory is updated and well-defined"
);
let _ = fs_extra::remove_items(&["./test_create_dir"]);
}
#[test]
fn test_remove_dir() {
let fs = FileSystem::default();
let _ = fs_extra::remove_items(&["./test_remove_dir"]);
assert_eq!(
fs.remove_dir(Path::new("/")),
Err(FsError::BaseNotDirectory),
"removing a directory that has no parent",
);
assert_eq!(
fs.remove_dir(Path::new("/foo")),
Err(FsError::EntryNotFound),
"cannot remove a directory that doesn't exist",
);
assert_eq!(
fs.create_dir(Path::new("./test_remove_dir")),
Ok(()),
"creating a directory",
);
assert_eq!(
fs.create_dir(Path::new("./test_remove_dir/foo")),
Ok(()),
"creating a directory",
);
assert_eq!(
fs.create_dir(Path::new("./test_remove_dir/foo/bar")),
Ok(()),
"creating a sub-directory",
);
assert!(
Path::new("./test_remove_dir/foo/bar").exists(),
"./foo/bar exists"
);
assert_eq!(
fs.remove_dir(Path::new("./test_remove_dir/foo")),
Err(FsError::DirectoryNotEmpty),
"removing a directory that has children",
);
assert_eq!(
fs.remove_dir(Path::new("./test_remove_dir/foo/bar")),
Ok(()),
"removing a sub-directory",
);
assert_eq!(
fs.remove_dir(Path::new("./test_remove_dir/foo")),
Ok(()),
"removing a directory",
);
let cur_dir = read_dir_names(&fs, "./test_remove_dir");
assert!(
!cur_dir.contains(&"foo".to_string()),
"the foo directory still exists"
);
let _ = fs_extra::remove_items(&["./test_remove_dir"]);
}
fn read_dir_names(fs: &dyn crate::FileSystem, path: &str) -> Vec<String> {
fs.read_dir(Path::new(path))
.unwrap()
.filter_map(|entry| Some(entry.ok()?.file_name().to_str()?.to_string()))
.collect::<Vec<_>>()
}
#[test]
fn test_rename() {
let fs = FileSystem::default();
let _ = fs_extra::remove_items(&["./test_rename"]);
assert_eq!(
fs.rename(Path::new("/"), Path::new("/bar")),
Err(FsError::BaseNotDirectory),
"renaming a directory that has no parent",
);
assert_eq!(
fs.rename(Path::new("/foo"), Path::new("/")),
Err(FsError::BaseNotDirectory),
"renaming to a directory that has no parent",
);
assert_eq!(fs.create_dir(Path::new("./test_rename")), Ok(()));
assert_eq!(fs.create_dir(Path::new("./test_rename/foo")), Ok(()));
assert_eq!(fs.create_dir(Path::new("./test_rename/foo/qux")), Ok(()));
assert_eq!(
fs.rename(
Path::new("./test_rename/foo"),
Path::new("./test_rename/bar/baz")
),
Err(FsError::EntryNotFound),
"renaming to a directory that has parent that doesn't exist",
);
assert_eq!(fs.create_dir(Path::new("./test_rename/bar")), Ok(()));
assert_eq!(
fs.rename(
Path::new("./test_rename/foo"),
Path::new("./test_rename/bar")
),
Ok(()),
"renaming to a directory that has parent that exists",
);
assert!(
matches!(
fs.new_open_options()
.write(true)
.create_new(true)
.open(Path::new("./test_rename/bar/hello1.txt")),
Ok(_),
),
"creating a new file (`hello1.txt`)",
);
assert!(
matches!(
fs.new_open_options()
.write(true)
.create_new(true)
.open(Path::new("./test_rename/bar/hello2.txt")),
Ok(_),
),
"creating a new file (`hello2.txt`)",
);
let cur_dir = read_dir_names(&fs, "./test_rename");
assert!(
!cur_dir.contains(&"foo".to_string()),
"the foo directory still exists"
);
assert!(
cur_dir.contains(&"bar".to_string()),
"the bar directory still exists"
);
let bar_dir = read_dir_names(&fs, "./test_rename/bar");
if !bar_dir.contains(&"qux".to_string()) {
println!("qux does not exist: {:?}", bar_dir)
}
let qux_dir = read_dir_names(&fs, "./test_rename/bar/qux");
assert!(qux_dir.is_empty(), "the qux directory is empty");
assert!(
Path::new("./test_rename/bar/hello1.txt").exists(),
"the /bar/hello1.txt file exists"
);
assert!(
Path::new("./test_rename/bar/hello2.txt").exists(),
"the /bar/hello2.txt file exists"
);
assert_eq!(
fs.create_dir(Path::new("./test_rename/foo")),
Ok(()),
"create ./foo again",
);
assert_eq!(
fs.rename(
Path::new("./test_rename/bar/hello2.txt"),
Path::new("./test_rename/foo/world2.txt")
),
Ok(()),
"renaming (and moving) a file",
);
assert_eq!(
fs.rename(
Path::new("./test_rename/foo"),
Path::new("./test_rename/bar/baz")
),
Ok(()),
"renaming a directory",
);
assert_eq!(
fs.rename(
Path::new("./test_rename/bar/hello1.txt"),
Path::new("./test_rename/bar/world1.txt")
),
Ok(()),
"renaming a file (in the same directory)",
);
assert!(Path::new("./test_rename/bar").exists(), "./bar exists");
assert!(
Path::new("./test_rename/bar/baz").exists(),
"./bar/baz exists"
);
assert!(
!Path::new("./test_rename/foo").exists(),
"foo does not exist anymore"
);
assert!(
Path::new("./test_rename/bar/baz/world2.txt").exists(),
"/bar/baz/world2.txt exists"
);
assert!(
Path::new("./test_rename/bar/world1.txt").exists(),
"/bar/world1.txt (ex hello1.txt) exists"
);
assert!(
!Path::new("./test_rename/bar/hello1.txt").exists(),
"hello1.txt was moved"
);
assert!(
!Path::new("./test_rename/bar/hello2.txt").exists(),
"hello2.txt was moved"
);
assert!(
Path::new("./test_rename/bar/baz/world2.txt").exists(),
"world2.txt was moved to the correct place"
);
let _ = fs_extra::remove_items(&["./test_rename"]);
}
#[test]
fn test_metadata() {
use std::thread::sleep;
use std::time::Duration;
let root_dir = env!("CARGO_MANIFEST_DIR");
let _ = std::env::set_current_dir(root_dir);
let fs = FileSystem::default();
let _ = fs_extra::remove_items(&["./test_metadata"]);
assert_eq!(fs.create_dir(Path::new("./test_metadata")), Ok(()));
let root_metadata = fs.metadata(Path::new("./test_metadata")).unwrap();
assert!(root_metadata.ft.dir);
assert!(root_metadata.accessed == root_metadata.created);
assert!(root_metadata.modified == root_metadata.created);
assert!(root_metadata.modified > 0);
assert_eq!(fs.create_dir(Path::new("./test_metadata/foo")), Ok(()));
let foo_metadata = fs.metadata(Path::new("./test_metadata/foo"));
assert!(foo_metadata.is_ok());
let foo_metadata = foo_metadata.unwrap();
assert!(foo_metadata.ft.dir);
assert!(foo_metadata.accessed == foo_metadata.created);
assert!(foo_metadata.modified == foo_metadata.created);
assert!(foo_metadata.modified > 0);
sleep(Duration::from_secs(3));
assert_eq!(
fs.rename(
Path::new("./test_metadata/foo"),
Path::new("./test_metadata/bar")
),
Ok(())
);
let bar_metadata = fs.metadata(Path::new("./test_metadata/bar")).unwrap();
assert!(bar_metadata.ft.dir);
assert!(bar_metadata.accessed == foo_metadata.accessed);
assert!(bar_metadata.created == foo_metadata.created);
assert!(bar_metadata.modified > foo_metadata.modified);
let root_metadata = fs.metadata(Path::new("./test_metadata/bar")).unwrap();
assert!(
root_metadata.modified > foo_metadata.modified,
"the parent modified time was updated"
);
let _ = fs_extra::remove_items(&["./test_metadata"]);
}
#[test]
fn test_remove_file() {
let fs = FileSystem::default();
let _ = fs_extra::remove_items(&["./test_remove_file"]);
assert!(fs.create_dir(Path::new("./test_remove_file")).is_ok());
assert!(
matches!(
fs.new_open_options()
.write(true)
.create_new(true)
.open(Path::new("./test_remove_file/foo.txt")),
Ok(_)
),
"creating a new file",
);
assert!(read_dir_names(&fs, "./test_remove_file").contains(&"foo.txt".to_string()));
assert!(Path::new("./test_remove_file/foo.txt").is_file());
assert_eq!(
fs.remove_file(Path::new("./test_remove_file/foo.txt")),
Ok(()),
"removing a file that exists",
);
assert!(!Path::new("./test_remove_file/foo.txt").exists());
assert_eq!(
fs.remove_file(Path::new("./test_remove_file/foo.txt")),
Err(FsError::EntryNotFound),
"removing a file that doesn't exists",
);
let _ = fs_extra::remove_items(&["./test_remove_file"]);
}
#[test]
fn test_readdir() {
let fs = FileSystem::default();
let _ = fs_extra::remove_items(&["./test_readdir"]);
assert_eq!(
fs.create_dir(Path::new("./test_readdir/")),
Ok(()),
"creating `test_readdir`"
);
assert_eq!(
fs.create_dir(Path::new("./test_readdir/foo")),
Ok(()),
"creating `foo`"
);
assert_eq!(
fs.create_dir(Path::new("./test_readdir/foo/sub")),
Ok(()),
"creating `sub`"
);
assert_eq!(
fs.create_dir(Path::new("./test_readdir/bar")),
Ok(()),
"creating `bar`"
);
assert_eq!(
fs.create_dir(Path::new("./test_readdir/baz")),
Ok(()),
"creating `bar`"
);
assert!(
matches!(
fs.new_open_options()
.write(true)
.create_new(true)
.open(Path::new("./test_readdir/a.txt")),
Ok(_)
),
"creating `a.txt`",
);
assert!(
matches!(
fs.new_open_options()
.write(true)
.create_new(true)
.open(Path::new("./test_readdir/b.txt")),
Ok(_)
),
"creating `b.txt`",
);
let readdir = fs.read_dir(Path::new("./test_readdir"));
assert!(readdir.is_ok(), "reading the directory `./test_readdir/`");
let mut readdir = readdir.unwrap();
let next = readdir.next().unwrap().unwrap();
assert!(next.path.ends_with("foo"), "checking entry #1");
assert!(next.path.is_dir(), "checking entry #1");
let next = readdir.next().unwrap().unwrap();
assert!(next.path.ends_with("bar"), "checking entry #2");
assert!(next.path.is_dir(), "checking entry #2");
let next = readdir.next().unwrap().unwrap();
assert!(next.path.ends_with("baz"), "checking entry #3");
assert!(next.path.is_dir(), "checking entry #3");
let next = readdir.next().unwrap().unwrap();
assert!(next.path.ends_with("a.txt"), "checking entry #2");
assert!(next.path.is_file(), "checking entry #4");
let next = readdir.next().unwrap().unwrap();
assert!(next.path.ends_with("b.txt"), "checking entry #2");
assert!(next.path.is_file(), "checking entry #5");
if let Some(s) = readdir.next() {
panic!("next: {s:?}");
}
let _ = fs_extra::remove_items(&["./test_readdir"]);
}
#[test]
fn test_canonicalize() {
let fs = FileSystem::default();
let root_dir = env!("CARGO_MANIFEST_DIR");
let _ = fs_extra::remove_items(&["./test_canonicalize"]);
assert_eq!(
fs.create_dir(Path::new("./test_canonicalize")),
Ok(()),
"creating `test_canonicalize`"
);
assert_eq!(
fs.create_dir(Path::new("./test_canonicalize/foo")),
Ok(()),
"creating `foo`"
);
assert_eq!(
fs.create_dir(Path::new("./test_canonicalize/foo/bar")),
Ok(()),
"creating `bar`"
);
assert_eq!(
fs.create_dir(Path::new("./test_canonicalize/foo/bar/baz")),
Ok(()),
"creating `baz`",
);
assert_eq!(
fs.create_dir(Path::new("./test_canonicalize/foo/bar/baz/qux")),
Ok(()),
"creating `qux`",
);
assert!(
matches!(
fs.new_open_options()
.write(true)
.create_new(true)
.open(Path::new("./test_canonicalize/foo/bar/baz/qux/hello.txt")),
Ok(_)
),
"creating `hello.txt`",
);
assert_eq!(
fs.canonicalize(Path::new("./test_canonicalize")),
Ok(Path::new(&format!("{root_dir}/test_canonicalize")).to_path_buf()),
"canonicalizing `/`",
);
assert_eq!(
fs.canonicalize(Path::new("foo")),
Err(FsError::InvalidInput),
"canonicalizing `foo`",
);
assert_eq!(
fs.canonicalize(Path::new("./test_canonicalize/././././foo/")),
Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo")).to_path_buf()),
"canonicalizing `/././././foo/`",
);
assert_eq!(
fs.canonicalize(Path::new("./test_canonicalize/foo/bar//")),
Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar")).to_path_buf()),
"canonicalizing `/foo/bar//`",
);
assert_eq!(
fs.canonicalize(Path::new("./test_canonicalize/foo/bar/../bar")),
Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar")).to_path_buf()),
"canonicalizing `/foo/bar/../bar`",
);
assert_eq!(
fs.canonicalize(Path::new("./test_canonicalize/foo/bar/../..")),
Ok(Path::new(&format!("{root_dir}/test_canonicalize")).to_path_buf()),
"canonicalizing `/foo/bar/../..`",
);
assert_eq!(
fs.canonicalize(Path::new("/foo/bar/../../..")),
Err(FsError::InvalidInput),
"canonicalizing `/foo/bar/../../..`",
);
assert_eq!(
fs.canonicalize(Path::new("C:/foo/")),
Err(FsError::InvalidInput),
"canonicalizing `C:/foo/`",
);
assert_eq!(
fs.canonicalize(Path::new(
"./test_canonicalize/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt"
)),
Ok(Path::new(&format!("{root_dir}/test_canonicalize/foo/bar/baz/qux/hello.txt")).to_path_buf()),
"canonicalizing a crazily stupid path name",
);
let _ = fs_extra::remove_items(&["./test_canonicalize"]);
}
}

View File

@ -9,23 +9,42 @@ use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use thiserror::Error;
#[cfg(all(not(feature = "host-fs"), not(feature = "mem-fs")))]
compile_error!("At least the `host-fs` or the `mem-fs` feature must be enabled. Please, pick one.");
//#[cfg(all(feature = "mem-fs", feature = "enable-serde"))]
//compile_warn!("`mem-fs` does not support `enable-serde` for the moment.");
pub mod arc_file;
pub mod arc_fs;
pub mod builder;
pub mod delegate_file;
pub mod empty_fs;
#[cfg(feature = "host-fs")]
pub mod host_fs;
#[cfg(feature = "mem-fs")]
pub mod mem_fs;
pub mod null_file;
pub mod passthru_fs;
pub mod special_file;
pub mod tmp_fs;
pub mod union_fs;
pub mod zero_file;
// tty_file -> see wasmer_wasi::tty_file
#[cfg(feature = "static-fs")]
pub mod static_fs;
#[cfg(feature = "webc-fs")]
pub mod webc_fs;
pub use arc_file::*;
pub use arc_fs::*;
pub use builder::*;
pub use delegate_file::*;
pub use empty_fs::*;
pub use null_file::*;
pub use passthru_fs::*;
pub use special_file::*;
pub use tmp_fs::*;
pub use union_fs::*;
pub use zero_file::*;
pub type Result<T> = std::result::Result<T, FsError>;
pub trait ClonableVirtualFile: VirtualFile + Clone {}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct FileDescriptor(usize);
@ -339,17 +358,18 @@ pub trait VirtualFile: fmt::Debug + Write + Read + Seek + Upcastable {
true
}
/// Returns a special file descriptor when opening this file rather than
/// generating a new one
fn get_special_fd(&self) -> Option<u32> {
None
}
/// Used for polling. Default returns `None` because this method cannot be implemented for most types
/// Returns the underlying host fd
fn get_fd(&self) -> Option<FileDescriptor> {
None
}
/// Used for "special" files such as `stdin`, `stdout` and `stderr`.
/// Always returns the same file descriptor (0, 1 or 2). Returns `None`
/// on normal files
fn get_special_fd(&self) -> Option<u32> {
None
}
}
struct VirtualFileAsyncRead<'a, T: ?Sized> {
@ -419,7 +439,6 @@ pub trait Upcastable {
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
}
pub trait ClonableVirtualFile: VirtualFile + Clone {}
impl<T: Any + fmt::Debug + 'static> Upcastable for T {
#[inline]
@ -601,6 +620,9 @@ impl ReadDir {
pub fn new(data: Vec<DirEntry>) -> Self {
Self { data, index: 0 }
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Debug, Clone)]

View File

@ -71,7 +71,7 @@ impl FileHandle {
let inode = fs.storage.get(self.inode);
match inode {
Some(Node::ArcFile { fs, path, .. }) => {
Some(Node::ArcFile(ArcFileNode { fs, path, .. })) => {
self.arc_file.replace(
fs.new_open_options()
.read(self.readable)
@ -88,7 +88,7 @@ impl FileHandle {
.as_mut()
.unwrap()
.as_mut()
.map_err(|err| err.clone())?
.map_err(|err| *err)?
.as_mut())
}
}
@ -143,13 +143,15 @@ impl VirtualFile for FileHandle {
let inode = fs.storage.get(self.inode);
match inode {
Some(Node::File { file, .. }) => file.len().try_into().unwrap_or(0),
Some(Node::ReadOnlyFile { file, .. }) => file.len().try_into().unwrap_or(0),
Some(Node::CustomFile { file, .. }) => {
let file = file.lock().unwrap();
file.size().try_into().unwrap_or(0)
Some(Node::File(FileNode { file, .. })) => file.len().try_into().unwrap_or(0),
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => {
file.len().try_into().unwrap_or(0)
}
Some(Node::ArcFile { fs, path, .. }) => match self.arc_file.as_ref() {
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let file = file.lock().unwrap();
file.size()
}
Some(Node::ArcFile(ArcFileNode { fs, path, .. })) => match self.arc_file.as_ref() {
Some(file) => file.as_ref().map(|file| file.size()).unwrap_or(0),
None => fs
.new_open_options()
@ -169,22 +171,25 @@ impl VirtualFile for FileHandle {
let inode = fs.storage.get_mut(self.inode);
match inode {
Some(Node::File { file, metadata, .. }) => {
Some(Node::File(FileNode { file, metadata, .. })) => {
file.buffer
.resize(new_size.try_into().map_err(|_| FsError::UnknownError)?, 0);
metadata.len = new_size;
}
Some(Node::CustomFile { file, metadata, .. }) => {
Some(Node::CustomFile(CustomFileNode { file, metadata, .. })) => {
let mut file = file.lock().unwrap();
file.set_len(new_size)?;
metadata.len = new_size;
}
Some(Node::ReadOnlyFile { .. }) => return Err(FsError::PermissionDenied),
Some(Node::ArcFile { .. }) => {
Some(Node::ReadOnlyFile(ReadOnlyFileNode { .. })) => {
return Err(FsError::PermissionDenied)
}
Some(Node::ArcFile(ArcFileNode { .. })) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.set_len(new_size))??;
}
None => return Err(FsError::EntryNotFound),
_ => return Err(FsError::NotAFile),
}
@ -205,7 +210,7 @@ impl VirtualFile for FileHandle {
.storage
.iter()
.find_map(|(inode_of_parent, node)| match node {
Node::Directory { children, .. } => {
Node::Directory(DirectoryNode { children, .. }) => {
children.iter().enumerate().find_map(|(nth, inode)| {
if inode == &inode_of_file {
Some((nth, inode_of_parent))
@ -241,17 +246,21 @@ impl VirtualFile for FileHandle {
let inode = fs.storage.get(self.inode);
match inode {
Some(Node::File { file, .. }) => Ok(file.buffer.len() - (self.cursor as usize)),
Some(Node::ReadOnlyFile { file, .. }) => Ok(file.buffer.len() - (self.cursor as usize)),
Some(Node::CustomFile { file, .. }) => {
Some(Node::File(FileNode { file, .. })) => {
Ok(file.buffer.len() - (self.cursor as usize))
}
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => {
Ok(file.buffer.len() - (self.cursor as usize))
}
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let file = file.lock().unwrap();
file.bytes_available()
}
Some(Node::ArcFile { fs, path, .. }) => match self.arc_file.as_ref() {
Some(Node::ArcFile(ArcFileNode { fs, path, .. })) => match self.arc_file.as_ref() {
Some(file) => file
.as_ref()
.map(|file| file.bytes_available())
.map_err(|err| err.clone())?,
.map_err(|err| *err)?,
None => fs
.new_open_options()
.read(self.readable)
@ -260,6 +269,7 @@ impl VirtualFile for FileHandle {
.open(path.as_path())
.map(|file| file.bytes_available())?,
},
None => Err(FsError::EntryNotFound),
_ => Err(FsError::NotAFile),
}
}
@ -278,11 +288,11 @@ impl VirtualFile for FileHandle {
let inode = fs.storage.get(self.inode);
match inode {
Some(Node::CustomFile { file, .. }) => {
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let file = file.lock().unwrap();
file.get_special_fd()
}
Some(Node::ArcFile { fs, path, .. }) => match self.arc_file.as_ref() {
Some(Node::ArcFile(ArcFileNode { fs, path, .. })) => match self.arc_file.as_ref() {
Some(file) => file
.as_ref()
.map(|file| file.get_special_fd())
@ -430,23 +440,23 @@ mod test_virtual_file {
assert!(
matches!(
fs_inner.storage.get(ROOT_INODE),
Some(Node::Directory {
Some(Node::Directory(DirectoryNode {
inode: ROOT_INODE,
name,
children,
..
}) if name == "/" && children == &[1]
})) if name == "/" && children == &[1]
),
"`/` contains `foo.txt`",
);
assert!(
matches!(
fs_inner.storage.get(1),
Some(Node::File {
Some(Node::File(FileNode {
inode: 1,
name,
..
}) if name == "foo.txt"
})) if name == "foo.txt"
),
"`foo.txt` exists and is a file",
);
@ -465,12 +475,12 @@ mod test_virtual_file {
assert!(
matches!(
fs_inner.storage.get(ROOT_INODE),
Some(Node::Directory {
Some(Node::Directory(DirectoryNode {
inode: ROOT_INODE,
name,
children,
..
}) if name == "/" && children.is_empty()
})) if name == "/" && children.is_empty()
),
"`/` is empty",
);
@ -530,16 +540,18 @@ impl Read for FileHandle {
let inode = fs.storage.get(self.inode);
match inode {
Some(Node::File { file, .. }) => file.read(buf, &mut self.cursor),
Some(Node::ReadOnlyFile { file, .. }) => file.read(buf, &mut self.cursor),
Some(Node::CustomFile { file, .. }) => {
Some(Node::File(FileNode { file, .. })) => file.read(buf, &mut self.cursor),
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => {
file.read(buf, &mut self.cursor)
}
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let mut file = file.lock().unwrap();
let _ = file.seek(io::SeekFrom::Start(self.cursor as u64));
let read = file.read(buf)?;
self.cursor += read as u64;
Ok(read)
}
Some(Node::ArcFile { .. }) => {
Some(Node::ArcFile(ArcFileNode { .. })) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.read(buf))
@ -577,16 +589,18 @@ impl Read for FileHandle {
let inode = fs.storage.get_mut(self.inode);
match inode {
Some(Node::File { file, .. }) => file.read_to_end(buf, &mut self.cursor),
Some(Node::ReadOnlyFile { file, .. }) => file.read_to_end(buf, &mut self.cursor),
Some(Node::CustomFile { file, .. }) => {
Some(Node::File(FileNode { file, .. })) => file.read_to_end(buf, &mut self.cursor),
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => {
file.read_to_end(buf, &mut self.cursor)
}
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let mut file = file.lock().unwrap();
let _ = file.seek(io::SeekFrom::Start(self.cursor as u64));
let read = file.read_to_end(buf)?;
self.cursor += read as u64;
Ok(read)
}
Some(Node::ArcFile { .. }) => {
Some(Node::ArcFile(ArcFileNode { .. })) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.read_to_end(buf))
@ -643,16 +657,18 @@ impl Read for FileHandle {
let inode = fs.storage.get(self.inode);
match inode {
Some(Node::File { file, .. }) => file.read_exact(buf, &mut self.cursor),
Some(Node::ReadOnlyFile { file, .. }) => file.read_exact(buf, &mut self.cursor),
Some(Node::CustomFile { file, .. }) => {
Some(Node::File(FileNode { file, .. })) => file.read_exact(buf, &mut self.cursor),
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => {
file.read_exact(buf, &mut self.cursor)
}
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let mut file = file.lock().unwrap();
let _ = file.seek(io::SeekFrom::Start(self.cursor as u64));
file.read_exact(buf)?;
self.cursor += buf.len() as u64;
Ok(())
}
Some(Node::ArcFile { .. }) => {
Some(Node::ArcFile(ArcFileNode { .. })) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.read_exact(buf))
@ -700,16 +716,18 @@ impl Seek for FileHandle {
let inode = fs.storage.get_mut(self.inode);
match inode {
Some(Node::File { file, .. }) => file.seek(position, &mut self.cursor),
Some(Node::ReadOnlyFile { file, .. }) => file.seek(position, &mut self.cursor),
Some(Node::CustomFile { file, .. }) => {
Some(Node::File(FileNode { file, .. })) => file.seek(position, &mut self.cursor),
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => {
file.seek(position, &mut self.cursor)
}
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let mut file = file.lock().unwrap();
let _ = file.seek(io::SeekFrom::Start(self.cursor as u64));
let pos = file.seek(position)?;
self.cursor = pos;
Ok(pos)
}
Some(Node::ArcFile { .. }) => {
Some(Node::ArcFile(_)) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.seek(position))
@ -749,25 +767,25 @@ impl Write for FileHandle {
let inode = fs.storage.get_mut(self.inode);
let bytes_written = match inode {
Some(Node::File { file, metadata, .. }) => {
Some(Node::File(FileNode { file, metadata, .. })) => {
let bytes_written = file.write(buf, &mut self.cursor)?;
metadata.len = file.len().try_into().unwrap();
bytes_written
}
Some(Node::ReadOnlyFile { file, metadata, .. }) => {
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, metadata, .. })) => {
let bytes_written = file.write(buf, &mut self.cursor)?;
metadata.len = file.len().try_into().unwrap();
bytes_written
}
Some(Node::CustomFile { file, metadata, .. }) => {
Some(Node::CustomFile(CustomFileNode { file, metadata, .. })) => {
let mut file = file.lock().unwrap();
let _ = file.seek(io::SeekFrom::Start(self.cursor as u64));
let bytes_written = file.write(buf)?;
self.cursor += bytes_written as u64;
metadata.len = file.size().try_into().unwrap();
metadata.len = file.size();
bytes_written
}
Some(Node::ArcFile { .. }) => {
Some(Node::ArcFile(_)) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.write(buf))
@ -796,13 +814,13 @@ impl Write for FileHandle {
let inode = fs.storage.get_mut(self.inode);
match inode {
Some(Node::File { file, .. }) => file.flush(),
Some(Node::ReadOnlyFile { file, .. }) => file.flush(),
Some(Node::CustomFile { file, .. }) => {
Some(Node::File(FileNode { file, .. })) => file.flush(),
Some(Node::ReadOnlyFile(ReadOnlyFileNode { file, .. })) => file.flush(),
Some(Node::CustomFile(CustomFileNode { file, .. })) => {
let mut file = file.lock().unwrap();
file.flush()
}
Some(Node::ArcFile { .. }) => {
Some(Node::ArcFile(ArcFileNode { .. })) => {
drop(fs);
self.lazy_load_arc_file_mut()
.map(|file| file.flush())
@ -1101,6 +1119,13 @@ impl File {
impl File {
pub fn read(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result<usize> {
let cur_pos = *cursor as usize;
let buffer_len = buf.len();
if *cursor > buffer_len as u64 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("file cursor {cursor} > buffer length {buffer_len}"),
));
}
let max_to_read = cmp::min(self.buffer.len() - cur_pos, buf.len());
let data_to_copy = &self.buffer[cur_pos..][..max_to_read];
@ -1115,25 +1140,17 @@ impl File {
pub fn read_to_end(&self, buf: &mut Vec<u8>, cursor: &mut u64) -> io::Result<usize> {
let cur_pos = *cursor as usize;
let data_to_copy = &self.buffer[cur_pos..];
let max_to_read = data_to_copy.len();
// `buf` is too small to contain the data. Let's resize it.
if max_to_read > buf.len() {
// Let's resize the capacity if needed.
if max_to_read > buf.capacity() {
buf.reserve_exact(max_to_read - buf.capacity());
}
// SAFETY: The space is reserved, and it's going to be
// filled with `copy_from_slice` below.
unsafe { buf.set_len(max_to_read) }
let buffer_len = buf.len();
if *cursor > buffer_len as u64 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("file cursor {cursor} > buffer length {buffer_len}"),
));
}
// SAFETY: `buf` and `data_to_copy` have the same size, see
// above.
buf.copy_from_slice(data_to_copy);
let data_to_copy = &self.buffer[cur_pos..];
let max_to_read = data_to_copy.len();
buf.extend_from_slice(data_to_copy);
*cursor += max_to_read as u64;
Ok(max_to_read)
@ -1220,14 +1237,29 @@ impl File {
// The cursor is somewhere in the buffer: not the happy path.
position => {
self.buffer.reserve_exact(buf.len());
let mut remainder = self.buffer.split_off(position as usize);
self.buffer.extend_from_slice(buf);
self.buffer.append(&mut remainder);
let position = position as usize;
if position >= self.buffer.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!(
"wrong cursor position {position} > buffer length {}",
self.buffer.len()
),
));
}
if position + buf.len() > self.buffer.len() {
let (a, b) = buf.split_at(self.buffer.len() - position);
self.buffer[position..].clone_from_slice(a);
self.buffer.extend_from_slice(b);
} else {
// pos + buffer fits in
self.buffer.truncate(position as usize + buf.len());
self.buffer[position..].clone_from_slice(buf);
}
}
}
*cursor += buf.len() as u64;
*cursor = cursor.saturating_add(buf.len() as u64);
Ok(buf.len())
}
@ -1237,7 +1269,8 @@ impl File {
}
}
/// Read only file that uses copy-on-write
/// Read only file that uses copy-on-write, used for mapping
/// files from the `pirita` filesystem
#[derive(Debug)]
pub(super) struct ReadOnlyFile {
buffer: Cow<'static, [u8]>,
@ -1269,6 +1302,13 @@ impl ReadOnlyFile {
}
pub fn read_to_end(&self, buf: &mut Vec<u8>, cursor: &mut u64) -> io::Result<usize> {
let buffer_len = self.buffer.len();
if *cursor > buffer_len as u64 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("file cursor {cursor} > buffer length {buffer_len}"),
));
}
let cur_pos = *cursor as usize;
let data_to_copy = &self.buffer[cur_pos..];
let max_to_read = data_to_copy.len();

View File

@ -39,7 +39,7 @@ impl FileOpener {
// Creating the file in the storage.
let inode_of_file = fs.storage.vacant_entry().key();
let real_inode_of_file = fs.storage.insert(Node::ReadOnlyFile {
let real_inode_of_file = fs.storage.insert(Node::ReadOnlyFile(ReadOnlyFileNode {
inode: inode_of_file,
name: name_of_file,
file,
@ -57,7 +57,7 @@ impl FileOpener {
len: 0,
}
},
});
}));
assert_eq!(
inode_of_file, real_inode_of_file,
@ -102,7 +102,7 @@ impl FileOpener {
// Creating the file in the storage.
let inode_of_file = fs_lock.storage.vacant_entry().key();
let real_inode_of_file = fs_lock.storage.insert(Node::ArcFile {
let real_inode_of_file = fs_lock.storage.insert(Node::ArcFile(ArcFileNode {
inode: inode_of_file,
name: name_of_file,
fs,
@ -120,7 +120,7 @@ impl FileOpener {
len: 0,
}
},
});
}));
assert_eq!(
inode_of_file, real_inode_of_file,
@ -165,25 +165,26 @@ impl FileOpener {
// Creating the file in the storage.
let inode_of_file = fs_lock.storage.vacant_entry().key();
let real_inode_of_file = fs_lock.storage.insert(Node::ArcDirectory {
inode: inode_of_file,
name: name_of_file,
fs,
path,
metadata: {
let time = time();
Metadata {
ft: FileType {
file: true,
..Default::default()
},
accessed: time,
created: time,
modified: time,
len: 0,
}
},
});
let real_inode_of_file =
fs_lock.storage.insert(Node::ArcDirectory(ArcDirectoryNode {
inode: inode_of_file,
name: name_of_file,
fs,
path,
metadata: {
let time = time();
Metadata {
ft: FileType {
file: true,
..Default::default()
},
accessed: time,
created: time,
modified: time,
len: 0,
}
},
}));
assert_eq!(
inode_of_file, real_inode_of_file,
@ -228,7 +229,7 @@ impl FileOpener {
// Creating the file in the storage.
let inode_of_file = fs_lock.storage.vacant_entry().key();
let real_inode_of_file = fs_lock.storage.insert(Node::CustomFile {
let real_inode_of_file = fs_lock.storage.insert(Node::CustomFile(CustomFileNode {
inode: inode_of_file,
name: name_of_file,
file: Mutex::new(file),
@ -245,7 +246,7 @@ impl FileOpener {
len: 0,
}
},
});
}));
assert_eq!(
inode_of_file, real_inode_of_file,
@ -367,7 +368,7 @@ impl crate::FileOpener for FileOpener {
let inode = fs.storage.get_mut(inode_of_file);
match inode {
Some(Node::File { metadata, file, .. }) => {
Some(Node::File(FileNode { metadata, file, .. })) => {
// Update the accessed time.
metadata.accessed = time();
@ -383,7 +384,7 @@ impl crate::FileOpener for FileOpener {
}
}
Some(Node::ReadOnlyFile { metadata, .. }) => {
Some(Node::ReadOnlyFile(ReadOnlyFileNode { metadata, .. })) => {
// Update the accessed time.
metadata.accessed = time();
@ -393,7 +394,7 @@ impl crate::FileOpener for FileOpener {
}
}
Some(Node::CustomFile { metadata, file, .. }) => {
Some(Node::CustomFile(CustomFileNode { metadata, file, .. })) => {
// Update the accessed time.
metadata.accessed = time();
@ -410,9 +411,9 @@ impl crate::FileOpener for FileOpener {
}
}
Some(Node::ArcFile {
Some(Node::ArcFile(ArcFileNode {
metadata, fs, path, ..
}) => {
})) => {
// Update the accessed time.
metadata.accessed = time();
@ -442,6 +443,7 @@ impl crate::FileOpener for FileOpener {
}
}
None => return Err(FsError::EntryNotFound),
_ => return Err(FsError::NotAFile),
}
@ -459,7 +461,7 @@ impl crate::FileOpener for FileOpener {
// Creating the file in the storage.
let inode_of_file = fs.storage.vacant_entry().key();
let real_inode_of_file = fs.storage.insert(Node::File {
let real_inode_of_file = fs.storage.insert(Node::File(FileNode {
inode: inode_of_file,
name: name_of_file,
file,
@ -477,7 +479,7 @@ impl crate::FileOpener for FileOpener {
len: 0,
}
},
});
}));
assert_eq!(
inode_of_file, real_inode_of_file,
@ -539,12 +541,12 @@ mod test_file_opener {
assert!(
matches!(
fs_inner.storage.get(ROOT_INODE),
Some(Node::Directory {
Some(Node::Directory(DirectoryNode {
inode: ROOT_INODE,
name,
children,
..
}) if name == "/" && children == &[1]
})) if name == "/" && children == &[1]
),
"`/` contains `foo.txt`",
);
@ -578,7 +580,7 @@ mod test_file_opener {
.write(true)
.create_new(true)
.open(path!("/foo/bar.txt")),
Err(FsError::NotAFile),
Err(FsError::EntryNotFound),
),
"creating a file in a directory that doesn't exist",
);

View File

@ -22,10 +22,9 @@ pub struct FileSystem {
impl FileSystem {
pub fn new_open_options_ext(&self) -> FileOpener {
let opener = FileOpener {
FileOpener {
filesystem: self.clone(),
};
opener
}
}
pub fn union(&self, other: &Arc<dyn crate::FileSystem + Send + Sync>) {
@ -48,27 +47,25 @@ impl FileSystem {
}
let _ = crate::FileSystem::create_dir(self, next.as_path());
if let Ok(dir) = other.read_dir(next.as_path()) {
for sub_dir in dir.into_iter() {
if let Ok(sub_dir) = sub_dir {
match sub_dir.file_type() {
Ok(t) if t.is_dir() => {
remaining.push_back(sub_dir.path());
}
Ok(t) if t.is_file() => {
if sub_dir.file_name().to_string_lossy().starts_with(".wh.") {
let rm = next.to_string_lossy();
let rm = &rm[".wh.".len()..];
let rm = PathBuf::from(rm);
let _ = crate::FileSystem::remove_dir(self, rm.as_path());
let _ = crate::FileSystem::remove_file(self, rm.as_path());
continue;
}
let _ = self
.new_open_options_ext()
.insert_arc_file(sub_dir.path(), other.clone());
}
_ => {}
for sub_dir in dir.flatten() {
match sub_dir.file_type() {
Ok(t) if t.is_dir() => {
remaining.push_back(sub_dir.path());
}
Ok(t) if t.is_file() => {
if sub_dir.file_name().to_string_lossy().starts_with(".wh.") {
let rm = next.to_string_lossy();
let rm = &rm[".wh.".len()..];
let rm = PathBuf::from(rm);
let _ = crate::FileSystem::remove_dir(self, rm.as_path());
let _ = crate::FileSystem::remove_file(self, rm.as_path());
continue;
}
let _ = self
.new_open_options_ext()
.insert_arc_file(sub_dir.path(), other.clone());
}
_ => {}
}
}
}
@ -119,7 +116,7 @@ impl FileSystem {
// Creating the directory in the storage.
let inode_of_directory = fs.storage.vacant_entry().key();
let real_inode_of_directory = fs.storage.insert(Node::ArcDirectory {
let real_inode_of_directory = fs.storage.insert(Node::ArcDirectory(ArcDirectoryNode {
inode: inode_of_directory,
name: name_of_directory,
fs: other.clone(),
@ -138,7 +135,7 @@ impl FileSystem {
len: 0,
}
},
});
}));
assert_eq!(
inode_of_directory, real_inode_of_directory,
@ -170,7 +167,7 @@ impl crate::FileSystem for FileSystem {
// Check it's a directory and fetch the immediate children as `DirEntry`.
let inode = guard.storage.get(inode_of_directory);
let children = match inode {
Some(Node::Directory { children, .. }) => children
Some(Node::Directory(DirectoryNode { children, .. })) => children
.iter()
.filter_map(|inode| guard.storage.get(*inode))
.map(|node| DirEntry {
@ -184,7 +181,7 @@ impl crate::FileSystem for FileSystem {
})
.collect(),
Some(Node::ArcDirectory { fs, path, .. }) => {
Some(Node::ArcDirectory(ArcDirectoryNode { fs, path, .. })) => {
return fs.read_dir(path.as_path());
}
@ -229,13 +226,17 @@ impl crate::FileSystem for FileSystem {
(inode_of_parent, name_of_directory)
};
if self.read_dir(path).is_ok() {
return Err(FsError::AlreadyExists);
}
{
// Write lock.
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
// Creating the directory in the storage.
let inode_of_directory = fs.storage.vacant_entry().key();
let real_inode_of_directory = fs.storage.insert(Node::Directory {
let real_inode_of_directory = fs.storage.insert(Node::Directory(DirectoryNode {
inode: inode_of_directory,
name: name_of_directory,
children: Vec::new(),
@ -253,7 +254,7 @@ impl crate::FileSystem for FileSystem {
len: 0,
}
},
});
}));
assert_eq!(
inode_of_directory, real_inode_of_directory,
@ -328,11 +329,9 @@ impl crate::FileSystem for FileSystem {
}
fn rename(&self, from: &Path, to: &Path) -> Result<()> {
let (
(position_of_from, inode, inode_of_from_parent),
(inode_of_to_parent, name_of_to),
inode_dest,
) = {
let name_of_to;
let ((position_of_from, inode, inode_of_from_parent), inode_of_to_parent) = {
// Read lock.
let fs = self.inner.read().map_err(|_| FsError::Lock)?;
@ -348,7 +347,7 @@ impl crate::FileSystem for FileSystem {
.file_name()
.ok_or(FsError::InvalidInput)?
.to_os_string();
let name_of_to = to.file_name().ok_or(FsError::InvalidInput)?.to_os_string();
name_of_to = to.file_name().ok_or(FsError::InvalidInput)?.to_os_string();
// Find the parent inodes.
let inode_of_from_parent = match fs.inode_of_parent(parent_of_from)? {
@ -364,20 +363,15 @@ impl crate::FileSystem for FileSystem {
}
};
// Find the inode of the dest file if it exists
let maybe_position_and_inode_of_file =
fs.as_parent_get_position_and_inode_of_file(inode_of_to_parent, &name_of_to)?;
// Get the child indexes to update in the parent nodes, in
// addition to the inode of the directory to update.
let (position_of_from, inode) = fs
.as_parent_get_position_and_inode(inode_of_from_parent, &name_of_from)?
.ok_or(FsError::NotAFile)?;
.ok_or(FsError::EntryNotFound)?;
(
(position_of_from, inode, inode_of_from_parent),
(inode_of_to_parent, name_of_to),
maybe_position_and_inode_of_file,
inode_of_to_parent,
)
};
@ -392,19 +386,26 @@ impl crate::FileSystem for FileSystem {
// Write lock.
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
if let Some((position, inode_of_file)) = inode_dest {
// Remove the file from the storage.
match inode_of_file {
InodeResolution::Found(inode_of_file) => {
fs.storage.remove(inode_of_file);
}
InodeResolution::Redirect(..) => {
return Err(FsError::InvalidInput);
}
}
// If we rename to a path that already exists, we've updated the name of the
// current inode, but we still have the old inode in there
if inode_of_from_parent == inode_of_to_parent {
let target_already_exists = fs
.as_parent_get_position_and_inode(inode_of_to_parent, &name_of_to)
.ok()
.and_then(|o| o);
if let Some((position_of_to, inode_of_to)) = target_already_exists {
let inode_of_to = match inode_of_to {
InodeResolution::Found(a) => a,
InodeResolution::Redirect(..) => {
return Err(FsError::InvalidInput);
}
};
// Remove the child from the parent directory.
fs.remove_child_from_node(inode_of_to_parent, position)?;
// Remove the file from the storage.
fs.storage.remove(inode_of_to);
fs.remove_child_from_node(inode_of_to_parent, position_of_to)?;
}
}
// Update the file name, and update the modified time.
@ -424,14 +425,14 @@ impl crate::FileSystem for FileSystem {
else {
let inode = fs.storage.get_mut(inode_of_from_parent);
match inode {
Some(Node::Directory {
Some(Node::Directory(DirectoryNode {
metadata: Metadata { modified, .. },
..
}) => *modified = time(),
Some(Node::ArcDirectory {
})) => *modified = time(),
Some(Node::ArcDirectory(ArcDirectoryNode {
metadata: Metadata { modified, .. },
..
}) => *modified = time(),
})) => *modified = time(),
_ => return Err(FsError::UnknownError),
}
}
@ -489,7 +490,7 @@ impl crate::FileSystem for FileSystem {
match maybe_position_and_inode_of_file {
Some((position, inode_of_file)) => (inode_of_parent, position, inode_of_file),
None => return Err(FsError::NotAFile),
None => return Err(FsError::EntryNotFound),
}
};
@ -561,23 +562,23 @@ impl FileSystemInner {
let mut components = path.components();
match components.next() {
Some(Component::RootDir) | None => {}
Some(Component::RootDir) => {}
_ => return Err(FsError::BaseNotDirectory),
}
while let Some(component) = components.next() {
node = match node {
Node::Directory { children, .. } => children
Node::Directory(DirectoryNode { children, .. }) => children
.iter()
.filter_map(|inode| self.storage.get(*inode))
.find(|node| node.name() == component.as_os_str())
.ok_or(FsError::EntryNotFound)?,
Node::ArcDirectory {
Node::ArcDirectory(ArcDirectoryNode {
fs, path: fs_path, ..
} => {
}) => {
let mut path = fs_path.clone();
path.push(PathBuf::from(component.as_os_str()));
while let Some(component) = components.next() {
for component in components.by_ref() {
path.push(PathBuf::from(component.as_os_str()));
}
return Ok(InodeResolution::Redirect(fs.clone(), path));
@ -596,14 +597,16 @@ impl FileSystemInner {
InodeResolution::Found(inode_of_parent) => {
// Ensure it is a directory.
match self.storage.get(inode_of_parent) {
Some(Node::Directory { .. }) => Ok(InodeResolution::Found(inode_of_parent)),
Some(Node::ArcDirectory { .. }) => Ok(InodeResolution::Found(inode_of_parent)),
Some(Node::Directory(DirectoryNode { .. })) => {
Ok(InodeResolution::Found(inode_of_parent))
}
Some(Node::ArcDirectory(ArcDirectoryNode { .. })) => {
Ok(InodeResolution::Found(inode_of_parent))
}
_ => Err(FsError::BaseNotDirectory),
}
}
InodeResolution::Redirect(fs, path) => {
return Ok(InodeResolution::Redirect(fs, path));
}
InodeResolution::Redirect(fs, path) => Ok(InodeResolution::Redirect(fs, path)),
}
}
@ -616,17 +619,17 @@ impl FileSystemInner {
directory_must_be_empty: DirectoryMustBeEmpty,
) -> Result<(usize, InodeResolution)> {
match self.storage.get(inode_of_parent) {
Some(Node::Directory { children, .. }) => children
Some(Node::Directory(DirectoryNode { children, .. })) => children
.iter()
.enumerate()
.filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
.find_map(|(nth, node)| match node {
Node::Directory {
Node::Directory(DirectoryNode {
inode,
name,
children,
..
} if name.as_os_str() == name_of_directory => {
}) if name.as_os_str() == name_of_directory => {
if directory_must_be_empty.no() || children.is_empty() {
Some(Ok((nth, InodeResolution::Found(*inode))))
} else {
@ -639,9 +642,9 @@ impl FileSystemInner {
.ok_or(FsError::InvalidInput)
.and_then(identity), // flatten
Some(Node::ArcDirectory {
Some(Node::ArcDirectory(ArcDirectoryNode {
fs, path: fs_path, ..
}) => {
})) => {
let mut path = fs_path.clone();
path.push(name_of_directory);
Ok((0, InodeResolution::Redirect(fs.clone(), path)))
@ -659,15 +662,15 @@ impl FileSystemInner {
name_of_file: &OsString,
) -> Result<Option<(usize, InodeResolution)>> {
match self.storage.get(inode_of_parent) {
Some(Node::Directory { children, .. }) => children
Some(Node::Directory(DirectoryNode { children, .. })) => children
.iter()
.enumerate()
.filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
.find_map(|(nth, node)| match node {
Node::File { inode, name, .. }
| Node::ReadOnlyFile { inode, name, .. }
| Node::CustomFile { inode, name, .. }
| Node::ArcFile { inode, name, .. }
Node::File(FileNode { inode, name, .. })
| Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. })
| Node::CustomFile(CustomFileNode { inode, name, .. })
| Node::ArcFile(ArcFileNode { inode, name, .. })
if name.as_os_str() == name_of_file =>
{
Some(Some((nth, InodeResolution::Found(*inode))))
@ -677,9 +680,9 @@ impl FileSystemInner {
.or(Some(None))
.ok_or(FsError::InvalidInput),
Some(Node::ArcDirectory {
Some(Node::ArcDirectory(ArcDirectoryNode {
fs, path: fs_path, ..
}) => {
})) => {
let mut path = fs_path.clone();
path.push(name_of_file);
Ok(Some((0, InodeResolution::Redirect(fs.clone(), path))))
@ -698,16 +701,16 @@ impl FileSystemInner {
name_of: &OsString,
) -> Result<Option<(usize, InodeResolution)>> {
match self.storage.get(inode_of_parent) {
Some(Node::Directory { children, .. }) => children
Some(Node::Directory(DirectoryNode { children, .. })) => children
.iter()
.enumerate()
.filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
.find_map(|(nth, node)| match node {
Node::File { inode, name, .. }
| Node::Directory { inode, name, .. }
| Node::ReadOnlyFile { inode, name, .. }
| Node::CustomFile { inode, name, .. }
| Node::ArcFile { inode, name, .. }
Node::File(FileNode { inode, name, .. })
| Node::Directory(DirectoryNode { inode, name, .. })
| Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. })
| Node::CustomFile(CustomFileNode { inode, name, .. })
| Node::ArcFile(ArcFileNode { inode, name, .. })
if name.as_os_str() == name_of =>
{
Some(Some((nth, InodeResolution::Found(*inode))))
@ -717,9 +720,9 @@ impl FileSystemInner {
.or(Some(None))
.ok_or(FsError::InvalidInput),
Some(Node::ArcDirectory {
Some(Node::ArcDirectory(ArcDirectoryNode {
fs, path: fs_path, ..
}) => {
})) => {
let mut path = fs_path.clone();
path.push(name_of);
Ok(Some((0, InodeResolution::Redirect(fs.clone(), path))))
@ -748,11 +751,11 @@ impl FileSystemInner {
/// `inode` must represents an existing directory.
pub(super) fn add_child_to_node(&mut self, inode: Inode, new_child: Inode) -> Result<()> {
match self.storage.get_mut(inode) {
Some(Node::Directory {
Some(Node::Directory(DirectoryNode {
children,
metadata: Metadata { modified, .. },
..
}) => {
})) => {
children.push(new_child);
*modified = time();
@ -772,11 +775,11 @@ impl FileSystemInner {
/// `inode` must represents an existing directory.
pub(super) fn remove_child_from_node(&mut self, inode: Inode, position: usize) -> Result<()> {
match self.storage.get_mut(inode) {
Some(Node::Directory {
Some(Node::Directory(DirectoryNode {
children,
metadata: Metadata { modified, .. },
..
}) => {
})) => {
children.remove(position);
*modified = time();
@ -866,19 +869,19 @@ impl fmt::Debug for FileSystemInner {
"{inode:<8} {ty:<4} {indentation_symbol:indentation_width$}{name}",
inode = node.inode(),
ty = match node {
Node::File { .. } => "file",
Node::ReadOnlyFile { .. } => "ro-file",
Node::ArcFile { .. } => "arc-file",
Node::CustomFile { .. } => "custom-file",
Node::Directory { .. } => "dir",
Node::ArcDirectory { .. } => "arc-dir",
Node::File(FileNode { .. }) => "file",
Node::ReadOnlyFile(ReadOnlyFileNode { .. }) => "ro-file",
Node::ArcFile(ArcFileNode { .. }) => "arc-file",
Node::CustomFile(CustomFileNode { .. }) => "custom-file",
Node::Directory(DirectoryNode { .. }) => "dir",
Node::ArcDirectory(ArcDirectoryNode { .. }) => "arc-dir",
},
name = node.name().to_string_lossy(),
indentation_symbol = " ",
indentation_width = indentation * 2 + 1,
)?;
if let Node::Directory { children, .. } = node {
if let Node::Directory(DirectoryNode { children, .. }) = node {
debug(
children
.iter()
@ -908,7 +911,7 @@ impl Default for FileSystemInner {
let time = time();
let mut slab = Slab::new();
slab.insert(Node::Directory {
slab.insert(Node::Directory(DirectoryNode {
inode: ROOT_INODE,
name: OsString::from("/"),
children: Vec::new(),
@ -922,7 +925,7 @@ impl Default for FileSystemInner {
modified: time,
len: 0,
},
});
}));
Self { storage: slab }
}
@ -1071,7 +1074,7 @@ mod test_filesystem {
assert_eq!(
fs.remove_dir(path!("/foo")),
Err(FsError::NotAFile),
Err(FsError::EntryNotFound),
"cannot remove a directory that doesn't exist",
);
@ -1136,7 +1139,7 @@ mod test_filesystem {
assert_eq!(
fs.rename(path!("/foo"), path!("/bar/baz")),
Err(FsError::NotAFile),
Err(FsError::EntryNotFound),
"renaming to a directory that has parent that doesn't exist",
);
@ -1484,7 +1487,7 @@ mod test_filesystem {
assert_eq!(
fs.remove_file(path!("/foo.txt")),
Err(FsError::NotAFile),
Err(FsError::EntryNotFound),
"removing a file that exists",
);
}

View File

@ -18,101 +18,119 @@ use std::{
type Inode = usize;
const ROOT_INODE: Inode = 0;
#[derive(Debug)]
struct FileNode {
inode: Inode,
name: OsString,
file: File,
metadata: Metadata,
}
#[derive(Debug)]
struct ReadOnlyFileNode {
inode: Inode,
name: OsString,
file: ReadOnlyFile,
metadata: Metadata,
}
#[derive(Debug)]
struct ArcFileNode {
inode: Inode,
name: OsString,
fs: Arc<dyn crate::FileSystem + Send + Sync>,
path: PathBuf,
metadata: Metadata,
}
#[derive(Debug)]
struct CustomFileNode {
inode: Inode,
name: OsString,
file: Mutex<Box<dyn crate::VirtualFile + Send + Sync>>,
metadata: Metadata,
}
#[derive(Debug)]
struct DirectoryNode {
inode: Inode,
name: OsString,
children: Vec<Inode>,
metadata: Metadata,
}
#[derive(Debug)]
struct ArcDirectoryNode {
inode: Inode,
name: OsString,
fs: Arc<dyn crate::FileSystem + Send + Sync>,
path: PathBuf,
metadata: Metadata,
}
#[derive(Debug)]
enum Node {
File {
inode: Inode,
name: OsString,
file: File,
metadata: Metadata,
},
ReadOnlyFile {
inode: Inode,
name: OsString,
file: ReadOnlyFile,
metadata: Metadata,
},
ArcFile {
inode: Inode,
name: OsString,
fs: Arc<dyn crate::FileSystem + Send + Sync>,
path: PathBuf,
metadata: Metadata,
},
CustomFile {
inode: Inode,
name: OsString,
file: Mutex<Box<dyn crate::VirtualFile + Send + Sync>>,
metadata: Metadata,
},
Directory {
inode: Inode,
name: OsString,
children: Vec<Inode>,
metadata: Metadata,
},
ArcDirectory {
inode: Inode,
name: OsString,
fs: Arc<dyn crate::FileSystem + Send + Sync>,
path: PathBuf,
metadata: Metadata,
},
File(FileNode),
ReadOnlyFile(ReadOnlyFileNode),
ArcFile(ArcFileNode),
CustomFile(CustomFileNode),
Directory(DirectoryNode),
ArcDirectory(ArcDirectoryNode),
}
impl Node {
fn inode(&self) -> Inode {
*match self {
Self::File { inode, .. } => inode,
Self::ReadOnlyFile { inode, .. } => inode,
Self::ArcFile { inode, .. } => inode,
Self::CustomFile { inode, .. } => inode,
Self::Directory { inode, .. } => inode,
Self::ArcDirectory { inode, .. } => inode,
Self::File(FileNode { inode, .. }) => inode,
Self::ReadOnlyFile(ReadOnlyFileNode { inode, .. }) => inode,
Self::ArcFile(ArcFileNode { inode, .. }) => inode,
Self::CustomFile(CustomFileNode { inode, .. }) => inode,
Self::Directory(DirectoryNode { inode, .. }) => inode,
Self::ArcDirectory(ArcDirectoryNode { inode, .. }) => inode,
}
}
fn name(&self) -> &OsStr {
match self {
Self::File { name, .. } => name.as_os_str(),
Self::ReadOnlyFile { name, .. } => name.as_os_str(),
Self::ArcFile { name, .. } => name.as_os_str(),
Self::CustomFile { name, .. } => name.as_os_str(),
Self::Directory { name, .. } => name.as_os_str(),
Self::ArcDirectory { name, .. } => name.as_os_str(),
Self::File(FileNode { name, .. }) => name.as_os_str(),
Self::ReadOnlyFile(ReadOnlyFileNode { name, .. }) => name.as_os_str(),
Self::ArcFile(ArcFileNode { name, .. }) => name.as_os_str(),
Self::CustomFile(CustomFileNode { name, .. }) => name.as_os_str(),
Self::Directory(DirectoryNode { name, .. }) => name.as_os_str(),
Self::ArcDirectory(ArcDirectoryNode { name, .. }) => name.as_os_str(),
}
}
fn metadata(&self) -> &Metadata {
match self {
Self::File { metadata, .. } => metadata,
Self::ReadOnlyFile { metadata, .. } => metadata,
Self::ArcFile { metadata, .. } => metadata,
Self::CustomFile { metadata, .. } => metadata,
Self::Directory { metadata, .. } => metadata,
Self::ArcDirectory { metadata, .. } => metadata,
Self::File(FileNode { metadata, .. }) => metadata,
Self::ReadOnlyFile(ReadOnlyFileNode { metadata, .. }) => metadata,
Self::ArcFile(ArcFileNode { metadata, .. }) => metadata,
Self::CustomFile(CustomFileNode { metadata, .. }) => metadata,
Self::Directory(DirectoryNode { metadata, .. }) => metadata,
Self::ArcDirectory(ArcDirectoryNode { metadata, .. }) => metadata,
}
}
fn metadata_mut(&mut self) -> &mut Metadata {
match self {
Self::File { metadata, .. } => metadata,
Self::ReadOnlyFile { metadata, .. } => metadata,
Self::ArcFile { metadata, .. } => metadata,
Self::CustomFile { metadata, .. } => metadata,
Self::Directory { metadata, .. } => metadata,
Self::ArcDirectory { metadata, .. } => metadata,
Self::File(FileNode { metadata, .. }) => metadata,
Self::ReadOnlyFile(ReadOnlyFileNode { metadata, .. }) => metadata,
Self::ArcFile(ArcFileNode { metadata, .. }) => metadata,
Self::CustomFile(CustomFileNode { metadata, .. }) => metadata,
Self::Directory(DirectoryNode { metadata, .. }) => metadata,
Self::ArcDirectory(ArcDirectoryNode { metadata, .. }) => metadata,
}
}
fn set_name(&mut self, new_name: OsString) {
match self {
Self::File { name, .. } => *name = new_name,
Self::ReadOnlyFile { name, .. } => *name = new_name,
Self::ArcFile { name, .. } => *name = new_name,
Self::CustomFile { name, .. } => *name = new_name,
Self::Directory { name, .. } => *name = new_name,
Self::ArcDirectory { name, .. } => *name = new_name,
Self::File(FileNode { name, .. }) => *name = new_name,
Self::ReadOnlyFile(ReadOnlyFileNode { name, .. }) => *name = new_name,
Self::ArcFile(ArcFileNode { name, .. }) => *name = new_name,
Self::CustomFile(CustomFileNode { name, .. }) => *name = new_name,
Self::Directory(DirectoryNode { name, .. }) => *name = new_name,
Self::ArcDirectory(ArcDirectoryNode { name, .. }) => *name = new_name,
}
}
}

View File

@ -1,7 +1,10 @@
//! NullFile is a special file for `/dev/null`, which returns 0 for all
//! operations except writing.
use std::io::{self, *};
use wasmer_vbus::FileDescriptor;
use wasmer_vfs::{ClonableVirtualFile, VirtualFile};
use crate::FileDescriptor;
use crate::{ClonableVirtualFile, VirtualFile};
#[derive(Debug, Clone, Default)]
pub struct NullFile {}
@ -11,6 +14,7 @@ impl Seek for NullFile {
Ok(0)
}
}
impl Write for NullFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
@ -39,13 +43,13 @@ impl VirtualFile for NullFile {
fn size(&self) -> u64 {
0
}
fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> {
fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
Ok(())
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
fn unlink(&mut self) -> crate::Result<()> {
Ok(())
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
fn bytes_available(&self) -> crate::Result<usize> {
Ok(0)
}
fn get_fd(&self) -> Option<FileDescriptor> {

View File

@ -1,8 +1,12 @@
//! Wraps a boxed file system with an implemented trait VirtualSystem -
//! this is needed so that a Box<dyn VirtualFileSystem> can be wrapped in
//! an Arc and shared - some of the interfaces pass around a Box<dyn VirtualFileSystem>
use std::path::Path;
#[allow(unused_imports, dead_code)]
use tracing::{debug, error, info, trace, warn};
use wasmer_vfs::*;
use crate::*;
#[derive(Debug)]
pub struct PassthruFileSystem {
@ -48,3 +52,39 @@ impl FileSystem for PassthruFileSystem {
self.fs.new_open_options()
}
}
#[test]
fn test_passthru_fs_2() {
let mem_fs = crate::mem_fs::FileSystem::default();
mem_fs
.new_open_options()
.read(true)
.write(true)
.create(true)
.open("/foo.txt")
.unwrap()
.write(b"hello")
.unwrap();
let mut buf = Vec::new();
mem_fs
.new_open_options()
.read(true)
.open("/foo.txt")
.unwrap()
.read_to_end(&mut buf)
.unwrap();
assert_eq!(buf, b"hello");
let passthru_fs = PassthruFileSystem::new(Box::new(mem_fs.clone()));
let mut buf = Vec::new();
passthru_fs
.new_open_options()
.read(true)
.open("/foo.txt")
.unwrap()
.read_to_end(&mut buf)
.unwrap();
assert_eq!(buf, b"hello");
}

View File

@ -1,9 +1,14 @@
//! Used for /dev/stdin, /dev/stdout, dev/stderr - returns a
//! static file descriptor (0, 1, 2)
use std::io::{self, *};
use wasmer_vbus::FileDescriptor;
use wasmer_vfs::VirtualFile;
use crate::FileDescriptor;
use crate::VirtualFile;
use wasmer_wasi_types::wasi::Fd;
/// A "special" file is a file that is locked
/// to one file descriptor (i.e. stdout => 0, stdin => 1), etc.
#[derive(Debug)]
pub struct SpecialFile {
fd: Fd,
@ -48,13 +53,13 @@ impl VirtualFile for SpecialFile {
fn size(&self) -> u64 {
0
}
fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> {
fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
Ok(())
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
fn unlink(&mut self) -> crate::Result<()> {
Ok(())
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
fn bytes_available(&self) -> crate::Result<usize> {
Ok(0)
}
fn get_special_fd(&self) -> Option<u32> {

View File

@ -1,3 +1,7 @@
//! Wraps the memory file system implementation - this has been
//! enhanced to support mounting file systems, shared static files,
//! readonly files, etc...
#![allow(dead_code)]
#![allow(unused)]
use std::collections::HashMap;
@ -12,21 +16,18 @@ use std::sync::Mutex;
#[allow(unused_imports, dead_code)]
use tracing::{debug, error, info, trace, warn};
use crate::{types as wasi_types, WasiFile, WasiFsError};
use wasmer_vfs::mem_fs;
use wasmer_vfs::Result as FsResult;
use wasmer_vfs::*;
use crate::mem_fs;
use crate::Result as FsResult;
use crate::*;
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct TmpFileSystem {
fs: mem_fs::FileSystem,
}
impl TmpFileSystem {
pub fn new() -> Self {
Self {
fs: mem_fs::FileSystem::default(),
}
Self::default()
}
pub fn new_open_options_ext(&self) -> mem_fs::FileOpener {

1076
lib/vfs/src/union_fs.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,10 @@
//! Used for /dev/zero - infinitely returns zero
//! which is useful for commands like `dd if=/dev/zero of=bigfile.img size=1G`
use std::io::{self, *};
use wasmer_vbus::FileDescriptor;
use wasmer_vfs::VirtualFile;
use crate::FileDescriptor;
use crate::VirtualFile;
#[derive(Debug, Default)]
pub struct ZeroFile {}
@ -22,9 +25,7 @@ impl Write for ZeroFile {
impl Read for ZeroFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
for b in buf.iter_mut() {
*b = 0;
}
buf.fill(0);
Ok(buf.len())
}
}
@ -42,13 +43,13 @@ impl VirtualFile for ZeroFile {
fn size(&self) -> u64 {
0
}
fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> {
fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
Ok(())
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
fn unlink(&mut self) -> crate::Result<()> {
Ok(())
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
fn bytes_available(&self) -> crate::Result<usize> {
Ok(0)
}
fn get_fd(&self) -> Option<FileDescriptor> {

View File

@ -13,6 +13,5 @@ bytes = "1"
async-trait = { version = "^0.1" }
[features]
default = ["mem_fs"]
mem_fs = ["wasmer-vfs/mem-fs"]
default = []
host_fs = ["wasmer-vfs/host-fs"]

View File

@ -24,5 +24,4 @@ async-trait = { version = "^0.1" }
[features]
default = ["host_fs"]
wasix = [ ]
host_fs = ["wasmer-vnet/host_fs", "wasmer-vfs/host-fs"]
mem_fs = ["wasmer-vnet/mem_fs", "wasmer-vfs/mem-fs"]
host_fs = ["wasmer-vnet/host_fs", "wasmer-vfs/host-fs"]

View File

@ -98,7 +98,7 @@ compiler-cranelift = [ "wasmer-compiler-cranelift" ]
compiler-llvm = [ "wasmer-compiler-llvm" ]
compiler-singlepass = [ "wasmer-compiler-singlepass" ]
js = ["wasmer/js", "mem-fs", "wasmer-vfs/no-time", "getrandom/js", "chrono", "wasmer-wasi-types/js"]
js = ["wasmer/js", "wasmer-vfs/no-time", "getrandom/js", "chrono", "wasmer-wasi-types/js"]
js-default = ["js", "wasmer/js-default"]
test-js = ["js", "wasmer/js-default", "wasmer/wat"]

View File

@ -5,9 +5,9 @@ use std::{
sync::{Arc, Mutex, RwLock},
};
use crate::{fs::TmpFileSystem, syscalls::platform_clock_time_get};
use crate::{syscalls::platform_clock_time_get};
use derivative::*;
use wasmer_vfs::FileSystem;
use wasmer_vfs::{FileSystem, TmpFileSystem};
use wasmer_wasi_types::wasi::Snapshot0Clockid;
use super::hash_of_binary;

View File

@ -1,173 +0,0 @@
use derivative::Derivative;
use std::{
io::{self, *},
sync::{Arc, RwLock},
};
use wasmer_vbus::FileDescriptor;
use wasmer_vfs::VirtualFile;
#[derive(Default)]
pub struct DelegateFileInner {
seek: Option<Box<dyn Fn(SeekFrom) -> io::Result<u64> + Send + Sync>>,
write: Option<Box<dyn Fn(&[u8]) -> io::Result<usize> + Send + Sync>>,
flush: Option<Box<dyn Fn() -> io::Result<()> + Send + Sync>>,
read: Option<Box<dyn Fn(&mut [u8]) -> io::Result<usize> + Send + Sync>>,
size: Option<Box<dyn Fn() -> u64 + Send + Sync>>,
set_len: Option<Box<dyn Fn(u64) -> wasmer_vfs::Result<()> + Send + Sync>>,
unlink: Option<Box<dyn Fn() -> wasmer_vfs::Result<()> + Send + Sync>>,
bytes_available: Option<Box<dyn Fn() -> wasmer_vfs::Result<usize> + Send + Sync>>,
}
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct DelegateFile {
#[derivative(Debug = "ignore")]
inner: Arc<RwLock<DelegateFileInner>>,
}
impl DelegateFile {
pub fn with_seek(
&self,
func: impl Fn(SeekFrom) -> io::Result<u64> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.seek.replace(Box::new(func));
self
}
pub fn with_write(
&self,
func: impl Fn(&[u8]) -> io::Result<usize> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.write.replace(Box::new(func));
self
}
pub fn with_flush(&self, func: impl Fn() -> io::Result<()> + Send + Sync + 'static) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.flush.replace(Box::new(func));
self
}
pub fn with_read(
&self,
func: impl Fn(&mut [u8]) -> io::Result<usize> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.read.replace(Box::new(func));
self
}
pub fn with_size(&self, func: impl Fn() -> u64 + Send + Sync + 'static) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.size.replace(Box::new(func));
self
}
pub fn with_set_len(
&self,
func: impl Fn(u64) -> wasmer_vfs::Result<()> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.set_len.replace(Box::new(func));
self
}
pub fn with_unlink(
&self,
func: impl Fn() -> wasmer_vfs::Result<()> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.unlink.replace(Box::new(func));
self
}
pub fn with_bytes_available(
&self,
func: impl Fn() -> wasmer_vfs::Result<usize> + Send + Sync + 'static,
) -> &Self {
let mut inner = self.inner.write().unwrap();
inner.bytes_available.replace(Box::new(func));
self
}
}
impl Default for DelegateFile {
fn default() -> Self {
Self {
inner: Arc::new(RwLock::new(DelegateFileInner::default())),
}
}
}
impl Seek for DelegateFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let inner = self.inner.read().unwrap();
inner.seek.as_ref().map(|seek| seek(pos)).unwrap_or(Ok(0))
}
}
impl Write for DelegateFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let inner = self.inner.read().unwrap();
inner
.write
.as_ref()
.map(|write| write(buf))
.unwrap_or(Ok(buf.len()))
}
fn flush(&mut self) -> io::Result<()> {
let inner = self.inner.read().unwrap();
inner.flush.as_ref().map(|flush| flush()).unwrap_or(Ok(()))
}
}
impl Read for DelegateFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let inner = self.inner.read().unwrap();
inner.read.as_ref().map(|read| read(buf)).unwrap_or(Ok(0))
}
}
impl VirtualFile for DelegateFile {
fn last_accessed(&self) -> u64 {
0
}
fn last_modified(&self) -> u64 {
0
}
fn created_time(&self) -> u64 {
0
}
fn size(&self) -> u64 {
let inner = self.inner.read().unwrap();
inner.size.as_ref().map(|size| size()).unwrap_or(0)
}
fn set_len(&mut self, new_size: u64) -> wasmer_vfs::Result<()> {
let inner = self.inner.read().unwrap();
inner
.set_len
.as_ref()
.map(|set_len| set_len(new_size))
.unwrap_or(Ok(()))
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
let inner = self.inner.read().unwrap();
inner
.unlink
.as_ref()
.map(|unlink| unlink())
.unwrap_or(Ok(()))
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
let inner = self.inner.read().unwrap();
inner
.bytes_available
.as_ref()
.map(|bytes_available| bytes_available())
.unwrap_or(Ok(0))
}
fn get_fd(&self) -> Option<FileDescriptor> {
None
}
}

View File

@ -1,25 +0,0 @@
mod arc_file;
mod arc_fs;
mod builder;
mod delegate_file;
mod empty_fs;
mod null_file;
mod passthru_fs;
mod special_file;
mod tmp_fs;
mod tty_file;
mod union_fs;
mod zero_file;
pub use arc_file::*;
pub use arc_fs::*;
pub use builder::*;
pub use delegate_file::*;
pub use empty_fs::*;
pub use null_file::*;
pub use passthru_fs::*;
pub use special_file::*;
pub use tmp_fs::*;
pub use tty_file::*;
pub use union_fs::*;
pub use zero_file::*;

View File

@ -1,81 +0,0 @@
use std::{
io::{self, *},
sync::Arc,
};
use wasmer_vbus::FileDescriptor;
use wasmer_vfs::VirtualFile;
#[derive(Debug)]
pub struct TtyFile {
runtime: Arc<dyn crate::WasiRuntimeImplementation + Send + Sync + 'static>,
stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
}
impl TtyFile {
pub fn new(
runtime: Arc<dyn crate::WasiRuntimeImplementation + Send + Sync + 'static>,
stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
) -> Self {
Self { runtime, stdin }
}
}
impl Seek for TtyFile {
fn seek(&mut self, _pos: SeekFrom) -> io::Result<u64> {
Ok(0)
}
}
impl Write for TtyFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.runtime.stdout(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Read for TtyFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stdin.read(buf)
}
}
impl VirtualFile for TtyFile {
fn last_accessed(&self) -> u64 {
self.stdin.last_accessed()
}
fn last_modified(&self) -> u64 {
self.stdin.last_modified()
}
fn created_time(&self) -> u64 {
self.stdin.created_time()
}
fn size(&self) -> u64 {
0
}
fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> {
Ok(())
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
Ok(())
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
self.stdin.bytes_available()
}
fn bytes_available_read(&self) -> wasmer_vfs::Result<usize> {
self.stdin.bytes_available_read()
}
fn bytes_available_write(&self) -> wasmer_vfs::Result<usize> {
self.stdin.bytes_available_write()
}
fn get_fd(&self) -> Option<FileDescriptor> {
None
}
fn is_open(&self) -> bool {
true
}
fn get_special_fd(&self) -> Option<u32> {
None
}
}

View File

@ -1,431 +0,0 @@
#![allow(dead_code)]
#![allow(unused)]
use std::borrow::Cow;
use std::ops::Add;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicU32;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
#[allow(unused_imports, dead_code)]
use tracing::{debug, error, info, trace, warn};
use wasmer_vfs::*;
#[derive(Debug)]
pub struct MountPoint {
pub path: String,
pub name: String,
pub fs: Option<Arc<Box<dyn FileSystem>>>,
pub weak_fs: Weak<Box<dyn FileSystem>>,
pub temp_holding: Arc<Mutex<Option<Arc<Box<dyn FileSystem>>>>>,
pub should_sanitize: bool,
pub new_path: Option<String>,
}
impl Clone for MountPoint {
fn clone(&self) -> Self {
Self {
path: self.path.clone(),
name: self.name.clone(),
fs: None,
weak_fs: self.weak_fs.clone(),
temp_holding: self.temp_holding.clone(),
should_sanitize: self.should_sanitize,
new_path: self.new_path.clone(),
}
}
}
impl MountPoint {
pub fn fs(&self) -> Option<Arc<Box<dyn FileSystem>>> {
match &self.fs {
Some(a) => Some(a.clone()),
None => self.weak_fs.upgrade(),
}
}
fn solidify(&mut self) {
if self.fs.is_none() {
self.fs = self.weak_fs.upgrade();
}
{
let mut guard = self.temp_holding.lock().unwrap();
let fs = guard.take();
if self.fs.is_none() {
self.fs = fs;
}
}
}
fn strong(&self) -> Option<StrongMountPoint> {
match self.fs() {
Some(fs) => Some(StrongMountPoint {
path: self.path.clone(),
name: self.name.clone(),
fs,
should_sanitize: self.should_sanitize,
new_path: self.new_path.clone(),
}),
None => None,
}
}
}
#[derive(Debug)]
pub struct StrongMountPoint {
pub path: String,
pub name: String,
pub fs: Arc<Box<dyn FileSystem>>,
pub should_sanitize: bool,
pub new_path: Option<String>,
}
#[derive(Debug, Clone)]
pub struct UnionFileSystem {
pub mounts: Vec<MountPoint>,
}
impl UnionFileSystem {
pub fn new() -> UnionFileSystem {
UnionFileSystem { mounts: Vec::new() }
}
pub fn clear(&mut self) {
self.mounts.clear();
}
}
impl UnionFileSystem {
pub fn mount(
&mut self,
name: &str,
path: &str,
should_sanitize: bool,
fs: Box<dyn FileSystem>,
new_path: Option<&str>,
) {
self.unmount(path);
let mut path = path.to_string();
if path.starts_with("/") == false {
path.insert(0, '/');
}
if path.ends_with("/") == false {
path += "/";
}
let new_path = new_path.map(|new_path| {
let mut new_path = new_path.to_string();
if new_path.ends_with("/") == false {
new_path += "/";
}
new_path
});
let fs = Arc::new(fs);
let mount = MountPoint {
path,
name: name.to_string(),
fs: None,
weak_fs: Arc::downgrade(&fs),
temp_holding: Arc::new(Mutex::new(Some(fs.clone()))),
should_sanitize,
new_path,
};
self.mounts.push(mount);
}
pub fn unmount(&mut self, path: &str) {
let path1 = path.to_string();
let mut path2 = path1.clone();
if path2.starts_with("/") == false {
path2.insert(0, '/');
}
let mut path3 = path2.clone();
if path3.ends_with("/") == false {
path3.push_str("/")
}
if path2.ends_with("/") {
path2 = (&path2[..(path2.len() - 1)]).to_string();
}
self.mounts
.retain(|mount| mount.path != path2 && mount.path != path3);
}
fn read_dir_internal(&self, path: &Path) -> Result<ReadDir> {
let path = path.to_string_lossy();
let mut ret = None;
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.read_dir(Path::new(path.as_str())) {
Ok(dir) => {
if ret.is_none() {
ret = Some(Vec::new());
}
let ret = ret.as_mut().unwrap();
for sub in dir {
if let Ok(sub) = sub {
ret.push(sub);
}
}
}
Err(err) => {
debug!("failed to read dir - {}", err);
}
}
}
match ret {
Some(ret) => Ok(ReadDir::new(ret)),
None => Err(FsError::EntryNotFound),
}
}
pub fn sanitize(mut self) -> Self {
self.solidify();
self.mounts.retain(|mount| mount.should_sanitize == false);
self
}
pub fn solidify(&mut self) {
for mount in self.mounts.iter_mut() {
mount.solidify();
}
}
}
impl FileSystem for UnionFileSystem {
fn read_dir(&self, path: &Path) -> Result<ReadDir> {
debug!("read_dir: path={}", path.display());
self.read_dir_internal(path)
}
fn create_dir(&self, path: &Path) -> Result<()> {
debug!("create_dir: path={}", path.display());
if self.read_dir_internal(path).is_ok() {
//return Err(FsError::AlreadyExists);
return Ok(());
}
let path = path.to_string_lossy();
let mut ret_error = FsError::EntryNotFound;
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.create_dir(Path::new(path.as_str())) {
Ok(ret) => {
return Ok(ret);
}
Err(err) => {
ret_error = err;
}
}
}
Err(ret_error)
}
fn remove_dir(&self, path: &Path) -> Result<()> {
debug!("remove_dir: path={}", path.display());
let mut ret_error = FsError::EntryNotFound;
let path = path.to_string_lossy();
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.remove_dir(Path::new(path.as_str())) {
Ok(ret) => {
return Ok(ret);
}
Err(err) => {
ret_error = err;
}
}
}
Err(ret_error)
}
fn rename(&self, from: &Path, to: &Path) -> Result<()> {
debug!("rename: from={} to={}", from.display(), to.display());
let mut ret_error = FsError::EntryNotFound;
let from = from.to_string_lossy();
let to = to.to_string_lossy();
for (path, mount) in filter_mounts(&self.mounts, from.as_ref()) {
let mut to = if to.starts_with(mount.path.as_str()) {
(&to[mount.path.len()..]).to_string()
} else {
ret_error = FsError::UnknownError;
continue;
};
if to.starts_with("/") == false {
to = format!("/{}", to);
}
match mount
.fs
.rename(Path::new(from.as_ref()), Path::new(to.as_str()))
{
Ok(ret) => {
trace!("rename ok");
return Ok(ret);
}
Err(err) => {
trace!("rename error (from={}, to={}) - {}", from, to, err);
ret_error = err;
}
}
}
trace!("rename failed - {}", ret_error);
Err(ret_error)
}
fn metadata(&self, path: &Path) -> Result<Metadata> {
debug!("metadata: path={}", path.display());
let mut ret_error = FsError::EntryNotFound;
let path = path.to_string_lossy();
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.metadata(Path::new(path.as_str())) {
Ok(ret) => {
return Ok(ret);
}
Err(err) => {
// This fixes a bug when attempting to create the directory /usr when it does not exist
// on the x86 version of memfs
// TODO: patch wasmer_vfs and remove
if let FsError::NotAFile = &err {
ret_error = FsError::EntryNotFound;
} else {
debug!("metadata failed: (path={}) - {}", path, err);
ret_error = err;
}
}
}
}
Err(ret_error)
}
fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
debug!("symlink_metadata: path={}", path.display());
let mut ret_error = FsError::EntryNotFound;
let path = path.to_string_lossy();
for (path_inner, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.symlink_metadata(Path::new(path_inner.as_str())) {
Ok(ret) => {
return Ok(ret);
}
Err(err) => {
// This fixes a bug when attempting to create the directory /usr when it does not exist
// on the x86 version of memfs
// TODO: patch wasmer_vfs and remove
if let FsError::NotAFile = &err {
ret_error = FsError::EntryNotFound;
} else {
debug!("metadata failed: (path={}) - {}", path, err);
ret_error = err;
}
}
}
}
debug!("symlink_metadata: failed={}", ret_error);
Err(ret_error)
}
fn remove_file(&self, path: &Path) -> Result<()> {
debug!("remove_file: path={}", path.display());
let mut ret_error = FsError::EntryNotFound;
let path = path.to_string_lossy();
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.remove_file(Path::new(path.as_str())) {
Ok(ret) => {
return Ok(ret);
}
Err(err) => {
ret_error = err;
}
}
}
Err(ret_error)
}
fn new_open_options(&self) -> OpenOptions {
let opener = Box::new(UnionFileOpener {
mounts: self.mounts.clone(),
});
OpenOptions::new(opener)
}
}
fn filter_mounts(
mounts: &Vec<MountPoint>,
mut target: &str,
) -> impl Iterator<Item = (String, StrongMountPoint)> {
let mut biggest_path = 0usize;
let mut ret = Vec::new();
for mount in mounts.iter().rev() {
let mut test_mount_path1 = mount.path.clone();
if test_mount_path1.ends_with("/") == false {
test_mount_path1.push_str("/");
}
let mut test_mount_path2 = mount.path.clone();
if test_mount_path2.ends_with("/") == true {
test_mount_path2 = test_mount_path2[..(test_mount_path2.len() - 1)].to_string();
}
if target == test_mount_path1 || target == test_mount_path2 {
if let Some(mount) = mount.strong() {
biggest_path = biggest_path.max(mount.path.len());
let mut path = "/".to_string();
if let Some(ref np) = mount.new_path {
path = np.to_string();
}
ret.push((path, mount));
}
} else if target.starts_with(test_mount_path1.as_str()) {
if let Some(mount) = mount.strong() {
biggest_path = biggest_path.max(mount.path.len());
let path = &target[test_mount_path2.len()..];
let mut path = path.to_string();
if let Some(ref np) = mount.new_path {
path = format!("{}{}", np, &path[1..]);
}
ret.push((path, mount));
}
}
}
ret.retain(|(a, b)| b.path.len() >= biggest_path);
ret.into_iter()
}
#[derive(Debug)]
pub struct UnionFileOpener {
mounts: Vec<MountPoint>,
}
impl FileOpener for UnionFileOpener {
fn open(
&mut self,
path: &Path,
conf: &OpenOptionsConfig,
) -> Result<Box<dyn VirtualFile + Send + Sync>> {
debug!("open: path={}", path.display());
let mut ret_err = FsError::EntryNotFound;
let path = path.to_string_lossy();
if conf.create() || conf.create_new() {
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
if let Ok(mut ret) = mount
.fs
.new_open_options()
.truncate(conf.truncate())
.append(conf.append())
.read(conf.read())
.write(conf.write())
.open(path)
{
if conf.create_new() {
ret.unlink();
continue;
}
return Ok(ret);
}
}
}
for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) {
match mount.fs.new_open_options().options(conf.clone()).open(path) {
Ok(ret) => return Ok(ret),
Err(err) if ret_err == FsError::EntryNotFound => {
ret_err = err;
}
_ => {}
}
}
Err(ret_err)
}
}

View File

@ -44,12 +44,12 @@ mod macros;
pub mod bin_factory;
#[cfg(feature = "os")]
pub mod builtins;
pub mod fs;
#[cfg(feature = "os")]
pub mod os;
pub mod runtime;
mod state;
mod syscalls;
mod tty_file;
mod utils;
#[cfg(feature = "os")]
pub mod wapm;
@ -71,6 +71,7 @@ pub use crate::state::{
ALL_RIGHTS, VIRTUAL_ROOT_FD,
};
pub use crate::syscalls::types;
pub use crate::tty_file::TtyFile;
#[cfg(feature = "wasix")]
pub use crate::utils::is_wasix_module;
pub use crate::utils::{get_wasi_version, get_wasi_versions, is_wasi_module, WasiVersion};

View File

@ -2,7 +2,6 @@
#[cfg(feature = "os")]
use crate::bin_factory::CachedCompiledModules;
use crate::fs::{ArcFile, TmpFileSystem};
use crate::state::{WasiFs, WasiFsRoot, WasiState};
use crate::syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO};
use crate::{
@ -17,7 +16,7 @@ use std::sync::Arc;
use std::sync::RwLock;
use thiserror::Error;
use wasmer::AsStoreMut;
use wasmer_vfs::{FsError, VirtualFile};
use wasmer_vfs::{ArcFile, FsError, TmpFileSystem, VirtualFile};
/// Creates an empty [`WasiStateBuilder`].
///

View File

@ -371,7 +371,7 @@ impl WasiInodes {
#[derive(Debug, Clone)]
pub enum WasiFsRoot {
Sandbox(Arc<crate::fs::TmpFileSystem>),
Sandbox(Arc<wasmer_vfs::tmp_fs::TmpFileSystem>),
Backing(Arc<Box<dyn FileSystem>>),
}
@ -505,7 +505,7 @@ pub fn default_fs_backing() -> Box<dyn wasmer_vfs::FileSystem + Send + Sync> {
cfg_if::cfg_if! {
if #[cfg(feature = "host-fs")] {
Box::new(wasmer_vfs::host_fs::FileSystem::default())
} else if #[cfg(feature = "mem-fs")] {
} else if #[cfg(not(feature = "host-fs"))] {
Box::new(wasmer_vfs::mem_fs::FileSystem::default())
} else {
Box::new(FallbackFileSystem::default())

View File

@ -11,7 +11,7 @@ use wasmer_wasi_types::wasi::{BusErrno, Errno, Rights};
pub use crate::{fs::NullFile as Stderr, fs::NullFile as Stdin, fs::NullFile as Stdout};
#[cfg(feature = "host-fs")]
pub use wasmer_vfs::host_fs::{Stderr, Stdin, Stdout};
#[cfg(all(feature = "mem-fs", not(feature = "host-fs")))]
#[cfg(not(feature = "host-fs"))]
pub use wasmer_vfs::mem_fs::{Stderr, Stdin, Stdout};
use wasmer_vfs::{FsError, VirtualFile};

166
lib/wasi/src/tty_file.rs Normal file
View File

@ -0,0 +1,166 @@
use std::{
io::{self, *},
sync::Arc,
};
use wasmer_vfs::FileDescriptor;
use wasmer_vfs::VirtualFile;
/// Special file for `/dev/tty` that can print to stdout
/// (hence the requirement for a `WasiRuntimeImplementation`)
#[derive(Debug)]
pub struct TtyFile {
runtime: Arc<dyn crate::WasiRuntimeImplementation + Send + Sync + 'static>,
stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
}
impl TtyFile {
pub fn new(
runtime: Arc<dyn crate::WasiRuntimeImplementation + Send + Sync + 'static>,
stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
) -> Self {
Self { runtime, stdin }
}
}
impl Seek for TtyFile {
fn seek(&mut self, _pos: SeekFrom) -> io::Result<u64> {
Ok(0)
}
}
impl Write for TtyFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.runtime.stdout(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Read for TtyFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stdin.read(buf)
}
}
impl VirtualFile for TtyFile {
fn last_accessed(&self) -> u64 {
self.stdin.last_accessed()
}
fn last_modified(&self) -> u64 {
self.stdin.last_modified()
}
fn created_time(&self) -> u64 {
self.stdin.created_time()
}
fn size(&self) -> u64 {
0
}
fn set_len(&mut self, _new_size: u64) -> wasmer_vfs::Result<()> {
Ok(())
}
fn unlink(&mut self) -> wasmer_vfs::Result<()> {
Ok(())
}
fn bytes_available(&self) -> wasmer_vfs::Result<usize> {
self.stdin.bytes_available()
}
fn bytes_available_read(&self) -> wasmer_vfs::Result<usize> {
self.stdin.bytes_available_read()
}
fn bytes_available_write(&self) -> wasmer_vfs::Result<usize> {
self.stdin.bytes_available_write()
}
fn get_fd(&self) -> Option<FileDescriptor> {
None
}
fn is_open(&self) -> bool {
true
}
fn get_special_fd(&self) -> Option<u32> {
None
}
}
#[cfg(test)]
mod tests {
use crate::{VirtualNetworking, WasiRuntimeImplementation, WasiThreadId};
use std::ops::Deref;
use std::sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex,
};
use wasmer_vbus::{UnsupportedVirtualBus, VirtualBus};
struct FakeRuntimeImplementation {
pub data: Arc<Mutex<Vec<u8>>>,
pub bus: Box<dyn VirtualBus + Sync>,
pub networking: Box<dyn VirtualNetworking + Sync>,
pub thread_id_seed: AtomicU32,
}
impl Default for FakeRuntimeImplementation {
fn default() -> Self {
FakeRuntimeImplementation {
data: Arc::new(Mutex::new(Vec::new())),
#[cfg(not(feature = "host-vnet"))]
networking: Box::new(wasmer_vnet::UnsupportedVirtualNetworking::default()),
#[cfg(feature = "host-vnet")]
networking: Box::new(wasmer_wasi_local_networking::LocalNetworking::default()),
bus: Box::new(UnsupportedVirtualBus::default()),
thread_id_seed: Default::default(),
}
}
}
impl FakeRuntimeImplementation {
fn get_stdout_written(&self) -> Option<Vec<u8>> {
let s = self.data.try_lock().ok()?;
Some(s.clone())
}
}
impl std::fmt::Debug for FakeRuntimeImplementation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "FakeRuntimeImplementation")
}
}
impl WasiRuntimeImplementation for FakeRuntimeImplementation {
fn bus(&self) -> &(dyn VirtualBus) {
self.bus.deref()
}
fn networking(&self) -> &(dyn VirtualNetworking) {
self.networking.deref()
}
fn thread_generate_id(&self) -> WasiThreadId {
self.thread_id_seed.fetch_add(1, Ordering::Relaxed).into()
}
fn stdout(&self, data: &[u8]) -> std::io::Result<()> {
if let Ok(mut s) = self.data.try_lock() {
s.extend_from_slice(data);
}
Ok(())
}
}
#[test]
fn test_tty_file() {
use crate::state::WasiBidirectionalPipePair;
use crate::tty_file::TtyFile;
use std::io::Write;
use std::sync::Arc;
let mut pair = WasiBidirectionalPipePair::new();
pair.set_blocking(false);
let rt = Arc::new(FakeRuntimeImplementation::default());
let mut tty_file = TtyFile::new(rt.clone(), Box::new(pair));
tty_file.write(b"hello");
assert_eq!(rt.get_stdout_written().unwrap(), b"hello".to_vec());
}
}

View File

@ -111,6 +111,7 @@ wasitests::nightly_2022_10_18::host_fs::poll_oneoff
wasitests::nightly_2022_10_18::host_fs::readlink
wasitests::nightly_2022_10_18::host_fs::unix_open_special_files
wasitests::nightly_2022_10_18::host_fs::writing
wasitests::nightly_2022_10_18::mem_fs::close_preopen_fd
wasitests::nightly_2022_10_18::mem_fs::create_dir
wasitests::nightly_2022_10_18::mem_fs::envvar
@ -123,9 +124,187 @@ wasitests::nightly_2022_10_18::mem_fs::readlink
wasitests::nightly_2022_10_18::mem_fs::unix_open_special_files
wasitests::nightly_2022_10_18::mem_fs::writing
# These tests are failing in CI for some reason, but didn't fail on older compiler versions
wasitests::nightly_2022_10_18::host_fs::path_symlink
wasitests::nightly_2022_10_18::mem_fs::path_symlink
wasitests::snapshot1::tmp_fs::close_preopen_fd
wasitests::snapshot1::tmp_fs::create_dir
wasitests::snapshot1::tmp_fs::envvar
wasitests::snapshot1::tmp_fs::fd_allocate
wasitests::snapshot1::tmp_fs::fd_close
wasitests::snapshot1::tmp_fs::fd_pread
wasitests::snapshot1::tmp_fs::fd_read
wasitests::snapshot1::tmp_fs::poll_oneoff
wasitests::snapshot1::tmp_fs::readlink
wasitests::snapshot1::tmp_fs::unix_open_special_files
wasitests::snapshot1::tmp_fs::writing
wasitests::unstable::tmp_fs::close_preopen_fd
wasitests::unstable::tmp_fs::create_dir
wasitests::unstable::tmp_fs::envvar
wasitests::unstable::tmp_fs::fd_allocate
wasitests::unstable::tmp_fs::fd_close
wasitests::unstable::tmp_fs::fd_pread
wasitests::unstable::tmp_fs::fd_read
wasitests::unstable::tmp_fs::poll_oneoff
wasitests::unstable::tmp_fs::readlink
wasitests::unstable::tmp_fs::unix_open_special_files
wasitests::unstable::tmp_fs::writing
wasitests::nightly_2022_10_18::tmp_fs::close_preopen_fd
wasitests::nightly_2022_10_18::tmp_fs::create_dir
wasitests::nightly_2022_10_18::tmp_fs::envvar
wasitests::nightly_2022_10_18::tmp_fs::fd_allocate
wasitests::nightly_2022_10_18::tmp_fs::fd_close
wasitests::nightly_2022_10_18::tmp_fs::fd_pread
wasitests::nightly_2022_10_18::tmp_fs::fd_read
wasitests::nightly_2022_10_18::tmp_fs::poll_oneoff
wasitests::nightly_2022_10_18::tmp_fs::readlink
wasitests::nightly_2022_10_18::tmp_fs::unix_open_special_files
wasitests::nightly_2022_10_18::tmp_fs::writing
wasitests::snapshot1::passthru_fs::close_preopen_fd
wasitests::snapshot1::passthru_fs::create_dir
wasitests::snapshot1::passthru_fs::envvar
wasitests::snapshot1::passthru_fs::fd_allocate
wasitests::snapshot1::passthru_fs::fd_close
wasitests::snapshot1::passthru_fs::fd_pread
wasitests::snapshot1::passthru_fs::fd_read
wasitests::snapshot1::passthru_fs::poll_oneoff
wasitests::snapshot1::passthru_fs::readlink
wasitests::snapshot1::passthru_fs::unix_open_special_files
wasitests::snapshot1::passthru_fs::writing
wasitests::unstable::passthru_fs::close_preopen_fd
wasitests::unstable::passthru_fs::create_dir
wasitests::unstable::passthru_fs::envvar
wasitests::unstable::passthru_fs::fd_allocate
wasitests::unstable::passthru_fs::fd_close
wasitests::unstable::passthru_fs::fd_pread
wasitests::unstable::passthru_fs::fd_read
wasitests::unstable::passthru_fs::poll_oneoff
wasitests::unstable::passthru_fs::readlink
wasitests::unstable::passthru_fs::unix_open_special_files
wasitests::unstable::passthru_fs::writing
wasitests::nightly_2022_10_18::passthru_fs::close_preopen_fd
wasitests::nightly_2022_10_18::passthru_fs::create_dir
wasitests::nightly_2022_10_18::passthru_fs::envvar
wasitests::nightly_2022_10_18::passthru_fs::fd_allocate
wasitests::nightly_2022_10_18::passthru_fs::fd_close
wasitests::nightly_2022_10_18::passthru_fs::fd_pread
wasitests::nightly_2022_10_18::passthru_fs::fd_read
wasitests::nightly_2022_10_18::passthru_fs::poll_oneoff
wasitests::nightly_2022_10_18::passthru_fs::readlink
wasitests::nightly_2022_10_18::passthru_fs::unix_open_special_files
wasitests::nightly_2022_10_18::passthru_fs::writing
wasitests::snapshot1::tmp_fs::close_preopen_fd
wasitests::snapshot1::tmp_fs::create_dir
wasitests::snapshot1::tmp_fs::envvar
wasitests::snapshot1::tmp_fs::fd_allocate
wasitests::snapshot1::tmp_fs::fd_close
wasitests::snapshot1::tmp_fs::fd_pread
wasitests::snapshot1::tmp_fs::fd_read
wasitests::snapshot1::tmp_fs::poll_oneoff
wasitests::snapshot1::tmp_fs::readlink
wasitests::snapshot1::tmp_fs::unix_open_special_files
wasitests::snapshot1::tmp_fs::writing
wasitests::unstable::tmp_fs::close_preopen_fd
wasitests::unstable::tmp_fs::create_dir
wasitests::unstable::tmp_fs::envvar
wasitests::unstable::tmp_fs::fd_allocate
wasitests::unstable::tmp_fs::fd_close
wasitests::unstable::tmp_fs::fd_pread
wasitests::unstable::tmp_fs::fd_read
wasitests::unstable::tmp_fs::poll_oneoff
wasitests::unstable::tmp_fs::readlink
wasitests::unstable::tmp_fs::unix_open_special_files
wasitests::unstable::tmp_fs::writing
wasitests::nightly_2022_10_18::tmp_fs::close_preopen_fd
wasitests::nightly_2022_10_18::tmp_fs::create_dir
wasitests::nightly_2022_10_18::tmp_fs::envvar
wasitests::nightly_2022_10_18::tmp_fs::fd_allocate
wasitests::nightly_2022_10_18::tmp_fs::fd_close
wasitests::nightly_2022_10_18::tmp_fs::fd_pread
wasitests::nightly_2022_10_18::tmp_fs::fd_read
wasitests::nightly_2022_10_18::tmp_fs::poll_oneoff
wasitests::nightly_2022_10_18::tmp_fs::readlink
wasitests::nightly_2022_10_18::tmp_fs::unix_open_special_files
wasitests::nightly_2022_10_18::tmp_fs::writing
wasitests::snapshot1::union_fs::close_preopen_fd
wasitests::snapshot1::union_fs::create_dir
wasitests::snapshot1::union_fs::envvar
wasitests::snapshot1::union_fs::fd_allocate
wasitests::snapshot1::union_fs::fd_close
wasitests::snapshot1::union_fs::fd_pread
wasitests::snapshot1::union_fs::fd_read
wasitests::snapshot1::union_fs::poll_oneoff
wasitests::snapshot1::union_fs::readlink
wasitests::snapshot1::union_fs::unix_open_special_files
wasitests::snapshot1::union_fs::writing
wasitests::unstable::union_fs::close_preopen_fd
wasitests::unstable::union_fs::create_dir
wasitests::unstable::union_fs::envvar
wasitests::unstable::union_fs::fd_allocate
wasitests::unstable::union_fs::fd_close
wasitests::unstable::union_fs::fd_pread
wasitests::unstable::union_fs::fd_read
wasitests::unstable::union_fs::poll_oneoff
wasitests::unstable::union_fs::readlink
wasitests::unstable::union_fs::unix_open_special_files
wasitests::unstable::union_fs::writing
wasitests::nightly_2022_10_18::union_fs::close_preopen_fd
wasitests::nightly_2022_10_18::union_fs::create_dir
wasitests::nightly_2022_10_18::union_fs::envvar
wasitests::nightly_2022_10_18::union_fs::fd_allocate
wasitests::nightly_2022_10_18::union_fs::fd_close
wasitests::nightly_2022_10_18::union_fs::fd_pread
wasitests::nightly_2022_10_18::union_fs::fd_read
wasitests::nightly_2022_10_18::union_fs::poll_oneoff
wasitests::nightly_2022_10_18::union_fs::readlink
wasitests::nightly_2022_10_18::union_fs::unix_open_special_files
wasitests::nightly_2022_10_18::union_fs::writing
wasitests::snapshot1::root_fs::close_preopen_fd
wasitests::snapshot1::root_fs::create_dir
wasitests::snapshot1::root_fs::envvar
wasitests::snapshot1::root_fs::fd_allocate
wasitests::snapshot1::root_fs::fd_close
wasitests::snapshot1::root_fs::fd_pread
wasitests::snapshot1::root_fs::fd_read
wasitests::snapshot1::root_fs::poll_oneoff
wasitests::snapshot1::root_fs::readlink
wasitests::snapshot1::root_fs::unix_open_special_files
wasitests::snapshot1::root_fs::writing
wasitests::unstable::root_fs::close_preopen_fd
wasitests::unstable::root_fs::create_dir
wasitests::unstable::root_fs::envvar
wasitests::unstable::root_fs::fd_allocate
wasitests::unstable::root_fs::fd_close
wasitests::unstable::root_fs::fd_pread
wasitests::unstable::root_fs::fd_read
wasitests::unstable::root_fs::poll_oneoff
wasitests::unstable::root_fs::readlink
wasitests::unstable::root_fs::unix_open_special_files
wasitests::unstable::root_fs::writing
wasitests::nightly_2022_10_18::root_fs::close_preopen_fd
wasitests::nightly_2022_10_18::root_fs::create_dir
wasitests::nightly_2022_10_18::root_fs::envvar
wasitests::nightly_2022_10_18::root_fs::fd_allocate
wasitests::nightly_2022_10_18::root_fs::fd_close
wasitests::nightly_2022_10_18::root_fs::fd_pread
wasitests::nightly_2022_10_18::root_fs::fd_read
wasitests::nightly_2022_10_18::root_fs::poll_oneoff
wasitests::nightly_2022_10_18::root_fs::readlink
wasitests::nightly_2022_10_18::root_fs::unix_open_special_files
wasitests::nightly_2022_10_18::root_fs::writing
# These tests are failing in CI for some reason, but didn't fail on older compiler versions
wasitests::nightly_2022_10_18::passthru_fs::path_symlink
wasitests::nightly_2022_10_18::root_fs::path_symlink
wasitests::nightly_2022_10_18::tmp_fs::path_symlink
wasitests::nightly_2022_10_18::union_fs::path_symlink
wasitests::nightly_2022_10_18::mem_fs::path_symlink
wasitests::nightly_2022_10_18::host_fs::path_symlink
wasitests::nightly_2022_10_18::mem_fs::fd_append
wasitests::nightly_2022_10_18::host_fs::fd_append
wasitests::nightly_2022_10_18::mem_fs::fd_append
wasitests::nightly_2022_10_18::passthru_fs::fd_append
wasitests::nightly_2022_10_18::root_fs::fd_append
wasitests::nightly_2022_10_18::tmp_fs::fd_append
wasitests::nightly_2022_10_18::union_fs::fd_append

View File

@ -4,7 +4,9 @@ use std::io::{self, Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc, Mutex};
use wasmer::{FunctionEnv, Imports, Instance, Module, Store};
use wasmer_vfs::{host_fs, mem_fs, FileSystem};
use wasmer_vfs::{
host_fs, mem_fs, passthru_fs, tmp_fs, union_fs, FileSystem, RootFileSystemBuilder,
};
use wasmer_wasi::types::wasi::{Filesize, Timestamp};
use wasmer_wasi::{
generate_import_object_from_env, get_wasi_version, FsError, VirtualFile,
@ -20,6 +22,18 @@ pub enum WasiFileSystemKind {
/// Instruct the test runner to use `wasmer_vfs::mem_fs`.
InMemory,
/// Instruct the test runner to use `wasmer_vfs::tmp_fs`
Tmp,
/// Instruct the test runner to use `wasmer_vfs::passtru_fs`
PassthruMemory,
/// Instruct the test runner to use `wasmer_vfs::union_fs<host_fs, mem_fs>`
UnionHostMemory,
/// Instruct the test runner to use the TempFs returned by `wasmer_vfs::builder::RootFileSystemBuilder`
RootFileSystemBuilder,
}
/// Crate holding metadata parsed from the WASI WAST about the test to be run.
@ -177,13 +191,47 @@ impl<'a> WasiTest<'a> {
builder.set_fs(Box::new(fs));
}
WasiFileSystemKind::InMemory => {
let fs = mem_fs::FileSystem::default();
other => {
let fs: Box<dyn FileSystem> = match other {
WasiFileSystemKind::InMemory => Box::new(mem_fs::FileSystem::default()),
WasiFileSystemKind::Tmp => Box::new(tmp_fs::TmpFileSystem::default()),
WasiFileSystemKind::PassthruMemory => {
Box::new(passthru_fs::PassthruFileSystem::new(Box::new(
mem_fs::FileSystem::default(),
)))
}
WasiFileSystemKind::RootFileSystemBuilder => {
Box::new(RootFileSystemBuilder::new().build())
}
WasiFileSystemKind::UnionHostMemory => {
let a = mem_fs::FileSystem::default();
let b = mem_fs::FileSystem::default();
let c = mem_fs::FileSystem::default();
let d = mem_fs::FileSystem::default();
let e = mem_fs::FileSystem::default();
let f = mem_fs::FileSystem::default();
let mut union = union_fs::UnionFileSystem::new();
union.mount("mem_fs", "/test_fs", false, Box::new(a), None);
union.mount("mem_fs_2", "/snapshot1", false, Box::new(b), None);
union.mount("mem_fs_3", "/tests", false, Box::new(c), None);
union.mount("mem_fs_4", "/nightly_2022_10_18", false, Box::new(d), None);
union.mount("mem_fs_5", "/unstable", false, Box::new(e), None);
union.mount("mem_fs_6", "/.tmp_wasmer_wast_0", false, Box::new(f), None);
Box::new(union)
}
_ => {
panic!("unexpected filesystem type {:?}", other);
}
};
let mut temp_dir_index: usize = 0;
let root = PathBuf::from("/");
map_host_fs_to_mem_fs(&fs, read_dir(BASE_TEST_DIR)?, &root)?;
map_host_fs_to_mem_fs(&*fs, read_dir(BASE_TEST_DIR)?, &root)?;
for (alias, real_dir) in &self.mapped_dirs {
let mut path = root.clone();
@ -206,7 +254,7 @@ impl<'a> WasiTest<'a> {
temp_dir_index += 1;
}
builder.set_fs(Box::new(fs));
builder.set_fs(fs);
}
}
@ -640,7 +688,7 @@ impl VirtualFile for OutputCapturerer {
/// because the host filesystem cannot be used. Instead, we are
/// copying `BASE_TEST_DIR` to the `mem_fs`.
fn map_host_fs_to_mem_fs(
fs: &mem_fs::FileSystem,
fs: &dyn FileSystem,
directory_reader: ReadDir,
path_prefix: &Path,
) -> anyhow::Result<()> {