Virtual file support (#701)

* Add support for virtual files (eg, not backed by an OS file).

Virtual files are implemented through trait objects, with a default
implementation that tries to behave like on-disk files, but entirely
backed by in-memory structures.

Co-authored-by: Dan Gohman <sunfish@mozilla.com>
This commit is contained in:
iximeow
2020-03-06 11:08:13 -08:00
committed by GitHub
parent 7f7196a655
commit 7e0d9decbf
19 changed files with 1568 additions and 188 deletions

View File

@@ -11,11 +11,19 @@ fn main() {
#[cfg(feature = "test_programs")] #[cfg(feature = "test_programs")]
mod wasi_tests { mod wasi_tests {
use std::env; use std::env;
use std::fs::{read_dir, DirEntry, File}; use std::fs::{read_dir, File};
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
#[derive(Clone, Copy, Debug)]
enum PreopenType {
/// Preopens should be satisfied with real OS files.
OS,
/// Preopens should be satisfied with virtual files.
Virtual,
}
pub(super) fn build_and_generate_tests() { pub(super) fn build_and_generate_tests() {
// Validate if any of test sources are present and if they changed // Validate if any of test sources are present and if they changed
// This should always work since there is no submodule to init anymore // This should always work since there is no submodule to init anymore
@@ -103,8 +111,21 @@ mod wasi_tests {
.replace("-", "_") .replace("-", "_")
)?; )?;
writeln!(out, " use super::{{runtime, utils, setup_log}};")?; writeln!(out, " use super::{{runtime, utils, setup_log}};")?;
writeln!(out, " use runtime::PreopenType;")?;
for dir_entry in dir_entries { for dir_entry in dir_entries {
write_testsuite_tests(out, dir_entry, testsuite)?; let test_path = dir_entry.path();
let stemstr = test_path
.file_stem()
.expect("file_stem")
.to_str()
.expect("to_str");
if no_preopens(testsuite, stemstr) {
write_testsuite_tests(out, &test_path, testsuite, PreopenType::OS)?;
} else {
write_testsuite_tests(out, &test_path, testsuite, PreopenType::OS)?;
write_testsuite_tests(out, &test_path, testsuite, PreopenType::Virtual)?;
}
} }
writeln!(out, "}}")?; writeln!(out, "}}")?;
Ok(()) Ok(())
@@ -112,10 +133,10 @@ mod wasi_tests {
fn write_testsuite_tests( fn write_testsuite_tests(
out: &mut File, out: &mut File,
dir_entry: DirEntry, path: &Path,
testsuite: &str, testsuite: &str,
preopen_type: PreopenType,
) -> io::Result<()> { ) -> io::Result<()> {
let path = dir_entry.path();
let stemstr = path let stemstr = path
.file_stem() .file_stem()
.expect("file_stem") .expect("file_stem")
@@ -123,14 +144,19 @@ mod wasi_tests {
.expect("to_str"); .expect("to_str");
writeln!(out, " #[test]")?; writeln!(out, " #[test]")?;
if ignore(testsuite, stemstr) { let test_fn_name = format!(
"{}{}",
&stemstr.replace("-", "_"),
if let PreopenType::Virtual = preopen_type {
"_virtualfs"
} else {
""
}
);
if ignore(testsuite, &test_fn_name) {
writeln!(out, " #[ignore]")?; writeln!(out, " #[ignore]")?;
} }
writeln!( writeln!(out, " fn r#{}() -> anyhow::Result<()> {{", test_fn_name,)?;
out,
" fn r#{}() -> anyhow::Result<()> {{",
&stemstr.replace("-", "_")
)?;
writeln!(out, " setup_log();")?; writeln!(out, " setup_log();")?;
writeln!( writeln!(
out, out,
@@ -145,16 +171,25 @@ mod wasi_tests {
let workspace = if no_preopens(testsuite, stemstr) { let workspace = if no_preopens(testsuite, stemstr) {
"None" "None"
} else { } else {
match preopen_type {
PreopenType::OS => {
writeln!( writeln!(
out, out,
" let workspace = utils::prepare_workspace(&bin_name)?;" " let workspace = utils::prepare_workspace(&bin_name)?;"
)?; )?;
"Some(workspace.path())" "Some(workspace.path())"
}
PreopenType::Virtual => "Some(std::path::Path::new(&bin_name))",
}
}; };
writeln!( writeln!(
out, out,
" runtime::instantiate(&data, &bin_name, {})", " runtime::instantiate(&data, &bin_name, {}, {})",
workspace workspace,
match preopen_type {
PreopenType::OS => "PreopenType::OS",
PreopenType::Virtual => "PreopenType::Virtual",
}
)?; )?;
writeln!(out, " }}")?; writeln!(out, " }}")?;
writeln!(out)?; writeln!(out)?;
@@ -164,8 +199,30 @@ mod wasi_tests {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(not(windows))] { if #[cfg(not(windows))] {
/// Ignore tests that aren't supported yet. /// Ignore tests that aren't supported yet.
fn ignore(_testsuite: &str, _name: &str) -> bool { fn ignore(testsuite: &str, name: &str) -> bool {
false if testsuite == "wasi-tests" {
match name {
// TODO: virtfs files cannot be poll_oneoff'd yet
"poll_oneoff_virtualfs" => true,
// TODO: virtfs does not support filetimes yet.
"path_filestat_virtualfs" |
"fd_filestat_set_virtualfs" => true,
// TODO: virtfs does not support symlinks yet.
"nofollow_errors_virtualfs" |
"path_link_virtualfs" |
"readlink_virtualfs" |
"readlink_no_buffer_virtualfs" |
"dangling_symlink_virtualfs" |
"symlink_loop_virtualfs" |
"path_symlink_trailing_slashes_virtualfs" => true,
// TODO: virtfs does not support rename yet.
"path_rename_trailing_slashes_virtualfs" |
"path_rename_virtualfs" => true,
_ => false,
}
} else {
unreachable!()
}
} }
} else { } else {
/// Ignore tests that aren't supported yet. /// Ignore tests that aren't supported yet.
@@ -178,6 +235,22 @@ mod wasi_tests {
"truncation_rights" => true, "truncation_rights" => true,
"path_link" => true, "path_link" => true,
"dangling_fd" => true, "dangling_fd" => true,
// TODO: virtfs files cannot be poll_oneoff'd yet
"poll_oneoff_virtualfs" => true,
// TODO: virtfs does not support filetimes yet.
"path_filestat_virtualfs" |
"fd_filestat_set_virtualfs" => true,
// TODO: virtfs does not support symlinks yet.
"nofollow_errors_virtualfs" |
"path_link_virtualfs" |
"readlink_virtualfs" |
"readlink_no_buffer_virtualfs" |
"dangling_symlink_virtualfs" |
"symlink_loop_virtualfs" |
"path_symlink_trailing_slashes_virtualfs" => true,
// TODO: virtfs does not support rename yet.
"path_rename_trailing_slashes_virtualfs" |
"path_rename_virtualfs" => true,
_ => false, _ => false,
} }
} else { } else {

View File

@@ -1,30 +1,44 @@
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use wasi_common::VirtualDirEntry;
use wasmtime::{Instance, Module, Store}; use wasmtime::{Instance, Module, Store};
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { #[derive(Clone, Copy, Debug)]
pub enum PreopenType {
/// Preopens should be satisfied with real OS files.
OS,
/// Preopens should be satisfied with virtual files.
Virtual,
}
pub fn instantiate(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
preopen_type: PreopenType,
) -> anyhow::Result<()> {
let store = Store::default(); let store = Store::default();
let get_preopens = |workspace: Option<&Path>| -> anyhow::Result<Vec<_>> {
if let Some(workspace) = workspace {
let preopen_dir = wasi_common::preopen_dir(workspace)
.context(format!("error while preopening {:?}", workspace))?;
Ok(vec![(".".to_owned(), preopen_dir)])
} else {
Ok(vec![])
}
};
// Create our wasi context with pretty standard arguments/inheritance/etc. // Create our wasi context with pretty standard arguments/inheritance/etc.
// Additionally register andy preopened directories if we have them. // Additionally register any preopened directories if we have them.
let mut builder = wasi_common::WasiCtxBuilder::new(); let mut builder = wasi_common::WasiCtxBuilder::new();
builder.arg(bin_name).arg(".").inherit_stdio(); builder.arg(bin_name).arg(".").inherit_stdio();
for (dir, file) in get_preopens(workspace)? { if let Some(workspace) = workspace {
builder.preopened_dir(file, dir); match preopen_type {
PreopenType::OS => {
let preopen_dir = wasi_common::preopen_dir(workspace)
.context(format!("error while preopening {:?}", workspace))?;
builder.preopened_dir(preopen_dir, ".");
}
PreopenType::Virtual => {
// we can ignore the workspace path for virtual preopens because virtual preopens
// don't exist in the filesystem anyway - no name conflict concerns.
builder.preopened_virt(VirtualDirEntry::empty_directory(), ".");
}
}
} }
// The nonstandard thing we do with `WasiCtxBuilder` is to ensure that // The nonstandard thing we do with `WasiCtxBuilder` is to ensure that

View File

@@ -50,7 +50,7 @@ unsafe fn test_fd_fdstat_set_flags(dir_fd: wasi::Fd) {
) )
.expect("reading file"), .expect("reading file"),
buffer.len(), buffer.len(),
"shoudl read {} bytes", "should read {} bytes",
buffer.len() buffer.len()
); );
@@ -87,7 +87,7 @@ unsafe fn test_fd_fdstat_set_flags(dir_fd: wasi::Fd) {
) )
.expect("reading file"), .expect("reading file"),
buffer.len(), buffer.len(),
"shoudl read {} bytes", "should read {} bytes",
buffer.len() buffer.len()
); );
@@ -126,7 +126,7 @@ unsafe fn test_fd_fdstat_set_flags(dir_fd: wasi::Fd) {
) )
.expect("reading file"), .expect("reading file"),
buffer.len(), buffer.len(),
"shoudl read {} bytes", "should read {} bytes",
buffer.len() buffer.len()
); );

View File

@@ -11,14 +11,14 @@ unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi::Fd) {
wasi::path_create_directory(dir_fd, "dir").expect("creating a directory"); wasi::path_create_directory(dir_fd, "dir").expect("creating a directory");
// Test that removing it with a trailing flash succeeds. // Test that removing it with a trailing slash succeeds.
wasi::path_remove_directory(dir_fd, "dir/") wasi::path_remove_directory(dir_fd, "dir/")
.expect("remove_directory with a trailing slash on a directory should succeed"); .expect("remove_directory with a trailing slash on a directory should succeed");
// Create a temporary file. // Create a temporary file.
create_file(dir_fd, "file"); create_file(dir_fd, "file");
// Test that removing it with no trailing flash fails. // Test that removing it with no trailing slash fails.
assert_eq!( assert_eq!(
wasi::path_remove_directory(dir_fd, "file") wasi::path_remove_directory(dir_fd, "file")
.expect_err("remove_directory without a trailing slash on a file should fail") .expect_err("remove_directory without a trailing slash on a file should fail")
@@ -27,7 +27,7 @@ unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi::Fd) {
"errno should be ERRNO_NOTDIR" "errno should be ERRNO_NOTDIR"
); );
// Test that removing it with a trailing flash fails. // Test that removing it with a trailing slash fails.
assert_eq!( assert_eq!(
wasi::path_remove_directory(dir_fd, "file/") wasi::path_remove_directory(dir_fd, "file/")
.expect_err("remove_directory with a trailing slash on a file should fail") .expect_err("remove_directory with a trailing slash on a file should fail")

View File

@@ -1,4 +1,6 @@
use crate::fdentry::FdEntry; use crate::fdentry::{Descriptor, FdEntry};
use crate::sys::fdentry_impl::OsHandle;
use crate::virtfs::{VirtualDir, VirtualDirEntry};
use crate::{wasi, Error, Result}; use crate::{wasi, Error, Result};
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
@@ -61,7 +63,7 @@ impl PendingCString {
/// A builder allowing customizable construction of `WasiCtx` instances. /// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder { pub struct WasiCtxBuilder {
fds: Option<HashMap<wasi::__wasi_fd_t, PendingFdEntry>>, fds: Option<HashMap<wasi::__wasi_fd_t, PendingFdEntry>>,
preopens: Option<Vec<(PathBuf, File)>>, preopens: Option<Vec<(PathBuf, Descriptor)>>,
args: Option<Vec<PendingCString>>, args: Option<Vec<PendingCString>>,
env: Option<HashMap<PendingCString, PendingCString>>, env: Option<HashMap<PendingCString, PendingCString>>,
} }
@@ -222,10 +224,46 @@ impl WasiCtxBuilder {
/// Add a preopened directory. /// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self { pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
self.preopens.as_mut().unwrap().push((
guest_path.as_ref().to_owned(),
Descriptor::OsHandle(OsHandle::from(dir)),
));
self
}
/// Add a preopened virtual directory.
pub fn preopened_virt<P: AsRef<Path>>(
&mut self,
dir: VirtualDirEntry,
guest_path: P,
) -> &mut Self {
fn populate_directory(virtentry: HashMap<String, VirtualDirEntry>, dir: &mut VirtualDir) {
for (path, entry) in virtentry.into_iter() {
match entry {
VirtualDirEntry::Directory(dir_entries) => {
let mut subdir = VirtualDir::new(true);
populate_directory(dir_entries, &mut subdir);
dir.add_dir(subdir, path);
}
VirtualDirEntry::File(content) => {
dir.add_file(content, path);
}
}
}
}
let dir = if let VirtualDirEntry::Directory(entries) = dir {
let mut dir = VirtualDir::new(true);
populate_directory(entries, &mut dir);
Box::new(dir)
} else {
panic!("the root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory");
};
self.preopens self.preopens
.as_mut() .as_mut()
.unwrap() .unwrap()
.push((guest_path.as_ref().to_owned(), dir)); .push((guest_path.as_ref().to_owned(), Descriptor::VirtualFile(dir)));
self self
} }
@@ -271,7 +309,7 @@ impl WasiCtxBuilder {
fds.insert(fd, f()?); fds.insert(fd, f()?);
} }
PendingFdEntry::File(f) => { PendingFdEntry::File(f) => {
fds.insert(fd, FdEntry::from(f)?); fds.insert(fd, FdEntry::from(Descriptor::OsHandle(OsHandle::from(f)))?);
} }
} }
} }
@@ -284,9 +322,21 @@ impl WasiCtxBuilder {
// unnecessarily if we have exactly the maximum number of file descriptors. // unnecessarily if we have exactly the maximum number of file descriptors.
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?; preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
if !dir.metadata()?.is_dir() { match &dir {
Descriptor::OsHandle(handle) => {
if !handle.metadata()?.is_dir() {
return Err(Error::EBADF); return Err(Error::EBADF);
} }
}
Descriptor::VirtualFile(virt) => {
if virt.get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY {
return Err(Error::EBADF);
}
}
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
panic!("implementation error, stdin/stdout/stderr shouldn't be in the list of preopens");
}
}
// We don't currently allow setting file descriptors other than 0-2, but this will avoid // We don't currently allow setting file descriptors other than 0-2, but this will avoid
// collisions if we restore that functionality in the future. // collisions if we restore that functionality in the future.

View File

@@ -2,36 +2,75 @@ use crate::sys::dev_null;
use crate::sys::fdentry_impl::{ use crate::sys::fdentry_impl::{
descriptor_as_oshandle, determine_type_and_access_rights, OsHandle, descriptor_as_oshandle, determine_type_and_access_rights, OsHandle,
}; };
use crate::virtfs::VirtualFile;
use crate::{wasi, Error, Result}; use crate::{wasi, Error, Result};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::PathBuf; use std::path::PathBuf;
use std::{fs, io}; use std::{fmt, fs, io};
#[derive(Debug)]
pub(crate) enum Descriptor { pub(crate) enum Descriptor {
OsHandle(OsHandle), OsHandle(OsHandle),
VirtualFile(Box<dyn VirtualFile>),
Stdin, Stdin,
Stdout, Stdout,
Stderr, Stderr,
} }
impl Descriptor { impl From<OsHandle> for Descriptor {
/// Return a reference to the `OsHandle` treating it as an actual file/dir, and fn from(handle: OsHandle) -> Self {
/// allowing operations which require an actual file and not just a stream or Descriptor::OsHandle(handle)
/// socket file descriptor. }
pub(crate) fn as_file(&self) -> Result<&OsHandle> { }
impl From<Box<dyn VirtualFile>> for Descriptor {
fn from(virt: Box<dyn VirtualFile>) -> Self {
Descriptor::VirtualFile(virt)
}
}
impl fmt::Debug for Descriptor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::OsHandle(file) => Ok(file), Descriptor::OsHandle(handle) => write!(f, "{:?}", handle),
Descriptor::VirtualFile(_) => write!(f, "VirtualFile"),
Descriptor::Stdin => write!(f, "Stdin"),
Descriptor::Stdout => write!(f, "Stdout"),
Descriptor::Stderr => write!(f, "Stderr"),
}
}
}
impl Descriptor {
pub(crate) fn try_clone(&self) -> io::Result<Descriptor> {
match self {
Descriptor::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into()),
Descriptor::VirtualFile(virt) => virt.try_clone().map(Descriptor::VirtualFile),
Descriptor::Stdin => Ok(Descriptor::Stdin),
Descriptor::Stdout => Ok(Descriptor::Stdout),
Descriptor::Stderr => Ok(Descriptor::Stderr),
}
}
/// Return a reference to the `OsHandle` or `VirtualFile` treating it as an
/// actual file/dir, and allowing operations which require an actual file and
/// not just a stream or socket file descriptor.
pub(crate) fn as_file<'descriptor>(&'descriptor self) -> Result<&'descriptor Descriptor> {
match self {
Self::OsHandle(_) => Ok(self),
Self::VirtualFile(_) => Ok(self),
_ => Err(Error::EBADF), _ => Err(Error::EBADF),
} }
} }
/// Like `as_file`, but return a mutable reference. /// Like `as_file`, but return a mutable reference.
pub(crate) fn as_file_mut(&mut self) -> Result<&mut OsHandle> { pub(crate) fn as_file_mut<'descriptor>(
&'descriptor mut self,
) -> Result<&'descriptor mut Descriptor> {
match self { match self {
Self::OsHandle(file) => Ok(file), Self::OsHandle(_) => Ok(self),
Self::VirtualFile(_) => Ok(self),
_ => Err(Error::EBADF), _ => Err(Error::EBADF),
} }
} }
@@ -61,16 +100,33 @@ pub(crate) struct FdEntry {
} }
impl FdEntry { impl FdEntry {
pub(crate) fn from(file: fs::File) -> Result<Self> { pub(crate) fn from(file: Descriptor) -> Result<Self> {
unsafe { determine_type_and_access_rights(&file) }.map( match file {
|(file_type, rights_base, rights_inheriting)| Self { Descriptor::OsHandle(handle) => unsafe { determine_type_and_access_rights(&handle) }
.map(|(file_type, rights_base, rights_inheriting)| Self {
file_type, file_type,
descriptor: Descriptor::OsHandle(OsHandle::from(file)), descriptor: handle.into(),
rights_base, rights_base,
rights_inheriting, rights_inheriting,
preopen_path: None, preopen_path: None,
}, }),
) Descriptor::VirtualFile(virt) => {
let file_type = virt.get_file_type();
let rights_base = virt.get_rights_base();
let rights_inheriting = virt.get_rights_inheriting();
Ok(Self {
file_type,
descriptor: virt.into(),
rights_base,
rights_inheriting,
preopen_path: None,
})
}
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
panic!("implementation error, stdin/stdout/stderr FdEntry must not be constructed from FdEntry::from");
}
}
} }
pub(crate) fn duplicate_stdin() -> Result<Self> { pub(crate) fn duplicate_stdin() -> Result<Self> {
@@ -110,7 +166,7 @@ impl FdEntry {
} }
pub(crate) fn null() -> Result<Self> { pub(crate) fn null() -> Result<Self> {
Self::from(dev_null()?) Self::from(OsHandle::from(dev_null()?).into())
} }
/// Convert this `FdEntry` into a host `Descriptor` object provided the specified /// Convert this `FdEntry` into a host `Descriptor` object provided the specified

View File

@@ -22,9 +22,9 @@ pub(crate) unsafe fn iovec_to_host_mut(iovec: &mut __wasi_iovec_t) -> io::IoSlic
} }
#[allow(dead_code)] // trouble with sockets #[allow(dead_code)] // trouble with sockets
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)] #[repr(u8)]
pub(crate) enum FileType { pub enum FileType {
Unknown = __WASI_FILETYPE_UNKNOWN, Unknown = __WASI_FILETYPE_UNKNOWN,
BlockDevice = __WASI_FILETYPE_BLOCK_DEVICE, BlockDevice = __WASI_FILETYPE_BLOCK_DEVICE,
CharacterDevice = __WASI_FILETYPE_CHARACTER_DEVICE, CharacterDevice = __WASI_FILETYPE_CHARACTER_DEVICE,
@@ -39,10 +39,25 @@ impl FileType {
pub(crate) fn to_wasi(&self) -> __wasi_filetype_t { pub(crate) fn to_wasi(&self) -> __wasi_filetype_t {
*self as __wasi_filetype_t *self as __wasi_filetype_t
} }
pub(crate) fn from_wasi(wasi_filetype: u8) -> Option<Self> {
use FileType::*;
match wasi_filetype {
__WASI_FILETYPE_UNKNOWN => Some(Unknown),
__WASI_FILETYPE_BLOCK_DEVICE => Some(BlockDevice),
__WASI_FILETYPE_CHARACTER_DEVICE => Some(CharacterDevice),
__WASI_FILETYPE_DIRECTORY => Some(Directory),
__WASI_FILETYPE_REGULAR_FILE => Some(RegularFile),
__WASI_FILETYPE_SOCKET_DGRAM => Some(SocketDgram),
__WASI_FILETYPE_SOCKET_STREAM => Some(SocketStream),
__WASI_FILETYPE_SYMBOLIC_LINK => Some(Symlink),
_ => None,
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Dirent { pub struct Dirent {
pub name: String, pub name: String,
pub ftype: FileType, pub ftype: FileType,
pub ino: u64, pub ino: u64,

View File

@@ -3,6 +3,7 @@ use super::fs_helpers::path_get;
use crate::ctx::WasiCtx; use crate::ctx::WasiCtx;
use crate::fdentry::{Descriptor, FdEntry}; use crate::fdentry::{Descriptor, FdEntry};
use crate::helpers::*; use crate::helpers::*;
use crate::host::Dirent;
use crate::memory::*; use crate::memory::*;
use crate::sandboxed_tty_writer::SandboxedTTYWriter; use crate::sandboxed_tty_writer::SandboxedTTYWriter;
use crate::sys::hostcalls_impl::fs_helpers::path_open_rights; use crate::sys::hostcalls_impl::fs_helpers::path_open_rights;
@@ -10,7 +11,7 @@ use crate::sys::{host_impl, hostcalls_impl};
use crate::{helpers, host, wasi, wasi32, Error, Result}; use crate::{helpers, host, wasi, wasi32, Error, Result};
use filetime::{set_file_handle_times, FileTime}; use filetime::{set_file_handle_times, FileTime};
use log::trace; use log::trace;
use std::fs::File; use std::convert::TryInto;
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::DerefMut; use std::ops::DerefMut;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -40,12 +41,15 @@ pub(crate) unsafe fn fd_datasync(
) -> Result<()> { ) -> Result<()> {
trace!("fd_datasync(fd={:?})", fd); trace!("fd_datasync(fd={:?})", fd);
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_DATASYNC, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_DATASYNC, 0)?;
.as_file()?;
fd.sync_data().map_err(Into::into) match file {
Descriptor::OsHandle(fd) => fd.sync_data().map_err(Into::into),
Descriptor::VirtualFile(virt) => virt.datasync(),
other => other.as_os_handle().sync_data().map_err(Into::into),
}
} }
pub(crate) unsafe fn fd_pread( pub(crate) unsafe fn fd_pread(
@@ -66,7 +70,7 @@ pub(crate) unsafe fn fd_pread(
nread nread
); );
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_READ | wasi::__WASI_RIGHTS_FD_SEEK, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_READ | wasi::__WASI_RIGHTS_FD_SEEK, 0)?
.as_file()?; .as_file()?;
@@ -76,9 +80,28 @@ pub(crate) unsafe fn fd_pread(
if offset > i64::max_value() as u64 { if offset > i64::max_value() as u64 {
return Err(Error::EIO); return Err(Error::EIO);
} }
let buf_size = iovs.iter().map(|v| v.buf_len).sum(); let buf_size = iovs
let mut buf = vec![0; buf_size]; .iter()
let host_nread = hostcalls_impl::fd_pread(fd, &mut buf, offset)?; .map(|iov| {
let cast_iovlen: wasi32::size_t = iov
.buf_len
.try_into()
.expect("iovec are bounded by wasi max sizes");
cast_iovlen
})
.fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov)))
.ok_or(Error::EINVAL)?;
let mut buf = vec![0; buf_size as usize];
let host_nread = match file {
Descriptor::OsHandle(fd) => hostcalls_impl::fd_pread(&fd, &mut buf, offset)?,
Descriptor::VirtualFile(virt) => virt.pread(&mut buf, offset)?,
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
};
let mut buf_offset = 0; let mut buf_offset = 0;
let mut left = host_nread; let mut left = host_nread;
for iov in &iovs { for iov in &iovs {
@@ -115,7 +138,7 @@ pub(crate) unsafe fn fd_pwrite(
nwritten nwritten
); );
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor( .as_descriptor(
wasi::__WASI_RIGHTS_FD_WRITE | wasi::__WASI_RIGHTS_FD_SEEK, wasi::__WASI_RIGHTS_FD_WRITE | wasi::__WASI_RIGHTS_FD_SEEK,
@@ -127,15 +150,33 @@ pub(crate) unsafe fn fd_pwrite(
if offset > i64::max_value() as u64 { if offset > i64::max_value() as u64 {
return Err(Error::EIO); return Err(Error::EIO);
} }
let buf_size = iovs.iter().map(|v| v.buf_len).sum(); let buf_size = iovs
let mut buf = Vec::with_capacity(buf_size); .iter()
.map(|iov| {
let cast_iovlen: wasi32::size_t = iov
.buf_len
.try_into()
.expect("iovec are bounded by wasi max sizes");
cast_iovlen
})
.fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov)))
.ok_or(Error::EINVAL)?;
let mut buf = Vec::with_capacity(buf_size as usize);
for iov in &iovs { for iov in &iovs {
buf.extend_from_slice(std::slice::from_raw_parts( buf.extend_from_slice(std::slice::from_raw_parts(
iov.buf as *const u8, iov.buf as *const u8,
iov.buf_len, iov.buf_len,
)); ));
} }
let host_nwritten = hostcalls_impl::fd_pwrite(fd, &buf, offset)?; let host_nwritten = match file {
Descriptor::OsHandle(fd) => hostcalls_impl::fd_pwrite(&fd, &buf, offset)?,
Descriptor::VirtualFile(virt) => virt.pwrite(buf.as_mut(), offset)?,
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
};
trace!(" | *nwritten={:?}", host_nwritten); trace!(" | *nwritten={:?}", host_nwritten);
@@ -168,8 +209,9 @@ pub(crate) unsafe fn fd_read(
.get_fd_entry_mut(fd)? .get_fd_entry_mut(fd)?
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READ, 0)? .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READ, 0)?
{ {
Descriptor::OsHandle(file) => file.read_vectored(&mut iovs), Descriptor::OsHandle(file) => file.read_vectored(&mut iovs).map_err(Into::into),
Descriptor::Stdin => io::stdin().read_vectored(&mut iovs), Descriptor::VirtualFile(virt) => virt.read_vectored(&mut iovs),
Descriptor::Stdin => io::stdin().read_vectored(&mut iovs).map_err(Into::into),
_ => return Err(Error::EBADF), _ => return Err(Error::EBADF),
}; };
@@ -232,7 +274,7 @@ pub(crate) unsafe fn fd_seek(
} else { } else {
wasi::__WASI_RIGHTS_FD_SEEK | wasi::__WASI_RIGHTS_FD_TELL wasi::__WASI_RIGHTS_FD_SEEK | wasi::__WASI_RIGHTS_FD_TELL
}; };
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry_mut(fd)? .get_fd_entry_mut(fd)?
.as_descriptor_mut(rights, 0)? .as_descriptor_mut(rights, 0)?
.as_file_mut()?; .as_file_mut()?;
@@ -243,7 +285,15 @@ pub(crate) unsafe fn fd_seek(
wasi::__WASI_WHENCE_SET => SeekFrom::Start(offset as u64), wasi::__WASI_WHENCE_SET => SeekFrom::Start(offset as u64),
_ => return Err(Error::EINVAL), _ => return Err(Error::EINVAL),
}; };
let host_newoffset = fd.seek(pos)?; let host_newoffset = match file {
Descriptor::OsHandle(fd) => fd.seek(pos)?,
Descriptor::VirtualFile(virt) => virt.seek(pos)?,
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
};
trace!(" | *newoffset={:?}", host_newoffset); trace!(" | *newoffset={:?}", host_newoffset);
@@ -258,12 +308,20 @@ pub(crate) unsafe fn fd_tell(
) -> Result<()> { ) -> Result<()> {
trace!("fd_tell(fd={:?}, newoffset={:#x?})", fd, newoffset); trace!("fd_tell(fd={:?}, newoffset={:#x?})", fd, newoffset);
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry_mut(fd)? .get_fd_entry_mut(fd)?
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_TELL, 0)? .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_TELL, 0)?
.as_file_mut()?; .as_file_mut()?;
let host_offset = fd.seek(SeekFrom::Current(0))?; let host_offset = match file {
Descriptor::OsHandle(fd) => fd.seek(SeekFrom::Current(0))?,
Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?,
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
};
trace!(" | *newoffset={:?}", host_offset); trace!(" | *newoffset={:?}", host_offset);
@@ -279,12 +337,13 @@ pub(crate) unsafe fn fd_fdstat_get(
trace!("fd_fdstat_get(fd={:?}, fdstat_ptr={:#x?})", fd, fdstat_ptr); trace!("fd_fdstat_get(fd={:?}, fdstat_ptr={:#x?})", fd, fdstat_ptr);
let mut fdstat = dec_fdstat_byref(memory, fdstat_ptr)?; let mut fdstat = dec_fdstat_byref(memory, fdstat_ptr)?;
let host_fd = wasi_ctx let wasi_file = wasi_ctx.get_fd_entry(fd)?.as_descriptor(0, 0)?;
.get_fd_entry(fd)?
.as_descriptor(0, 0)?
.as_os_handle();
let fs_flags = hostcalls_impl::fd_fdstat_get(&host_fd)?; let fs_flags = match wasi_file {
Descriptor::OsHandle(wasi_fd) => hostcalls_impl::fd_fdstat_get(&wasi_fd)?,
Descriptor::VirtualFile(virt) => virt.fdstat_get(),
other => hostcalls_impl::fd_fdstat_get(&other.as_os_handle())?,
};
let fe = wasi_ctx.get_fd_entry(fd)?; let fe = wasi_ctx.get_fd_entry(fd)?;
fdstat.fs_filetype = fe.file_type; fdstat.fs_filetype = fe.file_type;
@@ -309,11 +368,28 @@ pub(crate) unsafe fn fd_fdstat_set_flags(
.get_fd_entry_mut(fd)? .get_fd_entry_mut(fd)?
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_FDSTAT_SET_FLAGS, 0)?; .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_FDSTAT_SET_FLAGS, 0)?;
if let Some(new_handle) = match descriptor {
hostcalls_impl::fd_fdstat_set_flags(&descriptor.as_os_handle(), fdflags)? Descriptor::OsHandle(handle) => {
{ let set_result =
*descriptor = Descriptor::OsHandle(new_handle); hostcalls_impl::fd_fdstat_set_flags(&handle, fdflags)?.map(Descriptor::OsHandle);
if let Some(new_descriptor) = set_result {
*descriptor = new_descriptor;
} }
}
Descriptor::VirtualFile(handle) => {
handle.fdstat_set_flags(fdflags)?;
}
_ => {
let set_result =
hostcalls_impl::fd_fdstat_set_flags(&descriptor.as_os_handle(), fdflags)?
.map(Descriptor::OsHandle);
if let Some(new_descriptor) = set_result {
*descriptor = new_descriptor;
}
}
};
Ok(()) Ok(())
} }
@@ -351,11 +427,19 @@ pub(crate) unsafe fn fd_sync(
) -> Result<()> { ) -> Result<()> {
trace!("fd_sync(fd={:?})", fd); trace!("fd_sync(fd={:?})", fd);
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_SYNC, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_SYNC, 0)?
.as_file()?; .as_file()?;
fd.sync_all().map_err(Into::into) match file {
Descriptor::OsHandle(fd) => fd.sync_all().map_err(Into::into),
Descriptor::VirtualFile(virt) => virt.sync(),
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
}
} }
pub(crate) unsafe fn fd_write( pub(crate) unsafe fn fd_write(
@@ -389,6 +473,13 @@ pub(crate) unsafe fn fd_write(
file.write_vectored(&iovs)? file.write_vectored(&iovs)?
} }
} }
Descriptor::VirtualFile(virt) => {
if isatty {
unimplemented!("writes to virtual tty");
} else {
virt.write_vectored(&iovs)?
}
}
Descriptor::Stdin => return Err(Error::EBADF), Descriptor::Stdin => return Err(Error::EBADF),
Descriptor::Stdout => { Descriptor::Stdout => {
// lock for the duration of the scope // lock for the duration of the scope
@@ -415,7 +506,7 @@ pub(crate) unsafe fn fd_write(
} }
pub(crate) unsafe fn fd_advise( pub(crate) unsafe fn fd_advise(
wasi_ctx: &WasiCtx, wasi_ctx: &mut WasiCtx,
_memory: &mut [u8], _memory: &mut [u8],
fd: wasi::__wasi_fd_t, fd: wasi::__wasi_fd_t,
offset: wasi::__wasi_filesize_t, offset: wasi::__wasi_filesize_t,
@@ -430,12 +521,20 @@ pub(crate) unsafe fn fd_advise(
advice advice
); );
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry_mut(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_ADVISE, 0)? .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_ADVISE, 0)?
.as_file()?; .as_file_mut()?;
hostcalls_impl::fd_advise(fd, advice, offset, len) match file {
Descriptor::OsHandle(fd) => hostcalls_impl::fd_advise(&fd, advice, offset, len),
Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len),
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
}
} }
pub(crate) unsafe fn fd_allocate( pub(crate) unsafe fn fd_allocate(
@@ -447,11 +546,13 @@ pub(crate) unsafe fn fd_allocate(
) -> Result<()> { ) -> Result<()> {
trace!("fd_allocate(fd={:?}, offset={}, len={})", fd, offset, len); trace!("fd_allocate(fd={:?}, offset={}, len={})", fd, offset, len);
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_ALLOCATE, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_ALLOCATE, 0)?
.as_file()?; .as_file()?;
match file {
Descriptor::OsHandle(fd) => {
let metadata = fd.metadata()?; let metadata = fd.metadata()?;
let current_size = metadata.len(); let current_size = metadata.len();
@@ -466,6 +567,14 @@ pub(crate) unsafe fn fd_allocate(
} else { } else {
Ok(()) Ok(())
} }
}
Descriptor::VirtualFile(virt) => virt.allocate(offset, len),
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
}
} }
pub(crate) unsafe fn path_create_directory( pub(crate) unsafe fn path_create_directory(
@@ -490,7 +599,7 @@ pub(crate) unsafe fn path_create_directory(
let fe = wasi_ctx.get_fd_entry(dirfd)?; let fe = wasi_ctx.get_fd_entry(dirfd)?;
let resolved = path_get(fe, rights, 0, 0, path, false)?; let resolved = path_get(fe, rights, 0, 0, path, false)?;
hostcalls_impl::path_create_directory(resolved) resolved.path_create_directory()
} }
pub(crate) unsafe fn path_link( pub(crate) unsafe fn path_link(
@@ -607,7 +716,7 @@ pub(crate) unsafe fn path_open(
read, read,
write write
); );
let fd = hostcalls_impl::path_open(resolved, read, write, oflags, fs_flags)?; let fd = resolved.open_with(read, write, oflags, fs_flags)?;
let mut fe = FdEntry::from(fd)?; let mut fe = FdEntry::from(fd)?;
// We need to manually deny the rights which are not explicitly requested // We need to manually deny the rights which are not explicitly requested
@@ -652,7 +761,12 @@ pub(crate) unsafe fn path_readlink(
let mut buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?; let mut buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?;
let host_bufused = hostcalls_impl::path_readlink(resolved, &mut buf)?; let host_bufused = match resolved.dirfd() {
Descriptor::VirtualFile(_virt) => {
unimplemented!("virtual readlink");
}
_ => hostcalls_impl::path_readlink(resolved, &mut buf)?,
};
trace!(" | (buf_ptr,*buf_used)={:?}", buf); trace!(" | (buf_ptr,*buf_used)={:?}", buf);
trace!(" | *buf_used={:?}", host_bufused); trace!(" | *buf_used={:?}", host_bufused);
@@ -708,7 +822,15 @@ pub(crate) unsafe fn path_rename(
log::debug!("path_rename resolved_old={:?}", resolved_old); log::debug!("path_rename resolved_old={:?}", resolved_old);
log::debug!("path_rename resolved_new={:?}", resolved_new); log::debug!("path_rename resolved_new={:?}", resolved_new);
if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) =
(resolved_old.dirfd(), resolved_new.dirfd())
{
hostcalls_impl::path_rename(resolved_old, resolved_new) hostcalls_impl::path_rename(resolved_old, resolved_new)
} else {
// Virtual files do not support rename, at the moment, and streams don't have paths to
// rename, so any combination of Descriptor that gets here is an error in the making.
panic!("path_rename with one or more non-OS files");
}
} }
pub(crate) unsafe fn fd_filestat_get( pub(crate) unsafe fn fd_filestat_get(
@@ -727,7 +849,15 @@ pub(crate) unsafe fn fd_filestat_get(
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_GET, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_GET, 0)?
.as_file()?; .as_file()?;
let host_filestat = hostcalls_impl::fd_filestat_get(fd)?; let host_filestat = match fd {
Descriptor::OsHandle(fd) => hostcalls_impl::fd_filestat_get(&fd)?,
Descriptor::VirtualFile(virt) => virt.filestat_get()?,
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
};
trace!(" | *filestat_ptr={:?}", host_filestat); trace!(" | *filestat_ptr={:?}", host_filestat);
@@ -755,11 +885,11 @@ pub(crate) unsafe fn fd_filestat_set_times(
.as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_TIMES, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_TIMES, 0)?
.as_file()?; .as_file()?;
fd_filestat_set_times_impl(fd, st_atim, st_mtim, fst_flags) fd_filestat_set_times_impl(&fd, st_atim, st_mtim, fst_flags)
} }
pub(crate) fn fd_filestat_set_times_impl( pub(crate) fn fd_filestat_set_times_impl(
fd: &File, file: &Descriptor,
st_atim: wasi::__wasi_timestamp_t, st_atim: wasi::__wasi_timestamp_t,
st_mtim: wasi::__wasi_timestamp_t, st_mtim: wasi::__wasi_timestamp_t,
fst_flags: wasi::__wasi_fstflags_t, fst_flags: wasi::__wasi_fstflags_t,
@@ -791,7 +921,15 @@ pub(crate) fn fd_filestat_set_times_impl(
} else { } else {
None None
}; };
set_file_handle_times(fd, atim, mtim).map_err(Into::into) match file {
Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into),
Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim),
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
}
} }
pub(crate) unsafe fn fd_filestat_set_size( pub(crate) unsafe fn fd_filestat_set_size(
@@ -802,7 +940,7 @@ pub(crate) unsafe fn fd_filestat_set_size(
) -> Result<()> { ) -> Result<()> {
trace!("fd_filestat_set_size(fd={:?}, st_size={})", fd, st_size); trace!("fd_filestat_set_size(fd={:?}, st_size={})", fd, st_size);
let fd = wasi_ctx let file = wasi_ctx
.get_fd_entry(fd)? .get_fd_entry(fd)?
.as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_SIZE, 0)? .as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_SIZE, 0)?
.as_file()?; .as_file()?;
@@ -811,7 +949,15 @@ pub(crate) unsafe fn fd_filestat_set_size(
if st_size > i64::max_value() as u64 { if st_size > i64::max_value() as u64 {
return Err(Error::E2BIG); return Err(Error::E2BIG);
} }
fd.set_len(st_size).map_err(Into::into) match file {
Descriptor::OsHandle(fd) => fd.set_len(st_size).map_err(Into::into),
Descriptor::VirtualFile(virt) => virt.filestat_set_size(st_size),
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
}
} }
pub(crate) unsafe fn path_filestat_get( pub(crate) unsafe fn path_filestat_get(
@@ -845,7 +991,12 @@ pub(crate) unsafe fn path_filestat_get(
path, path,
false, false,
)?; )?;
let host_filestat = hostcalls_impl::path_filestat_get(resolved, dirflags)?; let host_filestat = match resolved.dirfd() {
Descriptor::VirtualFile(virt) => virt
.openat(std::path::Path::new(resolved.path()), false, false, 0, 0)?
.filestat_get()?,
_ => hostcalls_impl::path_filestat_get(resolved, dirflags)?,
};
trace!(" | *filestat_ptr={:?}", host_filestat); trace!(" | *filestat_ptr={:?}", host_filestat);
@@ -887,7 +1038,14 @@ pub(crate) unsafe fn path_filestat_set_times(
false, false,
)?; )?;
match resolved.dirfd() {
Descriptor::VirtualFile(_virt) => {
unimplemented!("virtual filestat_set_times");
}
_ => {
hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags) hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags)
}
}
} }
pub(crate) unsafe fn path_symlink( pub(crate) unsafe fn path_symlink(
@@ -917,7 +1075,12 @@ pub(crate) unsafe fn path_symlink(
let fe = wasi_ctx.get_fd_entry(dirfd)?; let fe = wasi_ctx.get_fd_entry(dirfd)?;
let resolved_new = path_get(fe, wasi::__WASI_RIGHTS_PATH_SYMLINK, 0, 0, new_path, true)?; let resolved_new = path_get(fe, wasi::__WASI_RIGHTS_PATH_SYMLINK, 0, 0, new_path, true)?;
hostcalls_impl::path_symlink(old_path, resolved_new) match resolved_new.dirfd() {
Descriptor::VirtualFile(_virt) => {
unimplemented!("virtual path_symlink");
}
_non_virtual => hostcalls_impl::path_symlink(old_path, resolved_new),
}
} }
pub(crate) unsafe fn path_unlink_file( pub(crate) unsafe fn path_unlink_file(
@@ -941,7 +1104,10 @@ pub(crate) unsafe fn path_unlink_file(
let fe = wasi_ctx.get_fd_entry(dirfd)?; let fe = wasi_ctx.get_fd_entry(dirfd)?;
let resolved = path_get(fe, wasi::__WASI_RIGHTS_PATH_UNLINK_FILE, 0, 0, path, false)?; let resolved = path_get(fe, wasi::__WASI_RIGHTS_PATH_UNLINK_FILE, 0, 0, path, false)?;
hostcalls_impl::path_unlink_file(resolved) match resolved.dirfd() {
Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()),
_ => hostcalls_impl::path_unlink_file(resolved),
}
} }
pub(crate) unsafe fn path_remove_directory( pub(crate) unsafe fn path_remove_directory(
@@ -974,7 +1140,10 @@ pub(crate) unsafe fn path_remove_directory(
log::debug!("path_remove_directory resolved={:?}", resolved); log::debug!("path_remove_directory resolved={:?}", resolved);
hostcalls_impl::path_remove_directory(resolved) match resolved.dirfd() {
Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()),
_ => hostcalls_impl::path_remove_directory(resolved),
}
} }
pub(crate) unsafe fn fd_prestat_get( pub(crate) unsafe fn fd_prestat_get(
@@ -1068,11 +1237,14 @@ pub(crate) unsafe fn fd_readdir(
.get_fd_entry_mut(fd)? .get_fd_entry_mut(fd)?
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READDIR, 0)? .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READDIR, 0)?
.as_file_mut()?; .as_file_mut()?;
let mut host_buf = dec_slice_of_mut_u8(memory, buf, buf_len)?; let host_buf = dec_slice_of_mut_u8(memory, buf, buf_len)?;
trace!(" | (buf,buf_len)={:?}", host_buf); trace!(" | (buf,buf_len)={:?}", host_buf);
let iter = hostcalls_impl::fd_readdir(file, cookie)?; fn copy_entities<T: Iterator<Item = Result<Dirent>>>(
iter: T,
mut host_buf: &mut [u8],
) -> Result<usize> {
let mut host_bufused = 0; let mut host_bufused = 0;
for dirent in iter { for dirent in iter {
let dirent_raw = dirent?.to_wasi_raw()?; let dirent_raw = dirent?.to_wasi_raw()?;
@@ -1085,6 +1257,20 @@ pub(crate) unsafe fn fd_readdir(
host_buf = &mut host_buf[offset..]; host_buf = &mut host_buf[offset..];
} }
} }
Ok(host_bufused)
}
let host_bufused = match file {
Descriptor::OsHandle(file) => {
copy_entities(hostcalls_impl::fd_readdir(file, cookie)?, host_buf)?
}
Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, host_buf)?,
_ => {
unreachable!(
"implementation error: fd should have been checked to not be a stream already"
);
}
};
trace!(" | *buf_used={:?}", host_bufused); trace!(" | *buf_used={:?}", host_bufused);

View File

@@ -1,24 +1,100 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
use crate::sys::fdentry_impl::OsHandle;
use crate::sys::host_impl; use crate::sys::host_impl;
use crate::sys::hostcalls_impl::fs_helpers::*; use crate::sys::hostcalls_impl::fs_helpers::*;
use crate::{error::WasiError, fdentry::FdEntry, wasi, Error, Result}; use crate::{error::WasiError, fdentry::Descriptor, fdentry::FdEntry, wasi, Error, Result};
use std::fs::File;
use std::path::{Component, Path}; use std::path::{Component, Path};
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PathGet { pub(crate) struct PathGet {
dirfd: File, dirfd: Descriptor,
path: String, path: String,
} }
impl PathGet { impl PathGet {
pub(crate) fn dirfd(&self) -> &File { pub(crate) fn dirfd(&self) -> &Descriptor {
&self.dirfd &self.dirfd
} }
pub(crate) fn path(&self) -> &str { pub(crate) fn path(&self) -> &str {
&self.path &self.path
} }
pub(crate) fn path_create_directory(self) -> Result<()> {
match &self.dirfd {
Descriptor::OsHandle(file) => {
crate::sys::hostcalls_impl::path_create_directory(&file, &self.path)
}
Descriptor::VirtualFile(virt) => virt.create_directory(&Path::new(&self.path)),
other => {
panic!("invalid descriptor to create directory: {:?}", other);
}
}
}
pub(crate) fn open_with(
self,
read: bool,
write: bool,
oflags: u16,
fs_flags: u16,
) -> Result<Descriptor> {
match &self.dirfd {
Descriptor::OsHandle(_) => {
crate::sys::hostcalls_impl::path_open(self, read, write, oflags, fs_flags)
.map_err(Into::into)
}
Descriptor::VirtualFile(virt) => virt
.openat(Path::new(&self.path), read, write, oflags, fs_flags)
.map(|file| Descriptor::VirtualFile(file)),
other => {
panic!("invalid descriptor to path_open: {:?}", other);
}
}
}
}
struct PathRef<'a, 'b> {
dirfd: &'a Descriptor,
path: &'b str,
}
impl<'a, 'b> PathRef<'a, 'b> {
fn new(dirfd: &'a Descriptor, path: &'b str) -> Self {
PathRef { dirfd, path }
}
fn open(&self) -> Result<Descriptor> {
match self.dirfd {
Descriptor::OsHandle(file) => Ok(Descriptor::OsHandle(OsHandle::from(openat(
&file, &self.path,
)?))),
Descriptor::VirtualFile(virt) => virt
.openat(
Path::new(&self.path),
false,
false,
wasi::__WASI_OFLAGS_DIRECTORY,
0,
)
.map(|file| Descriptor::VirtualFile(file)),
other => {
panic!("invalid descriptor for open: {:?}", other);
}
}
}
fn readlink(&self) -> Result<String> {
match self.dirfd {
Descriptor::OsHandle(file) => readlinkat(file, self.path),
Descriptor::VirtualFile(virt) => {
virt.readlinkat(Path::new(self.path)).map_err(Into::into)
}
other => {
panic!("invalid descriptor for readlink: {:?}", other);
}
}
}
} }
/// Normalizes a path to ensure that the target path is located under the directory provided. /// Normalizes a path to ensure that the target path is located under the directory provided.
@@ -112,7 +188,9 @@ pub(crate) fn path_get(
} }
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) { if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
match openat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) { match PathRef::new(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head)
.open()
{
Ok(new_dir) => { Ok(new_dir) => {
dir_stack.push(new_dir); dir_stack.push(new_dir);
} }
@@ -125,10 +203,11 @@ pub(crate) fn path_get(
// this with ENOTDIR because of the O_DIRECTORY flag. // this with ENOTDIR because of the O_DIRECTORY flag.
{ {
// attempt symlink expansion // attempt symlink expansion
let mut link_path = readlinkat( let mut link_path = PathRef::new(
dir_stack.last().ok_or(Error::ENOTCAPABLE)?, dir_stack.last().ok_or(Error::ENOTCAPABLE)?,
&head, &head,
)?; )
.readlink()?;
symlink_expansions += 1; symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS { if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
@@ -159,7 +238,9 @@ pub(crate) fn path_get(
{ {
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
// symlink expansion // symlink expansion
match readlinkat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) { match PathRef::new(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head)
.readlink()
{
Ok(mut link_path) => { Ok(mut link_path) => {
symlink_expansions += 1; symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS { if symlink_expansions > MAX_SYMLINK_EXPANSIONS {

View File

@@ -32,6 +32,8 @@ mod memory;
pub mod old; pub mod old;
mod sandboxed_tty_writer; mod sandboxed_tty_writer;
mod sys; mod sys;
mod virtfs;
pub use virtfs::{FileContents, VirtualDirEntry};
pub mod wasi; pub mod wasi;
pub mod wasi32; pub mod wasi32;

View File

@@ -207,7 +207,7 @@ pub(crate) fn dec_ciovec_slice(
.iter() .iter()
.map(|raw_iov| { .map(|raw_iov| {
let len = dec_usize(PrimInt::from_le(raw_iov.buf_len)); let len = dec_usize(PrimInt::from_le(raw_iov.buf_len));
let buf = PrimInt::from_le(raw_iov.buf); let buf: u32 = PrimInt::from_le(raw_iov.buf);
Ok(host::__wasi_ciovec_t { Ok(host::__wasi_ciovec_t {
buf: dec_ptr(memory, buf, len)? as *const u8, buf: dec_ptr(memory, buf, len)? as *const u8,
buf_len: len, buf_len: len,

View File

@@ -11,6 +11,7 @@ impl AsRawFd for Descriptor {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
match self { match self {
Self::OsHandle(file) => file.as_raw_fd(), Self::OsHandle(file) => file.as_raw_fd(),
Self::VirtualFile(_) => panic!("virtual files do not have a raw fd"),
Self::Stdin => io::stdin().as_raw_fd(), Self::Stdin => io::stdin().as_raw_fd(),
Self::Stdout => io::stdout().as_raw_fd(), Self::Stdout => io::stdout().as_raw_fd(),
Self::Stderr => io::stderr().as_raw_fd(), Self::Stderr => io::stderr().as_raw_fd(),

View File

@@ -1,5 +1,6 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(unused_unsafe)] #![allow(unused_unsafe)]
use crate::fdentry::Descriptor;
use crate::host::Dirent; use crate::host::Dirent;
use crate::hostcalls_impl::PathGet; use crate::hostcalls_impl::PathGet;
use crate::sys::{fdentry_impl::OsHandle, host_impl, unix::sys_impl}; use crate::sys::{fdentry_impl::OsHandle, host_impl, unix::sys_impl};
@@ -60,16 +61,9 @@ pub(crate) fn fd_advise(
unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into) unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into)
} }
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> { pub(crate) fn path_create_directory(base: &File, path: &str) -> Result<()> {
use yanix::file::{mkdirat, Mode}; use yanix::file::{mkdirat, Mode};
unsafe { unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777)) }.map_err(Into::into)
mkdirat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
Mode::from_bits_truncate(0o777),
)
}
.map_err(Into::into)
} }
pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
@@ -92,7 +86,7 @@ pub(crate) fn path_open(
write: bool, write: bool,
oflags: wasi::__wasi_oflags_t, oflags: wasi::__wasi_oflags_t,
fs_flags: wasi::__wasi_fdflags_t, fs_flags: wasi::__wasi_fdflags_t,
) -> Result<File> { ) -> Result<Descriptor> {
use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag}; use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag};
let mut nix_all_oflags = if read && write { let mut nix_all_oflags = if read && write {
@@ -119,14 +113,15 @@ pub(crate) fn path_open(
log::debug!("path_open resolved = {:?}", resolved); log::debug!("path_open resolved = {:?}", resolved);
log::debug!("path_open oflags = {:?}", nix_all_oflags); log::debug!("path_open oflags = {:?}", nix_all_oflags);
let new_fd = match unsafe { let fd_no = unsafe {
openat( openat(
resolved.dirfd().as_raw_fd(), resolved.dirfd().as_raw_fd(),
resolved.path(), resolved.path(),
nix_all_oflags, nix_all_oflags,
Mode::from_bits_truncate(0o666), Mode::from_bits_truncate(0o666),
) )
} { };
let new_fd = match fd_no {
Ok(fd) => fd, Ok(fd) => fd,
Err(e) => { Err(e) => {
if let yanix::Error::Io(ref err) = e { if let yanix::Error::Io(ref err) = e {
@@ -188,7 +183,7 @@ pub(crate) fn path_open(
log::debug!("path_open (host) new_fd = {:?}", new_fd); log::debug!("path_open (host) new_fd = {:?}", new_fd);
// Determine the type of the new file descriptor and which rights contradict with this type // Determine the type of the new file descriptor and which rights contradict with this type
Ok(unsafe { File::from_raw_fd(new_fd) }) Ok(OsHandle::from(unsafe { File::from_raw_fd(new_fd) }).into())
} }
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> { pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
@@ -263,7 +258,7 @@ pub(crate) fn path_filestat_set_times(
}; };
utimensat( utimensat(
resolved.dirfd(), &resolved.dirfd().as_os_handle(),
resolved.path(), resolved.path(),
atim, atim,
mtim, mtim,
@@ -274,6 +269,7 @@ pub(crate) fn path_filestat_set_times(
pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> { pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> {
use yanix::file::{unlinkat, AtFlag}; use yanix::file::{unlinkat, AtFlag};
unsafe { unsafe {
unlinkat( unlinkat(
resolved.dirfd().as_raw_fd(), resolved.dirfd().as_raw_fd(),

View File

@@ -1,3 +1,4 @@
use crate::fdentry::Descriptor;
use crate::hostcalls_impl::PathGet; use crate::hostcalls_impl::PathGet;
use crate::Result; use crate::Result;
use std::os::unix::prelude::AsRawFd; use std::os::unix::prelude::AsRawFd;
@@ -26,15 +27,22 @@ pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use yanix::file::renameat; use yanix::file::renameat;
match (resolved_old.dirfd(), resolved_new.dirfd()) {
(Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => {
unsafe { unsafe {
renameat( renameat(
resolved_old.dirfd().as_raw_fd(), resolved_old_file.as_raw_fd(),
resolved_old.path(), resolved_old.path(),
resolved_new.dirfd().as_raw_fd(), resolved_new_file.as_raw_fd(),
resolved_new.path(), resolved_new.path(),
) )
} }
.map_err(Into::into) .map_err(Into::into)
}
_ => {
unimplemented!("path_link with one or more virtual files");
}
}
} }
pub(crate) mod fd_readdir_impl { pub(crate) mod fd_readdir_impl {

View File

@@ -39,6 +39,9 @@ impl AsRawHandle for Descriptor {
fn as_raw_handle(&self) -> RawHandle { fn as_raw_handle(&self) -> RawHandle {
match self { match self {
Self::OsHandle(file) => file.as_raw_handle(), Self::OsHandle(file) => file.as_raw_handle(),
Self::VirtualFile(_file) => {
unimplemented!("virtual as_raw_handle");
}
Self::Stdin => io::stdin().as_raw_handle(), Self::Stdin => io::stdin().as_raw_handle(),
Self::Stdout => io::stdout().as_raw_handle(), Self::Stdout => io::stdout().as_raw_handle(),
Self::Stderr => io::stderr().as_raw_handle(), Self::Stderr => io::stderr().as_raw_handle(),

View File

@@ -2,7 +2,7 @@
#![allow(unused)] #![allow(unused)]
use super::fs_helpers::*; use super::fs_helpers::*;
use crate::ctx::WasiCtx; use crate::ctx::WasiCtx;
use crate::fdentry::FdEntry; use crate::fdentry::{Descriptor, FdEntry};
use crate::host::{Dirent, FileType}; use crate::host::{Dirent, FileType};
use crate::hostcalls_impl::{fd_filestat_set_times_impl, PathGet}; use crate::hostcalls_impl::{fd_filestat_set_times_impl, PathGet};
use crate::sys::fdentry_impl::{determine_type_rights, OsHandle}; use crate::sys::fdentry_impl::{determine_type_rights, OsHandle};
@@ -119,8 +119,8 @@ pub(crate) fn fd_advise(
Ok(()) Ok(())
} }
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> { pub(crate) fn path_create_directory(file: &File, path: &str) -> Result<()> {
let path = resolved.concatenate()?; let path = concatenate(file, path)?;
std::fs::create_dir(&path).map_err(Into::into) std::fs::create_dir(&path).map_err(Into::into)
} }
@@ -134,7 +134,7 @@ pub(crate) fn path_open(
write: bool, write: bool,
oflags: wasi::__wasi_oflags_t, oflags: wasi::__wasi_oflags_t,
fdflags: wasi::__wasi_fdflags_t, fdflags: wasi::__wasi_fdflags_t,
) -> Result<File> { ) -> Result<Descriptor> {
use winx::file::{AccessMode, CreationDisposition, Flags}; use winx::file::{AccessMode, CreationDisposition, Flags};
let is_trunc = oflags & wasi::__WASI_OFLAGS_TRUNC != 0; let is_trunc = oflags & wasi::__WASI_OFLAGS_TRUNC != 0;
@@ -207,6 +207,7 @@ pub(crate) fn path_open(
opts.access_mode(access_mode.bits()) opts.access_mode(access_mode.bits())
.custom_flags(file_flags_from_fdflags(fdflags).bits()) .custom_flags(file_flags_from_fdflags(fdflags).bits())
.open(&path) .open(&path)
.map(|f| OsHandle::from(f).into())
.map_err(Into::into) .map_err(Into::into)
} }
@@ -371,7 +372,7 @@ pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize>
// we need to strip the prefix from the absolute path // we need to strip the prefix from the absolute path
// as otherwise we will error out since WASI is not capable // as otherwise we will error out since WASI is not capable
// of dealing with absolute paths // of dealing with absolute paths
let dir_path = get_file_path(resolved.dirfd())?; let dir_path = get_file_path(&resolved.dirfd().as_os_handle())?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path let target_path = target_path
.strip_prefix(dir_path) .strip_prefix(dir_path)
@@ -401,7 +402,7 @@ pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize>
fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result<Option<PathBuf>> { fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result<Option<PathBuf>> {
if resolved.path().ends_with('/') { if resolved.path().ends_with('/') {
let suffix = resolved.path().trim_end_matches('/'); let suffix = resolved.path().trim_end_matches('/');
concatenate(resolved.dirfd(), Path::new(suffix)).map(Some) concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some)
} else { } else {
Ok(None) Ok(None)
} }
@@ -492,14 +493,15 @@ pub(crate) fn path_filestat_set_times(
let file = OpenOptions::new() let file = OpenOptions::new()
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits()) .access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
.open(path)?; .open(path)?;
fd_filestat_set_times_impl(&file, st_atim, st_mtim, fst_flags) let modifiable_fd = Descriptor::OsHandle(OsHandle::from(file));
fd_filestat_set_times_impl(&modifiable_fd, st_atim, st_mtim, fst_flags)
} }
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
use winx::winerror::WinError; use winx::winerror::WinError;
let old_path = concatenate(resolved.dirfd(), Path::new(old_path))?; let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?;
let new_path = resolved.concatenate()?; let new_path = resolved.concatenate()?;
// try creating a file symlink // try creating a file symlink

View File

@@ -1,4 +1,5 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
use crate::fdentry::Descriptor;
use crate::hostcalls_impl::PathGet; use crate::hostcalls_impl::PathGet;
use crate::{wasi, Error, Result}; use crate::{wasi, Error, Result};
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
@@ -12,7 +13,15 @@ pub(crate) trait PathGetExt {
impl PathGetExt for PathGet { impl PathGetExt for PathGet {
fn concatenate(&self) -> Result<PathBuf> { fn concatenate(&self) -> Result<PathBuf> {
concatenate(self.dirfd(), Path::new(self.path())) match self.dirfd() {
Descriptor::OsHandle(file) => concatenate(file, Path::new(self.path())),
Descriptor::VirtualFile(_virt) => {
panic!("concatenate on a virtual base");
}
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
unreachable!("streams do not have paths and should not be accessible via PathGet");
}
}
} }
} }
@@ -126,7 +135,7 @@ pub(crate) fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
} }
} }
pub(crate) fn concatenate<P: AsRef<Path>>(dirfd: &File, path: P) -> Result<PathBuf> { pub(crate) fn concatenate<P: AsRef<Path>>(file: &File, path: P) -> Result<PathBuf> {
use winx::file::get_file_path; use winx::file::get_file_path;
// WASI is not able to deal with absolute paths // WASI is not able to deal with absolute paths
@@ -135,7 +144,7 @@ pub(crate) fn concatenate<P: AsRef<Path>>(dirfd: &File, path: P) -> Result<PathB
return Err(Error::ENOTCAPABLE); return Err(Error::ENOTCAPABLE);
} }
let dir_path = get_file_path(dirfd)?; let dir_path = get_file_path(file)?;
// concatenate paths // concatenate paths
let mut out_path = PathBuf::from(dir_path); let mut out_path = PathBuf::from(dir_path);
out_path.push(path.as_ref()); out_path.push(path.as_ref());

View File

@@ -222,6 +222,9 @@ fn handle_rw_event(event: FdEventData, out_events: &mut Vec<wasi::__wasi_event_t
Descriptor::Stdin => Ok(1), Descriptor::Stdin => Ok(1),
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows. // On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
Descriptor::Stdout | Descriptor::Stderr => Ok(0), Descriptor::Stdout | Descriptor::Stderr => Ok(0),
Descriptor::VirtualFile(_) => {
panic!("virtual files do not get rw events");
}
}; };
let new_event = make_rw_event(&event, size); let new_event = make_rw_event(&event, size);
@@ -295,6 +298,9 @@ pub(crate) fn poll_oneoff(
unreachable!(); unreachable!();
} }
} }
Descriptor::VirtualFile(_) => {
panic!("virtual files do not get rw events");
}
} }
} }

View File

@@ -0,0 +1,878 @@
use crate::host::Dirent;
use crate::host::FileType;
use crate::{wasi, wasi32, Error, Result};
use filetime::FileTime;
use log::trace;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::convert::TryInto;
use std::io;
use std::io::SeekFrom;
use std::path::{Path, PathBuf};
use std::rc::Rc;
pub enum VirtualDirEntry {
Directory(HashMap<String, VirtualDirEntry>),
File(Box<dyn FileContents>),
}
impl VirtualDirEntry {
pub fn empty_directory() -> Self {
VirtualDirEntry::Directory(HashMap::new())
}
}
/// Files and directories may be moved, and for implementation reasons retain a reference to their
/// parent VirtualFile, so files that can be moved must provide an interface to update their parent
/// reference.
pub(crate) trait MovableFile {
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>);
}
/// `VirtualFile` encompasses the whole interface of a `File`, `Directory`, or `Stream`-like
/// object, suitable for forwarding from `wasi-common` public interfaces. `File` and
/// `Directory`-style objects can be moved, so implemetors of this trait must also implement
/// `MovableFile`.
///
/// Default implementations of functions here fail in ways that are intended to mimic a file-like
/// object with no permissions, no content, and that cannot be used in any way.
pub(crate) trait VirtualFile: MovableFile {
fn fdstat_get(&self) -> wasi::__wasi_fdflags_t {
0
}
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>>;
fn readlinkat(&self, _path: &Path) -> Result<String> {
Err(Error::EACCES)
}
fn openat(
&self,
_path: &Path,
_read: bool,
_write: bool,
_oflags: wasi::__wasi_oflags_t,
_fd_flags: wasi::__wasi_fdflags_t,
) -> Result<Box<dyn VirtualFile>> {
Err(Error::EACCES)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::EACCES)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::EACCES)
}
fn datasync(&self) -> Result<()> {
Err(Error::EINVAL)
}
fn sync(&self) -> Result<()> {
Ok(())
}
fn create_directory(&self, _path: &Path) -> Result<()> {
Err(Error::EACCES)
}
fn readdir(
&self,
_cookie: wasi::__wasi_dircookie_t,
) -> Result<Box<dyn Iterator<Item = Result<Dirent>>>> {
Err(Error::EBADF)
}
fn write_vectored(&mut self, _iovs: &[io::IoSlice]) -> Result<usize> {
Err(Error::EBADF)
}
fn pread(&self, _buf: &mut [u8], _offset: u64) -> Result<usize> {
Err(Error::EBADF)
}
fn pwrite(&self, _buf: &mut [u8], _offset: u64) -> Result<usize> {
Err(Error::EBADF)
}
fn seek(&mut self, _offset: SeekFrom) -> Result<u64> {
Err(Error::EBADF)
}
fn advise(
&self,
_advice: wasi::__wasi_advice_t,
_offset: wasi::__wasi_filesize_t,
_len: wasi::__wasi_filesize_t,
) -> Result<()> {
Err(Error::EBADF)
}
fn allocate(
&self,
_offset: wasi::__wasi_filesize_t,
_len: wasi::__wasi_filesize_t,
) -> Result<()> {
Err(Error::EBADF)
}
fn filestat_get(&self) -> Result<wasi::__wasi_filestat_t> {
Err(Error::EBADF)
}
fn filestat_set_times(&self, _atim: Option<FileTime>, _mtim: Option<FileTime>) -> Result<()> {
Err(Error::EBADF)
}
fn filestat_set_size(&self, _st_size: wasi::__wasi_filesize_t) -> Result<()> {
Err(Error::EBADF)
}
fn fdstat_set_flags(&mut self, _fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
Err(Error::EBADF)
}
fn read_vectored(&mut self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Err(Error::EBADF)
}
fn get_file_type(&self) -> wasi::__wasi_filetype_t;
fn get_rights_base(&self) -> wasi::__wasi_rights_t {
0
}
fn get_rights_inheriting(&self) -> wasi::__wasi_rights_t {
0
}
}
pub trait FileContents {
/// The implementation-defined maximum size of the store corresponding to a `FileContents`
/// implementation.
fn max_size(&self) -> wasi::__wasi_filesize_t;
/// The current number of bytes this `FileContents` describes.
fn size(&self) -> wasi::__wasi_filesize_t;
/// Resize to hold `new_size` number of bytes, or error if this is not possible.
fn resize(&mut self, new_size: wasi::__wasi_filesize_t) -> Result<()>;
/// Write a list of `IoSlice` starting at `offset`. `offset` plus the total size of all `iovs`
/// is guaranteed to not exceed `max_size`. Implementations must not indicate more bytes have
/// been written than can be held by `iovs`.
fn pwritev(&mut self, iovs: &[io::IoSlice], offset: wasi::__wasi_filesize_t) -> Result<usize>;
/// Read from the file from `offset`, filling a list of `IoSlice`. The returend size must not
/// be more than the capactiy of `iovs`, and must not exceed the limit reported by
/// `self.max_size()`.
fn preadv(&self, iovs: &mut [io::IoSliceMut], offset: wasi::__wasi_filesize_t)
-> Result<usize>;
/// Write contents from `buf` to this file starting at `offset`. `offset` plus the length of
/// `buf` is guaranteed to not exceed `max_size`. Implementations must not indicate more bytes
/// have been written than the size of `buf`.
fn pwrite(&mut self, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize>;
/// Read from the file at `offset`, filling `buf`. The returned size must not be more than the
/// capacity of `buf`, and `offset` plus the returned size must not exceed `self.max_size()`.
fn pread(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> Result<usize>;
}
impl FileContents for VecFileContents {
fn max_size(&self) -> wasi::__wasi_filesize_t {
std::usize::MAX as wasi::__wasi_filesize_t
}
fn size(&self) -> wasi::__wasi_filesize_t {
self.content.len() as wasi::__wasi_filesize_t
}
fn resize(&mut self, new_size: wasi::__wasi_filesize_t) -> Result<()> {
let new_size: usize = new_size.try_into().map_err(|_| Error::EINVAL)?;
self.content.resize(new_size, 0);
Ok(())
}
fn preadv(
&self,
iovs: &mut [io::IoSliceMut],
offset: wasi::__wasi_filesize_t,
) -> Result<usize> {
let mut read_total = 0usize;
for iov in iovs.iter_mut() {
let read = self.pread(iov, offset)?;
read_total = read_total.checked_add(read).expect("FileContents::preadv must not be called when reads could total to more bytes than the return value can hold");
}
Ok(read_total)
}
fn pwritev(&mut self, iovs: &[io::IoSlice], offset: wasi::__wasi_filesize_t) -> Result<usize> {
let mut write_total = 0usize;
for iov in iovs.iter() {
let written = self.pwrite(iov, offset)?;
write_total = write_total.checked_add(written).expect("FileContents::pwritev must not be called when writes could total to more bytes than the return value can hold");
}
Ok(write_total)
}
fn pread(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
trace!(" | pread(buf.len={}, offset={})", buf.len(), offset);
let offset: usize = offset.try_into().map_err(|_| Error::EINVAL)?;
let data_remaining = self.content.len().saturating_sub(offset);
let read_count = std::cmp::min(buf.len(), data_remaining);
(&mut buf[..read_count]).copy_from_slice(&self.content[offset..][..read_count]);
let res = Ok(read_count);
trace!(" | pread={:?}", res);
res
}
fn pwrite(&mut self, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
let offset: usize = offset.try_into().map_err(|_| Error::EINVAL)?;
let write_end = offset.checked_add(buf.len()).ok_or(Error::EFBIG)?;
if write_end > self.content.len() {
self.content.resize(write_end, 0);
}
(&mut self.content[offset..][..buf.len()]).copy_from_slice(buf);
Ok(buf.len())
}
}
struct VecFileContents {
content: Vec<u8>,
}
impl VecFileContents {
fn new() -> Self {
Self {
content: Vec::new(),
}
}
}
/// An `InMemoryFile` is a shared handle to some underlying data. The relationship is analagous to
/// a filesystem wherein a file descriptor is one view into a possibly-shared underlying collection
/// of data and permissions on a filesystem.
pub struct InMemoryFile {
cursor: wasi::__wasi_filesize_t,
parent: Rc<RefCell<Option<Box<dyn VirtualFile>>>>,
fd_flags: wasi::__wasi_fdflags_t,
data: Rc<RefCell<Box<dyn FileContents>>>,
}
impl InMemoryFile {
pub fn memory_backed() -> Self {
Self {
cursor: 0,
parent: Rc::new(RefCell::new(None)),
fd_flags: 0,
data: Rc::new(RefCell::new(Box::new(VecFileContents::new()))),
}
}
pub fn new(contents: Box<dyn FileContents>) -> Self {
Self {
cursor: 0,
fd_flags: 0,
parent: Rc::new(RefCell::new(None)),
data: Rc::new(RefCell::new(contents)),
}
}
}
impl MovableFile for InMemoryFile {
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>) {
*self.parent.borrow_mut() = new_parent;
}
}
impl VirtualFile for InMemoryFile {
fn fdstat_get(&self) -> wasi::__wasi_fdflags_t {
self.fd_flags
}
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>> {
Ok(Box::new(InMemoryFile {
cursor: 0,
fd_flags: self.fd_flags,
parent: Rc::clone(&self.parent),
data: Rc::clone(&self.data),
}))
}
fn readlinkat(&self, _path: &Path) -> Result<String> {
// no symlink support, so always say it's invalid.
Err(Error::ENOTDIR)
}
fn openat(
&self,
path: &Path,
read: bool,
write: bool,
oflags: wasi::__wasi_oflags_t,
fd_flags: wasi::__wasi_fdflags_t,
) -> Result<Box<dyn VirtualFile>> {
log::trace!(
"InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
path,
read,
write,
oflags,
fd_flags
);
if oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 {
log::trace!(
"InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.",
path
);
log::trace!(" return ENOTDIR");
return Err(Error::ENOTDIR);
}
if path == Path::new(".") {
return self.try_clone().map_err(Into::into);
} else if path == Path::new("..") {
match &*self.parent.borrow() {
Some(file) => file.try_clone().map_err(Into::into),
None => self.try_clone().map_err(Into::into),
}
} else {
Err(Error::EACCES)
}
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::ENOTDIR)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::ENOTDIR)
}
fn fdstat_set_flags(&mut self, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
self.fd_flags = fdflags;
Ok(())
}
fn write_vectored(&mut self, iovs: &[io::IoSlice]) -> Result<usize> {
trace!("write_vectored(iovs={:?})", iovs);
let mut data = self.data.borrow_mut();
let append_mode = self.fd_flags & wasi::__WASI_FDFLAGS_APPEND != 0;
trace!(" | fd_flags={:o}", self.fd_flags);
// If this file is in append mode, we write to the end.
let write_start = if append_mode {
data.size()
} else {
self.cursor
};
let max_size = iovs
.iter()
.map(|iov| {
let cast_iovlen: wasi32::size_t = iov
.len()
.try_into()
.expect("iovec are bounded by wasi max sizes");
cast_iovlen
})
.fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov)))
.expect("write_vectored will not be called with invalid iovs");
if let Some(end) = write_start.checked_add(max_size as wasi::__wasi_filesize_t) {
if end > data.max_size() {
return Err(Error::EFBIG);
}
} else {
return Err(Error::EFBIG);
}
trace!(" | *write_start={:?}", write_start);
let written = data.pwritev(iovs, write_start)?;
// If we are not appending, adjust the cursor appropriately for the write, too. This can't
// overflow, as we checked against that before writing any data.
if !append_mode {
self.cursor += written as u64;
}
Ok(written)
}
fn read_vectored(&mut self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
trace!("read_vectored(iovs={:?})", iovs);
trace!(" | *read_start={:?}", self.cursor);
self.data.borrow_mut().preadv(iovs, self.cursor)
}
fn pread(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
self.data.borrow_mut().pread(buf, offset)
}
fn pwrite(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
self.data.borrow_mut().pwrite(buf, offset)
}
fn seek(&mut self, offset: SeekFrom) -> Result<wasi::__wasi_filesize_t> {
let content_len = self.data.borrow().size();
match offset {
SeekFrom::Current(offset) => {
let new_cursor = if offset < 0 {
self.cursor
.checked_sub(offset.wrapping_neg() as u64)
.ok_or(Error::EINVAL)?
} else {
self.cursor
.checked_add(offset as u64)
.ok_or(Error::EINVAL)?
};
self.cursor = std::cmp::min(content_len, new_cursor);
}
SeekFrom::End(offset) => {
// A negative offset from the end would be past the end of the file,
let offset: u64 = offset.try_into().map_err(|_| Error::EINVAL)?;
self.cursor = content_len.saturating_sub(offset);
}
SeekFrom::Start(offset) => {
// A negative offset from the end would be before the start of the file.
let offset: u64 = offset.try_into().map_err(|_| Error::EINVAL)?;
self.cursor = std::cmp::min(content_len, offset);
}
}
Ok(self.cursor)
}
fn advise(
&self,
advice: wasi::__wasi_advice_t,
_offset: wasi::__wasi_filesize_t,
_len: wasi::__wasi_filesize_t,
) -> Result<()> {
// we'll just ignore advice for now, unless it's totally invalid
match advice {
wasi::__WASI_ADVICE_DONTNEED
| wasi::__WASI_ADVICE_SEQUENTIAL
| wasi::__WASI_ADVICE_WILLNEED
| wasi::__WASI_ADVICE_NOREUSE
| wasi::__WASI_ADVICE_RANDOM
| wasi::__WASI_ADVICE_NORMAL => Ok(()),
_ => Err(Error::EINVAL),
}
}
fn allocate(
&self,
offset: wasi::__wasi_filesize_t,
len: wasi::__wasi_filesize_t,
) -> Result<()> {
let new_limit = offset.checked_add(len).ok_or(Error::EFBIG)?;
let mut data = self.data.borrow_mut();
if new_limit > data.max_size() {
return Err(Error::EFBIG);
}
if new_limit > data.size() {
data.resize(new_limit)?;
}
Ok(())
}
fn filestat_set_size(&self, st_size: wasi::__wasi_filesize_t) -> Result<()> {
let mut data = self.data.borrow_mut();
if st_size > data.max_size() {
return Err(Error::EFBIG);
}
data.resize(st_size)
}
fn filestat_get(&self) -> Result<wasi::__wasi_filestat_t> {
let stat = wasi::__wasi_filestat_t {
dev: 0,
ino: 0,
nlink: 0,
size: self.data.borrow().size(),
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn get_file_type(&self) -> wasi::__wasi_filetype_t {
wasi::__WASI_FILETYPE_REGULAR_FILE
}
fn get_rights_base(&self) -> wasi::__wasi_rights_t {
wasi::RIGHTS_REGULAR_FILE_BASE
}
fn get_rights_inheriting(&self) -> wasi::__wasi_rights_t {
wasi::RIGHTS_REGULAR_FILE_INHERITING
}
}
/// A clonable read/write directory.
pub struct VirtualDir {
writable: bool,
// All copies of this `VirtualDir` must share `parent`, and changes in one copy's `parent`
// must be reflected in all handles, so they share `Rc` of an underlying `parent`.
parent: Rc<RefCell<Option<Box<dyn VirtualFile>>>>,
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn VirtualFile>>>>,
}
impl VirtualDir {
pub fn new(writable: bool) -> Self {
VirtualDir {
writable,
parent: Rc::new(RefCell::new(None)),
entries: Rc::new(RefCell::new(HashMap::new())),
}
}
#[allow(dead_code)]
pub fn with_dir<P: AsRef<Path>>(mut self, dir: VirtualDir, path: P) -> Self {
self.add_dir(dir, path);
self
}
#[allow(dead_code)]
pub fn add_dir<P: AsRef<Path>>(&mut self, dir: VirtualDir, path: P) {
let entry = Box::new(dir);
entry.set_parent(Some(self.try_clone().expect("can clone self")));
self.entries
.borrow_mut()
.insert(path.as_ref().to_owned(), entry);
}
#[allow(dead_code)]
pub fn with_file<P: AsRef<Path>>(mut self, content: Box<dyn FileContents>, path: P) -> Self {
self.add_file(content, path);
self
}
#[allow(dead_code)]
pub fn add_file<P: AsRef<Path>>(&mut self, content: Box<dyn FileContents>, path: P) {
let entry = Box::new(InMemoryFile::new(content));
entry.set_parent(Some(self.try_clone().expect("can clone self")));
self.entries
.borrow_mut()
.insert(path.as_ref().to_owned(), entry);
}
}
impl MovableFile for VirtualDir {
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>) {
*self.parent.borrow_mut() = new_parent;
}
}
const SELF_DIR_COOKIE: u32 = 0;
const PARENT_DIR_COOKIE: u32 = 1;
// This MUST be the number of constants above. This limit is used to prevent allocation of files
// that would wrap and be mapped to the same dir cookies as `self` or `parent`.
const RESERVED_ENTRY_COUNT: u32 = 2;
impl VirtualFile for VirtualDir {
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>> {
Ok(Box::new(VirtualDir {
writable: self.writable,
parent: Rc::clone(&self.parent),
entries: Rc::clone(&self.entries),
}))
}
fn readlinkat(&self, _path: &Path) -> Result<String> {
// Files are not symbolic links or directories, faithfully report ENOTDIR.
Err(Error::ENOTDIR)
}
fn openat(
&self,
path: &Path,
read: bool,
write: bool,
oflags: wasi::__wasi_oflags_t,
fd_flags: wasi::__wasi_fdflags_t,
) -> Result<Box<dyn VirtualFile>> {
log::trace!(
"VirtualDir::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
path,
read,
write,
oflags,
fd_flags
);
if path == Path::new(".") {
return self.try_clone().map_err(Into::into);
} else if path == Path::new("..") {
match &*self.parent.borrow() {
Some(file) => {
return file.try_clone().map_err(Into::into);
}
None => {
return self.try_clone().map_err(Into::into);
}
}
}
// openat may have been passed a path with a trailing slash, but files are mapped to paths
// with trailing slashes normalized out.
let file_name = path.file_name().ok_or(Error::EINVAL)?;
let mut entries = self.entries.borrow_mut();
let entry_count = entries.len();
match entries.entry(Path::new(file_name).to_path_buf()) {
Entry::Occupied(e) => {
let creat_excl_mask = wasi::__WASI_OFLAGS_CREAT | wasi::__WASI_OFLAGS_EXCL;
if (oflags & creat_excl_mask) == creat_excl_mask {
log::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name);
log::trace!(" return EEXIST");
return Err(Error::EEXIST);
}
if (oflags & wasi::__WASI_OFLAGS_DIRECTORY) != 0
&& e.get().get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY
{
log::trace!(
"VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.",
file_name
);
log::trace!(" return ENOTDIR");
return Err(Error::ENOTDIR);
}
e.get().try_clone().map_err(Into::into)
}
Entry::Vacant(v) => {
if self.writable {
// Enforce a hard limit at `u32::MAX - 2` files.
// This is to have a constant limit (rather than target-dependent limit we
// would have with `usize`. The limit is the full `u32` range minus two so we
// can reserve "self" and "parent" cookie values.
if entry_count >= (std::u32::MAX - RESERVED_ENTRY_COUNT) as usize {
return Err(Error::ENOSPC);
}
log::trace!(
"VirtualDir::openat creating an InMemoryFile named {}",
path.display()
);
let mut file = Box::new(InMemoryFile::memory_backed());
file.fd_flags = fd_flags;
file.set_parent(Some(self.try_clone().expect("can clone self")));
v.insert(file).try_clone().map_err(Into::into)
} else {
Err(Error::EACCES)
}
}
}
}
fn remove_directory(&self, path: &str) -> Result<()> {
let trimmed_path = path.trim_end_matches('/');
let mut entries = self.entries.borrow_mut();
match entries.entry(Path::new(trimmed_path).to_path_buf()) {
Entry::Occupied(e) => {
// first, does this name a directory?
if e.get().get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY {
return Err(Error::ENOTDIR);
}
// Okay, but is the directory empty?
let iter = e.get().readdir(wasi::__WASI_DIRCOOKIE_START)?;
if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() {
return Err(Error::ENOTEMPTY);
}
// Alright, it's an empty directory. We can remove it.
let removed = e.remove_entry();
// And sever the file's parent ref to avoid Rc cycles.
removed.1.set_parent(None);
Ok(())
}
Entry::Vacant(_) => {
log::trace!(
"VirtualDir::remove_directory failed to remove {}, no such entry",
trimmed_path
);
Err(Error::ENOENT)
}
}
}
fn unlink_file(&self, path: &str) -> Result<()> {
let trimmed_path = path.trim_end_matches('/');
// Special case: we may be unlinking this directory itself if path is `"."`. In that case,
// fail with EISDIR, since this is a directory. Alternatively, we may be unlinking `".."`,
// which is bound the same way, as this is by definition contained in a directory.
if trimmed_path == "." || trimmed_path == ".." {
return Err(Error::EISDIR);
}
let mut entries = self.entries.borrow_mut();
match entries.entry(Path::new(trimmed_path).to_path_buf()) {
Entry::Occupied(e) => {
// Directories must be removed through `remove_directory`, not `unlink_file`.
if e.get().get_file_type() == wasi::__WASI_FILETYPE_DIRECTORY {
return Err(Error::EISDIR);
}
let removed = e.remove_entry();
// Sever the file's parent ref to avoid Rc cycles.
removed.1.set_parent(None);
Ok(())
}
Entry::Vacant(_) => {
log::trace!(
"VirtualDir::unlink_file failed to remove {}, no such entry",
trimmed_path
);
Err(Error::ENOENT)
}
}
}
fn create_directory(&self, path: &Path) -> Result<()> {
let mut entries = self.entries.borrow_mut();
match entries.entry(path.to_owned()) {
Entry::Occupied(_) => Err(Error::EEXIST),
Entry::Vacant(v) => {
if self.writable {
let new_dir = Box::new(VirtualDir::new(true));
new_dir.set_parent(Some(self.try_clone()?));
v.insert(new_dir);
Ok(())
} else {
Err(Error::EACCES)
}
}
}
}
fn write_vectored(&mut self, _iovs: &[io::IoSlice]) -> Result<usize> {
Err(Error::EBADF)
}
fn readdir(
&self,
cookie: wasi::__wasi_dircookie_t,
) -> Result<Box<dyn Iterator<Item = Result<Dirent>>>> {
struct VirtualDirIter {
start: u32,
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn VirtualFile>>>>,
}
impl Iterator for VirtualDirIter {
type Item = Result<Dirent>;
fn next(&mut self) -> Option<Self::Item> {
log::trace!("VirtualDirIter::next continuing from {}", self.start);
if self.start == SELF_DIR_COOKIE {
self.start += 1;
return Some(Ok(Dirent {
name: ".".to_owned(),
ftype: FileType::from_wasi(wasi::__WASI_FILETYPE_DIRECTORY)
.expect("directories are valid file types"),
ino: 0,
cookie: self.start as u64,
}));
}
if self.start == PARENT_DIR_COOKIE {
self.start += 1;
return Some(Ok(Dirent {
name: "..".to_owned(),
ftype: FileType::from_wasi(wasi::__WASI_FILETYPE_DIRECTORY)
.expect("directories are valid file types"),
ino: 0,
cookie: self.start as u64,
}));
}
let entries = self.entries.borrow();
// Adjust `start` to be an appropriate number of HashMap entries.
let start = self.start - RESERVED_ENTRY_COUNT;
if start as usize >= entries.len() {
return None;
}
self.start += 1;
let (path, file) = entries
.iter()
.skip(start as usize)
.next()
.expect("seeked less than the length of entries");
let entry = Dirent {
name: path
.to_str()
.expect("wasi paths are valid utf8 strings")
.to_owned(),
ftype: FileType::from_wasi(file.get_file_type())
.expect("virtfs reports valid wasi file types"),
ino: 0,
cookie: self.start as u64,
};
Some(Ok(entry))
}
}
let cookie = match cookie.try_into() {
Ok(cookie) => cookie,
Err(_) => {
// Cookie is larger than u32. it doesn't seem like there's an explicit error
// condition in POSIX or WASI, so just start from the start?
0
}
};
Ok(Box::new(VirtualDirIter {
start: cookie,
entries: Rc::clone(&self.entries),
}))
}
fn filestat_get(&self) -> Result<wasi::__wasi_filestat_t> {
let stat = wasi::__wasi_filestat_t {
dev: 0,
ino: 0,
nlink: 0,
size: 0,
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn get_file_type(&self) -> wasi::__wasi_filetype_t {
wasi::__WASI_FILETYPE_DIRECTORY
}
fn get_rights_base(&self) -> wasi::__wasi_rights_t {
wasi::RIGHTS_DIRECTORY_BASE
}
fn get_rights_inheriting(&self) -> wasi::__wasi_rights_t {
wasi::RIGHTS_DIRECTORY_INHERITING
}
}