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:
@@ -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 {
|
||||||
writeln!(
|
match preopen_type {
|
||||||
out,
|
PreopenType::OS => {
|
||||||
" let workspace = utils::prepare_workspace(&bin_name)?;"
|
writeln!(
|
||||||
)?;
|
out,
|
||||||
"Some(workspace.path())"
|
" let workspace = utils::prepare_workspace(&bin_name)?;"
|
||||||
|
)?;
|
||||||
|
"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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,8 +322,20 @@ 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 {
|
||||||
return Err(Error::EBADF);
|
Descriptor::OsHandle(handle) => {
|
||||||
|
if !handle.metadata()?.is_dir() {
|
||||||
|
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
|
||||||
|
|||||||
@@ -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) }
|
||||||
file_type,
|
.map(|(file_type, rights_base, rights_inheriting)| Self {
|
||||||
descriptor: Descriptor::OsHandle(OsHandle::from(file)),
|
file_type,
|
||||||
rights_base,
|
descriptor: handle.into(),
|
||||||
rights_inheriting,
|
rights_base,
|
||||||
preopen_path: None,
|
rights_inheriting,
|
||||||
},
|
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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,24 +546,34 @@ 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()?;
|
||||||
|
|
||||||
let metadata = fd.metadata()?;
|
match file {
|
||||||
|
Descriptor::OsHandle(fd) => {
|
||||||
|
let metadata = fd.metadata()?;
|
||||||
|
|
||||||
let current_size = metadata.len();
|
let current_size = metadata.len();
|
||||||
let wanted_size = offset.checked_add(len).ok_or(Error::E2BIG)?;
|
let wanted_size = offset.checked_add(len).ok_or(Error::E2BIG)?;
|
||||||
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
||||||
if wanted_size > i64::max_value() as u64 {
|
if wanted_size > i64::max_value() as u64 {
|
||||||
return Err(Error::E2BIG);
|
return Err(Error::E2BIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
if wanted_size > current_size {
|
if wanted_size > current_size {
|
||||||
fd.set_len(wanted_size).map_err(Into::into)
|
fd.set_len(wanted_size).map_err(Into::into)
|
||||||
} 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"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|
||||||
hostcalls_impl::path_rename(resolved_old, resolved_new)
|
if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) =
|
||||||
|
(resolved_old.dirfd(), resolved_new.dirfd())
|
||||||
|
{
|
||||||
|
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,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,24 +1237,41 @@ 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>>>(
|
||||||
let mut host_bufused = 0;
|
iter: T,
|
||||||
for dirent in iter {
|
mut host_buf: &mut [u8],
|
||||||
let dirent_raw = dirent?.to_wasi_raw()?;
|
) -> Result<usize> {
|
||||||
let offset = dirent_raw.len();
|
let mut host_bufused = 0;
|
||||||
if host_buf.len() < offset {
|
for dirent in iter {
|
||||||
break;
|
let dirent_raw = dirent?.to_wasi_raw()?;
|
||||||
} else {
|
let offset = dirent_raw.len();
|
||||||
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
if host_buf.len() < offset {
|
||||||
host_bufused += offset;
|
break;
|
||||||
host_buf = &mut host_buf[offset..];
|
} else {
|
||||||
|
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
||||||
|
host_bufused += 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);
|
||||||
|
|
||||||
enc_usize_byref(memory, buf_used, host_bufused)
|
enc_usize_byref(memory, buf_used, host_bufused)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;
|
||||||
unsafe {
|
match (resolved_old.dirfd(), resolved_new.dirfd()) {
|
||||||
renameat(
|
(Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => {
|
||||||
resolved_old.dirfd().as_raw_fd(),
|
unsafe {
|
||||||
resolved_old.path(),
|
renameat(
|
||||||
resolved_new.dirfd().as_raw_fd(),
|
resolved_old_file.as_raw_fd(),
|
||||||
resolved_new.path(),
|
resolved_old.path(),
|
||||||
)
|
resolved_new_file.as_raw_fd(),
|
||||||
|
resolved_new.path(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!("path_link with one or more virtual files");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod fd_readdir_impl {
|
pub(crate) mod fd_readdir_impl {
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
878
crates/wasi-common/src/virtfs.rs
Normal file
878
crates/wasi-common/src/virtfs.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user