Merge pull request #2160 from bytecodealliance/pch/wasi_common_array_writer
wasi-common: factor out common string array writer code
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2419,6 +2419,7 @@ dependencies = [
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
"test-programs",
|
||||
"tracing-subscriber",
|
||||
"wasi-common",
|
||||
"wasmtime",
|
||||
"wasmtime-cache",
|
||||
|
||||
@@ -51,6 +51,7 @@ tempfile = "3.1.0"
|
||||
test-programs = { path = "crates/test-programs" }
|
||||
wasmtime-fuzzing = { path = "crates/fuzzing" }
|
||||
wasmtime-runtime = { path = "crates/runtime" }
|
||||
tracing-subscriber = "0.2.0"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.19"
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use crate::entry::{Entry, EntryHandle};
|
||||
use crate::fdpool::FdPool;
|
||||
use crate::handle::Handle;
|
||||
use crate::string_array::{PendingString, StringArray, StringArrayError};
|
||||
use crate::sys::osdir::OsDir;
|
||||
use crate::sys::stdio::NullDevice;
|
||||
use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt};
|
||||
use crate::virtfs::{VirtualDir, VirtualDirEntry};
|
||||
use crate::wasi::types;
|
||||
use crate::{Error, Result};
|
||||
use crate::Error;
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::{self, CString, OsString};
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::{env, io, string};
|
||||
use std::{env, io};
|
||||
|
||||
/// Possible errors when `WasiCtxBuilder` fails building
|
||||
/// `WasiCtx`.
|
||||
@@ -24,17 +24,12 @@ pub enum WasiCtxBuilderError {
|
||||
/// General I/O error was encountered.
|
||||
#[error("general I/O error encountered: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
/// Provided sequence of bytes was not a valid UTF-8.
|
||||
#[error("provided sequence is not valid UTF-8: {0}")]
|
||||
InvalidUtf8(#[from] string::FromUtf8Error),
|
||||
/// Provided sequence of bytes was not a valid UTF-16.
|
||||
///
|
||||
/// This error is expected to only occur on Windows hosts.
|
||||
#[error("provided sequence is not valid UTF-16: {0}")]
|
||||
InvalidUtf16(#[from] string::FromUtf16Error),
|
||||
/// Provided sequence of bytes contained an unexpected NUL byte.
|
||||
#[error("provided sequence contained an unexpected NUL byte")]
|
||||
UnexpectedNul(#[from] ffi::NulError),
|
||||
/// Error constructing arguments
|
||||
#[error("while constructing arguments: {0}")]
|
||||
Args(#[source] StringArrayError),
|
||||
/// Error constructing environment
|
||||
#[error("while constructing environment: {0}")]
|
||||
Env(#[source] StringArrayError),
|
||||
/// The root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory.
|
||||
#[error("the root of a VirtualDirEntry tree at {} must be a VirtualDirEntry::Directory", .0.display())]
|
||||
VirtualDirEntryRootNotADirectory(PathBuf),
|
||||
@@ -63,51 +58,6 @@ impl std::fmt::Debug for PendingEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||
enum PendingCString {
|
||||
Bytes(Vec<u8>),
|
||||
OsString(OsString),
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for PendingCString {
|
||||
fn from(bytes: Vec<u8>) -> Self {
|
||||
Self::Bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsString> for PendingCString {
|
||||
fn from(s: OsString) -> Self {
|
||||
Self::OsString(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingCString {
|
||||
fn into_string(self) -> WasiCtxBuilderResult<String> {
|
||||
let res = match self {
|
||||
Self::Bytes(v) => String::from_utf8(v)?,
|
||||
#[cfg(unix)]
|
||||
Self::OsString(s) => {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
String::from_utf8(s.into_vec())?
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Self::OsString(s) => {
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
let bytes: Vec<u16> = s.encode_wide().collect();
|
||||
String::from_utf16(&bytes)?
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Create a `CString` containing valid UTF-8.
|
||||
fn into_utf8_cstring(self) -> WasiCtxBuilderResult<CString> {
|
||||
let s = self.into_string()?;
|
||||
let s = CString::new(s)?;
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingPreopen(Box<dyn FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>>>);
|
||||
|
||||
impl PendingPreopen {
|
||||
@@ -129,8 +79,8 @@ pub struct WasiCtxBuilder {
|
||||
stdout: Option<PendingEntry>,
|
||||
stderr: Option<PendingEntry>,
|
||||
preopens: Option<Vec<(PathBuf, PendingPreopen)>>,
|
||||
args: Option<Vec<PendingCString>>,
|
||||
env: Option<HashMap<PendingCString, PendingCString>>,
|
||||
args: Option<Vec<PendingString>>,
|
||||
env: Option<HashMap<PendingString, PendingString>>,
|
||||
}
|
||||
|
||||
impl WasiCtxBuilder {
|
||||
@@ -179,7 +129,7 @@ impl WasiCtxBuilder {
|
||||
pub fn inherit_args(&mut self) -> &mut Self {
|
||||
let args = self.args.as_mut().unwrap();
|
||||
args.clear();
|
||||
args.extend(env::args_os().map(PendingCString::OsString));
|
||||
args.extend(env::args_os().map(PendingString::OsString));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -323,34 +273,13 @@ impl WasiCtxBuilder {
|
||||
/// If any of the arguments or environment variables in this builder cannot be converted into
|
||||
/// `CString`s, either due to NUL bytes or Unicode conversions, this will fail.
|
||||
pub fn build(&mut self) -> WasiCtxBuilderResult<WasiCtx> {
|
||||
// Process arguments and environment variables into `CString`s, failing quickly if they
|
||||
// Process arguments and environment variables into `String`s, failing quickly if they
|
||||
// contain any NUL bytes, or if conversion from `OsString` fails.
|
||||
let args = self
|
||||
.args
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|arg| arg.into_utf8_cstring())
|
||||
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
|
||||
|
||||
let env = self
|
||||
.env
|
||||
.take()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
k.into_string().and_then(|mut pair| {
|
||||
v.into_string().and_then(|v| {
|
||||
pair.push('=');
|
||||
pair.push_str(v.as_str());
|
||||
// We have valid UTF-8, but the keys and values have not yet been checked
|
||||
// for NULs, so we do a final check here.
|
||||
let s = CString::new(pair)?;
|
||||
Ok(s)
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
|
||||
let args =
|
||||
StringArray::from_pending_vec(self.args.take().expect("WasiCtxBuilder has args"))
|
||||
.map_err(WasiCtxBuilderError::Args)?;
|
||||
let env = StringArray::from_pending_map(self.env.take().expect("WasiCtxBuilder has env"))
|
||||
.map_err(WasiCtxBuilderError::Env)?;
|
||||
|
||||
let mut entries = EntryTable::new();
|
||||
// Populate the non-preopen entries.
|
||||
@@ -440,8 +369,8 @@ impl EntryTable {
|
||||
|
||||
pub struct WasiCtx {
|
||||
entries: RefCell<EntryTable>,
|
||||
pub(crate) args: Vec<CString>,
|
||||
pub(crate) env: Vec<CString>,
|
||||
pub(crate) args: StringArray,
|
||||
pub(crate) env: StringArray,
|
||||
}
|
||||
|
||||
impl WasiCtx {
|
||||
@@ -466,7 +395,7 @@ impl WasiCtx {
|
||||
}
|
||||
|
||||
/// Get an immutable `Entry` corresponding to the specified raw WASI `fd`.
|
||||
pub(crate) fn get_entry(&self, fd: types::Fd) -> Result<Rc<Entry>> {
|
||||
pub(crate) fn get_entry(&self, fd: types::Fd) -> Result<Rc<Entry>, Error> {
|
||||
match self.entries.borrow().get(&fd) {
|
||||
Some(entry) => Ok(entry),
|
||||
None => Err(Error::Badf),
|
||||
@@ -477,7 +406,7 @@ impl WasiCtx {
|
||||
///
|
||||
/// The `Entry` will automatically get another free raw WASI `fd` assigned. Note that
|
||||
/// the two subsequent free raw WASI `fd`s do not have to be stored contiguously.
|
||||
pub(crate) fn insert_entry(&self, entry: Entry) -> Result<types::Fd> {
|
||||
pub(crate) fn insert_entry(&self, entry: Entry) -> Result<types::Fd, Error> {
|
||||
self.entries.borrow_mut().insert(entry).ok_or(Error::Mfile)
|
||||
}
|
||||
|
||||
@@ -488,7 +417,17 @@ impl WasiCtx {
|
||||
}
|
||||
|
||||
/// Remove `Entry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object.
|
||||
pub(crate) fn remove_entry(&self, fd: types::Fd) -> Result<Rc<Entry>> {
|
||||
pub(crate) fn remove_entry(&self, fd: types::Fd) -> Result<Rc<Entry>, Error> {
|
||||
self.entries.borrow_mut().remove(fd).ok_or(Error::Badf)
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn args(&self) -> &impl StringArrayWriter {
|
||||
&self.args
|
||||
}
|
||||
|
||||
pub(crate) fn env(&self) -> &impl StringArrayWriter {
|
||||
&self.env
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ mod path;
|
||||
mod poll;
|
||||
mod sandboxed_tty_writer;
|
||||
pub mod snapshots;
|
||||
mod string_array;
|
||||
mod sys;
|
||||
pub mod virtfs;
|
||||
pub mod wasi;
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::wasi::types;
|
||||
use crate::{Error, Result};
|
||||
use std::path::{Component, Path};
|
||||
use std::str;
|
||||
use wiggle::GuestPtr;
|
||||
|
||||
pub(crate) use crate::sys::path::{from_host, open_rights};
|
||||
|
||||
@@ -15,15 +14,12 @@ pub(crate) fn get(
|
||||
entry: &Entry,
|
||||
required_rights: &HandleRights,
|
||||
dirflags: types::Lookupflags,
|
||||
path_ptr: &GuestPtr<'_, str>,
|
||||
path: &str,
|
||||
needs_final_component: bool,
|
||||
) -> Result<(Box<dyn Handle>, String)> {
|
||||
const MAX_SYMLINK_EXPANSIONS: usize = 128;
|
||||
|
||||
// Extract path as &str from guest's memory.
|
||||
let path = path_ptr.as_str()?;
|
||||
|
||||
tracing::trace!(path = &*path);
|
||||
tracing::trace!(path = path);
|
||||
|
||||
if path.contains('\0') {
|
||||
// if contains NUL, return Ilseq
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::wasi::{types, AsBytes};
|
||||
use crate::{path, poll, Error, Result, WasiCtx};
|
||||
use std::convert::TryInto;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::ops::Deref;
|
||||
use tracing::{debug, trace};
|
||||
use wiggle::{GuestPtr, GuestSlice};
|
||||
|
||||
@@ -15,29 +16,11 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
|
||||
argv_buf: &GuestPtr<'b, u8>,
|
||||
) -> Result<()> {
|
||||
let mut argv = argv.clone();
|
||||
let mut argv_buf = argv_buf.clone();
|
||||
|
||||
for arg in &self.args {
|
||||
let arg_bytes = arg.as_bytes_with_nul();
|
||||
let elems = arg_bytes.len().try_into()?;
|
||||
argv_buf.as_array(elems).copy_from_slice(arg_bytes)?;
|
||||
argv.write(argv_buf)?;
|
||||
argv = argv.add(1)?;
|
||||
argv_buf = argv_buf.add(elems)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.args.write_to_guest(argv_buf, argv)
|
||||
}
|
||||
|
||||
fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> {
|
||||
let argc = self.args.len().try_into()?;
|
||||
let mut argv_size: types::Size = 0;
|
||||
for arg in &self.args {
|
||||
let arg_len = arg.as_bytes_with_nul().len().try_into()?;
|
||||
argv_size = argv_size.checked_add(arg_len).ok_or(Error::Overflow)?;
|
||||
}
|
||||
Ok((argc, argv_size))
|
||||
Ok((self.args.number_elements, self.args.cumulative_size))
|
||||
}
|
||||
|
||||
fn environ_get<'b>(
|
||||
@@ -45,29 +28,11 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
|
||||
environ_buf: &GuestPtr<'b, u8>,
|
||||
) -> Result<()> {
|
||||
let mut environ = environ.clone();
|
||||
let mut environ_buf = environ_buf.clone();
|
||||
|
||||
for e in &self.env {
|
||||
let environ_bytes = e.as_bytes_with_nul();
|
||||
let elems = environ_bytes.len().try_into()?;
|
||||
environ_buf.as_array(elems).copy_from_slice(environ_bytes)?;
|
||||
environ.write(environ_buf)?;
|
||||
environ = environ.add(1)?;
|
||||
environ_buf = environ_buf.add(elems)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.env.write_to_guest(environ_buf, environ)
|
||||
}
|
||||
|
||||
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> {
|
||||
let environ_count = self.env.len().try_into()?;
|
||||
let mut environ_size: types::Size = 0;
|
||||
for environ in &self.env {
|
||||
let env_len = environ.as_bytes_with_nul().len().try_into()?;
|
||||
environ_size = environ_size.checked_add(env_len).ok_or(Error::Overflow)?;
|
||||
}
|
||||
Ok((environ_count, environ_size))
|
||||
Ok((self.env.number_elements, self.env.cumulative_size))
|
||||
}
|
||||
|
||||
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp> {
|
||||
@@ -438,11 +403,12 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY,
|
||||
);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let path = path.as_str()?;
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
path.deref(),
|
||||
false,
|
||||
)?;
|
||||
dirfd.create_directory(&path)
|
||||
@@ -456,7 +422,8 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<types::Filestat> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_GET);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?;
|
||||
let path = path.as_str()?;
|
||||
let (dirfd, path) = path::get(&entry, &required_rights, flags, path.deref(), false)?;
|
||||
let host_filestat =
|
||||
dirfd.filestat_get_at(&path, flags.contains(&types::Lookupflags::SYMLINK_FOLLOW))?;
|
||||
Ok(host_filestat)
|
||||
@@ -473,7 +440,8 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?;
|
||||
let path = path.as_str()?;
|
||||
let (dirfd, path) = path::get(&entry, &required_rights, flags, path.deref(), false)?;
|
||||
dirfd.filestat_set_times_at(
|
||||
&path,
|
||||
atim,
|
||||
@@ -494,22 +462,30 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_SOURCE);
|
||||
let old_entry = self.get_entry(old_fd)?;
|
||||
let (old_dirfd, old_path) = path::get(
|
||||
&old_entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
old_path,
|
||||
false,
|
||||
)?;
|
||||
let (old_dirfd, old_path) = {
|
||||
// Borrow old_path for just this scope
|
||||
let old_path = old_path.as_str()?;
|
||||
path::get(
|
||||
&old_entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
old_path.deref(),
|
||||
false,
|
||||
)?
|
||||
};
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_TARGET);
|
||||
let new_entry = self.get_entry(new_fd)?;
|
||||
let (new_dirfd, new_path) = path::get(
|
||||
&new_entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path,
|
||||
false,
|
||||
)?;
|
||||
let (new_dirfd, new_path) = {
|
||||
// Borrow new_path for just this scope
|
||||
let new_path = new_path.as_str()?;
|
||||
path::get(
|
||||
&new_entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path.deref(),
|
||||
false,
|
||||
)?
|
||||
};
|
||||
old_dirfd.link(
|
||||
&old_path,
|
||||
new_dirfd,
|
||||
@@ -535,13 +511,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
);
|
||||
trace!(" | needed_rights={}", needed_rights);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&needed_rights,
|
||||
dirflags,
|
||||
path,
|
||||
oflags & types::Oflags::CREAT != types::Oflags::empty(),
|
||||
)?;
|
||||
let (dirfd, path) = {
|
||||
let path = path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&needed_rights,
|
||||
dirflags,
|
||||
path.deref(),
|
||||
oflags & types::Oflags::CREAT != types::Oflags::empty(),
|
||||
)?
|
||||
};
|
||||
// which open mode do we need?
|
||||
let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR)
|
||||
!= types::Rights::empty();
|
||||
@@ -577,13 +556,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<types::Size> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_READLINK);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
false,
|
||||
)?;
|
||||
let (dirfd, path) = {
|
||||
// borrow path for just this scope
|
||||
let path = path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path.deref(),
|
||||
false,
|
||||
)?
|
||||
};
|
||||
let mut slice = buf.as_array(buf_len).as_slice()?;
|
||||
let host_bufused = dirfd.readlink(&path, &mut *slice)?.try_into()?;
|
||||
Ok(host_bufused)
|
||||
@@ -592,13 +575,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
true,
|
||||
)?;
|
||||
let (dirfd, path) = {
|
||||
let path = path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path.deref(),
|
||||
true,
|
||||
)?
|
||||
};
|
||||
dirfd.remove_directory(&path)
|
||||
}
|
||||
|
||||
@@ -611,22 +597,28 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_SOURCE);
|
||||
let entry = self.get_entry(old_fd)?;
|
||||
let (old_dirfd, old_path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
old_path,
|
||||
true,
|
||||
)?;
|
||||
let (old_dirfd, old_path) = {
|
||||
let old_path = old_path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
old_path.deref(),
|
||||
true,
|
||||
)?
|
||||
};
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_TARGET);
|
||||
let entry = self.get_entry(new_fd)?;
|
||||
let (new_dirfd, new_path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path,
|
||||
true,
|
||||
)?;
|
||||
let (new_dirfd, new_path) = {
|
||||
let new_path = new_path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path.deref(),
|
||||
true,
|
||||
)?
|
||||
};
|
||||
old_dirfd.rename(&old_path, new_dirfd, &new_path)
|
||||
}
|
||||
|
||||
@@ -638,28 +630,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_SYMLINK);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (new_fd, new_path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path,
|
||||
true,
|
||||
)?;
|
||||
let (new_fd, new_path) = {
|
||||
let new_path = new_path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path.deref(),
|
||||
true,
|
||||
)?
|
||||
};
|
||||
let old_path = old_path.as_str()?;
|
||||
trace!(old_path = &*old_path);
|
||||
trace!(old_path = old_path.deref());
|
||||
new_fd.symlink(&old_path, &new_path)
|
||||
}
|
||||
|
||||
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||
let required_rights = HandleRights::from_base(types::Rights::PATH_UNLINK_FILE);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
false,
|
||||
)?;
|
||||
let (dirfd, path) = {
|
||||
let path = path.as_str()?;
|
||||
path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path.deref(),
|
||||
false,
|
||||
)?
|
||||
};
|
||||
dirfd.unlink_file(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
145
crates/wasi-common/src/string_array.rs
Normal file
145
crates/wasi-common/src/string_array.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use crate::Error;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::{CString, OsString};
|
||||
use wiggle::GuestPtr;
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||
pub enum PendingString {
|
||||
Bytes(Vec<u8>),
|
||||
OsString(OsString),
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for PendingString {
|
||||
fn from(bytes: Vec<u8>) -> Self {
|
||||
Self::Bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsString> for PendingString {
|
||||
fn from(s: OsString) -> Self {
|
||||
Self::OsString(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingString {
|
||||
pub fn into_string(self) -> Result<String, StringArrayError> {
|
||||
let res = match self {
|
||||
Self::Bytes(v) => String::from_utf8(v)?,
|
||||
#[cfg(unix)]
|
||||
Self::OsString(s) => {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
String::from_utf8(s.into_vec())?
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Self::OsString(s) => {
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
let bytes: Vec<u16> = s.encode_wide().collect();
|
||||
String::from_utf16(&bytes)?
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum StringArrayError {
|
||||
/// Provided sequence of bytes contained an unexpected NUL byte.
|
||||
#[error("provided sequence contained an unexpected NUL byte")]
|
||||
Nul(#[from] std::ffi::NulError),
|
||||
/// Too many elements: must fit into u32
|
||||
#[error("too many elements")]
|
||||
NumElements,
|
||||
/// Element size: must fit into u32
|
||||
#[error("element too big")]
|
||||
ElemSize,
|
||||
/// Cumulative element size: must fit into u32
|
||||
#[error("cumulative element size too big")]
|
||||
CumElemSize,
|
||||
/// Provided sequence of bytes was not a valid UTF-8.
|
||||
#[error("provided sequence is not valid UTF-8: {0}")]
|
||||
InvalidUtf8(#[from] std::string::FromUtf8Error),
|
||||
/// Provided sequence of bytes was not a valid UTF-16.
|
||||
///
|
||||
/// This error is expected to only occur on Windows hosts.
|
||||
#[error("provided sequence is not valid UTF-16: {0}")]
|
||||
InvalidUtf16(#[from] std::string::FromUtf16Error),
|
||||
}
|
||||
|
||||
pub struct StringArray {
|
||||
elems: Vec<CString>,
|
||||
pub number_elements: u32,
|
||||
pub cumulative_size: u32,
|
||||
}
|
||||
impl StringArray {
|
||||
pub fn from_pending_vec(elems: Vec<PendingString>) -> Result<Self, StringArrayError> {
|
||||
let elems = elems
|
||||
.into_iter()
|
||||
.map(|arg| arg.into_string())
|
||||
.collect::<Result<Vec<String>, StringArrayError>>()?;
|
||||
Self::from_strings(elems)
|
||||
}
|
||||
pub fn from_pending_map(
|
||||
elems: HashMap<PendingString, PendingString>,
|
||||
) -> Result<Self, StringArrayError> {
|
||||
let mut pairs = Vec::new();
|
||||
for (k, v) in elems.into_iter() {
|
||||
let mut pair = k.into_string()?;
|
||||
pair.push('=');
|
||||
pair.push_str(&v.into_string()?);
|
||||
pairs.push(pair);
|
||||
}
|
||||
Self::from_strings(pairs)
|
||||
}
|
||||
pub fn from_strings(elems: Vec<String>) -> Result<Self, StringArrayError> {
|
||||
let elems = elems
|
||||
.into_iter()
|
||||
.map(|s| CString::new(s))
|
||||
.collect::<Result<Vec<CString>, _>>()?;
|
||||
let number_elements = elems
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| StringArrayError::NumElements)?;
|
||||
let mut cumulative_size: u32 = 0;
|
||||
for elem in elems.iter() {
|
||||
let elem_bytes = elem
|
||||
.as_bytes_with_nul()
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| StringArrayError::ElemSize)?;
|
||||
cumulative_size = cumulative_size
|
||||
.checked_add(elem_bytes)
|
||||
.ok_or(StringArrayError::CumElemSize)?;
|
||||
}
|
||||
Ok(Self {
|
||||
elems,
|
||||
number_elements,
|
||||
cumulative_size,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_to_guest<'a>(
|
||||
&self,
|
||||
buffer: &GuestPtr<'a, u8>,
|
||||
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
|
||||
) -> Result<(), Error> {
|
||||
let element_heads = element_heads.as_array(self.number_elements);
|
||||
let buffer = buffer.as_array(self.cumulative_size);
|
||||
let mut cursor = 0;
|
||||
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
|
||||
let bytes = elem.as_bytes_with_nul();
|
||||
let len: u32 = bytes.len().try_into()?;
|
||||
let elem_buffer = buffer
|
||||
.get_range(cursor..(cursor + len))
|
||||
.ok_or(Error::Inval)?; // Elements don't fit in buffer provided
|
||||
elem_buffer.copy_from_slice(bytes)?;
|
||||
head?.write(
|
||||
elem_buffer
|
||||
.get(0)
|
||||
.expect("all elem buffers at least length 1"),
|
||||
)?;
|
||||
cursor += len;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -449,7 +449,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
||||
self.pointer.0
|
||||
}
|
||||
|
||||
/// For slices, returns the length of the slice, in units.
|
||||
/// For slices, returns the length of the slice, in elements.
|
||||
pub fn len(&self) -> u32 {
|
||||
self.pointer.1
|
||||
}
|
||||
@@ -542,6 +542,41 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
||||
pub fn as_ptr(&self) -> GuestPtr<'a, T> {
|
||||
GuestPtr::new(self.mem, self.offset_base())
|
||||
}
|
||||
|
||||
pub fn get(&self, index: u32) -> Option<GuestPtr<'a, T>>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
if index < self.len() {
|
||||
Some(
|
||||
self.as_ptr()
|
||||
.add(index)
|
||||
.expect("just performed bounds check"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_range(&self, r: std::ops::Range<u32>) -> Option<GuestPtr<'a, [T]>>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
if r.end < r.start {
|
||||
return None;
|
||||
}
|
||||
let range_length = r.end - r.start;
|
||||
if r.start <= self.len() && r.end <= self.len() {
|
||||
Some(
|
||||
self.as_ptr()
|
||||
.add(r.start)
|
||||
.expect("just performed bounds check")
|
||||
.as_array(range_length),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GuestPtr<'a, str> {
|
||||
|
||||
@@ -102,10 +102,11 @@ impl HostMemory {
|
||||
out
|
||||
}
|
||||
|
||||
pub fn byte_slice_strat(size: u32, exclude: &MemAreas) -> BoxedStrategy<MemArea> {
|
||||
pub fn byte_slice_strat(size: u32, align: u32, exclude: &MemAreas) -> BoxedStrategy<MemArea> {
|
||||
let available: Vec<MemArea> = Self::invert(exclude)
|
||||
.iter()
|
||||
.flat_map(|a| a.inside(size))
|
||||
.filter(|a| a.ptr % align == 0)
|
||||
.collect();
|
||||
|
||||
Just(available)
|
||||
@@ -256,18 +257,18 @@ mod test {
|
||||
s2: u32,
|
||||
s3: u32,
|
||||
) -> BoxedStrategy<(MemArea, MemArea, MemArea)> {
|
||||
HostMemory::byte_slice_strat(s1, &MemAreas::new())
|
||||
HostMemory::byte_slice_strat(s1, 1, &MemAreas::new())
|
||||
.prop_flat_map(move |a1| {
|
||||
(
|
||||
Just(a1),
|
||||
HostMemory::byte_slice_strat(s2, &MemAreas::from(&[a1])),
|
||||
HostMemory::byte_slice_strat(s2, 1, &MemAreas::from(&[a1])),
|
||||
)
|
||||
})
|
||||
.prop_flat_map(move |(a1, a2)| {
|
||||
(
|
||||
Just(a1),
|
||||
Just(a2),
|
||||
HostMemory::byte_slice_strat(s3, &MemAreas::from(&[a1, a2])),
|
||||
HostMemory::byte_slice_strat(s3, 1, &MemAreas::from(&[a1, a2])),
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proptest::prelude::*;
|
||||
use wiggle::{GuestMemory, GuestPtr};
|
||||
use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
|
||||
use wiggle::{GuestMemory, GuestPtr, GuestType};
|
||||
use wiggle_test::{impl_errno, HostMemory, MemArea, MemAreas, WasiCtx};
|
||||
|
||||
wiggle::from_witx!({
|
||||
witx: ["$CARGO_MANIFEST_DIR/tests/arrays.witx"],
|
||||
@@ -208,3 +208,190 @@ proptest! {
|
||||
e.test()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> array_traversal::ArrayTraversal for WasiCtx<'a> {
|
||||
fn sum_of_element(
|
||||
&self,
|
||||
elements: &GuestPtr<[types::PairInts]>,
|
||||
index: u32,
|
||||
) -> Result<i32, types::Errno> {
|
||||
let elem_ptr = elements.get(index).ok_or(types::Errno::InvalidArg)?;
|
||||
let pair = elem_ptr.read().map_err(|_| types::Errno::DontWantTo)?;
|
||||
Ok(pair.first.wrapping_add(pair.second))
|
||||
}
|
||||
fn sum_of_elements(
|
||||
&self,
|
||||
elements: &GuestPtr<[types::PairInts]>,
|
||||
start: u32,
|
||||
end: u32,
|
||||
) -> Result<i32, types::Errno> {
|
||||
let elem_range = elements
|
||||
.get_range(start..end)
|
||||
.ok_or(types::Errno::InvalidArg)?;
|
||||
let mut sum: i32 = 0;
|
||||
for e in elem_range.iter() {
|
||||
let pair = e
|
||||
.map_err(|_| types::Errno::DontWantTo)?
|
||||
.read()
|
||||
.map_err(|_| types::Errno::PhysicallyUnable)?;
|
||||
sum = sum.wrapping_add(pair.first).wrapping_add(pair.second);
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
}
|
||||
|
||||
impl types::PairInts {
|
||||
pub fn strat() -> BoxedStrategy<Self> {
|
||||
(prop::num::i32::ANY, prop::num::i32::ANY)
|
||||
.prop_map(|(first, second)| types::PairInts { first, second })
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SumElementsExercise {
|
||||
elements: Vec<types::PairInts>,
|
||||
element_loc: MemArea,
|
||||
return_loc: MemArea,
|
||||
start_ix: u32,
|
||||
end_ix: u32,
|
||||
}
|
||||
|
||||
impl SumElementsExercise {
|
||||
pub fn strat() -> BoxedStrategy<Self> {
|
||||
(
|
||||
prop::collection::vec(types::PairInts::strat(), 1..256),
|
||||
HostMemory::mem_area_strat(4),
|
||||
)
|
||||
.prop_flat_map(|(elements, return_loc)| {
|
||||
let len = elements.len() as u32;
|
||||
(
|
||||
Just(elements),
|
||||
HostMemory::byte_slice_strat(
|
||||
len * types::PairInts::guest_size(),
|
||||
types::PairInts::guest_size(),
|
||||
&MemAreas::from([return_loc]),
|
||||
),
|
||||
Just(return_loc),
|
||||
0..len,
|
||||
0..len,
|
||||
)
|
||||
})
|
||||
.prop_map(
|
||||
|(elements, element_loc, return_loc, start_ix, end_ix)| SumElementsExercise {
|
||||
elements,
|
||||
element_loc,
|
||||
return_loc,
|
||||
start_ix,
|
||||
end_ix,
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
pub fn test(&self) {
|
||||
let ctx = WasiCtx::new();
|
||||
let host_memory = HostMemory::new();
|
||||
|
||||
// Populate array
|
||||
let ptr = host_memory
|
||||
.ptr::<[types::PairInts]>((self.element_loc.ptr, self.elements.len() as u32));
|
||||
for (ptr, val) in ptr.iter().zip(&self.elements) {
|
||||
ptr.expect("should be valid pointer")
|
||||
.write(val.clone())
|
||||
.expect("failed to write value");
|
||||
}
|
||||
|
||||
let res = array_traversal::sum_of_element(
|
||||
&ctx,
|
||||
&host_memory,
|
||||
self.element_loc.ptr as i32,
|
||||
self.elements.len() as i32,
|
||||
self.start_ix as i32,
|
||||
self.return_loc.ptr as i32,
|
||||
);
|
||||
assert_eq!(res, types::Errno::Ok.into(), "sum_of_element errno");
|
||||
let result_ptr = host_memory.ptr::<i32>(self.return_loc.ptr);
|
||||
let result = result_ptr.read().expect("read result");
|
||||
|
||||
let e = self
|
||||
.elements
|
||||
.get(self.start_ix as usize)
|
||||
.expect("start_ix must be in bounds");
|
||||
assert_eq!(result, e.first.wrapping_add(e.second), "sum of element");
|
||||
|
||||
// Off the end of the array:
|
||||
let res = array_traversal::sum_of_element(
|
||||
&ctx,
|
||||
&host_memory,
|
||||
self.element_loc.ptr as i32,
|
||||
self.elements.len() as i32,
|
||||
self.elements.len() as i32,
|
||||
self.return_loc.ptr as i32,
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
types::Errno::InvalidArg.into(),
|
||||
"out of bounds sum_of_element errno"
|
||||
);
|
||||
|
||||
let res = array_traversal::sum_of_elements(
|
||||
&ctx,
|
||||
&host_memory,
|
||||
self.element_loc.ptr as i32,
|
||||
self.elements.len() as i32,
|
||||
self.start_ix as i32,
|
||||
self.end_ix as i32,
|
||||
self.return_loc.ptr as i32,
|
||||
);
|
||||
if self.start_ix <= self.end_ix {
|
||||
assert_eq!(
|
||||
res,
|
||||
types::Errno::Ok.into(),
|
||||
"expected ok sum_of_elements errno"
|
||||
);
|
||||
let result_ptr = host_memory.ptr::<i32>(self.return_loc.ptr);
|
||||
let result = result_ptr.read().expect("read result");
|
||||
|
||||
let mut expected_sum: i32 = 0;
|
||||
for elem in self
|
||||
.elements
|
||||
.get(self.start_ix as usize..self.end_ix as usize)
|
||||
.unwrap()
|
||||
.iter()
|
||||
{
|
||||
expected_sum = expected_sum
|
||||
.wrapping_add(elem.first)
|
||||
.wrapping_add(elem.second);
|
||||
}
|
||||
assert_eq!(result, expected_sum, "sum of elements");
|
||||
} else {
|
||||
assert_eq!(
|
||||
res,
|
||||
types::Errno::InvalidArg.into(),
|
||||
"expected error out-of-bounds sum_of_elements"
|
||||
);
|
||||
}
|
||||
|
||||
// Index an array off the end of the array:
|
||||
let res = array_traversal::sum_of_elements(
|
||||
&ctx,
|
||||
&host_memory,
|
||||
self.element_loc.ptr as i32,
|
||||
self.elements.len() as i32,
|
||||
self.start_ix as i32,
|
||||
self.elements.len() as i32 + 1,
|
||||
self.return_loc.ptr as i32,
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
types::Errno::InvalidArg.into(),
|
||||
"out of bounds sum_of_elements errno"
|
||||
);
|
||||
}
|
||||
}
|
||||
proptest! {
|
||||
#[test]
|
||||
fn sum_elements(e in SumElementsExercise::strat()) {
|
||||
e.test()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,3 +15,22 @@
|
||||
(result $error $errno)
|
||||
)
|
||||
)
|
||||
|
||||
(typename $pair_ints
|
||||
(struct
|
||||
(field $first s32)
|
||||
(field $second s32)))
|
||||
|
||||
(module $array_traversal
|
||||
(@interface func (export "sum_of_element")
|
||||
(param $elements (array $pair_ints))
|
||||
(param $index (@witx usize))
|
||||
(result $error $errno)
|
||||
(result $sum s32))
|
||||
|
||||
(@interface func (export "sum_of_elements")
|
||||
(param $elements (array $pair_ints))
|
||||
(param $start (@witx usize))
|
||||
(param $end (@witx usize))
|
||||
(result $error $errno)
|
||||
(result $sum s32)))
|
||||
|
||||
@@ -125,7 +125,11 @@ impl MultiStringExercise {
|
||||
Just(a.clone()),
|
||||
Just(b.clone()),
|
||||
Just(c.clone()),
|
||||
HostMemory::byte_slice_strat(a.len() as u32, &MemAreas::from([return_ptr_loc])),
|
||||
HostMemory::byte_slice_strat(
|
||||
a.len() as u32,
|
||||
1,
|
||||
&MemAreas::from([return_ptr_loc]),
|
||||
),
|
||||
Just(return_ptr_loc),
|
||||
)
|
||||
})
|
||||
@@ -137,6 +141,7 @@ impl MultiStringExercise {
|
||||
Just(sa_ptr_loc),
|
||||
HostMemory::byte_slice_strat(
|
||||
b.len() as u32,
|
||||
1,
|
||||
&MemAreas::from([sa_ptr_loc, return_ptr_loc]),
|
||||
),
|
||||
Just(return_ptr_loc),
|
||||
@@ -151,6 +156,7 @@ impl MultiStringExercise {
|
||||
Just(sb_ptr_loc),
|
||||
HostMemory::byte_slice_strat(
|
||||
c.len() as u32,
|
||||
1,
|
||||
&MemAreas::from([sa_ptr_loc, sb_ptr_loc, return_ptr_loc]),
|
||||
),
|
||||
Just(return_ptr_loc),
|
||||
|
||||
@@ -473,6 +473,7 @@ impl SumArrayExercise {
|
||||
Just(inputs.clone()),
|
||||
HostMemory::byte_slice_strat(
|
||||
inputs.len() as u32,
|
||||
1,
|
||||
&MemAreas::from([input_struct_loc, output_loc]),
|
||||
),
|
||||
Just(input_struct_loc.clone()),
|
||||
|
||||
@@ -8,6 +8,11 @@ use wasmtime::*;
|
||||
use wasmtime_wasi::{Wasi, WasiCtx};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.with_ansi(true)
|
||||
.init();
|
||||
|
||||
let store = Store::default();
|
||||
let mut linker = Linker::new(&store);
|
||||
|
||||
|
||||
@@ -64,13 +64,7 @@ RUST_BACKTRACE=1 cargo test \
|
||||
--package wiggle-generate \
|
||||
--package wiggle-test \
|
||||
--package wiggle-macro \
|
||||
--package wasi-common \
|
||||
|
||||
# Test wasmtime-wasi-c, which doesn't support Windows.
|
||||
if [ "${OS:-Not}" != "Windows_NT" ]; then
|
||||
RUST_BACKTRACE=1 cargo test \
|
||||
--package wasmtime-wasi-c
|
||||
fi
|
||||
--package wasi-common
|
||||
|
||||
# Make sure the documentation builds.
|
||||
banner "Rust documentation: $topdir/target/doc/wasmtime/index.html"
|
||||
|
||||
Reference in New Issue
Block a user