delete wasi-common, yanix, winx

This commit is contained in:
Pat Hickey
2021-01-28 15:12:38 -08:00
parent d628677fae
commit dd005208b6
95 changed files with 0 additions and 11947 deletions

View File

@@ -1,433 +0,0 @@
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::Fd;
use crate::Error;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{env, io};
/// Possible errors when `WasiCtxBuilder` fails building
/// `WasiCtx`.
#[derive(Debug, thiserror::Error)]
pub enum WasiCtxBuilderError {
/// General I/O error was encountered.
#[error("general I/O error encountered: {0}")]
Io(#[from] io::Error),
/// 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),
/// `WasiCtx` has too many opened files.
#[error("context object has too many opened files")]
TooManyFilesOpen,
}
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
enum PendingEntry {
Thunk(fn() -> io::Result<Box<dyn Handle>>),
Handle(Box<dyn Handle>),
}
impl std::fmt::Debug for PendingEntry {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Thunk(f) => write!(
fmt,
"PendingEntry::Thunk({:p})",
f as *const fn() -> io::Result<Box<dyn Handle>>
),
Self::Handle(handle) => write!(fmt, "PendingEntry::Handle({:p})", handle),
}
}
}
struct PendingPreopen(Box<dyn FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>>>);
impl PendingPreopen {
fn new<F>(f: F) -> Self
where
F: FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>> + 'static,
{
Self(Box::new(f))
}
fn into(self) -> WasiCtxBuilderResult<Box<dyn Handle>> {
self.0()
}
}
/// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder {
stdin: Option<PendingEntry>,
stdout: Option<PendingEntry>,
stderr: Option<PendingEntry>,
preopens: Option<Vec<(PathBuf, PendingPreopen)>>,
args: Option<Vec<PendingString>>,
env: Option<HashMap<PendingString, PendingString>>,
}
impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`.
pub fn new() -> Self {
let stdin = Some(PendingEntry::Handle(Box::new(NullDevice::new())));
let stdout = Some(PendingEntry::Handle(Box::new(NullDevice::new())));
let stderr = Some(PendingEntry::Handle(Box::new(NullDevice::new())));
Self {
stdin,
stdout,
stderr,
preopens: Some(Vec::new()),
args: Some(Vec::new()),
env: Some(HashMap::new()),
}
}
/// Add arguments to the command-line arguments list.
///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args
.as_mut()
.unwrap()
.extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
self
}
/// Add an argument to the command-line arguments list.
///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
self.args
.as_mut()
.unwrap()
.push(arg.as_ref().to_vec().into());
self
}
/// Inherit the command-line arguments from the host process.
///
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
/// fail.
pub fn inherit_args(&mut self) -> &mut Self {
let args = self.args.as_mut().unwrap();
args.clear();
args.extend(env::args_os().map(PendingString::OsString));
self
}
/// Inherit stdin from the host process.
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Stdin::stdin));
self
}
/// Inherit stdout from the host process.
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout = Some(PendingEntry::Thunk(Stdout::stdout));
self
}
/// Inherit stderr from the host process.
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr = Some(PendingEntry::Thunk(Stderr::stderr));
self
}
/// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Stdin::stdin));
self.stdout = Some(PendingEntry::Thunk(Stdout::stdout));
self.stderr = Some(PendingEntry::Thunk(Stderr::stderr));
self
}
/// Inherit the environment variables from the host process.
///
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for
/// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail.
pub fn inherit_env(&mut self) -> &mut Self {
let env = self.env.as_mut().unwrap();
env.clear();
env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into())));
self
}
/// Add an entry to the environment.
///
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail.
pub fn env<S: AsRef<[u8]>>(&mut self, k: S, v: S) -> &mut Self {
self.env
.as_mut()
.unwrap()
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
self
}
/// Add entries to the environment.
///
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail.
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
&mut self,
envs: impl IntoIterator<Item = T>,
) -> &mut Self {
self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| {
let (k, v) = t.borrow();
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
}));
self
}
/// Provide a `Handle` to use as stdin
pub fn stdin<T: Handle + 'static>(&mut self, handle: T) -> &mut Self {
self.stdin = Some(PendingEntry::Handle(Box::new(handle)));
self
}
/// Provide a `Handle` to use as stdout
pub fn stdout<T: Handle + 'static>(&mut self, handle: T) -> &mut Self {
self.stdout = Some(PendingEntry::Handle(Box::new(handle)));
self
}
/// Provide a `Handle` to use as stderr
pub fn stderr<T: Handle + 'static>(&mut self, handle: T) -> &mut Self {
self.stderr = Some(PendingEntry::Handle(Box::new(handle)));
self
}
/// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
let preopen = PendingPreopen::new(move || {
let dir = OsDir::try_from(dir).map_err(WasiCtxBuilderError::from)?;
Ok(Box::new(dir))
});
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), preopen));
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 guest_path_owned = guest_path.as_ref().to_owned();
let preopen = PendingPreopen::new(move || {
if let VirtualDirEntry::Directory(entries) = dir {
let mut dir = VirtualDir::new(true);
populate_directory(entries, &mut dir);
Ok(Box::new(dir))
} else {
Err(WasiCtxBuilderError::VirtualDirEntryRootNotADirectory(
guest_path_owned,
))
}
});
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), preopen));
self
}
/// Build a `WasiCtx`, consuming this `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 `String`s, failing quickly if they
// contain any NUL bytes, or if conversion from `OsString` fails.
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.
for pending in vec![
self.stdin.take().unwrap(),
self.stdout.take().unwrap(),
self.stderr.take().unwrap(),
] {
tracing::debug!(
pending = tracing::field::debug(&pending),
"WasiCtx inserting entry"
);
let fd = match pending {
PendingEntry::Thunk(f) => {
let handle = EntryHandle::from(f()?);
let entry = Entry::new(handle);
entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
}
PendingEntry::Handle(handle) => {
let handle = EntryHandle::from(handle);
let entry = Entry::new(handle);
entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
}
};
tracing::debug!(fd = tracing::field::debug(fd), "WasiCtx inserted");
}
// Then add the preopen entries.
for (guest_path, preopen) in self.preopens.take().unwrap() {
let handle = EntryHandle::from(preopen.into()?);
let mut entry = Entry::new(handle);
entry.preopen_path = Some(guest_path);
let fd = entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
tracing::debug!(fd = tracing::field::debug(fd), "WasiCtx inserted",);
}
Ok(WasiCtx {
args,
env,
entries: RefCell::new(entries),
})
}
}
struct EntryTable {
fd_pool: FdPool,
entries: HashMap<Fd, Rc<Entry>>,
}
impl EntryTable {
fn new() -> Self {
Self {
fd_pool: FdPool::new(),
entries: HashMap::new(),
}
}
fn contains(&self, fd: &Fd) -> bool {
self.entries.contains_key(fd)
}
fn insert(&mut self, entry: Entry) -> Option<Fd> {
let fd = self.fd_pool.allocate()?;
self.entries.insert(fd, Rc::new(entry));
Some(fd)
}
fn insert_at(&mut self, fd: &Fd, entry: Rc<Entry>) {
self.entries.insert(*fd, entry);
}
fn get(&self, fd: &Fd) -> Option<Rc<Entry>> {
self.entries.get(fd).map(Rc::clone)
}
fn remove(&mut self, fd: Fd) -> Option<Rc<Entry>> {
let entry = self.entries.remove(&fd)?;
self.fd_pool.deallocate(fd);
Some(entry)
}
}
pub struct WasiCtx {
entries: RefCell<EntryTable>,
pub(crate) args: StringArray,
pub(crate) env: StringArray,
}
impl WasiCtx {
/// Make a new `WasiCtx` with some default settings.
///
/// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process.
///
/// - Environment variables are inherited from the host process.
///
/// To override these behaviors, use `WasiCtxBuilder`.
pub fn new<S: AsRef<[u8]>>(args: impl IntoIterator<Item = S>) -> WasiCtxBuilderResult<Self> {
WasiCtxBuilder::new()
.args(args)
.inherit_stdio()
.inherit_env()
.build()
}
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
pub(crate) fn contains_entry(&self, fd: Fd) -> bool {
self.entries.borrow().contains(&fd)
}
/// Get an immutable `Entry` corresponding to the specified raw WASI `fd`.
pub(crate) fn get_entry(&self, fd: Fd) -> Result<Rc<Entry>, Error> {
match self.entries.borrow().get(&fd) {
Some(entry) => Ok(entry),
None => Err(Error::Badf),
}
}
/// Insert the specified `Entry` into the `WasiCtx` object.
///
/// 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<Fd, Error> {
self.entries.borrow_mut().insert(entry).ok_or(Error::Mfile)
}
/// Insert the specified `Entry` with the specified raw WASI `fd` key into the `WasiCtx`
/// object.
pub(crate) fn insert_entry_at(&self, fd: Fd, entry: Rc<Entry>) {
self.entries.borrow_mut().insert_at(&fd, entry)
}
/// Remove `Entry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object.
pub(crate) fn remove_entry(&self, fd: 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
}
*/
}

View File

@@ -1,103 +0,0 @@
use crate::handle::{Filetype, Handle, HandleRights};
use crate::{Error, Result};
use std::ops::Deref;
use std::path::PathBuf;
use std::rc::Rc;
pub struct EntryHandle(Rc<dyn Handle>);
impl EntryHandle {
#[allow(dead_code)]
pub(crate) fn new<T: Handle + 'static>(handle: T) -> Self {
Self(Rc::new(handle))
}
pub(crate) fn get(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
impl std::fmt::Debug for EntryHandle {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("EntryHandle").field("opaque", &()).finish()
}
}
impl From<Box<dyn Handle>> for EntryHandle {
fn from(handle: Box<dyn Handle>) -> Self {
Self(handle.into())
}
}
impl Deref for EntryHandle {
type Target = dyn Handle;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
/// An abstraction struct serving as a wrapper for a `Handle` object.
///
/// Here, the `handle` field stores an instance of `Handle` type (such as a file descriptor, or
/// stdin handle), and accessing it can only be done via the provided `Entry::as_handle` method
/// which require a set of base and inheriting rights to be specified, verifying whether the stored
/// `Handle` object is valid for the rights specified.
pub(crate) struct Entry {
handle: EntryHandle,
pub(crate) preopen_path: Option<PathBuf>,
// TODO: directories
}
impl Entry {
pub(crate) fn new(handle: EntryHandle) -> Self {
let preopen_path = None;
Self {
handle,
preopen_path,
}
}
pub(crate) fn get_file_type(&self) -> Filetype {
self.handle.get_file_type()
}
pub(crate) fn get_rights(&self) -> HandleRights {
self.handle.get_rights()
}
pub(crate) fn set_rights(&self, rights: HandleRights) {
self.handle.set_rights(rights)
}
/// Convert this `Entry` into a `Handle` object provided the specified
/// `rights` rights are set on this `Entry` object.
///
/// The `Entry` can only be converted into a valid `Handle` object if
/// the specified set of base rights, and inheriting rights encapsulated within `rights`
/// `HandleRights` structure is a subset of rights attached to this `Entry`. The check is
/// performed using `Entry::validate_rights` method. If the check fails, `Error::Notcapable`
/// is returned.
pub(crate) fn as_handle(&self, rights: HandleRights) -> Result<EntryHandle> {
self.validate_rights(rights)?;
Ok(self.handle.get())
}
/// Check if this `Entry` object satisfies the specified `HandleRights`; i.e., if
/// rights attached to this `Entry` object are a superset.
///
/// Upon unsuccessful check, `Error::Notcapable` is returned.
pub(crate) fn validate_rights(&self, rights: HandleRights) -> Result<()> {
let this_rights = self.handle.get_rights();
if this_rights.contains(rights) {
Ok(())
} else {
tracing::trace!(
required = tracing::field::display(rights),
actual = tracing::field::display(this_rights),
"validate_rights failed",
);
Err(Error::Notcapable)
}
}
}

View File

@@ -1,199 +0,0 @@
use cfg_if::cfg_if;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
/// Internal error type for the `wasi-common` crate.
/// Contains variants of the WASI `$errno` type are added according to what is actually used internally by
/// the crate. Not all values are represented presently.
#[derive(Debug, Error)]
pub enum Error {
#[error("Wiggle GuestError: {0}")]
Guest(#[from] wiggle::GuestError),
#[error("TryFromIntError: {0}")]
TryFromInt(#[from] std::num::TryFromIntError),
#[error("Utf8Error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("GetRandom: {0}")]
GetRandom(#[from] getrandom::Error),
/// Some corners of the WASI standard are unsupported.
#[error("Unsupported: {0}")]
Unsupported(&'static str),
/// The host OS may return an io error that doesn't match one of the
/// wasi errno variants we expect. We do not expose the details of this
/// error to the user.
#[error("Unexpected IoError: {0}")]
UnexpectedIo(#[source] std::io::Error),
// Below this, all variants are from the `$errno` type:
/// Errno::TooBig: Argument list too long
#[error("TooBig: Argument list too long")]
TooBig,
/// Errno::Acces: Permission denied
#[error("Acces: Permission denied")]
Acces,
/// Errno::Badf: Bad file descriptor
#[error("Badf: Bad file descriptor")]
Badf,
/// Errno::Busy: Device or resource busy
#[error("Busy: Device or resource busy")]
Busy,
/// Errno::Exist: File exists
#[error("Exist: File exists")]
Exist,
/// Errno::Fault: Bad address
#[error("Fault: Bad address")]
Fault,
/// Errno::Fbig: File too large
#[error("Fbig: File too large")]
Fbig,
/// Errno::Ilseq: Illegal byte sequence
#[error("Ilseq: Illegal byte sequence")]
Ilseq,
/// Errno::Inval: Invalid argument
#[error("Inval: Invalid argument")]
Inval,
/// Errno::Io: I/O error
#[error("Io: I/o error")]
Io,
/// Errno::Isdir: Is a directory
#[error("Isdir: Is a directory")]
Isdir,
/// Errno::Loop: Too many levels of symbolic links
#[error("Loop: Too many levels of symbolic links")]
Loop,
/// Errno::Mfile: File descriptor value too large
#[error("Mfile: File descriptor value too large")]
Mfile,
/// Errno::Mlink: Too many links
#[error("Mlink: Too many links")]
Mlink,
/// Errno::Nametoolong: Filename too long
#[error("Nametoolong: Filename too long")]
Nametoolong,
/// Errno::Nfile: Too many files open in system
#[error("Nfile: Too many files open in system")]
Nfile,
/// Errno::Noent: No such file or directory
#[error("Noent: No such file or directory")]
Noent,
/// Errno::Nomem: Not enough space
#[error("Nomem: Not enough space")]
Nomem,
/// Errno::Nospc: No space left on device
#[error("Nospc: No space left on device")]
Nospc,
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
#[error("Notdir: Not a directory or a symbolic link to a directory")]
Notdir,
/// Errno::Notempty: Directory not empty.
#[error("Notempty: Directory not empty")]
Notempty,
/// Errno::Notsup: Not supported, or operation not supported on socket.
#[error("Notsup: Not supported, or operation not supported on socket")]
Notsup,
/// Errno::Overflow: Value too large to be stored in data type.
#[error("Overflow: Value too large to be stored in data type")]
Overflow,
/// Errno::Pipe: Broken pipe
#[error("Pipe: Broken pipe")]
Pipe,
/// Errno::Perm: Operation not permitted
#[error("Perm: Operation not permitted")]
Perm,
/// Errno::Spipe: Invalid seek
#[error("Spipe: Invalid seek")]
Spipe,
/// Errno::Notcapable: Extension: Capabilities insufficient
#[error("Notcapable: cabailities insufficient")]
Notcapable,
}
impl From<std::convert::Infallible> for Error {
fn from(_err: std::convert::Infallible) -> Self {
unreachable!("should be impossible: From<Infallible>")
}
}
// Turning an io::Error into an Error has platform-specific behavior
cfg_if! {
if #[cfg(windows)] {
use winapi::shared::winerror;
use std::io;
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(code) => match code as u32 {
winerror::ERROR_BAD_ENVIRONMENT => Self::TooBig,
winerror::ERROR_FILE_NOT_FOUND => Self::Noent,
winerror::ERROR_PATH_NOT_FOUND => Self::Noent,
winerror::ERROR_TOO_MANY_OPEN_FILES => Self::Nfile,
winerror::ERROR_ACCESS_DENIED => Self::Acces,
winerror::ERROR_SHARING_VIOLATION => Self::Acces,
winerror::ERROR_PRIVILEGE_NOT_HELD => Self::Notcapable,
winerror::ERROR_INVALID_HANDLE => Self::Badf,
winerror::ERROR_INVALID_NAME => Self::Noent,
winerror::ERROR_NOT_ENOUGH_MEMORY => Self::Nomem,
winerror::ERROR_OUTOFMEMORY => Self::Nomem,
winerror::ERROR_DIR_NOT_EMPTY => Self::Notempty,
winerror::ERROR_NOT_READY => Self::Busy,
winerror::ERROR_BUSY => Self::Busy,
winerror::ERROR_NOT_SUPPORTED => Self::Notsup,
winerror::ERROR_FILE_EXISTS => Self::Exist,
winerror::ERROR_BROKEN_PIPE => Self::Pipe,
winerror::ERROR_BUFFER_OVERFLOW => Self::Nametoolong,
winerror::ERROR_NOT_A_REPARSE_POINT => Self::Inval,
winerror::ERROR_NEGATIVE_SEEK => Self::Inval,
winerror::ERROR_DIRECTORY => Self::Notdir,
winerror::ERROR_ALREADY_EXISTS => Self::Exist,
_ => Self::UnexpectedIo(err),
},
None => Self::UnexpectedIo(err),
}
}
}
} else {
use std::io;
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(code) => match code {
libc::EPIPE => Self::Pipe,
libc::EPERM => Self::Perm,
libc::ENOENT => Self::Noent,
libc::ENOMEM => Self::Nomem,
libc::E2BIG => Self::TooBig,
libc::EIO => Self::Io,
libc::EBADF => Self::Badf,
libc::EBUSY => Self::Busy,
libc::EACCES => Self::Acces,
libc::EFAULT => Self::Fault,
libc::ENOTDIR => Self::Notdir,
libc::EISDIR => Self::Isdir,
libc::EINVAL => Self::Inval,
libc::EEXIST => Self::Exist,
libc::EFBIG => Self::Fbig,
libc::ENOSPC => Self::Nospc,
libc::ESPIPE => Self::Spipe,
libc::EMFILE => Self::Mfile,
libc::EMLINK => Self::Mlink,
libc::ENAMETOOLONG => Self::Nametoolong,
libc::ENFILE => Self::Nfile,
libc::ENOTEMPTY => Self::Notempty,
libc::ELOOP => Self::Loop,
libc::EOVERFLOW => Self::Overflow,
libc::EILSEQ => Self::Ilseq,
libc::ENOTSUP => Self::Notsup,
_ => Self::UnexpectedIo(err),
},
None => {
Self::UnexpectedIo(err)
}
}
}
}
}
}

View File

@@ -1,142 +0,0 @@
//! Contains mechanism for managing the WASI file descriptor
//! pool. It's intended to be mainly used within the `WasiCtx`
//! object(s).
/// Any type wishing to be treated as a valid WASI file descriptor
/// should implement this trait.
///
/// This trait is required as internally we use `u32` to represent
/// and manage raw file descriptors.
pub(crate) trait Fd {
/// Convert to `u32`.
fn as_raw(&self) -> u32;
/// Convert from `u32`.
fn from_raw(raw_fd: u32) -> Self;
}
impl Fd for u32 {
fn as_raw(&self) -> u32 {
*self
}
fn from_raw(raw_fd: u32) -> Self {
raw_fd
}
}
/// This container tracks and manages all file descriptors that
/// were already allocated.
/// Internally, we use `u32` to represent the file descriptors;
/// however, the caller may supply any type `T` such that it
/// implements the `Fd` trait when requesting a new descriptor
/// via the `allocate` method, or when returning one back via
/// the `deallocate` method.
#[derive(Debug)]
pub(crate) struct FdPool {
next_alloc: Option<u32>,
available: Vec<u32>,
}
impl FdPool {
pub fn new() -> Self {
Self {
next_alloc: Some(0),
available: Vec::new(),
}
}
/// Obtain another valid WASI file descriptor.
///
/// If we've handed out the maximum possible amount of file
/// descriptors (which would be equal to `2^32 + 1` accounting for `0`),
/// then this method will return `None` to signal that case.
/// Otherwise, a new file descriptor is return as `Some(fd)`.
pub fn allocate<T: Fd>(&mut self) -> Option<T> {
if let Some(fd) = self.available.pop() {
// Since we've had free, unclaimed handle in the pool,
// simply claim it and return.
return Some(T::from_raw(fd));
}
// There are no free handles available in the pool, so try
// allocating an additional one into the pool. If we've
// reached our max number of handles, we will fail with None
// instead.
let fd = self.next_alloc.take()?;
// It's OK to not unpack the result of `fd.checked_add()` here which
// can fail since we check for `None` in the snippet above.
self.next_alloc = fd.checked_add(1);
Some(T::from_raw(fd))
}
/// Return a file descriptor back to the pool.
///
/// If the caller tries to return a file descriptor that was
/// not yet allocated (via spoofing, etc.), this method
/// will panic.
pub fn deallocate<T: Fd>(&mut self, fd: T) {
let fd = fd.as_raw();
if let Some(next_alloc) = self.next_alloc {
assert!(fd < next_alloc);
}
debug_assert!(!self.available.contains(&fd));
self.available.push(fd);
}
}
#[cfg(test)]
mod test {
use super::FdPool;
use std::ops::Deref;
#[derive(Debug)]
struct Fd(u32);
impl super::Fd for Fd {
fn as_raw(&self) -> u32 {
self.0
}
fn from_raw(raw_fd: u32) -> Self {
Self(raw_fd)
}
}
impl Deref for Fd {
type Target = u32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[test]
fn basics() {
let mut fd_pool = FdPool::new();
let mut fd: Fd = fd_pool.allocate().expect("success allocating 0");
assert_eq!(*fd, 0);
fd = fd_pool.allocate().expect("success allocating 1");
assert_eq!(*fd, 1);
fd = fd_pool.allocate().expect("success allocating 2");
assert_eq!(*fd, 2);
fd_pool.deallocate(1u32);
fd_pool.deallocate(0u32);
fd = fd_pool.allocate().expect("success reallocating 0");
assert_eq!(*fd, 0);
fd = fd_pool.allocate().expect("success reallocating 1");
assert_eq!(*fd, 1);
fd = fd_pool.allocate().expect("success allocating 3");
assert_eq!(*fd, 3);
}
#[test]
#[should_panic]
fn deallocate_nonexistent() {
let mut fd_pool = FdPool::new();
fd_pool.deallocate(0u32);
}
#[test]
fn max_allocation() {
let mut fd_pool = FdPool::new();
// Spoof reaching the limit of allocs.
fd_pool.next_alloc = None;
assert!(fd_pool.allocate::<Fd>().is_none());
}
}

View File

@@ -1,217 +0,0 @@
use crate::fs::{Fd, File, OpenOptions, ReadDir};
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::WasiCtx;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::{io, path::Path};
/// A reference to an open directory on the filesystem.
///
/// TODO: Implement `Dir`-using versions of `std::fs`'s free functions:
/// `copy`, `create_dir`, `create_dir_all`, `hard_link`, `metadata`,
/// `read_link`, `read_to_string`, `remove_dir`, `remove_dir_all`,
/// `remove_file`, `rename`, `set_permissions`, `symlink_metadata`, and
/// `write`.
///
/// Unlike `std::fs`, this API has no `canonicalize`, because absolute paths
/// don't interoperate well with the capability-oriented security model.
pub struct Dir<'ctx> {
ctx: &'ctx WasiCtx,
fd: Fd,
}
impl<'ctx> Dir<'ctx> {
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
pub unsafe fn from_raw_wasi_fd(ctx: &'ctx WasiCtx, fd: Fd) -> Self {
Self { ctx, fd }
}
/// Attempts to open a file in read-only mode.
///
/// This corresponds to [`std::fs::File::open`], but only accesses paths
/// relative to and within `self`.
///
/// TODO: Not yet implemented. Refactor the hostcalls functions to split out the
/// encoding/decoding parts from the underlying functionality, so that we can call
/// into the underlying functionality directly.
///
/// [`std::fs::File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open
pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<File> {
let path = path.as_ref();
let mut fd = Fd::from(0);
// TODO: Refactor the hostcalls functions to split out the encoding/decoding
// parts from the underlying functionality, so that we can call into the
// underlying functionality directly.
//
// TODO: Set the requested rights to be readonly.
//
// TODO: Handle paths for non-Unix platforms which don't have `as_bytes()`
// on `OsStrExt`.
unimplemented!("Dir::open_file");
/*
wasi_errno_to_io_error(hostcalls::path_open(
self.ctx,
self.fd,
wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
path.as_os_str().len(),
0,
!0,
!0,
0,
&mut fd,
))?;
*/
let ctx = self.ctx;
Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) })
}
/// Opens a file at `path` with the options specified by `self`.
///
/// This corresponds to [`std::fs::OpenOptions::open`].
///
/// Instead of being a method on `OpenOptions`, this is a method on `Dir`,
/// and it only accesses functions relative to and within `self`.
///
/// TODO: Not yet implemented.
///
/// [`std::fs::OpenOptions::open`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open
pub fn open_file_with<P: AsRef<Path>>(
&mut self,
path: P,
options: &OpenOptions,
) -> io::Result<File> {
unimplemented!("Dir::open_file_with");
}
/// Attempts to open a directory.
///
/// TODO: Not yet implemented. See the comment in `open_file`.
pub fn open_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Self> {
let path = path.as_ref();
let mut fd = Fd::from(0);
// TODO: See the comment in `open_file`.
unimplemented!("Dir::open_dir");
/*
wasi_errno_to_io_error(hostcalls::path_open(
self.ctx,
self.fd,
wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
wasi::__WASI_OFLAGS_DIRECTORY,
!0,
!0,
0,
&mut fd,
))?;
*/
let ctx = self.ctx;
Ok(unsafe { Dir::from_raw_wasi_fd(ctx, fd) })
}
/// Opens a file in write-only mode.
///
/// This corresponds to [`std::fs::File::create`], but only accesses paths
/// relative to and within `self`.
///
/// TODO: Not yet implemented. See the comment in `open_file`.
///
/// [`std::fs::File::create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create
pub fn create_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<File> {
let path = path.as_ref();
let mut fd = Fd::from(0);
// TODO: See the comments in `open_file`.
//
// TODO: Set the requested rights to be read+write.
unimplemented!("Dir::create_file");
/*
wasi_errno_to_io_error(hostcalls::path_open(
self.ctx,
self.fd,
wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
path.as_os_str().len(),
wasi::__WASI_OFLAGS_CREAT | wasi::__WASI_OFLAGS_TRUNC,
!0,
!0,
0,
&mut fd,
))?;
*/
let ctx = self.ctx;
Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) })
}
/// Returns an iterator over the entries within a directory.
///
/// This corresponds to [`std::fs::read_dir`], but reads the directory
/// represented by `self`.
///
/// TODO: Not yet implemented. We may need to wait until we have the ability
/// to duplicate file descriptors before we can implement read safely. For
/// now, use `into_read` instead.
///
/// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
pub fn read(&mut self) -> io::Result<ReadDir> {
unimplemented!("Dir::read")
}
/// Consumes self and returns an iterator over the entries within a directory
/// in the manner of `read`.
pub fn into_read(self) -> ReadDir {
unsafe { ReadDir::from_raw_wasi_fd(self.fd) }
}
/// Read the entire contents of a file into a bytes vector.
///
/// This corresponds to [`std::fs::read`], but only accesses paths
/// relative to and within `self`.
///
/// [`std::fs::read`]: https://doc.rust-lang.org/std/fs/fn.read.html
pub fn read_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Vec<u8>> {
use io::Read;
let mut file = self.open_file(path)?;
let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
/// Returns an iterator over the entries within a directory.
///
/// This corresponds to [`std::fs::read_dir`], but only accesses paths
/// relative to and within `self`.
///
/// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
pub fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
self.open_dir(path)?.read()
}
}
impl<'ctx> Drop for Dir<'ctx> {
fn drop(&mut self) {
// Note that errors are ignored when closing a file descriptor. The
// reason for this is that if an error occurs we don't actually know if
// the file descriptor was closed or not, and if we retried (for
// something like EINTR), we might close another valid file descriptor
// opened after we closed ours.
let _ = self.ctx.fd_close(self.fd);
}
}
/// Indicates how large a buffer to pre-allocate before reading the entire file.
///
/// Derived from the function of the same name in libstd.
fn initial_buffer_size(file: &File) -> usize {
// Allocate one extra byte so the buffer doesn't need to grow before the
// final `read` call at the end of the file. Don't worry about `usize`
// overflow because reading will fail regardless in that case.
file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
}
// TODO: impl Debug for Dir

View File

@@ -1,49 +0,0 @@
use std::{io, path::Path};
/// A builder used to create directories in various manners.
///
/// This corresponds to [`std::fs::DirBuilder`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirBuilder`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html
pub struct DirBuilder {}
impl DirBuilder {
/// Creates a new set of options with default mode/security settings for all platforms and also non-recursive.
///
/// This corresponds to [`std::fs::DirBuilder::new`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirBuilder::new`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.new
pub fn new() -> Self {
unimplemented!("DirBuilder::new");
}
/// Indicates that directories should be created recursively, creating all parent directories.
///
/// This corresponds to [`std::fs::DirBuilder::recursive`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirBuilder::recursive`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.recursive
pub fn recursive(&mut self, recursive: bool) -> &mut Self {
unimplemented!("DirBuilder::recursive");
}
/// Creates the specified directory with the options configured in this builder.
///
/// This corresponds to [`std::fs::DirBuilder::create`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirBuilder::create`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.create
pub fn create<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
unimplemented!("DirBuilder::create");
}
}
// TODO: functions from DirBuilderExt?
// TODO: impl Debug for DirBuilder

View File

@@ -1,53 +0,0 @@
use crate::fs::{FileType, Metadata};
use std::{ffi, io};
/// Entries returned by the ReadDir iterator.
///
/// This corresponds to [`std::fs::DirEntry`].
///
/// Unlike `std::fs::DirEntry`, this API has no `DirEntry::path`, because
/// absolute paths don't interoperate well with the capability-oriented
/// security model.
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirEntry`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html
pub struct DirEntry {}
impl DirEntry {
/// Returns the metadata for the file that this entry points at.
///
/// This corresponds to [`std::fs::DirEntry::metadata`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirEntry::metadata`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.metadata
pub fn metadata(&self) -> io::Result<Metadata> {
unimplemented!("DirEntry::metadata");
}
/// Returns the file type for the file that this entry points at.
///
/// This to [`std::fs::DirEntry::file_type`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirEntry::file_type`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_type
pub fn file_type(&self) -> io::Result<FileType> {
unimplemented!("DirEntry::file_type");
}
/// Returns the bare file name of this directory entry without any other leading path component.
///
/// This corresponds to [`std::fs::DirEntry::file_name`], though it returns
/// `String` rather than `OsString`.
///
/// TODO: Not yet implemented.
///
/// [`std::fs::DirEntry::file_name`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_name
pub fn file_name(&self) -> String {
unimplemented!("DirEntry::file_name");
}
}
// TODO: impl Debug for DirEntry

View File

@@ -1,113 +0,0 @@
use crate::fs::{Fd, Metadata};
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::Result;
use crate::WasiCtx;
use std::io;
/// A reference to an open file on the filesystem.
///
/// This corresponds to [`std::fs::File`].
///
/// Note that this `File` has no `open` or `create` methods. To open or create
/// a file, you must first obtain a [`Dir`] containing the file, and then call
/// [`Dir::open_file`] or [`Dir::create_file`].
///
/// [`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
/// [`Dir`]: struct.Dir.html
/// [`Dir::open_file`]: struct.Dir.html#method.open_file
/// [`Dir::create_file`]: struct.Dir.html#method.create_file
pub struct File<'ctx> {
ctx: &'ctx WasiCtx,
fd: Fd,
}
impl<'ctx> File<'ctx> {
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
///
/// This corresponds to [`std::fs::File::from_raw_fd`].
///
/// [`std::fs::File::from_raw_fd`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.from_raw_fd
pub unsafe fn from_raw_wasi_fd(ctx: &'ctx WasiCtx, fd: Fd) -> Self {
Self { ctx, fd }
}
/// Attempts to sync all OS-internal metadata to disk.
///
/// This corresponds to [`std::fs::File::sync_all`].
///
/// [`std::fs::File::sync_all`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all
pub fn sync_all(&self) -> Result<()> {
self.ctx.fd_sync(self.fd)?;
Ok(())
}
/// This function is similar to `sync_all`, except that it may not synchronize
/// file metadata to the filesystem.
///
/// This corresponds to [`std::fs::File::sync_data`].
///
/// [`std::fs::File::sync_data`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data
pub fn sync_data(&self) -> Result<()> {
self.ctx.fd_datasync(self.fd)?;
Ok(())
}
/// Truncates or extends the underlying file, updating the size of this file
/// to become size.
///
/// This corresponds to [`std::fs::File::set_len`].
///
/// [`std::fs::File::set_len`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len
pub fn set_len(&self, size: u64) -> Result<()> {
self.ctx.fd_filestat_set_size(self.fd, size)?;
Ok(())
}
/// Queries metadata about the underlying file.
///
/// This corresponds to [`std::fs::File::metadata`].
///
/// [`std::fs::File::metadata`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata
pub fn metadata(&self) -> Result<Metadata> {
Ok(Metadata {})
}
}
impl<'ctx> Drop for File<'ctx> {
fn drop(&mut self) {
// Note that errors are ignored when closing a file descriptor. The
// reason for this is that if an error occurs we don't actually know if
// the file descriptor was closed or not, and if we retried (for
// something like EINTR), we might close another valid file descriptor
// opened after we closed ours.
let _ = self.ctx.fd_close(self.fd);
}
}
impl<'ctx> io::Read for File<'ctx> {
/// TODO: Not yet implemented. See the comment in `Dir::open_file`.
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
// TODO
// let iov = [Iovec {
// buf: buf.as_mut_ptr() as *mut u8,
// buf_len: buf.len(),
// }];
let mut nread = 0;
// TODO: See the comment in `Dir::open_file`.
unimplemented!("File::read");
/*
wasi_errno_to_io_error(unsafe {
hostcalls::fd_read(self.ctx, self.fd, &iov, 1, &mut nread)
})?;
*/
Ok(nread)
}
}
// TODO: traits to implement: Write, Seek
// TODO: functions from FileExt?
// TODO: impl Debug for File

View File

@@ -1,49 +0,0 @@
/// A structure representing a type of file with accessors for each file type.
/// It is returned by `Metadata::file_type` method.
///
/// This corresponds to [`std::fs::FileType`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct FileType {}
impl FileType {
/// Tests whether this file type represents a directory.
///
/// This corresponds to [`std::fs::FileType::is_dir`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::FileType::is_dir`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_dir
pub fn is_dir(&self) -> bool {
unimplemented!("FileType::is_dir");
}
/// Tests whether this file type represents a regular file.
///
/// This corresponds to [`std::fs::FileType::is_file`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::FileType::is_file`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_file
pub fn is_file(&self) -> bool {
unimplemented!("FileType::is_file");
}
/// Tests whether this file type represents a symbolic link.
///
/// This corresponds to [`std::fs::FileType::is_symlink`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::FileType::is_symlink`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_symlink
pub fn is_symlink(&self) -> bool {
unimplemented!("FileType::is_symlink");
}
}
// TODO: functions from FileTypeExt?
// TODO: impl Debug for FileType

View File

@@ -1,106 +0,0 @@
use crate::fs::{FileType, Permissions};
use std::{io, time::SystemTime};
/// Metadata information about a file.
///
/// This corresponds to [`std::fs::Metadata`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html
#[derive(Clone)]
pub struct Metadata {}
impl Metadata {
/// Returns the file type for this metadata.
///
/// This corresponds to [`std::fs::Metadata::file_type`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::file_type`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.file_type
pub fn file_type(&self) -> FileType {
unimplemented!("Metadata::file_type");
}
/// Returns true if this metadata is for a directory.
///
/// This corresponds to [`std::fs::Metadata::is_dir`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::is_dir`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.is_dir
pub fn is_dir(&self) -> bool {
unimplemented!("Metadata::is_dir");
}
/// Returns true if this metadata is for a regular file.
///
/// This corresponds to [`std::fs::Metadata::is_file`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::is_file`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.is_file
pub fn is_file(&self) -> bool {
unimplemented!("Metadata::is_file");
}
/// Returns the size of the file, in bytes, this metadata is for.
///
/// This corresponds to [`std::fs::Metadata::len`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::len`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.len
pub fn len(&self) -> u64 {
unimplemented!("Metadata::len");
}
/// Returns the permissions of the file this metadata is for.
///
/// This corresponds to [`std::fs::Metadata::permissions`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::permissions`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.permissions
pub fn permissions(&self) -> Permissions {
unimplemented!("Metadata::permissions");
}
/// Returns the last modification time listed in this metadata.
///
/// This corresponds to [`std::fs::Metadata::modified`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::modified`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.modified
pub fn modified(&self) -> io::Result<SystemTime> {
unimplemented!("Metadata::modified");
}
/// Returns the last access time of this metadata.
///
/// This corresponds to [`std::fs::Metadata::accessed`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::accessed`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed
pub fn accessed(&self) -> io::Result<SystemTime> {
unimplemented!("Metadata::accessed");
}
/// Returns the creation time listed in this metadata.
///
/// This corresponds to [`std::fs::Metadata::created`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Metadata::created`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.created
pub fn created(&self) -> io::Result<SystemTime> {
unimplemented!("Metadata::created");
}
}
// TODO: Functions from MetadataExt?
// TODO: impl Debug for Metadata

View File

@@ -1,52 +0,0 @@
//! A very experimental module modeled providing a high-level and safe
//! filesystem interface, modeled after `std::fs`, implemented on top of
//! WASI functions.
//!
//! Most functions in this API are not yet implemented!
//!
//! This corresponds to [`std::fs`].
//!
//! Instead of [`std::fs`'s free functions] which operate on paths, this
//! crate has methods on [`Dir`] which operate on paths which must be
//! relative to and within the directory.
//!
//! Since all functions which expose raw file descriptors are `unsafe`,
//! I/O handles in this API are unforgeable (unsafe code notwithstanding).
//! This combined with WASI's lack of absolute paths provides a natural
//! capability-oriented interface.
//!
//! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html
//! [`std::fs`'s free functions]: https://doc.rust-lang.org/std/fs/index.html#functions
//! [`DIR`]: struct.Dir.html
// TODO: When more things are implemented, remove these.
#![allow(
unused_imports,
unreachable_code,
unused_variables,
unused_mut,
unused_unsafe,
dead_code
)]
mod dir;
mod dir_builder;
mod dir_entry;
mod file;
mod file_type;
mod metadata;
mod open_options;
mod permissions;
mod readdir;
pub use crate::wasi::types::Fd;
pub use dir::*;
pub use dir_builder::*;
pub use dir_entry::*;
pub use file::*;
pub use file_type::*;
pub use metadata::*;
pub use open_options::*;
pub use permissions::*;
pub use readdir::*;

View File

@@ -1,99 +0,0 @@
/// Options and flags which can be used to configure how a file is opened.
///
/// This corresponds to [`std::fs::OpenOptions`].
///
/// Note that this `OpenOptions` has no `open` method. To open a file with
/// an `OptionOptions`, you must first obtain a [`Dir`] containing the file, and
/// then call [`Dir::open_file_with`].
///
/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
/// [`Dir`]: struct.Dir.html
/// [`Dir::open_file_with`]: struct.Dir.html#method.open_file_with
pub struct OpenOptions {
pub(crate) read: bool,
pub(crate) write: bool,
pub(crate) append: bool,
pub(crate) truncate: bool,
pub(crate) create: bool,
pub(crate) create_new: bool,
}
impl OpenOptions {
/// Creates a blank new set of options ready for configuration.
///
/// This corresponds to [`std::fs::OpenOptions::new`].
///
/// [`std::fs::OpenOptions::new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.new
pub fn new() -> Self {
Self {
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
}
}
/// Sets the option for read access.
///
/// This corresponds to [`std::fs::OpenOptions::read`].
///
/// [`std::fs::OpenOptions::read`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.read
pub fn read(&mut self, read: bool) -> &mut Self {
self.read = read;
self
}
/// Sets the option for write access.
///
/// This corresponds to [`std::fs::OpenOptions::write`].
///
/// [`std::fs::OpenOptions::write`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.write
pub fn write(&mut self, write: bool) -> &mut Self {
self.write = write;
self
}
/// Sets the option for the append mode.
///
/// This corresponds to [`std::fs::OpenOptions::append`].
///
/// [`std::fs::OpenOptions::append`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.append
pub fn append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
/// Sets the option for truncating a previous file.
///
/// This corresponds to [`std::fs::OpenOptions::truncate`].
///
/// [`std::fs::OpenOptions::truncate`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.truncate
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.truncate = truncate;
self
}
/// Sets the option to create a new file.
///
/// This corresponds to [`std::fs::OpenOptions::create`].
///
/// [`std::fs::OpenOptions::create`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
self
}
/// Sets the option to always create a new file.
///
/// This corresponds to [`std::fs::OpenOptions::create_new`].
///
/// [`std::fs::OpenOptions::create_new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
self.create_new = create_new;
self
}
}
// TODO: Functions from OpenOptionsExt?

View File

@@ -1,37 +0,0 @@
/// Representation of the various permissions on a file.
///
/// This corresponds to [`std::fs::Permissions`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html
#[derive(Eq, PartialEq, Clone)]
pub struct Permissions {}
impl Permissions {
/// Returns true if these permissions describe a readonly (unwritable) file.
///
/// This corresponds to [`std::fs::Permissions::readonly`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Permissions::readonly`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.readonly
pub fn readonly(&self) -> bool {
unimplemented!("Permissions::readonly");
}
/// Modifies the readonly flag for this set of permissions.
///
/// This corresponds to [`std::fs::Permissions::set_readonly`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::Permissions::set_readonly`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly
pub fn set_readonly(&mut self, readonly: bool) {
unimplemented!("Permissions::set_readonly");
}
}
// TODO: functions from PermissionsExt?
// TODO: impl Debug for Permissions

View File

@@ -1,31 +0,0 @@
use crate::fs::{DirEntry, Fd};
/// Iterator over the entries in a directory.
///
/// This corresponds to [`std::fs::ReadDir`].
///
/// TODO: Not yet implemented.
///
/// [`std::fs::ReadDir`]: https://doc.rust-lang.org/std/fs/struct.ReadDir.html
pub struct ReadDir {
fd: Fd,
}
impl ReadDir {
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
pub unsafe fn from_raw_wasi_fd(fd: Fd) -> Self {
Self { fd }
}
}
/// TODO: Not yet implemented.
impl Iterator for ReadDir {
type Item = DirEntry;
/// TODO: Not yet implemented.
fn next(&mut self) -> Option<Self::Item> {
unimplemented!("ReadDir::next");
}
}
// TODO: impl Debug for ReadDir

View File

@@ -1,360 +0,0 @@
pub use crate::wasi::types::{
Advice, Dircookie, Dirent, Fdflags, Fdstat, Filedelta, Filesize, Filestat, Filetype, Fstflags,
Lookupflags, Oflags, Prestat, PrestatDir, Rights, Size, Timestamp, Whence,
};
use crate::{Error, Result};
use std::any::Any;
use std::fmt;
use std::io::{self, SeekFrom};
/// Represents rights of a `Handle`, either already held or required.
#[derive(Debug, Copy, Clone)]
pub struct HandleRights {
pub(crate) base: Rights,
pub(crate) inheriting: Rights,
}
impl HandleRights {
/// Creates new `HandleRights` instance from `base` and `inheriting` rights.
pub fn new(base: Rights, inheriting: Rights) -> Self {
Self { base, inheriting }
}
/// Creates new `HandleRights` instance from `base` rights only, keeping
/// `inheriting` set to none.
pub fn from_base(base: Rights) -> Self {
Self {
base,
inheriting: Rights::empty(),
}
}
/// Creates new `HandleRights` instance with both `base` and `inheriting`
/// rights set to none.
pub fn empty() -> Self {
Self {
base: Rights::empty(),
inheriting: Rights::empty(),
}
}
/// Checks if `other` is a subset of those rights.
pub fn contains(&self, other: Self) -> bool {
self.base.contains(other.base) && self.inheriting.contains(other.inheriting)
}
/// Returns base rights.
pub fn base(&self) -> Rights {
self.base
}
/// Returns inheriting rights.
pub fn inheriting(&self) -> Rights {
self.inheriting
}
}
impl fmt::Display for HandleRights {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"HandleRights {{ base: {}, inheriting: {} }}",
self.base, self.inheriting
)
}
}
/// Generic interface for all WASI-compatible handles. We currently group these into two groups:
/// * OS-based resources (actual, real resources): `OsFile`, `OsDir`, `OsOther`, and `Stdio`,
/// * virtual files and directories: VirtualDir`, and `InMemoryFile`.
///
/// # Constructing `Handle`s representing OS-based resources
///
/// Each type of handle can either be constructed directly (see docs entry for a specific handle
/// type such as `OsFile`), or you can let the `wasi_common` crate's machinery work it out
/// automatically for you using `std::convert::TryInto` from `std::fs::File`:
///
/// ```rust,no_run
/// use std::convert::TryInto;
/// use wasi_common::Handle;
/// use std::fs::OpenOptions;
///
/// let some_file = OpenOptions::new().read(true).open("some_file").unwrap();
/// let wasi_handle: Box<dyn Handle> = some_file.try_into().unwrap();
/// ```
pub trait Handle {
fn as_any(&self) -> &dyn Any;
fn try_clone(&self) -> io::Result<Box<dyn Handle>>;
fn get_file_type(&self) -> Filetype;
fn get_rights(&self) -> HandleRights {
HandleRights::empty()
}
fn set_rights(&self, rights: HandleRights);
fn is_directory(&self) -> bool {
self.get_file_type() == Filetype::Directory
}
/// Test whether this descriptor is considered a tty within WASI.
/// Note that since WASI itself lacks an `isatty` syscall and relies
/// on a conservative approximation, we use the same approximation here.
fn is_tty(&self) -> bool {
let file_type = self.get_file_type();
let rights = self.get_rights();
let required_rights = HandleRights::from_base(Rights::FD_SEEK | Rights::FD_TELL);
file_type == Filetype::CharacterDevice && rights.contains(required_rights)
}
// TODO perhaps should be a separate trait?
// FdOps
fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Badf)
}
fn allocate(&self, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Badf)
}
fn datasync(&self) -> Result<()> {
Err(Error::Inval)
}
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(Fdflags::empty())
}
fn fdstat_set_flags(&self, _fdflags: Fdflags) -> Result<()> {
Err(Error::Badf)
}
fn filestat_get(&self) -> Result<Filestat> {
Err(Error::Badf)
}
fn filestat_set_size(&self, _st_size: Filesize) -> Result<()> {
Err(Error::Badf)
}
fn filestat_set_times(
&self,
_atim: Timestamp,
_mtim: Timestamp,
_fst_flags: Fstflags,
) -> Result<()> {
Err(Error::Badf)
}
fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result<usize> {
Err(Error::Badf)
}
fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result<usize> {
Err(Error::Badf)
}
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Err(Error::Badf)
}
fn readdir<'a>(
&'a self,
_cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>> + 'a>> {
Err(Error::Badf)
}
fn seek(&self, _offset: SeekFrom) -> Result<u64> {
Err(Error::Badf)
}
fn sync(&self) -> Result<()> {
Ok(())
}
fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result<usize> {
Err(Error::Badf)
}
// TODO perhaps should be a separate trait?
// PathOps
fn create_directory(&self, _path: &str) -> Result<()> {
Err(Error::Acces)
}
fn filestat_get_at(&self, _path: &str, _follow: bool) -> Result<Filestat> {
Err(Error::Acces)
}
fn filestat_set_times_at(
&self,
_path: &str,
_atim: Timestamp,
_mtim: Timestamp,
_fst_flags: Fstflags,
_follow: bool,
) -> Result<()> {
Err(Error::Acces)
}
fn openat(
&self,
_path: &str,
_read: bool,
_write: bool,
_oflags: Oflags,
_fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
Err(Error::Acces)
}
fn link(
&self,
_old_path: &str,
_new_handle: Box<dyn Handle>,
_new_path: &str,
_follow: bool,
) -> Result<()> {
Err(Error::Acces)
}
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
Err(Error::Acces)
}
fn readlinkat(&self, _path: &str) -> Result<String> {
Err(Error::Acces)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::Acces)
}
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
Err(Error::Acces)
}
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
Err(Error::Acces)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::Acces)
}
}
impl From<std::fs::FileType> for Filetype {
fn from(ftype: std::fs::FileType) -> Self {
if ftype.is_file() {
Self::RegularFile
} else if ftype.is_dir() {
Self::Directory
} else if ftype.is_symlink() {
Self::SymbolicLink
} else {
Self::Unknown
}
}
}
pub(crate) trait AsBytes {
fn as_bytes(&self) -> Result<Vec<u8>>;
}
impl AsBytes for Dirent {
fn as_bytes(&self) -> Result<Vec<u8>> {
use std::convert::TryInto;
use wiggle::GuestType;
assert_eq!(
Self::guest_size(),
std::mem::size_of::<Self>() as _,
"guest repr of Dirent and host repr should match"
);
let offset = Self::guest_size().try_into()?;
let mut bytes: Vec<u8> = Vec::with_capacity(offset);
bytes.resize(offset, 0);
let ptr = bytes.as_mut_ptr() as *mut Self;
unsafe { ptr.write_unaligned(self.clone()) };
Ok(bytes)
}
}
pub(crate) trait RightsExt: Sized {
fn block_device_base() -> Self;
fn block_device_inheriting() -> Self;
fn character_device_base() -> Self;
fn character_device_inheriting() -> Self;
fn directory_base() -> Self;
fn directory_inheriting() -> Self;
fn regular_file_base() -> Self;
fn regular_file_inheriting() -> Self;
fn socket_base() -> Self;
fn socket_inheriting() -> Self;
fn tty_base() -> Self;
fn tty_inheriting() -> Self;
}
impl RightsExt for Rights {
// Block and character device interaction is outside the scope of
// WASI. Simply allow everything.
fn block_device_base() -> Self {
Self::all()
}
fn block_device_inheriting() -> Self {
Self::all()
}
fn character_device_base() -> Self {
Self::all()
}
fn character_device_inheriting() -> Self {
Self::all()
}
// Only allow directory operations on directories. Directories can only
// yield file descriptors to other directories and files.
fn directory_base() -> Self {
Self::FD_FDSTAT_SET_FLAGS
| Self::FD_SYNC
| Self::FD_ADVISE
| Self::PATH_CREATE_DIRECTORY
| Self::PATH_CREATE_FILE
| Self::PATH_LINK_SOURCE
| Self::PATH_LINK_TARGET
| Self::PATH_OPEN
| Self::FD_READDIR
| Self::PATH_READLINK
| Self::PATH_RENAME_SOURCE
| Self::PATH_RENAME_TARGET
| Self::PATH_FILESTAT_GET
| Self::PATH_FILESTAT_SET_SIZE
| Self::PATH_FILESTAT_SET_TIMES
| Self::FD_FILESTAT_GET
| Self::FD_FILESTAT_SET_TIMES
| Self::PATH_SYMLINK
| Self::PATH_UNLINK_FILE
| Self::PATH_REMOVE_DIRECTORY
| Self::POLL_FD_READWRITE
}
fn directory_inheriting() -> Self {
Self::all() ^ Self::SOCK_SHUTDOWN
}
// Operations that apply to regular files.
fn regular_file_base() -> Self {
Self::FD_DATASYNC
| Self::FD_READ
| Self::FD_SEEK
| Self::FD_FDSTAT_SET_FLAGS
| Self::FD_SYNC
| Self::FD_TELL
| Self::FD_WRITE
| Self::FD_ADVISE
| Self::FD_ALLOCATE
| Self::FD_FILESTAT_GET
| Self::FD_FILESTAT_SET_SIZE
| Self::FD_FILESTAT_SET_TIMES
| Self::POLL_FD_READWRITE
}
fn regular_file_inheriting() -> Self {
Self::empty()
}
// Operations that apply to sockets and socket pairs.
fn socket_base() -> Self {
Self::FD_READ
| Self::FD_FDSTAT_SET_FLAGS
| Self::FD_WRITE
| Self::FD_FILESTAT_GET
| Self::POLL_FD_READWRITE
| Self::SOCK_SHUTDOWN
}
fn socket_inheriting() -> Self {
Self::all()
}
// Operations that apply to TTYs.
fn tty_base() -> Self {
Self::FD_READ
| Self::FD_FDSTAT_SET_FLAGS
| Self::FD_WRITE
| Self::FD_FILESTAT_GET
| Self::POLL_FD_READWRITE
}
fn tty_inheriting() -> Self {
Self::empty()
}
}
pub(crate) const DIRCOOKIE_START: Dircookie = 0;

View File

@@ -1,45 +0,0 @@
#![deny(
// missing_docs,
trivial_numeric_casts,
unused_extern_crates,
unstable_features,
clippy::use_self
)]
#![warn(unused_import_braces)]
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
#![cfg_attr(
feature = "cargo-clippy",
warn(
clippy::float_arithmetic,
clippy::mut_mut,
clippy::nonminimal_bool,
clippy::map_unwrap_or,
clippy::clippy::unicode_not_nfc,
clippy::use_self
)
)]
mod ctx;
mod entry;
mod error;
mod fdpool;
pub mod fs;
mod handle;
mod path;
mod sandboxed_tty_writer;
pub(crate) mod sched;
pub mod snapshots;
mod string_array;
mod sys;
pub mod virtfs;
pub mod wasi;
pub use ctx::{WasiCtx, WasiCtxBuilder, WasiCtxBuilderError};
pub use error::{Error, Result};
pub use handle::{Handle, HandleRights};
pub use sys::osdir::OsDir;
pub use sys::osfile::OsFile;
pub use sys::osother::OsOther;
pub use sys::preopen_dir;
pub use virtfs::{FileContents, VirtualDirEntry};

View File

@@ -1,190 +0,0 @@
use crate::entry::Entry;
use crate::handle::{Fdflags, Filetype, Handle, HandleRights, Lookupflags, Oflags};
use crate::{Error, Result};
use std::path::{Component, Path};
use std::str;
pub(crate) use crate::sys::path::{from_host, open_rights};
/// Normalizes a path to ensure that the target path is located under the directory provided.
///
/// This is a workaround for not having Capsicum support in the OS.
pub(crate) fn get(
entry: &Entry,
required_rights: HandleRights,
dirflags: Lookupflags,
path: &str,
needs_final_component: bool,
) -> Result<(Box<dyn Handle>, String)> {
const MAX_SYMLINK_EXPANSIONS: usize = 128;
tracing::trace!(path = path);
if path.contains('\0') {
// if contains NUL, return Ilseq
return Err(Error::Ilseq);
}
if entry.get_file_type() != Filetype::Directory {
// if `dirfd` doesn't refer to a directory, return `Notdir`.
return Err(Error::Notdir);
}
let handle = entry.as_handle(required_rights)?;
let dirfd = handle.try_clone()?;
// Stack of directory file descriptors. Index 0 always corresponds with the directory provided
// to this function. Entering a directory causes a file descriptor to be pushed, while handling
// ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply
// escaping the base directory.
let mut dir_stack = vec![dirfd];
// Stack of paths left to process. This is initially the `path` argument to this function, but
// any symlinks we encounter are processed by pushing them on the stack.
let mut path_stack = vec![path.to_owned()];
// Track the number of symlinks we've expanded, so we can return `ELOOP` after too many.
let mut symlink_expansions = 0;
// TODO: rewrite this using a custom posix path type, with a component iterator that respects
// trailing slashes. This version does way too much allocation, and is way too fiddly.
loop {
match path_stack.pop() {
Some(cur_path) => {
tracing::debug!(cur_path = tracing::field::display(&cur_path), "path get");
let ends_with_slash = cur_path.ends_with('/');
let mut components = Path::new(&cur_path).components();
let head = match components.next() {
None => return Err(Error::Noent),
Some(p) => p,
};
let tail = components.as_path();
if tail.components().next().is_some() {
let mut tail = from_host(tail.as_os_str())?;
if ends_with_slash {
tail.push('/');
}
path_stack.push(tail);
}
tracing::debug!(path_stack = tracing::field::debug(&path_stack), "path_get");
match head {
Component::Prefix(_) | Component::RootDir => {
// path is absolute!
return Err(Error::Notcapable);
}
Component::CurDir => {
// "." so skip
}
Component::ParentDir => {
// ".." so pop a dir
let _ = dir_stack.pop().ok_or(Error::Notcapable)?;
// we're not allowed to pop past the original directory
if dir_stack.is_empty() {
return Err(Error::Notcapable);
}
}
Component::Normal(head) => {
let mut head = from_host(head)?;
if ends_with_slash {
// preserve trailing slash
head.push('/');
}
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
let fd = dir_stack.last().ok_or(Error::Notcapable)?;
match fd.openat(
&head,
false,
false,
Oflags::DIRECTORY,
Fdflags::empty(),
) {
Ok(new_dir) => {
dir_stack.push(new_dir);
}
Err(e) => {
match e {
Error::Loop | Error::Mlink | Error::Notdir =>
// Check to see if it was a symlink. Linux indicates
// this with ENOTDIR because of the O_DIRECTORY flag.
{
// attempt symlink expansion
let fd = dir_stack.last().ok_or(Error::Notcapable)?;
let mut link_path = fd.readlinkat(&head)?;
symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
return Err(Error::Loop);
}
if head.ends_with('/') {
link_path.push('/');
}
tracing::debug!(
"attempted symlink expansion link_path={:?}",
link_path
);
path_stack.push(link_path);
}
_ => {
return Err(e);
}
}
}
}
continue;
} else if ends_with_slash || dirflags.contains(Lookupflags::SYMLINK_FOLLOW)
{
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
// symlink expansion
let fd = dir_stack.last().ok_or(Error::Notcapable)?;
match fd.readlinkat(&head) {
Ok(mut link_path) => {
symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
return Err(Error::Loop);
}
if head.ends_with('/') {
link_path.push('/');
}
tracing::debug!(
"attempted symlink expansion link_path={:?}",
link_path
);
path_stack.push(link_path);
continue;
}
Err(Error::Inval) | Err(Error::Noent) | Err(Error::Notdir) => {
// this handles the cases when trying to link to
// a destination that already exists, and the target
// path contains a slash
}
Err(e) => {
return Err(e);
}
}
}
// not a symlink, so we're done;
return Ok((dir_stack.pop().ok_or(Error::Notcapable)?, head));
}
}
}
None => {
// no further components to process. means we've hit a case like "." or "a/..", or if the
// input path has trailing slashes and `needs_final_component` is not set
return Ok((dir_stack.pop().ok_or(Error::Notcapable)?, String::from(".")));
}
}
}
}

View File

@@ -1,199 +0,0 @@
use std::io::{IoSlice, Result, Write};
/// An adapter around a `Write` stream that guarantees that its output
/// is valid UTF-8 and contains no control characters. It does this by
/// replacing characters with inert control pictures and replacement
/// characters.
pub(crate) struct SandboxedTTYWriter<'writer, Writer>
where
Writer: Write,
{
inner: &'writer mut Writer,
}
impl<'writer, Writer> SandboxedTTYWriter<'writer, Writer>
where
Writer: Write,
{
/// Construct a new `SandboxedTTYWriter` with the given inner `Writer`.
pub(crate) fn new(inner: &'writer mut Writer) -> Self {
Self { inner }
}
/// Write a single character to the output.
pub(crate) fn write_char(&mut self, c: char) -> Result<()> {
self.inner.write(
match c {
'\u{0000}' => '␀',
'\u{0001}' => '␁',
'\u{0002}' => '␂',
'\u{0003}' => '␃',
'\u{0004}' => '␄',
'\u{0005}' => '␅',
'\u{0006}' => '␆',
'\u{0007}' => '␇',
'\u{0008}' => '␈',
'\u{0009}' => '\t',
'\u{000A}' => '\n',
'\u{000B}' => '␋',
'\u{000C}' => '␌',
'\u{000D}' => '\r',
'\u{000E}' => '␎',
'\u{000F}' => '␏',
'\u{0010}' => '␐',
'\u{0011}' => '␑',
'\u{0012}' => '␒',
'\u{0013}' => '␓',
'\u{0014}' => '␔',
'\u{0015}' => '␕',
'\u{0016}' => '␖',
'\u{0017}' => '␗',
'\u{0018}' => '␘',
'\u{0019}' => '␙',
'\u{001A}' => '␚',
'\u{001B}' => '␛',
'\u{001C}' => '␜',
'\u{001D}' => '␝',
'\u{001E}' => '␞',
'\u{001F}' => '␟',
'\u{007F}' => '␡',
x if x.is_control() => '<27>',
x => x,
}
.encode_utf8(&mut [0; 4]) // UTF-8 encoding of a `char` is at most 4 bytes.
.as_bytes(),
)?;
Ok(())
}
/// Write a string to the output.
pub(crate) fn write_str(&mut self, s: &str) -> Result<usize> {
let mut result = 0;
for c in s.chars() {
self.write_char(c)?;
// Note that we use the encoding length of the given char, rather than
// how many bytes we actually wrote, because our users don't know about
// what's really being written.
result += c.len_utf8();
}
Ok(result)
}
}
impl<'writer, Writer> Write for SandboxedTTYWriter<'writer, Writer>
where
Writer: Write,
{
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let mut input = buf;
let mut result = 0;
// Decode the string without heap-allocating it. See the example here
// for more details:
// https://doc.rust-lang.org/std/str/struct.Utf8Error.html#examples
loop {
match std::str::from_utf8(input) {
Ok(valid) => {
result += self.write_str(valid)?;
break;
}
Err(error) => {
let (valid, after_valid) = input.split_at(error.valid_up_to());
result += self.write_str(unsafe { std::str::from_utf8_unchecked(valid) })?;
self.write_char('<27>')?;
if let Some(invalid_sequence_length) = error.error_len() {
// An invalid sequence was encountered. Tell the application we've
// written those bytes (though actually, we replaced them with U+FFFD).
result += invalid_sequence_length;
// Set up `input` to resume writing after the end of the sequence.
input = &after_valid[invalid_sequence_length..];
} else {
// The end of the buffer was encountered unexpectedly. Tell the application
// we've written out the remainder of the buffer.
result += after_valid.len();
break;
}
}
}
}
return Ok(result);
}
fn write_vectored(&mut self, bufs: &[IoSlice]) -> Result<usize> {
// Terminal output is [not expected to be atomic], so just write all the
// individual buffers in sequence.
//
// [not expected to be atomic]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_08
let mut total_written = 0;
for buf in bufs {
let written = self.write(buf)?;
total_written += written;
// Stop at the first point where the OS writes less than we asked.
if written < buf.len() {
break;
}
}
Ok(total_written)
}
fn flush(&mut self) -> Result<()> {
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::SandboxedTTYWriter;
use std::io::{Result, Write};
#[test]
fn basic() -> Result<()> {
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write_str("a\0b\u{0080}")?;
safe.write_char('\u{0007}')?;
safe.write(&[0x80])?;
safe.write(&[0xed, 0xa0, 0x80, 0xff, 0xfe])?;
assert_eq!(
buffer,
"a\u{2400}b\u{FFFD}\u{2407}\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}".as_bytes()
);
Ok(())
}
#[test]
fn how_many_replacements() -> Result<()> {
// See https://hsivonen.fi/broken-utf-8/ for background.
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0x80, 0x80, 0x80, 0x80])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}\u{FFFD}\u{FFFD}".as_bytes());
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0xF0, 0x80, 0x80, 0x41])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}\u{FFFD}A".as_bytes());
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0xF0, 0x80, 0x80])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}\u{FFFD}".as_bytes());
let mut buffer = Vec::new();
let mut safe = SandboxedTTYWriter::new(&mut buffer);
safe.write(&[0xF4, 0x80, 0x80, 0xC0])?;
assert_eq!(buffer, "\u{FFFD}\u{FFFD}".as_bytes());
Ok(())
}
}

View File

@@ -1,17 +0,0 @@
use crate::entry::EntryHandle;
pub use crate::wasi::types::{
Clockid, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, Subclockflags,
SubscriptionClock, Timestamp, Userdata,
};
#[derive(Debug, Copy, Clone)]
pub struct ClockEventData {
pub delay: u128, // delay is expressed in nanoseconds
pub userdata: Userdata,
}
#[derive(Debug)]
pub struct FdEventData {
pub handle: EntryHandle,
pub r#type: Eventtype,
pub userdata: Userdata,
}

View File

@@ -1,2 +0,0 @@
pub mod wasi_snapshot_preview1;
pub mod wasi_unstable;

View File

@@ -1,862 +0,0 @@
use crate::entry::{Entry, EntryHandle};
use crate::handle::{AsBytes, HandleRights};
use crate::sys::{clock, poll};
use crate::wasi::types;
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::{path, sched, Error, Result, WasiCtx};
use std::cmp::min;
use std::convert::TryInto;
use std::io::{self, SeekFrom};
use std::ops::Deref;
use tracing::{debug, trace};
use wiggle::{GuestPtr, GuestSliceMut};
impl<'a> WasiSnapshotPreview1 for WasiCtx {
fn args_get<'b>(
&self,
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
argv_buf: &GuestPtr<'b, u8>,
) -> Result<()> {
self.args.write_to_guest(argv_buf, argv)
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> {
Ok((self.args.number_elements, self.args.cumulative_size))
}
fn environ_get<'b>(
&self,
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
environ_buf: &GuestPtr<'b, u8>,
) -> Result<()> {
self.env.write_to_guest(environ_buf, environ)
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> {
Ok((self.env.number_elements, self.env.cumulative_size))
}
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp> {
let resolution = clock::res_get(id)?;
Ok(resolution)
}
fn clock_time_get(
&self,
id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp> {
let time = clock::time_get(id)?;
Ok(time)
}
fn fd_advise(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_ADVISE);
let entry = self.get_entry(fd)?;
entry
.as_handle(required_rights)?
.advise(advice, offset, len)
}
fn fd_allocate(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_ALLOCATE);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.allocate(offset, len)
}
fn fd_close(&self, fd: types::Fd) -> Result<()> {
if let Ok(fe) = self.get_entry(fd) {
// can't close preopened files
if fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
let _ = self.remove_entry(fd)?;
Ok(())
}
fn fd_datasync(&self, fd: types::Fd) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_DATASYNC);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.datasync()
}
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat> {
let entry = self.get_entry(fd)?;
let file = entry.as_handle(HandleRights::empty())?;
let fs_flags = file.fdstat_get()?;
let rights = entry.get_rights();
let fdstat = types::Fdstat {
fs_filetype: entry.get_file_type(),
fs_rights_base: rights.base,
fs_rights_inheriting: rights.inheriting,
fs_flags,
};
Ok(fdstat)
}
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.fdstat_set_flags(flags)
}
fn fd_fdstat_set_rights(
&self,
fd: types::Fd,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
) -> Result<()> {
let rights = HandleRights::new(fs_rights_base, fs_rights_inheriting);
let entry = self.get_entry(fd)?;
if !entry.get_rights().contains(rights) {
return Err(Error::Notcapable);
}
entry.set_rights(rights);
Ok(())
}
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_GET);
let entry = self.get_entry(fd)?;
let host_filestat = entry.as_handle(required_rights)?.filestat_get()?;
Ok(host_filestat)
}
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.filestat_set_size(size)
}
fn fd_filestat_set_times(
&self,
fd: types::Fd,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES);
let entry = self.get_entry(fd)?;
entry
.as_handle(required_rights)?
.filestat_set_times(atim, mtim, fst_flags)
}
fn fd_pread(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size> {
let mut guest_slices: Vec<GuestSliceMut<'_, u8>> = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
guest_slices.push(iov.buf.as_array(iov.buf_len).as_slice_mut()?);
}
let required_rights =
HandleRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK);
let entry = self.get_entry(fd)?;
if offset > i64::max_value() as u64 {
return Err(Error::Io);
}
let host_nread = {
let mut buf = guest_slices
.iter_mut()
.map(|s| io::IoSliceMut::new(&mut *s))
.collect::<Vec<io::IoSliceMut<'_>>>();
entry
.as_handle(required_rights)?
.preadv(&mut buf, offset)?
.try_into()?
};
Ok(host_nread)
}
fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat> {
// TODO: should we validate any rights here?
let entry = self.get_entry(fd)?;
let po_path = entry.preopen_path.as_ref().ok_or(Error::Notsup)?;
if entry.get_file_type() != types::Filetype::Directory {
return Err(Error::Notdir);
}
let path = path::from_host(po_path.as_os_str())?;
let prestat = types::PrestatDir {
pr_name_len: path.len().try_into()?,
};
Ok(types::Prestat::Dir(prestat))
}
fn fd_prestat_dir_name(
&self,
fd: types::Fd,
path: &GuestPtr<u8>,
path_len: types::Size,
) -> Result<()> {
// TODO: should we validate any rights here?
let entry = self.get_entry(fd)?;
let po_path = entry.preopen_path.as_ref().ok_or(Error::Notsup)?;
if entry.get_file_type() != types::Filetype::Directory {
return Err(Error::Notdir);
}
let host_path = path::from_host(po_path.as_os_str())?;
let host_path_len = host_path.len().try_into()?;
if host_path_len > path_len {
return Err(Error::Nametoolong);
}
trace!(" | path='{}'", host_path);
path.as_array(host_path_len)
.copy_from_slice(host_path.as_bytes())?;
Ok(())
}
fn fd_pwrite(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
offset: types::Filesize,
) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for ciov_ptr in ciovs.iter() {
let ciov_ptr = ciov_ptr?;
let ciov: types::Ciovec = ciov_ptr.read()?;
guest_slices.push(ciov.buf.as_array(ciov.buf_len).as_slice()?);
}
let required_rights =
HandleRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK);
let entry = self.get_entry(fd)?;
if offset > i64::max_value() as u64 {
return Err(Error::Io);
}
let host_nwritten = {
let buf: Vec<io::IoSlice> =
guest_slices.iter().map(|s| io::IoSlice::new(&*s)).collect();
entry
.as_handle(required_rights)?
.pwritev(&buf, offset)?
.try_into()?
};
Ok(host_nwritten)
}
fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr?;
let iov: types::Iovec = iov_ptr.read()?;
guest_slices.push(iov.buf.as_array(iov.buf_len).as_slice_mut()?);
}
let required_rights = HandleRights::from_base(types::Rights::FD_READ);
let entry = self.get_entry(fd)?;
let host_nread = {
let mut slices: Vec<io::IoSliceMut> = guest_slices
.iter_mut()
.map(|s| io::IoSliceMut::new(&mut *s))
.collect();
entry
.as_handle(required_rights)?
.read_vectored(&mut slices)?
.try_into()?
};
Ok(host_nread)
}
fn fd_readdir(
&self,
fd: types::Fd,
buf: &GuestPtr<u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size> {
let required_rights = HandleRights::from_base(types::Rights::FD_READDIR);
let entry = self.get_entry(fd)?;
let mut bufused = 0;
let mut buf = buf.clone();
for pair in entry.as_handle(required_rights)?.readdir(cookie)? {
let (dirent, name) = pair?;
let dirent_raw = dirent.as_bytes()?;
let dirent_len: types::Size = dirent_raw.len().try_into()?;
let name_raw = name.as_bytes();
let name_len = name_raw.len().try_into()?;
let offset = dirent_len.checked_add(name_len).ok_or(Error::Overflow)?;
// Copy as many bytes of the dirent as we can, up to the end of the buffer.
let dirent_copy_len = min(dirent_len, buf_len - bufused);
buf.as_array(dirent_copy_len)
.copy_from_slice(&dirent_raw[..dirent_copy_len as usize])?;
// If the dirent struct wasn't copied entirely, return that we
// filled the buffer, which tells libc that we're not at EOF.
if dirent_copy_len < dirent_len {
return Ok(buf_len);
}
buf = buf.add(dirent_copy_len)?;
// Copy as many bytes of the name as we can, up to the end of the buffer.
let name_copy_len = min(name_len, buf_len - bufused);
buf.as_array(name_copy_len)
.copy_from_slice(&name_raw[..name_copy_len as usize])?;
// If the dirent struct wasn't copied entirely, return that we
// filled the buffer, which tells libc that we're not at EOF.
if name_copy_len < name_len {
return Ok(buf_len);
}
buf = buf.add(name_copy_len)?;
bufused += offset;
}
Ok(bufused)
}
fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<()> {
if !self.contains_entry(from) {
return Err(Error::Badf);
}
// Don't allow renumbering over a pre-opened resource.
// TODO: Eventually, we do want to permit this, once libpreopen in
// userspace is capable of removing entries from its tables as well.
if let Ok(from_fe) = self.get_entry(from) {
if from_fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
if let Ok(to_fe) = self.get_entry(to) {
if to_fe.preopen_path.is_some() {
return Err(Error::Notsup);
}
}
let fe = self.remove_entry(from)?;
self.insert_entry_at(to, fe);
Ok(())
}
fn fd_seek(
&self,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize> {
let base = if offset == 0 && whence == types::Whence::Cur {
types::Rights::FD_TELL
} else {
types::Rights::FD_SEEK | types::Rights::FD_TELL
};
let required_rights = HandleRights::from_base(base);
let entry = self.get_entry(fd)?;
let pos = match whence {
types::Whence::Cur => SeekFrom::Current(offset),
types::Whence::End => SeekFrom::End(offset),
types::Whence::Set => SeekFrom::Start(offset as u64),
};
let host_newoffset = entry.as_handle(required_rights)?.seek(pos)?;
Ok(host_newoffset)
}
fn fd_sync(&self, fd: types::Fd) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::FD_SYNC);
let entry = self.get_entry(fd)?;
entry.as_handle(required_rights)?.sync()
}
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize> {
let required_rights = HandleRights::from_base(types::Rights::FD_TELL);
let entry = self.get_entry(fd)?;
let host_offset = entry
.as_handle(required_rights)?
.seek(SeekFrom::Current(0))?;
Ok(host_offset)
}
fn fd_write(&self, fd: types::Fd, ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
let mut guest_slices = Vec::new();
for ciov_ptr in ciovs.iter() {
let ciov_ptr = ciov_ptr?;
let ciov: types::Ciovec = ciov_ptr.read()?;
guest_slices.push(ciov.buf.as_array(ciov.buf_len).as_slice()?);
}
let required_rights = HandleRights::from_base(types::Rights::FD_WRITE);
let entry = self.get_entry(fd)?;
let host_nwritten = {
let slices: Vec<io::IoSlice> =
guest_slices.iter().map(|s| io::IoSlice::new(&*s)).collect();
entry
.as_handle(required_rights)?
.write_vectored(&slices)?
.try_into()?
};
Ok(host_nwritten)
}
fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
let required_rights = HandleRights::from_base(
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.deref(),
false,
)?;
dirfd.create_directory(&path)
}
fn path_filestat_get(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
) -> Result<types::Filestat> {
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_GET);
let entry = self.get_entry(dirfd)?;
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)
}
fn path_filestat_set_times(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
let entry = self.get_entry(dirfd)?;
let path = path.as_str()?;
let (dirfd, path) = path::get(&entry, required_rights, flags, path.deref(), false)?;
dirfd.filestat_set_times_at(
&path,
atim,
mtim,
fst_flags,
flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)?;
Ok(())
}
fn path_link(
&self,
old_fd: types::Fd,
old_flags: types::Lookupflags,
old_path: &GuestPtr<'_, str>,
new_fd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> 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) = {
// 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) = {
// 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,
&new_path,
old_flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)
}
fn path_open(
&self,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: &GuestPtr<'_, str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
fdflags: types::Fdflags,
) -> Result<types::Fd> {
let needed_rights = path::open_rights(
&HandleRights::new(fs_rights_base, fs_rights_inheriting),
oflags,
fdflags,
);
trace!(" | needed_rights={}", needed_rights);
let entry = self.get_entry(dirfd)?;
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();
let write = fs_rights_base
& (types::Rights::FD_DATASYNC
| types::Rights::FD_WRITE
| types::Rights::FD_ALLOCATE
| types::Rights::FD_FILESTAT_SET_SIZE)
!= types::Rights::empty();
trace!(
" | calling path_open impl: read={}, write={}",
read,
write
);
let fd = dirfd.openat(&path, read, write, oflags, fdflags)?;
let entry = Entry::new(EntryHandle::from(fd));
// We need to manually deny the rights which are not explicitly requested
// because Entry::from will assign maximal consistent rights.
let mut rights = entry.get_rights();
rights.base &= fs_rights_base;
rights.inheriting &= fs_rights_inheriting;
entry.set_rights(rights);
let guest_fd = self.insert_entry(entry)?;
Ok(guest_fd)
}
fn path_readlink(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
buf: &GuestPtr<u8>,
buf_len: types::Size,
) -> Result<types::Size> {
let required_rights = HandleRights::from_base(types::Rights::PATH_READLINK);
let entry = self.get_entry(dirfd)?;
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_mut()?;
let host_bufused = dirfd.readlink(&path, &mut *slice)?.try_into()?;
Ok(host_bufused)
}
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) = {
let path = path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
path.deref(),
true,
)?
};
dirfd.remove_directory(&path)
}
fn path_rename(
&self,
old_fd: types::Fd,
old_path: &GuestPtr<'_, str>,
new_fd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_SOURCE);
let entry = self.get_entry(old_fd)?;
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) = {
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)
}
fn path_symlink(
&self,
old_path: &GuestPtr<'_, str>,
dirfd: types::Fd,
new_path: &GuestPtr<'_, str>,
) -> Result<()> {
let required_rights = HandleRights::from_base(types::Rights::PATH_SYMLINK);
let entry = self.get_entry(dirfd)?;
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.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) = {
let path = path.as_str()?;
path::get(
&entry,
required_rights,
types::Lookupflags::empty(),
path.deref(),
false,
)?
};
dirfd.unlink_file(&path)?;
Ok(())
}
fn poll_oneoff(
&self,
in_: &GuestPtr<types::Subscription>,
out: &GuestPtr<types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size> {
if u64::from(nsubscriptions) > types::Filesize::max_value() {
return Err(Error::Inval);
}
let mut subscriptions = Vec::new();
let subs = in_.as_array(nsubscriptions);
for sub_ptr in subs.iter() {
let sub_ptr = sub_ptr?;
let sub: types::Subscription = sub_ptr.read()?;
subscriptions.push(sub);
}
let events = self.poll_oneoff_impl(&subscriptions)?;
let nevents = events.len().try_into()?;
let out_events = out.as_array(nevents);
for (event, event_ptr) in events.into_iter().zip(out_events.iter()) {
let event_ptr = event_ptr?;
event_ptr.write(event)?;
}
trace!(nevents = nevents);
Ok(nevents)
}
fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
// Check that the status is within WASI's range.
if status < 126 {
wiggle::Trap::I32Exit(status as i32)
} else {
wiggle::Trap::String("exit with invalid exit status outside of [0..126)".to_owned())
}
}
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
Err(Error::Unsupported("proc_raise"))
}
fn sched_yield(&self) -> Result<()> {
std::thread::yield_now();
Ok(())
}
fn random_get(&self, buf: &GuestPtr<u8>, buf_len: types::Size) -> Result<()> {
let mut slice = buf.as_array(buf_len).as_slice_mut()?;
getrandom::getrandom(&mut *slice)?;
Ok(())
}
fn sock_recv(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags)> {
Err(Error::Unsupported("sock_recv"))
}
fn sock_send(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
) -> Result<types::Size> {
Err(Error::Unsupported("sock_send"))
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
Err(Error::Unsupported("sock_shutdown"))
}
}
impl WasiCtx {
pub(crate) fn poll_oneoff_impl(
&self,
subscriptions: &[types::Subscription],
) -> Result<Vec<types::Event>> {
let mut events = Vec::new();
let mut timeout: Option<sched::ClockEventData> = None;
let mut fd_events = Vec::new();
// As mandated by the WASI spec:
// > If `nsubscriptions` is 0, returns `errno::inval`.
if subscriptions.is_empty() {
return Err(Error::Inval);
}
for subscription in subscriptions {
match &subscription.u {
types::SubscriptionU::Clock(clock) => {
let delay = clock::to_relative_ns_delay(&clock)?;
debug!(
clock = tracing::field::debug(&clock),
delay_ns = tracing::field::debug(delay),
"poll_oneoff"
);
let current = sched::ClockEventData {
delay,
userdata: subscription.userdata,
};
let timeout = timeout.get_or_insert(current);
if current.delay < timeout.delay {
*timeout = current;
}
}
types::SubscriptionU::FdRead(fd_read) => {
let fd = fd_read.file_descriptor;
let required_rights = HandleRights::from_base(
types::Rights::FD_READ | types::Rights::POLL_FD_READWRITE,
);
let entry = match self.get_entry(fd) {
Ok(entry) => entry,
Err(error) => {
events.push(types::Event {
userdata: subscription.userdata,
error: error.try_into().expect("non-trapping error"),
type_: types::Eventtype::FdRead,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
},
});
continue;
}
};
fd_events.push(sched::FdEventData {
handle: entry.as_handle(required_rights)?,
r#type: types::Eventtype::FdRead,
userdata: subscription.userdata,
});
}
types::SubscriptionU::FdWrite(fd_write) => {
let fd = fd_write.file_descriptor;
let required_rights = HandleRights::from_base(
types::Rights::FD_WRITE | types::Rights::POLL_FD_READWRITE,
);
let entry = match self.get_entry(fd) {
Ok(entry) => entry,
Err(error) => {
events.push(types::Event {
userdata: subscription.userdata,
error: error.try_into().expect("non-trapping error"),
type_: types::Eventtype::FdWrite,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::empty(),
},
});
continue;
}
};
fd_events.push(sched::FdEventData {
handle: entry.as_handle(required_rights)?,
r#type: types::Eventtype::FdWrite,
userdata: subscription.userdata,
});
}
}
}
debug!(
events = tracing::field::debug(&events),
timeout = tracing::field::debug(timeout),
"poll_oneoff"
);
// The underlying implementation should successfully and immediately return
// if no events have been passed. Such situation may occur if all provided
// events have been filtered out as errors in the code above.
poll::oneoff(timeout, fd_events, &mut events)?;
Ok(events)
}
}

View File

@@ -1,939 +0,0 @@
use crate::wasi::{types as types_new, wasi_snapshot_preview1::WasiSnapshotPreview1};
use crate::{Error, WasiCtx};
use std::convert::{TryFrom, TryInto};
use types::*;
wiggle::from_witx!({
witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"],
ctx: WasiCtx,
errors: { errno => Error },
});
impl wiggle::GuestErrorType for Errno {
fn success() -> Self {
Self::Success
}
}
impl types::GuestErrorConversion for WasiCtx {
fn into_errno(&self, e: wiggle::GuestError) -> Errno {
tracing::debug!("Guest error: {:?}", e);
e.into()
}
}
impl types::UserErrorConversion for WasiCtx {
fn errno_from_error(&self, e: Error) -> Result<Errno, wiggle::Trap> {
tracing::debug!("Error: {:?}", e);
e.try_into()
}
}
impl TryFrom<Error> for Errno {
type Error = wiggle::Trap;
fn try_from(e: Error) -> Result<Errno, wiggle::Trap> {
Ok(types_new::Errno::try_from(e)?.into())
}
}
impl From<wiggle::GuestError> for Errno {
fn from(err: wiggle::GuestError) -> Self {
types_new::Errno::from(err).into()
}
}
impl wasi_unstable::WasiUnstable for WasiCtx {
fn args_get<'a>(
&self,
argv: &wiggle::GuestPtr<'a, wiggle::GuestPtr<'a, u8>>,
argv_buf: &wiggle::GuestPtr<'a, u8>,
) -> Result<(), Error> {
WasiSnapshotPreview1::args_get(self, argv, argv_buf)
}
fn args_sizes_get(&self) -> Result<(Size, Size), Error> {
WasiSnapshotPreview1::args_sizes_get(self)
}
fn environ_get<'a>(
&self,
environ: &wiggle::GuestPtr<'a, wiggle::GuestPtr<'a, u8>>,
environ_buf: &wiggle::GuestPtr<'a, u8>,
) -> Result<(), Error> {
WasiSnapshotPreview1::environ_get(self, environ, environ_buf)
}
fn environ_sizes_get(&self) -> Result<(Size, Size), Error> {
WasiSnapshotPreview1::environ_sizes_get(self)
}
fn clock_res_get(&self, id: Clockid) -> Result<Timestamp, Error> {
WasiSnapshotPreview1::clock_res_get(self, id.into())
}
fn clock_time_get(&self, id: Clockid, precision: Timestamp) -> Result<Timestamp, Error> {
WasiSnapshotPreview1::clock_time_get(self, id.into(), precision)
}
fn fd_advise(
&self,
fd: Fd,
offset: Filesize,
len: Filesize,
advice: Advice,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_advise(self, fd.into(), offset, len, advice.into())
}
fn fd_allocate(&self, fd: Fd, offset: Filesize, len: Filesize) -> Result<(), Error> {
WasiSnapshotPreview1::fd_allocate(self, fd.into(), offset, len)
}
fn fd_close(&self, fd: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_close(self, fd.into())
}
fn fd_datasync(&self, fd: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_datasync(self, fd.into())
}
fn fd_fdstat_get(&self, fd: Fd) -> Result<Fdstat, Error> {
WasiSnapshotPreview1::fd_fdstat_get(self, fd.into()).map(|s| s.into())
}
fn fd_fdstat_set_flags(&self, fd: Fd, flags: Fdflags) -> Result<(), Error> {
WasiSnapshotPreview1::fd_fdstat_set_flags(self, fd.into(), flags.into())
}
fn fd_fdstat_set_rights(
&self,
fd: Fd,
fs_rights_base: Rights,
fs_rights_inheriting: Rights,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_fdstat_set_rights(
self,
fd.into(),
fs_rights_base.into(),
fs_rights_inheriting.into(),
)
}
fn fd_filestat_get(&self, fd: Fd) -> Result<Filestat, Error> {
WasiSnapshotPreview1::fd_filestat_get(self, fd.into()).and_then(|e| e.try_into())
}
fn fd_filestat_set_size(&self, fd: Fd, size: Filesize) -> Result<(), Error> {
WasiSnapshotPreview1::fd_filestat_set_size(self, fd.into(), size)
}
fn fd_filestat_set_times(
&self,
fd: Fd,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into())
}
fn fd_pread<'a>(&self, fd: Fd, iovs: &IovecArray<'a>, offset: Filesize) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_pread(self, fd.into(), &cvt_iovec(iovs), offset)
}
fn fd_prestat_get(&self, fd: Fd) -> Result<Prestat, Error> {
WasiSnapshotPreview1::fd_prestat_get(self, fd.into()).map(|e| e.into())
}
fn fd_prestat_dir_name<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, u8>,
path_len: Size,
) -> Result<(), Error> {
WasiSnapshotPreview1::fd_prestat_dir_name(self, fd.into(), path, path_len)
}
fn fd_pwrite<'a>(
&self,
fd: Fd,
iovs: &CiovecArray<'a>,
offset: Filesize,
) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_pwrite(self, fd.into(), &cvt_ciovec(iovs), offset)
}
fn fd_read<'a>(&self, fd: Fd, iovs: &IovecArray<'a>) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_read(self, fd.into(), &cvt_iovec(iovs))
}
fn fd_readdir<'a>(
&self,
fd: Fd,
buf: &wiggle::GuestPtr<'a, u8>,
buf_len: Size,
cookie: Dircookie,
) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_readdir(self, fd.into(), buf, buf_len, cookie)
}
fn fd_renumber(&self, from: Fd, to: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_renumber(self, from.into(), to.into())
}
fn fd_seek(&self, fd: Fd, offset: Filedelta, whence: Whence) -> Result<Filesize, Error> {
WasiSnapshotPreview1::fd_seek(self, fd.into(), offset, whence.into())
}
fn fd_sync(&self, fd: Fd) -> Result<(), Error> {
WasiSnapshotPreview1::fd_sync(self, fd.into())
}
fn fd_tell(&self, fd: Fd) -> Result<Filesize, Error> {
WasiSnapshotPreview1::fd_tell(self, fd.into())
}
fn fd_write<'a>(&self, fd: Fd, iovs: &CiovecArray<'a>) -> Result<Size, Error> {
WasiSnapshotPreview1::fd_write(self, fd.into(), &cvt_ciovec(iovs))
}
fn path_create_directory<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_create_directory(self, fd.into(), path)
}
fn path_filestat_get<'a>(
&self,
fd: Fd,
flags: Lookupflags,
path: &wiggle::GuestPtr<'a, str>,
) -> Result<Filestat, Error> {
WasiSnapshotPreview1::path_filestat_get(self, fd.into(), flags.into(), path)
.and_then(|e| e.try_into())
}
fn path_filestat_set_times<'a>(
&self,
fd: Fd,
flags: Lookupflags,
path: &wiggle::GuestPtr<'a, str>,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_filestat_set_times(
self,
fd.into(),
flags.into(),
path,
atim,
mtim,
fst_flags.into(),
)
}
fn path_link<'a>(
&self,
old_fd: Fd,
old_flags: Lookupflags,
old_path: &wiggle::GuestPtr<'a, str>,
new_fd: Fd,
new_path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_link(
self,
old_fd.into(),
old_flags.into(),
old_path,
new_fd.into(),
new_path,
)
}
fn path_open<'a>(
&self,
fd: Fd,
dirflags: Lookupflags,
path: &wiggle::GuestPtr<'a, str>,
oflags: Oflags,
fs_rights_base: Rights,
fs_rights_inheriting: Rights,
fdflags: Fdflags,
) -> Result<Fd, Error> {
WasiSnapshotPreview1::path_open(
self,
fd.into(),
dirflags.into(),
path,
oflags.into(),
fs_rights_base.into(),
fs_rights_inheriting.into(),
fdflags.into(),
)
.map(|e| e.into())
}
fn path_readlink<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, str>,
buf: &wiggle::GuestPtr<'a, u8>,
buf_len: Size,
) -> Result<Size, Error> {
WasiSnapshotPreview1::path_readlink(self, fd.into(), path, buf, buf_len)
}
fn path_remove_directory<'a>(
&self,
fd: Fd,
path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_remove_directory(self, fd.into(), path)
}
fn path_rename<'a>(
&self,
fd: Fd,
old_path: &wiggle::GuestPtr<'a, str>,
new_fd: Fd,
new_path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_rename(self, fd.into(), old_path, new_fd.into(), new_path)
}
fn path_symlink<'a>(
&self,
old_path: &wiggle::GuestPtr<'a, str>,
fd: Fd,
new_path: &wiggle::GuestPtr<'a, str>,
) -> Result<(), Error> {
WasiSnapshotPreview1::path_symlink(self, old_path, fd.into(), new_path)
}
fn path_unlink_file<'a>(&self, fd: Fd, path: &wiggle::GuestPtr<'a, str>) -> Result<(), Error> {
WasiSnapshotPreview1::path_unlink_file(self, fd.into(), path)
}
fn poll_oneoff<'a>(
&self,
in_: &wiggle::GuestPtr<'a, Subscription>,
out: &wiggle::GuestPtr<'a, Event>,
nsubscriptions: Size,
) -> Result<Size, Error> {
if u64::from(nsubscriptions) > types::Filesize::max_value() {
return Err(Error::Inval);
}
let mut subscriptions = Vec::new();
let subs = in_.as_array(nsubscriptions);
for sub_ptr in subs.iter() {
let sub_ptr = sub_ptr?;
let sub: types::Subscription = sub_ptr.read()?;
subscriptions.push(sub.into());
}
let events = self.poll_oneoff_impl(&subscriptions)?;
let nevents = events.len().try_into()?;
let out_events = out.as_array(nevents);
for (event, event_ptr) in events.into_iter().zip(out_events.iter()) {
let event_ptr = event_ptr?;
event_ptr.write(event.into())?;
}
Ok(nevents)
}
fn proc_exit(&self, rval: Exitcode) -> wiggle::Trap {
WasiSnapshotPreview1::proc_exit(self, rval)
}
fn proc_raise(&self, sig: Signal) -> Result<(), Error> {
WasiSnapshotPreview1::proc_raise(self, sig.into())
}
fn sched_yield(&self) -> Result<(), Error> {
WasiSnapshotPreview1::sched_yield(self)
}
fn random_get<'a>(&self, buf: &wiggle::GuestPtr<'a, u8>, buf_len: Size) -> Result<(), Error> {
WasiSnapshotPreview1::random_get(self, buf, buf_len)
}
fn sock_recv<'a>(
&self,
fd: Fd,
ri_data: &IovecArray<'a>,
ri_flags: Riflags,
) -> Result<(Size, Roflags), Error> {
WasiSnapshotPreview1::sock_recv(self, fd.into(), &cvt_iovec(ri_data), ri_flags.into())
.map(|(s, f)| (s, f.into()))
}
fn sock_send<'a>(
&self,
fd: Fd,
si_data: &CiovecArray<'a>,
si_flags: Siflags,
) -> Result<Size, Error> {
WasiSnapshotPreview1::sock_send(self, fd.into(), &cvt_ciovec(si_data), si_flags.into())
}
fn sock_shutdown(&self, fd: Fd, how: Sdflags) -> Result<(), Error> {
WasiSnapshotPreview1::sock_shutdown(self, fd.into(), how.into())
}
}
impl From<Clockid> for types_new::Clockid {
fn from(id: Clockid) -> types_new::Clockid {
match id {
Clockid::Realtime => types_new::Clockid::Realtime,
Clockid::Monotonic => types_new::Clockid::Monotonic,
Clockid::ProcessCputimeId => types_new::Clockid::ProcessCputimeId,
Clockid::ThreadCputimeId => types_new::Clockid::ThreadCputimeId,
}
}
}
impl From<Fd> for types_new::Fd {
fn from(fd: Fd) -> types_new::Fd {
types_new::Fd::from(u32::from(fd))
}
}
impl From<types_new::Fd> for Fd {
fn from(fd: types_new::Fd) -> Fd {
Fd::from(u32::from(fd))
}
}
impl From<Advice> for types_new::Advice {
fn from(e: Advice) -> types_new::Advice {
match e {
Advice::Normal => types_new::Advice::Normal,
Advice::Sequential => types_new::Advice::Sequential,
Advice::Random => types_new::Advice::Random,
Advice::Willneed => types_new::Advice::Willneed,
Advice::Dontneed => types_new::Advice::Dontneed,
Advice::Noreuse => types_new::Advice::Noreuse,
}
}
}
impl From<types_new::Fdstat> for Fdstat {
fn from(e: types_new::Fdstat) -> Fdstat {
Fdstat {
fs_filetype: e.fs_filetype.into(),
fs_flags: e.fs_flags.into(),
fs_rights_base: e.fs_rights_base.into(),
fs_rights_inheriting: e.fs_rights_inheriting.into(),
}
}
}
fn assert_rights_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u64::from(Rights::$id), u64::from(types_new::Rights::$id));
)*});
}
assert_same! {
FD_DATASYNC
FD_READ
FD_SEEK
FD_FDSTAT_SET_FLAGS
FD_SYNC
FD_TELL
FD_WRITE
FD_ADVISE
FD_ALLOCATE
PATH_CREATE_DIRECTORY
PATH_CREATE_FILE
PATH_LINK_SOURCE
PATH_LINK_TARGET
PATH_OPEN
FD_READDIR
PATH_READLINK
PATH_RENAME_SOURCE
PATH_RENAME_TARGET
PATH_FILESTAT_GET
PATH_FILESTAT_SET_TIMES
PATH_FILESTAT_SET_SIZE
FD_FILESTAT_GET
FD_FILESTAT_SET_SIZE
FD_FILESTAT_SET_TIMES
PATH_SYMLINK
PATH_REMOVE_DIRECTORY
PATH_UNLINK_FILE
POLL_FD_READWRITE
SOCK_SHUTDOWN
}
}
impl From<Rights> for types_new::Rights {
fn from(e: Rights) -> types_new::Rights {
assert_rights_same();
u64::from(e).try_into().unwrap()
}
}
impl From<types_new::Rights> for Rights {
fn from(e: types_new::Rights) -> Rights {
assert_rights_same();
u64::from(e).try_into().unwrap()
}
}
impl From<Filetype> for types_new::Filetype {
fn from(e: Filetype) -> types_new::Filetype {
match e {
Filetype::Unknown => types_new::Filetype::Unknown,
Filetype::BlockDevice => types_new::Filetype::BlockDevice,
Filetype::CharacterDevice => types_new::Filetype::CharacterDevice,
Filetype::Directory => types_new::Filetype::Directory,
Filetype::RegularFile => types_new::Filetype::RegularFile,
Filetype::SocketDgram => types_new::Filetype::SocketDgram,
Filetype::SocketStream => types_new::Filetype::SocketStream,
Filetype::SymbolicLink => types_new::Filetype::SymbolicLink,
}
}
}
impl From<types_new::Filetype> for Filetype {
fn from(e: types_new::Filetype) -> Filetype {
match e {
types_new::Filetype::Unknown => Filetype::Unknown,
types_new::Filetype::BlockDevice => Filetype::BlockDevice,
types_new::Filetype::CharacterDevice => Filetype::CharacterDevice,
types_new::Filetype::Directory => Filetype::Directory,
types_new::Filetype::RegularFile => Filetype::RegularFile,
types_new::Filetype::SocketDgram => Filetype::SocketDgram,
types_new::Filetype::SocketStream => Filetype::SocketStream,
types_new::Filetype::SymbolicLink => Filetype::SymbolicLink,
}
}
}
fn assert_fdflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Fdflags::$id), u16::from(types_new::Fdflags::$id));
)*});
}
assert_same! {
APPEND
DSYNC
NONBLOCK
RSYNC
SYNC
}
}
impl From<Fdflags> for types_new::Fdflags {
fn from(e: Fdflags) -> types_new::Fdflags {
assert_fdflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Fdflags> for Fdflags {
fn from(e: types_new::Fdflags) -> Fdflags {
assert_fdflags_same();
u16::from(e).try_into().unwrap()
}
}
impl TryFrom<types_new::Filestat> for Filestat {
type Error = Error;
fn try_from(e: types_new::Filestat) -> Result<Filestat, Error> {
Ok(Filestat {
dev: e.dev,
ino: e.ino,
filetype: e.filetype.into(),
// wasi_snapshot_preview1 has a 64-bit nlink field but we have a
// 32-bit field, so we need to perform a fallible conversion.
nlink: e.nlink.try_into()?,
size: e.size,
atim: e.atim,
mtim: e.mtim,
ctim: e.ctim,
})
}
}
fn assert_fstflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Fstflags::$id), u16::from(types_new::Fstflags::$id));
)*});
}
assert_same! {
ATIM
ATIM_NOW
MTIM
MTIM_NOW
}
}
impl From<Fstflags> for types_new::Fstflags {
fn from(e: Fstflags) -> types_new::Fstflags {
assert_fstflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Fstflags> for Fstflags {
fn from(e: types_new::Fstflags) -> Fstflags {
assert_fstflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Prestat> for Prestat {
fn from(e: types_new::Prestat) -> Prestat {
match e {
types_new::Prestat::Dir(d) => Prestat::Dir(d.into()),
}
}
}
impl From<types_new::PrestatDir> for PrestatDir {
fn from(e: types_new::PrestatDir) -> PrestatDir {
PrestatDir {
pr_name_len: e.pr_name_len,
}
}
}
impl From<Whence> for types_new::Whence {
fn from(e: Whence) -> types_new::Whence {
match e {
Whence::Set => types_new::Whence::Set,
Whence::Cur => types_new::Whence::Cur,
Whence::End => types_new::Whence::End,
}
}
}
fn assert_lookupflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u32::from(Lookupflags::$id), u32::from(types_new::Lookupflags::$id));
)*});
}
assert_same! {
SYMLINK_FOLLOW
}
}
impl From<Lookupflags> for types_new::Lookupflags {
fn from(e: Lookupflags) -> types_new::Lookupflags {
assert_lookupflags_same();
u32::from(e).try_into().unwrap()
}
}
fn assert_oflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Oflags::$id), u16::from(types_new::Oflags::$id));
)*});
}
assert_same! {
CREAT
DIRECTORY
EXCL
TRUNC
}
}
impl From<Oflags> for types_new::Oflags {
fn from(e: Oflags) -> types_new::Oflags {
assert_oflags_same();
u16::from(e).try_into().unwrap()
}
}
fn assert_sdflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u8::from(Sdflags::$id), u8::from(types_new::Sdflags::$id));
)*});
}
assert_same! {
RD WR
}
}
impl From<Sdflags> for types_new::Sdflags {
fn from(e: Sdflags) -> types_new::Sdflags {
assert_sdflags_same();
u8::from(e).try_into().unwrap()
}
}
impl From<Signal> for types_new::Signal {
fn from(e: Signal) -> types_new::Signal {
match e {
Signal::None => types_new::Signal::None,
Signal::Hup => types_new::Signal::Hup,
Signal::Int => types_new::Signal::Int,
Signal::Quit => types_new::Signal::Quit,
Signal::Ill => types_new::Signal::Ill,
Signal::Trap => types_new::Signal::Trap,
Signal::Abrt => types_new::Signal::Abrt,
Signal::Bus => types_new::Signal::Bus,
Signal::Fpe => types_new::Signal::Fpe,
Signal::Kill => types_new::Signal::Kill,
Signal::Usr1 => types_new::Signal::Usr1,
Signal::Segv => types_new::Signal::Segv,
Signal::Usr2 => types_new::Signal::Usr2,
Signal::Pipe => types_new::Signal::Pipe,
Signal::Alrm => types_new::Signal::Alrm,
Signal::Term => types_new::Signal::Term,
Signal::Chld => types_new::Signal::Chld,
Signal::Cont => types_new::Signal::Cont,
Signal::Stop => types_new::Signal::Stop,
Signal::Tstp => types_new::Signal::Tstp,
Signal::Ttin => types_new::Signal::Ttin,
Signal::Ttou => types_new::Signal::Ttou,
Signal::Urg => types_new::Signal::Urg,
Signal::Xcpu => types_new::Signal::Xcpu,
Signal::Xfsz => types_new::Signal::Xfsz,
Signal::Vtalrm => types_new::Signal::Vtalrm,
Signal::Prof => types_new::Signal::Prof,
Signal::Winch => types_new::Signal::Winch,
Signal::Poll => types_new::Signal::Poll,
Signal::Pwr => types_new::Signal::Pwr,
Signal::Sys => types_new::Signal::Sys,
}
}
}
// For `wasi_unstable` and `wasi_snapshot_preview1` the memory layout of these
// two types was manually verified. It should be fine to effectively cast
// between the two types and get the same behavior.
fn cvt_iovec<'a>(e: &IovecArray<'a>) -> types_new::IovecArray<'a> {
wiggle::GuestPtr::new(e.mem(), (e.offset_base(), e.len()))
}
fn cvt_ciovec<'a>(e: &CiovecArray<'a>) -> types_new::CiovecArray<'a> {
wiggle::GuestPtr::new(e.mem(), (e.offset_base(), e.len()))
}
fn assert_riflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Riflags::$id), u16::from(types_new::Riflags::$id));
)*});
}
assert_same! {
RECV_PEEK
RECV_WAITALL
}
}
impl From<Riflags> for types_new::Riflags {
fn from(e: Riflags) -> types_new::Riflags {
assert_riflags_same();
u16::from(e).try_into().unwrap()
}
}
fn assert_roflags_same() {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Roflags::$id), u16::from(types_new::Roflags::$id));
)*});
}
assert_same! {
RECV_DATA_TRUNCATED
}
}
impl From<types_new::Roflags> for Roflags {
fn from(e: types_new::Roflags) -> Roflags {
assert_roflags_same();
u16::from(e).try_into().unwrap()
}
}
impl From<Subscription> for types_new::Subscription {
fn from(e: Subscription) -> types_new::Subscription {
types_new::Subscription {
userdata: e.userdata,
u: e.u.into(),
}
}
}
impl From<SubscriptionU> for types_new::SubscriptionU {
fn from(e: SubscriptionU) -> types_new::SubscriptionU {
match e {
SubscriptionU::Clock(c) => {
types_new::SubscriptionU::Clock(types_new::SubscriptionClock {
id: c.id.into(),
timeout: c.timeout,
precision: c.precision,
flags: c.flags.into(),
})
}
SubscriptionU::FdRead(c) => {
types_new::SubscriptionU::FdRead(types_new::SubscriptionFdReadwrite {
file_descriptor: c.file_descriptor.into(),
})
}
SubscriptionU::FdWrite(c) => {
types_new::SubscriptionU::FdWrite(types_new::SubscriptionFdReadwrite {
file_descriptor: c.file_descriptor.into(),
})
}
}
}
}
impl From<Subclockflags> for types_new::Subclockflags {
fn from(e: Subclockflags) -> types_new::Subclockflags {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Subclockflags::$id), u16::from(types_new::Subclockflags::$id));
)*});
}
assert_same! {
SUBSCRIPTION_CLOCK_ABSTIME
}
u16::from(e).try_into().unwrap()
}
}
impl From<types_new::Event> for Event {
fn from(e: types_new::Event) -> Event {
Event {
userdata: e.userdata,
error: e.error.into(),
type_: e.type_.into(),
fd_readwrite: e.fd_readwrite.into(),
}
}
}
impl From<types_new::Errno> for Errno {
fn from(e: types_new::Errno) -> Errno {
match e {
types_new::Errno::Success => Errno::Success,
types_new::Errno::TooBig => Errno::TooBig,
types_new::Errno::Acces => Errno::Acces,
types_new::Errno::Addrinuse => Errno::Addrinuse,
types_new::Errno::Addrnotavail => Errno::Addrnotavail,
types_new::Errno::Afnosupport => Errno::Afnosupport,
types_new::Errno::Again => Errno::Again,
types_new::Errno::Already => Errno::Already,
types_new::Errno::Badf => Errno::Badf,
types_new::Errno::Badmsg => Errno::Badmsg,
types_new::Errno::Busy => Errno::Busy,
types_new::Errno::Canceled => Errno::Canceled,
types_new::Errno::Child => Errno::Child,
types_new::Errno::Connaborted => Errno::Connaborted,
types_new::Errno::Connrefused => Errno::Connrefused,
types_new::Errno::Connreset => Errno::Connreset,
types_new::Errno::Deadlk => Errno::Deadlk,
types_new::Errno::Destaddrreq => Errno::Destaddrreq,
types_new::Errno::Dom => Errno::Dom,
types_new::Errno::Dquot => Errno::Dquot,
types_new::Errno::Exist => Errno::Exist,
types_new::Errno::Fault => Errno::Fault,
types_new::Errno::Fbig => Errno::Fbig,
types_new::Errno::Hostunreach => Errno::Hostunreach,
types_new::Errno::Idrm => Errno::Idrm,
types_new::Errno::Ilseq => Errno::Ilseq,
types_new::Errno::Inprogress => Errno::Inprogress,
types_new::Errno::Intr => Errno::Intr,
types_new::Errno::Inval => Errno::Inval,
types_new::Errno::Io => Errno::Io,
types_new::Errno::Isconn => Errno::Isconn,
types_new::Errno::Isdir => Errno::Isdir,
types_new::Errno::Loop => Errno::Loop,
types_new::Errno::Mfile => Errno::Mfile,
types_new::Errno::Mlink => Errno::Mlink,
types_new::Errno::Msgsize => Errno::Msgsize,
types_new::Errno::Multihop => Errno::Multihop,
types_new::Errno::Nametoolong => Errno::Nametoolong,
types_new::Errno::Netdown => Errno::Netdown,
types_new::Errno::Netreset => Errno::Netreset,
types_new::Errno::Netunreach => Errno::Netunreach,
types_new::Errno::Nfile => Errno::Nfile,
types_new::Errno::Nobufs => Errno::Nobufs,
types_new::Errno::Nodev => Errno::Nodev,
types_new::Errno::Noent => Errno::Noent,
types_new::Errno::Noexec => Errno::Noexec,
types_new::Errno::Nolck => Errno::Nolck,
types_new::Errno::Nolink => Errno::Nolink,
types_new::Errno::Nomem => Errno::Nomem,
types_new::Errno::Nomsg => Errno::Nomsg,
types_new::Errno::Noprotoopt => Errno::Noprotoopt,
types_new::Errno::Nospc => Errno::Nospc,
types_new::Errno::Nosys => Errno::Nosys,
types_new::Errno::Notconn => Errno::Notconn,
types_new::Errno::Notdir => Errno::Notdir,
types_new::Errno::Notempty => Errno::Notempty,
types_new::Errno::Notrecoverable => Errno::Notrecoverable,
types_new::Errno::Notsock => Errno::Notsock,
types_new::Errno::Notsup => Errno::Notsup,
types_new::Errno::Notty => Errno::Notty,
types_new::Errno::Nxio => Errno::Nxio,
types_new::Errno::Overflow => Errno::Overflow,
types_new::Errno::Ownerdead => Errno::Ownerdead,
types_new::Errno::Perm => Errno::Perm,
types_new::Errno::Pipe => Errno::Pipe,
types_new::Errno::Proto => Errno::Proto,
types_new::Errno::Protonosupport => Errno::Protonosupport,
types_new::Errno::Prototype => Errno::Prototype,
types_new::Errno::Range => Errno::Range,
types_new::Errno::Rofs => Errno::Rofs,
types_new::Errno::Spipe => Errno::Spipe,
types_new::Errno::Srch => Errno::Srch,
types_new::Errno::Stale => Errno::Stale,
types_new::Errno::Timedout => Errno::Timedout,
types_new::Errno::Txtbsy => Errno::Txtbsy,
types_new::Errno::Xdev => Errno::Xdev,
types_new::Errno::Notcapable => Errno::Notcapable,
}
}
}
impl From<types_new::Eventtype> for Eventtype {
fn from(e: types_new::Eventtype) -> Eventtype {
match e {
types_new::Eventtype::Clock => Eventtype::Clock,
types_new::Eventtype::FdRead => Eventtype::FdRead,
types_new::Eventtype::FdWrite => Eventtype::FdWrite,
}
}
}
impl From<types_new::EventFdReadwrite> for EventFdReadwrite {
fn from(e: types_new::EventFdReadwrite) -> EventFdReadwrite {
EventFdReadwrite {
nbytes: e.nbytes,
flags: e.flags.into(),
}
}
}
impl From<types_new::Eventrwflags> for Eventrwflags {
fn from(e: types_new::Eventrwflags) -> Eventrwflags {
macro_rules! assert_same {
($($id:ident)*) => ({$(
assert_eq!(u16::from(Eventrwflags::$id), u16::from(types_new::Eventrwflags::$id));
)*});
}
assert_same! {
FD_READWRITE_HANGUP
}
u16::from(e).try_into().unwrap()
}
}

View File

@@ -1,145 +0,0 @@
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(())
}
}

View File

@@ -1,17 +0,0 @@
use crate::sched::{Subclockflags, SubscriptionClock};
use crate::{Error, Result};
use std::time::SystemTime;
pub(crate) use super::sys_impl::clock::*;
pub(crate) fn to_relative_ns_delay(clock: &SubscriptionClock) -> Result<u128> {
if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME {
return Ok(u128::from(clock.timeout));
}
let now: u128 = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| Error::Notcapable)?
.as_nanos();
let deadline = u128::from(clock.timeout);
Ok(deadline.saturating_sub(now))
}

View File

@@ -1,45 +0,0 @@
use crate::handle::{Fstflags, Timestamp};
use crate::{Error, Result};
use filetime::{set_file_handle_times, FileTime};
use std::fs::File;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub(crate) use super::sys_impl::fd::*;
pub(crate) fn filestat_set_times(
file: &File,
st_atim: Timestamp,
st_mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<()> {
let set_atim = fst_flags.contains(Fstflags::ATIM);
let set_atim_now = fst_flags.contains(Fstflags::ATIM_NOW);
let set_mtim = fst_flags.contains(Fstflags::MTIM);
let set_mtim_now = fst_flags.contains(Fstflags::MTIM_NOW);
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
return Err(Error::Inval);
}
let atim = if set_atim {
let time = UNIX_EPOCH + Duration::from_nanos(st_atim);
Some(FileTime::from_system_time(time))
} else if set_atim_now {
let time = SystemTime::now();
Some(FileTime::from_system_time(time))
} else {
None
};
let mtim = if set_mtim {
let time = UNIX_EPOCH + Duration::from_nanos(st_mtim);
Some(FileTime::from_system_time(time))
} else if set_mtim_now {
let time = SystemTime::now();
Some(FileTime::from_system_time(time))
} else {
None
};
set_file_handle_times(file, atim, mtim)?;
Ok(())
}

View File

@@ -1,95 +0,0 @@
pub(crate) mod clock;
pub(crate) mod fd;
pub(crate) mod osdir;
pub(crate) mod osfile;
pub(crate) mod osother;
pub(crate) mod stdio;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(unix)] {
mod unix;
use unix as sys_impl;
pub use unix::preopen_dir;
} else if #[cfg(windows)] {
mod windows;
use windows as sys_impl;
pub use windows::preopen_dir;
} else {
compile_error!("wasi-common doesn't compile for this platform yet");
}
}
pub(crate) use sys_impl::path;
pub(crate) use sys_impl::poll;
use super::handle::{Filetype, Handle};
use osdir::OsDir;
use osfile::OsFile;
use osother::OsOther;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use stdio::{Stderr, Stdin, Stdout};
use sys_impl::get_file_type;
pub(crate) trait AsFile {
fn as_file(&self) -> io::Result<ManuallyDrop<File>>;
}
impl AsFile for dyn Handle + 'static {
fn as_file(&self) -> io::Result<ManuallyDrop<File>> {
if let Some(file) = self.as_any().downcast_ref::<OsFile>() {
file.as_file()
} else if let Some(dir) = self.as_any().downcast_ref::<OsDir>() {
dir.as_file()
} else if let Some(stdin) = self.as_any().downcast_ref::<Stdin>() {
stdin.as_file()
} else if let Some(stdout) = self.as_any().downcast_ref::<Stdout>() {
stdout.as_file()
} else if let Some(stderr) = self.as_any().downcast_ref::<Stderr>() {
stderr.as_file()
} else if let Some(other) = self.as_any().downcast_ref::<OsOther>() {
other.as_file()
} else {
tracing::error!("tried to make std::fs::File from non-OS handle");
Err(io::Error::from_raw_os_error(libc::EBADF))
}
}
}
impl TryFrom<File> for Box<dyn Handle> {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let file_type = get_file_type(&file)?;
match file_type {
Filetype::RegularFile => {
let handle = OsFile::try_from(file)?;
tracing::debug!(
handle = tracing::field::debug(&handle),
"Created new instance of OsFile"
);
Ok(Box::new(handle))
}
Filetype::Directory => {
let handle = OsDir::try_from(file)?;
tracing::debug!(
handle = tracing::field::debug(&handle),
"Created new instance of OsDir"
);
Ok(Box::new(handle))
}
_ => {
let handle = OsOther::try_from(file)?;
tracing::debug!(
handle = tracing::field::debug(&handle),
"Created new instance of OsOther"
);
Ok(Box::new(handle))
}
}
}
}

View File

@@ -1,145 +0,0 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, path, AsFile};
use crate::handle::{
Dircookie, Dirent, Fdflags, Filestat, Filetype, Fstflags, Handle, HandleRights, Oflags,
};
use crate::sched::Timestamp;
use crate::{Error, Result};
use std::any::Any;
use std::io;
use std::ops::Deref;
use tracing::{debug, error};
// TODO could this be cleaned up?
// The actual `OsDir` struct is OS-dependent, therefore we delegate
// its definition to OS-specific modules.
pub use super::sys_impl::osdir::OsDir;
impl Deref for OsDir {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsDir {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let handle = self.handle.try_clone()?;
let new = Self::new(self.rights.get(), handle)?;
Ok(Box::new(new))
}
fn get_file_type(&self) -> Filetype {
Filetype::Directory
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(new_file) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(new_file);
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn filestat_set_times(
&self,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<()> {
fd::filestat_set_times(&*self.as_file()?, atim, mtim, fst_flags)
}
fn readdir<'a>(
&'a self,
cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>> + 'a>> {
fd::readdir(self, cookie)
}
// PathOps
fn create_directory(&self, path: &str) -> Result<()> {
path::create_directory(self, path)
}
fn filestat_get_at(&self, path: &str, follow: bool) -> Result<Filestat> {
path::filestat_get_at(self, path, follow)
}
fn filestat_set_times_at(
&self,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
follow: bool,
) -> Result<()> {
path::filestat_set_times_at(self, path, atim, mtim, fst_flags, follow)
}
fn openat(
&self,
path: &str,
read: bool,
write: bool,
oflags: Oflags,
fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
path::open(self, path, read, write, oflags, fd_flags)
}
fn link(
&self,
old_path: &str,
new_handle: Box<dyn Handle>,
new_path: &str,
follow: bool,
) -> Result<()> {
let new_handle = match new_handle.as_any().downcast_ref::<Self>() {
None => {
error!("Tried to link with handle that's not an OsDir");
return Err(Error::Badf);
}
Some(handle) => handle,
};
path::link(self, old_path, new_handle, new_path, follow)
}
fn symlink(&self, old_path: &str, new_path: &str) -> Result<()> {
path::symlink(old_path, self, new_path)
}
fn readlink(&self, path: &str, buf: &mut [u8]) -> Result<usize> {
path::readlink(self, path, buf)
}
fn readlinkat(&self, path: &str) -> Result<String> {
path::readlinkat(self, path)
}
fn rename(&self, old_path: &str, new_handle: Box<dyn Handle>, new_path: &str) -> Result<()> {
let new_handle = match new_handle.as_any().downcast_ref::<Self>() {
None => {
error!("Tried to rename with handle that's not an OsDir");
return Err(Error::Badf);
}
Some(handle) => handle,
};
debug!("rename (old_dirfd, old_path)=({:?}, {:?})", self, old_path);
debug!(
"rename (new_dirfd, new_path)=({:?}, {:?})",
new_handle, new_path
);
path::rename(self, old_path, new_handle, new_path)
}
fn remove_directory(&self, path: &str) -> Result<()> {
debug!("remove_directory (dirfd, path)=({:?}, {:?})", self, path);
path::remove_directory(self, path)
}
fn unlink_file(&self, path: &str) -> Result<()> {
path::unlink_file(self, path)
}
}

View File

@@ -1,148 +0,0 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, AsFile};
use crate::handle::{
Advice, Fdflags, Filesize, Filestat, Filetype, Fstflags, Handle, HandleRights,
};
use crate::sched::Timestamp;
use crate::{Error, Result};
use std::any::Any;
use std::cell::Cell;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::Deref;
#[derive(Debug)]
/// A file backed by the operating system's file system. Dereferences to a
/// `RawOsHandle`. Its impl of `Handle` uses Rust's `std` to implement all
/// file descriptor operations.
///
/// # Constructing `OsFile`
///
/// `OsFile` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsFile;
///
/// let file = OpenOptions::new().read(true).open("some_file").unwrap();
/// let os_file = OsFile::try_from(file).unwrap();
/// ```
pub struct OsFile {
rights: Cell<HandleRights>,
handle: RawOsHandle,
}
impl OsFile {
pub(super) fn new(rights: HandleRights, handle: RawOsHandle) -> Self {
let rights = Cell::new(rights);
Self { rights, handle }
}
}
impl Deref for OsFile {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsFile {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let handle = self.handle.try_clone()?;
let rights = self.rights.clone();
Ok(Box::new(Self { rights, handle }))
}
fn get_file_type(&self) -> Filetype {
Filetype::RegularFile
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn advise(&self, advice: Advice, offset: Filesize, len: Filesize) -> Result<()> {
fd::advise(self, advice, offset, len)
}
fn allocate(&self, offset: Filesize, len: Filesize) -> Result<()> {
let fd = self.as_file()?;
let metadata = fd.metadata()?;
let current_size = metadata.len();
let wanted_size = offset.checked_add(len).ok_or(Error::TooBig)?;
// This check will be unnecessary when rust-lang/rust#63326 is fixed
if wanted_size > i64::max_value() as u64 {
return Err(Error::TooBig);
}
if wanted_size > current_size {
fd.set_len(wanted_size)?;
}
Ok(())
}
fn datasync(&self) -> Result<()> {
self.as_file()?.sync_data()?;
Ok(())
}
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(new_handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(new_handle);
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn filestat_set_size(&self, size: Filesize) -> Result<()> {
self.as_file()?.set_len(size)?;
Ok(())
}
fn filestat_set_times(
&self,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
) -> Result<()> {
fd::filestat_set_times(&*self.as_file()?, atim, mtim, fst_flags)
}
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: u64) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let cur_pos = fd.seek(SeekFrom::Current(0))?;
fd.seek(SeekFrom::Start(offset))?;
let nread = self.read_vectored(buf)?;
fd.seek(SeekFrom::Start(cur_pos))?;
Ok(nread)
}
fn pwritev(&self, buf: &[io::IoSlice], offset: u64) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let cur_pos = fd.seek(SeekFrom::Current(0))?;
fd.seek(SeekFrom::Start(offset))?;
let nwritten = self.write_vectored(&buf)?;
fd.seek(SeekFrom::Start(cur_pos))?;
Ok(nwritten)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = self.as_file()?.read_vectored(iovs)?;
Ok(nread)
}
fn seek(&self, offset: SeekFrom) -> Result<u64> {
let pos = self.as_file()?.seek(offset)?;
Ok(pos)
}
fn sync(&self) -> Result<()> {
self.as_file()?.sync_all()?;
Ok(())
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let nwritten = self.as_file()?.write_vectored(&iovs)?;
Ok(nwritten)
}
}

View File

@@ -1,103 +0,0 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, AsFile};
use crate::handle::{Fdflags, Filetype, Handle, HandleRights};
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
use crate::Result;
use std::any::Any;
use std::cell::Cell;
use std::fs::File;
use std::io::{self, Read, Write};
use std::ops::Deref;
/// `OsOther` is something of a catch-all for everything not covered with the specific handle
/// types (`OsFile`, `OsDir`, `Stdio`). It currently encapsulates handles such as OS pipes,
/// sockets, streams, etc. As such, when redirecting stdio within `WasiCtxBuilder`, the redirected
/// pipe should be encapsulated within this instance _and not_ `OsFile` which represents a regular
/// OS file.
///
/// # Constructing `OsOther`
///
/// `OsOther` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsOther;
///
/// let pipe = OpenOptions::new().read(true).open("a_pipe").unwrap();
/// let os_other = OsOther::try_from(pipe).unwrap();
/// ```
#[derive(Debug)]
pub struct OsOther {
file_type: Filetype,
rights: Cell<HandleRights>,
handle: RawOsHandle,
}
impl OsOther {
pub(super) fn new(file_type: Filetype, rights: HandleRights, handle: RawOsHandle) -> Self {
let rights = Cell::new(rights);
Self {
file_type,
rights,
handle,
}
}
}
impl Deref for OsOther {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsOther {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let file_type = self.file_type;
let handle = self.handle.try_clone()?;
let rights = self.rights.clone();
Ok(Box::new(Self {
file_type,
rights,
handle,
}))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(handle);
}
Ok(())
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = self.as_file()?.read_vectored(iovs)?;
Ok(nread)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let nwritten = if self.is_tty() {
SandboxedTTYWriter::new(&mut fd).write_vectored(&iovs)?
} else {
fd.write_vectored(iovs)?
};
Ok(nwritten)
}
}

View File

@@ -1,241 +0,0 @@
// The reason we have a separate Stdio wrappers is to correctly facilitate redirects on Windows.
// To elaborate further, in POSIX, we can get a stdio handle by opening a specific fd {0,1,2}.
// On Windows however, we need to issue a syscall that's separate from standard Windows "open"
// to get a console handle, and this is GetStdHandle. This is exactly what Rust does and what
// is wrapped inside their Stdio object in the libstd. We wrap it here as well because of this
// nuance on Windows:
//
// The standard handles of a process may be redirected by a call to SetStdHandle, in which
// case GetStdHandle returns the redirected handle.
//
// The MSDN also says this however:
//
// If the standard handles have been redirected, you can specify the CONIN$ value in a call
// to the CreateFile function to get a handle to a console's input buffer. Similarly, you
// can specify the CONOUT$ value to get a handle to a console's active screen buffer.
//
// TODO it might worth re-investigating the suitability of this type on Windows.
use super::{fd, AsFile};
use crate::handle::{Fdflags, Filestat, Filetype, Handle, HandleRights, Rights, RightsExt, Size};
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
use crate::{Error, Result};
use std::any::Any;
use std::cell::Cell;
use std::convert::TryInto;
use std::io::{self, Read, Write};
pub(crate) trait StdinExt: Sized {
/// Create `Stdin` from `io::stdin`.
fn stdin() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stdin {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stdin {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = io::stdin().read_vectored(iovs)?;
Ok(nread)
}
}
pub(crate) trait StdoutExt: Sized {
/// Create `Stdout` from `io::stdout`.
fn stdout() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stdout {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stdout {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
// lock for the duration of the scope
let stdout = io::stdout();
let mut stdout = stdout.lock();
let nwritten = if self.is_tty() {
SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)?
} else {
stdout.write_vectored(iovs)?
};
stdout.flush()?;
Ok(nwritten)
}
}
pub(crate) trait StderrExt: Sized {
/// Create `Stderr` from `io::stderr`.
fn stderr() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stderr {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stderr {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
// Always sanitize stderr, even if it's not directly connected to a tty,
// because stderr is meant for diagnostics rather than binary output,
// and may be redirected to a file which could end up being displayed
// on a tty later.
let nwritten = SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?;
Ok(nwritten)
}
}
#[derive(Debug, Clone)]
pub(crate) struct NullDevice {
pub(crate) rights: Cell<HandleRights>,
pub(crate) fd_flags: Cell<Fdflags>,
}
impl NullDevice {
pub(crate) fn new() -> Self {
let rights = HandleRights::new(
Rights::character_device_base(),
Rights::character_device_inheriting(),
);
let rights = Cell::new(rights);
let fd_flags = Fdflags::empty();
let fd_flags = Cell::new(fd_flags);
Self { rights, fd_flags }
}
}
impl Handle for NullDevice {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
Filetype::CharacterDevice
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(self.fd_flags.get())
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
self.fd_flags.set(fdflags);
Ok(())
}
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Ok(0)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let mut total_len = 0u32;
for iov in iovs {
let len: Size = iov.len().try_into()?;
total_len = total_len.checked_add(len).ok_or(Error::Overflow)?;
}
Ok(total_len as usize)
}
}

View File

@@ -1,4 +0,0 @@
pub(crate) mod osdir;
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::SYNC;

View File

@@ -1,65 +0,0 @@
use crate::handle::HandleRights;
use crate::sys::sys_impl::oshandle::RawOsHandle;
use crate::Result;
use std::cell::{Cell, RefCell, RefMut};
use std::io;
use yanix::dir::Dir;
#[derive(Debug)]
/// A directory in the operating system's file system. Its impl of `Handle` is
/// in `sys::osdir`. This type is exposed to all other modules as
/// `sys::osdir::OsDir` when configured.
///
/// # Constructing `OsDir`
///
/// `OsDir` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsDir;
///
/// let dir = OpenOptions::new().read(true).open("some_dir").unwrap();
/// let os_dir = OsDir::try_from(dir).unwrap();
/// ```
pub struct OsDir {
pub(crate) rights: Cell<HandleRights>,
pub(crate) handle: RawOsHandle,
// When the client makes a `fd_readdir` syscall on this descriptor,
// we will need to cache the `libc::DIR` pointer manually in order
// to be able to seek on it later. While on Linux, this is handled
// by the OS, BSD Unixes require the client to do this caching.
//
// This comes directly from the BSD man pages on `readdir`:
// > Values returned by telldir() are good only for the lifetime
// > of the DIR pointer, dirp, from which they are derived.
// > If the directory is closed and then reopened, prior values
// > returned by telldir() will no longer be valid.
stream_ptr: RefCell<Dir>,
}
impl OsDir {
pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result<Self> {
let rights = Cell::new(rights);
// We need to duplicate the handle, because `opendir(3)`:
// Upon successful return from fdopendir(), the file descriptor is under
// control of the system, and if any attempt is made to close the file
// descriptor, or to modify the state of the associated description other
// than by means of closedir(), readdir(), readdir_r(), or rewinddir(),
// the behaviour is undefined.
let stream_ptr = Dir::from(handle.try_clone()?)?;
let stream_ptr = RefCell::new(stream_ptr);
Ok(Self {
rights,
handle,
stream_ptr,
})
}
/// Returns the `Dir` stream pointer associated with this `OsDir`. Duck
/// typing: sys::unix::fd::readdir expects the configured OsDir to have
/// this method.
pub(crate) fn stream_ptr(&self) -> Result<RefMut<Dir>> {
Ok(self.stream_ptr.borrow_mut())
}
}

View File

@@ -1,118 +0,0 @@
use crate::sys::osdir::OsDir;
use crate::{Error, Result};
use std::os::unix::prelude::AsRawFd;
pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> {
use yanix::file::{unlinkat, AtFlags};
match unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlags::empty()) } {
Err(err) => {
let raw_errno = err.raw_os_error().unwrap();
// Non-Linux implementations may return EPERM when attempting to remove a
// directory without REMOVEDIR. While that's what POSIX specifies, it's
// less useful. Adjust this to EISDIR. It doesn't matter that this is not
// atomic with the unlinkat, because if the file is removed and a directory
// is created before fstatat sees it, we're racing with that change anyway
// and unlinkat could have legitimately seen the directory if the race had
// turned out differently.
use yanix::file::{fstatat, FileType};
if raw_errno == libc::EPERM {
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlags::SYMLINK_NOFOLLOW) } {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory {
return Err(Error::Isdir);
}
}
Err(err) => {
tracing::debug!("path_unlink_file fstatat error: {:?}", err);
}
}
}
Err(err.into())
}
Ok(()) => Ok(()),
}
}
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path: &str) -> Result<()> {
use yanix::file::{fstatat, symlinkat, AtFlags};
tracing::debug!("path_symlink old_path = {:?}", old_path);
tracing::debug!(
"path_symlink (new_dirfd, new_path) = ({:?}, {:?})",
new_dirfd,
new_path
);
match unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path) } {
Err(err) => {
if err.raw_os_error().unwrap() == libc::ENOTDIR {
// On BSD, symlinkat returns ENOTDIR when it should in fact
// return a EEXIST. It seems that it gets confused with by
// the trailing slash in the target path. Thus, we strip
// the trailing slash and check if the path exists, and
// adjust the error code appropriately.
let new_path = new_path.trim_end_matches('/');
match unsafe { fstatat(new_dirfd.as_raw_fd(), new_path, AtFlags::SYMLINK_NOFOLLOW) }
{
Ok(_) => return Err(Error::Exist),
Err(err) => {
tracing::debug!("path_symlink fstatat error: {:?}", err);
}
}
}
Err(err.into())
}
Ok(()) => Ok(()),
}
}
pub(crate) fn rename(
old_dirfd: &OsDir,
old_path: &str,
new_dirfd: &OsDir,
new_path: &str,
) -> Result<()> {
use yanix::file::{fstatat, renameat, AtFlags};
match unsafe {
renameat(
old_dirfd.as_raw_fd(),
old_path,
new_dirfd.as_raw_fd(),
new_path,
)
} {
Err(err) => {
// Currently, this is verified to be correct on macOS, where
// ENOENT can be returned in case when we try to rename a file
// into a name with a trailing slash. On macOS, if the latter does
// not exist, an ENOENT is thrown, whereas on Linux we observe the
// correct behaviour of throwing an ENOTDIR since the destination is
// indeed not a directory.
//
// TODO
// Verify on other BSD-based OSes.
if err.raw_os_error().unwrap() == libc::ENOENT {
// check if the source path exists
match unsafe { fstatat(old_dirfd.as_raw_fd(), old_path, AtFlags::SYMLINK_NOFOLLOW) }
{
Ok(_) => {
// check if destination contains a trailing slash
if new_path.contains('/') {
return Err(Error::Notdir);
} else {
return Err(Error::Noent);
}
}
Err(err) => {
tracing::debug!("path_rename fstatat error: {:?}", err);
}
}
}
Err(err.into())
}
Ok(()) => Ok(()),
}
}

View File

@@ -1,36 +0,0 @@
use crate::sched::{Clockid, Timestamp};
use crate::{Error, Result};
use yanix::clock::{clock_getres, clock_gettime, ClockId};
pub(crate) fn res_get(clock_id: Clockid) -> Result<Timestamp> {
let clock_id: ClockId = clock_id.into();
let timespec = clock_getres(clock_id)?;
// convert to nanoseconds, returning EOVERFLOW in case of overflow;
// this is freelancing a bit from the spec but seems like it'll
// be an unusual situation to hit
(timespec.tv_sec as Timestamp)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as Timestamp))
.map_or(Err(Error::Overflow), |resolution| {
// a supported clock can never return zero; this case will probably never get hit, but
// make sure we follow the spec
if resolution == 0 {
Err(Error::Inval)
} else {
Ok(resolution)
}
})
}
pub(crate) fn time_get(clock_id: Clockid) -> Result<Timestamp> {
let clock_id: ClockId = clock_id.into();
let timespec = clock_gettime(clock_id)?;
// convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit
// from the spec but seems like it'll be an unusual situation to hit
(timespec.tv_sec as Timestamp)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as Timestamp))
.map_or(Err(Error::Overflow), Ok)
}

View File

@@ -1,6 +0,0 @@
#[path = "../linux/osdir.rs"]
pub(crate) mod osdir;
#[path = "../linux/path.rs"]
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::RSYNC;

View File

@@ -1,77 +0,0 @@
use super::oshandle::RawOsHandle;
use crate::handle::{Advice, Dircookie, Dirent, Fdflags, Filesize, Filestat, DIRCOOKIE_START};
use crate::sys::osdir::OsDir;
use crate::sys::osfile::OsFile;
use crate::Result;
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn fdstat_get(fd: &File) -> Result<Fdflags> {
let fdflags = unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd())? };
Ok(fdflags.into())
}
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: Fdflags) -> Result<Option<RawOsHandle>> {
unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), fdflags.into())? };
// We return None here to signal that the operation succeeded on the original
// file descriptor and mutating the original WASI Descriptor is thus unnecessary.
// This is needed as on Windows this operation required reopening a file.
Ok(None)
}
pub(crate) fn advise(file: &OsFile, advice: Advice, offset: Filesize, len: Filesize) -> Result<()> {
use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice};
let offset = offset.try_into()?;
let len = len.try_into()?;
let host_advice = match advice {
Advice::Dontneed => PosixFadviseAdvice::DontNeed,
Advice::Sequential => PosixFadviseAdvice::Sequential,
Advice::Willneed => PosixFadviseAdvice::WillNeed,
Advice::Noreuse => PosixFadviseAdvice::NoReuse,
Advice::Random => PosixFadviseAdvice::Random,
Advice::Normal => PosixFadviseAdvice::Normal,
};
unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice)? };
Ok(())
}
pub(crate) fn filestat_get(file: &File) -> Result<Filestat> {
use yanix::file::fstat;
let stat = unsafe { fstat(file.as_raw_fd())? };
Ok(stat.try_into()?)
}
pub(crate) fn readdir<'a>(
dirfd: &'a OsDir,
cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>> + 'a>> {
use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc};
// Get an instance of `Dir`; this is host-specific due to intricasies
// of managing a dir stream between Linux and BSD *nixes
let mut dir = dirfd.stream_ptr()?;
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
// new items may not be returned to the caller.
if cookie == DIRCOOKIE_START {
tracing::trace!("fd_readdir: doing rewinddir");
dir.rewind();
} else {
tracing::trace!("fd_readdir: doing seekdir to {}", cookie);
let loc = unsafe { SeekLoc::from_raw(cookie as i64)? };
dir.seek(loc);
}
Ok(Box::new(DirIter::new(dir).map(|entry| {
let entry: Entry = entry?;
let name = entry.file_name().to_str()?.to_owned();
let dirent = Dirent {
d_next: entry.seek_loc()?.to_raw().try_into()?,
d_ino: entry.ino(),
d_namlen: name.len().try_into()?,
d_type: entry.file_type().into(),
};
Ok((dirent, name))
})))
}

View File

@@ -1,4 +0,0 @@
pub(crate) mod osdir;
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::RSYNC;

View File

@@ -1,53 +0,0 @@
use crate::handle::HandleRights;
use crate::sys::sys_impl::oshandle::RawOsHandle;
use crate::Result;
use std::cell::Cell;
use std::io;
use yanix::dir::Dir;
#[derive(Debug)]
/// A directory in the operating system's file system. Its impl of `Handle` is
/// in `sys::osdir`. This type is exposed to all other modules as
/// `sys::osdir::OsDir` when configured.
///
/// # Constructing `OsDir`
///
/// `OsDir` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use wasi_common::OsDir;
///
/// let dir = OpenOptions::new().read(true).open("some_dir").unwrap();
/// let os_dir = OsDir::try_from(dir).unwrap();
/// ```
pub struct OsDir {
pub(crate) rights: Cell<HandleRights>,
pub(crate) handle: RawOsHandle,
}
impl OsDir {
pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result<Self> {
let rights = Cell::new(rights);
Ok(Self { rights, handle })
}
/// Returns the `Dir` stream pointer associated with this `OsDir`. Duck typing:
/// sys::unix::fd::readdir expects the configured OsDir to have this method.
pub(crate) fn stream_ptr(&self) -> Result<Box<Dir>> {
// We need to duplicate the handle, because `opendir(3)`:
// After a successful call to fdopendir(), fd is used internally by the implementation,
// and should not otherwise be used by the application.
// `opendir(3p)` also says that it's undefined behavior to
// modify the state of the fd in a different way than by accessing DIR*.
//
// Still, rewinddir will be needed because the two file descriptors
// share progress. But we can safely execute closedir now.
let file = self.handle.try_clone()?;
// TODO This doesn't look very clean. Can we do something about it?
// Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter`
// where `T: Deref<Target = Dir>`.
Ok(Box::new(Dir::from(file)?))
}
}

View File

@@ -1,41 +0,0 @@
use crate::sys::osdir::OsDir;
use crate::Result;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> {
use yanix::file::{unlinkat, AtFlags};
unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlags::empty())? };
Ok(())
}
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path: &str) -> Result<()> {
use yanix::file::symlinkat;
tracing::debug!(
old_path = old_path,
new_dirfd = tracing::field::debug(new_dirfd),
new_path = new_path,
"path symlink"
);
unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path)? };
Ok(())
}
pub(crate) fn rename(
old_dirfd: &OsDir,
old_path: &str,
new_dirfd: &OsDir,
new_path: &str,
) -> Result<()> {
use yanix::file::renameat;
unsafe {
renameat(
old_dirfd.as_raw_fd(),
old_path,
new_dirfd.as_raw_fd(),
new_path,
)?
};
Ok(())
}

View File

@@ -1,276 +0,0 @@
pub(crate) mod clock;
pub(crate) mod fd;
pub(crate) mod osdir;
pub(crate) mod osfile;
pub(crate) mod oshandle;
pub(crate) mod osother;
pub(crate) mod path;
pub(crate) mod poll;
pub(crate) mod stdio;
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux",
target_os = "android"))] {
mod linux;
use linux as sys_impl;
} else if #[cfg(target_os = "emscripten")] {
mod emscripten;
use emscripten as sys_impl;
} else if #[cfg(any(target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "ios",
target_os = "dragonfly"))] {
mod bsd;
use bsd as sys_impl;
}
}
use crate::handle::{
Fdflags, Filesize, Filestat, Filetype, HandleRights, Lookupflags, Oflags, Rights, RightsExt,
};
use crate::sched::{Clockid, Timestamp};
use crate::sys::AsFile;
use crate::{Error, Result};
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd};
use std::path::Path;
use yanix::clock::ClockId;
use yanix::file::{AtFlags, OFlags};
pub(crate) use sys_impl::*;
impl<T: AsRawFd> AsFile for T {
fn as_file(&self) -> io::Result<ManuallyDrop<File>> {
let file = unsafe { File::from_raw_fd(self.as_raw_fd()) };
Ok(ManuallyDrop::new(file))
}
}
pub(super) fn get_file_type(file: &File) -> io::Result<Filetype> {
let ft = file.metadata()?.file_type();
let file_type = if ft.is_block_device() {
tracing::debug!(
host_fd = tracing::field::display(file.as_raw_fd()),
"Host fd is a block device"
);
Filetype::BlockDevice
} else if ft.is_char_device() {
tracing::debug!("Host fd {:?} is a char device", file.as_raw_fd());
Filetype::CharacterDevice
} else if ft.is_dir() {
tracing::debug!("Host fd {:?} is a directory", file.as_raw_fd());
Filetype::Directory
} else if ft.is_file() {
tracing::debug!("Host fd {:?} is a file", file.as_raw_fd());
Filetype::RegularFile
} else if ft.is_socket() {
tracing::debug!("Host fd {:?} is a socket", file.as_raw_fd());
use yanix::socket::{get_socket_type, SockType};
match unsafe { get_socket_type(file.as_raw_fd())? } {
SockType::Datagram => Filetype::SocketDgram,
SockType::Stream => Filetype::SocketStream,
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
}
} else if ft.is_fifo() {
tracing::debug!("Host fd {:?} is a fifo", file.as_raw_fd());
Filetype::Unknown
} else {
tracing::debug!("Host fd {:?} is unknown", file.as_raw_fd());
return Err(io::Error::from_raw_os_error(libc::EINVAL));
};
Ok(file_type)
}
pub(super) fn get_rights(file: &File, file_type: &Filetype) -> io::Result<HandleRights> {
use yanix::{fcntl, file::OFlags};
let (base, inheriting) = match file_type {
Filetype::BlockDevice => (
Rights::block_device_base(),
Rights::block_device_inheriting(),
),
Filetype::CharacterDevice => {
use yanix::file::isatty;
if unsafe { isatty(file.as_raw_fd())? } {
(Rights::tty_base(), Rights::tty_base())
} else {
(
Rights::character_device_base(),
Rights::character_device_inheriting(),
)
}
}
Filetype::SocketDgram | Filetype::SocketStream => {
(Rights::socket_base(), Rights::socket_inheriting())
}
Filetype::SymbolicLink | Filetype::Unknown => (
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
),
Filetype::Directory => (Rights::directory_base(), Rights::directory_inheriting()),
Filetype::RegularFile => (
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
),
};
let mut rights = HandleRights::new(base, inheriting);
let flags = unsafe { fcntl::get_status_flags(file.as_raw_fd())? };
let accmode = flags & OFlags::ACCMODE;
if accmode == OFlags::RDONLY {
rights.base &= !Rights::FD_WRITE;
} else if accmode == OFlags::WRONLY {
rights.base &= !Rights::FD_READ;
}
Ok(rights)
}
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
File::open(path)
}
impl From<Clockid> for ClockId {
fn from(clock_id: Clockid) -> Self {
use Clockid::*;
match clock_id {
Realtime => Self::Realtime,
Monotonic => Self::Monotonic,
ProcessCputimeId => Self::ProcessCPUTime,
ThreadCputimeId => Self::ThreadCPUTime,
}
}
}
impl From<Fdflags> for OFlags {
fn from(fdflags: Fdflags) -> Self {
let mut nix_flags = Self::empty();
if fdflags.contains(Fdflags::APPEND) {
nix_flags.insert(Self::APPEND);
}
if fdflags.contains(Fdflags::DSYNC) {
nix_flags.insert(Self::DSYNC);
}
if fdflags.contains(Fdflags::NONBLOCK) {
nix_flags.insert(Self::NONBLOCK);
}
if fdflags.contains(Fdflags::RSYNC) {
nix_flags.insert(O_RSYNC);
}
if fdflags.contains(Fdflags::SYNC) {
nix_flags.insert(Self::SYNC);
}
nix_flags
}
}
impl From<OFlags> for Fdflags {
fn from(oflags: OFlags) -> Self {
let mut fdflags = Self::empty();
if oflags.contains(OFlags::APPEND) {
fdflags |= Self::APPEND;
}
if oflags.contains(OFlags::DSYNC) {
fdflags |= Self::DSYNC;
}
if oflags.contains(OFlags::NONBLOCK) {
fdflags |= Self::NONBLOCK;
}
if oflags.contains(O_RSYNC) {
fdflags |= Self::RSYNC;
}
if oflags.contains(OFlags::SYNC) {
fdflags |= Self::SYNC;
}
fdflags
}
}
impl From<Oflags> for OFlags {
fn from(oflags: Oflags) -> Self {
let mut nix_flags = Self::empty();
if oflags.contains(Oflags::CREAT) {
nix_flags.insert(Self::CREAT);
}
if oflags.contains(Oflags::DIRECTORY) {
nix_flags.insert(Self::DIRECTORY);
}
if oflags.contains(Oflags::EXCL) {
nix_flags.insert(Self::EXCL);
}
if oflags.contains(Oflags::TRUNC) {
nix_flags.insert(Self::TRUNC);
}
nix_flags
}
}
impl TryFrom<libc::stat> for Filestat {
type Error = Error;
fn try_from(filestat: libc::stat) -> Result<Self> {
fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result<Timestamp> {
secs.checked_mul(1_000_000_000)
.and_then(|sec_nsec| sec_nsec.checked_add(nsecs))
.ok_or(Error::Overflow)
}
let filetype = yanix::file::FileType::from_stat_st_mode(filestat.st_mode);
let dev = filestat.st_dev.try_into()?;
let ino = filestat.st_ino.try_into()?;
let atim = filestat_to_timestamp(
filestat.st_atime.try_into()?,
filestat.st_atime_nsec.try_into()?,
)?;
let ctim = filestat_to_timestamp(
filestat.st_ctime.try_into()?,
filestat.st_ctime_nsec.try_into()?,
)?;
let mtim = filestat_to_timestamp(
filestat.st_mtime.try_into()?,
filestat.st_mtime_nsec.try_into()?,
)?;
Ok(Self {
dev,
ino,
nlink: filestat.st_nlink.into(),
size: filestat.st_size as Filesize,
atim,
ctim,
mtim,
filetype: filetype.into(),
})
}
}
impl From<yanix::file::FileType> for Filetype {
fn from(ft: yanix::file::FileType) -> Self {
use yanix::file::FileType::*;
match ft {
RegularFile => Self::RegularFile,
Symlink => Self::SymbolicLink,
Directory => Self::Directory,
BlockDevice => Self::BlockDevice,
CharacterDevice => Self::CharacterDevice,
/* Unknown | Socket | Fifo */
_ => Self::Unknown,
// TODO how to discriminate between STREAM and DGRAM?
// Perhaps, we should create a more general WASI filetype
// such as __WASI_FILETYPE_SOCKET, and then it would be
// up to the client to check whether it's actually
// STREAM or DGRAM?
}
}
}
impl From<Lookupflags> for AtFlags {
fn from(flags: Lookupflags) -> Self {
match flags {
Lookupflags::SYMLINK_FOLLOW => Self::empty(),
_ => Self::SYMLINK_NOFOLLOW,
}
}
}

View File

@@ -1,35 +0,0 @@
use super::oshandle::RawOsHandle;
use crate::handle::{HandleRights, Rights, RightsExt};
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd};
pub use super::sys_impl::osdir::OsDir;
impl TryFrom<File> for OsDir {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let ft = file.metadata()?.file_type();
if !ft.is_dir() {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let rights = get_rights(&file)?;
let handle = unsafe { RawOsHandle::from_raw_fd(file.into_raw_fd()) };
Self::new(rights, handle)
}
}
fn get_rights(file: &File) -> io::Result<HandleRights> {
use yanix::{fcntl, file::OFlags};
let mut rights = HandleRights::new(Rights::directory_base(), Rights::directory_inheriting());
let flags = unsafe { fcntl::get_status_flags(file.as_raw_fd())? };
let accmode = flags & OFlags::ACCMODE;
if accmode == OFlags::RDONLY {
rights.base &= !Rights::FD_WRITE;
} else if accmode == OFlags::WRONLY {
rights.base &= !Rights::FD_READ;
}
Ok(rights)
}

View File

@@ -1,37 +0,0 @@
use super::oshandle::RawOsHandle;
use crate::handle::{HandleRights, Rights, RightsExt};
use crate::sys::osfile::OsFile;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd};
impl TryFrom<File> for OsFile {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let ft = file.metadata()?.file_type();
if !ft.is_file() {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let rights = get_rights(&file)?;
let handle = unsafe { RawOsHandle::from_raw_fd(file.into_raw_fd()) };
Ok(Self::new(rights, handle))
}
}
fn get_rights(file: &File) -> io::Result<HandleRights> {
use yanix::{fcntl, file::OFlags};
let mut rights = HandleRights::new(
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
);
let flags = unsafe { fcntl::get_status_flags(file.as_raw_fd())? };
let accmode = flags & OFlags::ACCMODE;
if accmode == OFlags::RDONLY {
rights.base &= !Rights::FD_WRITE;
} else if accmode == OFlags::WRONLY {
rights.base &= !Rights::FD_READ;
}
Ok(rights)
}

View File

@@ -1,40 +0,0 @@
use std::fs::File;
use std::io;
use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
#[derive(Debug)]
pub struct RawOsHandle(File);
impl RawOsHandle {
/// Tries clone `self`.
pub(crate) fn try_clone(&self) -> io::Result<Self> {
let fd = self.0.try_clone()?;
Ok(unsafe { Self::from_raw_fd(fd.into_raw_fd()) })
}
/// Consumes `other` taking the ownership of the underlying
/// `RawFd` file descriptor.
///
/// Note that the state of `Dir` stream pointer *will* not be carried
/// across from `other` to `self`.
pub(crate) fn update_from(&self, _other: Self) {
panic!("RawOsHandle::update_from should never be issued on Unix!")
}
}
impl AsRawFd for RawOsHandle {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for RawOsHandle {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
impl FromRawFd for RawOsHandle {
unsafe fn from_raw_fd(raw: RawFd) -> Self {
Self(File::from_raw_fd(raw))
}
}

View File

@@ -1,22 +0,0 @@
use super::oshandle::RawOsHandle;
use super::{get_file_type, get_rights};
use crate::handle::Filetype;
use crate::sys::osother::OsOther;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::os::unix::prelude::{FromRawFd, IntoRawFd};
impl TryFrom<File> for OsOther {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let file_type = get_file_type(&file)?;
if file_type == Filetype::RegularFile || file_type == Filetype::Directory {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let rights = get_rights(&file, &file_type)?;
let handle = unsafe { RawOsHandle::from_raw_fd(file.into_raw_fd()) };
Ok(Self::new(file_type, rights, handle))
}
}

View File

@@ -1,271 +0,0 @@
use crate::handle::{Fdflags, Filestat, Fstflags, Handle, HandleRights, Oflags, Rights};
use crate::sched::Timestamp;
use crate::sys::osdir::OsDir;
use crate::sys::AsFile;
use crate::{Error, Result};
use std::convert::{TryFrom, TryInto};
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt};
use std::str;
use yanix::file::OFlags;
pub(crate) use super::sys_impl::path::*;
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
/// `__WASI_ERRNO_ILSEQ` error is returned.
pub(crate) fn from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
let s = str::from_utf8(s.as_ref().as_bytes())?;
Ok(s.to_owned())
}
pub(crate) fn open_rights(
input_rights: &HandleRights,
oflags: Oflags,
fs_flags: Fdflags,
) -> HandleRights {
// which rights are needed on the dirfd?
let mut needed_base = Rights::PATH_OPEN;
let mut needed_inheriting = input_rights.base | input_rights.inheriting;
// convert open flags
let oflags: OFlags = oflags.into();
if oflags.contains(OFlags::CREAT) {
needed_base |= Rights::PATH_CREATE_FILE;
}
if oflags.contains(OFlags::TRUNC) {
needed_base |= Rights::PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
let fdflags: OFlags = fs_flags.into();
if fdflags.contains(OFlags::DSYNC) {
needed_inheriting |= Rights::FD_DATASYNC;
}
if fdflags.intersects(super::O_RSYNC | OFlags::SYNC) {
needed_inheriting |= Rights::FD_SYNC;
}
HandleRights::new(needed_base, needed_inheriting)
}
pub(crate) fn readlinkat(dirfd: &OsDir, path: &str) -> Result<String> {
use std::os::unix::prelude::AsRawFd;
use yanix::file::readlinkat;
tracing::debug!(path = path, "path_get readlinkat");
let path = unsafe { readlinkat(dirfd.as_raw_fd(), path)? };
let path = from_host(path)?;
Ok(path)
}
pub(crate) fn create_directory(base: &OsDir, path: &str) -> Result<()> {
use yanix::file::{mkdirat, Mode};
unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? };
Ok(())
}
pub(crate) fn link(
old_dirfd: &OsDir,
old_path: &str,
new_dirfd: &OsDir,
new_path: &str,
follow_symlinks: bool,
) -> Result<()> {
use yanix::file::{linkat, AtFlags};
let flags = if follow_symlinks {
AtFlags::SYMLINK_FOLLOW
} else {
AtFlags::empty()
};
unsafe {
linkat(
old_dirfd.as_raw_fd(),
old_path,
new_dirfd.as_raw_fd(),
new_path,
flags,
)?
};
Ok(())
}
pub(crate) fn open(
dirfd: &OsDir,
path: &str,
read: bool,
write: bool,
oflags: Oflags,
fs_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
use yanix::file::{fstatat, openat, AtFlags, FileType, Mode, OFlags};
let mut nix_all_oflags = if read && write {
OFlags::RDWR
} else if write {
OFlags::WRONLY
} else {
OFlags::RDONLY
};
// on non-Capsicum systems, we always want nofollow
nix_all_oflags.insert(OFlags::NOFOLLOW);
// convert open flags
nix_all_oflags.insert(oflags.into());
// convert file descriptor flags
nix_all_oflags.insert(fs_flags.into());
// Call openat. Use mode 0o666 so that we follow whatever the user's
// umask is, but don't set the executable flag, because it isn't yet
// meaningful for WASI programs to create executable files.
tracing::debug!(
dirfd = tracing::field::debug(dirfd),
path = tracing::field::debug(path),
oflags = tracing::field::debug(nix_all_oflags),
"path_open"
);
let fd_no = unsafe {
openat(
dirfd.as_raw_fd(),
path,
nix_all_oflags,
Mode::from_bits_truncate(0o666),
)
};
let new_fd = match fd_no {
Ok(fd) => fd,
Err(e) => {
match e.raw_os_error().unwrap() {
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
libc::ENXIO => {
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlags::SYMLINK_NOFOLLOW) } {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket {
return Err(Error::Notsup);
}
}
Err(err) => {
tracing::debug!(
error = tracing::field::debug(&err),
"path_open fstatat error",
);
}
}
}
// Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
// on a symlink.
libc::ENOTDIR
if !(nix_all_oflags & (OFlags::NOFOLLOW | OFlags::DIRECTORY)).is_empty() =>
{
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlags::SYMLINK_NOFOLLOW) } {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink {
return Err(Error::Loop);
}
}
Err(err) => {
tracing::debug!(
error = tracing::field::debug(&err),
"path_open fstatat error",
);
}
}
}
// FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
// a symlink.
libc::EMLINK if !(nix_all_oflags & OFlags::NOFOLLOW).is_empty() => {
return Err(Error::Loop);
}
_ => {}
}
return Err(e.into());
}
};
tracing::debug!(new_fd = tracing::field::debug(new_fd));
// Determine the type of the new file descriptor and which rights contradict with this type
let file = unsafe { File::from_raw_fd(new_fd) };
let handle = <Box<dyn Handle>>::try_from(file)?;
Ok(handle)
}
pub(crate) fn readlink(dirfd: &OsDir, path: &str, buf: &mut [u8]) -> Result<usize> {
use std::cmp::min;
use yanix::file::readlinkat;
let read_link = unsafe { readlinkat(dirfd.as_raw_fd(), path)? };
let read_link = from_host(read_link)?;
let copy_len = min(read_link.len(), buf.len());
if copy_len > 0 {
buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]);
}
Ok(copy_len)
}
pub(crate) fn remove_directory(dirfd: &OsDir, path: &str) -> Result<()> {
use yanix::file::{unlinkat, AtFlags};
unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlags::REMOVEDIR)? };
Ok(())
}
pub(crate) fn filestat_get_at(dirfd: &OsDir, path: &str, follow: bool) -> Result<Filestat> {
use yanix::file::{fstatat, AtFlags};
let flags = if follow {
AtFlags::empty()
} else {
AtFlags::SYMLINK_NOFOLLOW
};
let stat = unsafe { fstatat(dirfd.as_raw_fd(), path, flags)? };
let stat = stat.try_into()?;
Ok(stat)
}
pub(crate) fn filestat_set_times_at(
dirfd: &OsDir,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
follow: bool,
) -> Result<()> {
use std::time::{Duration, UNIX_EPOCH};
use yanix::filetime::*;
let set_atim = fst_flags.contains(Fstflags::ATIM);
let set_atim_now = fst_flags.contains(Fstflags::ATIM_NOW);
let set_mtim = fst_flags.contains(Fstflags::MTIM);
let set_mtim_now = fst_flags.contains(Fstflags::MTIM_NOW);
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
return Err(Error::Inval);
}
let atim = if set_atim {
let time = UNIX_EPOCH + Duration::from_nanos(atim);
FileTime::FileTime(filetime::FileTime::from_system_time(time))
} else if set_atim_now {
FileTime::Now
} else {
FileTime::Omit
};
let mtim = if set_mtim {
let time = UNIX_EPOCH + Duration::from_nanos(mtim);
FileTime::FileTime(filetime::FileTime::from_system_time(time))
} else if set_mtim_now {
FileTime::Now
} else {
FileTime::Omit
};
utimensat(&*dirfd.as_file()?, path, atim, mtim, !follow)?;
Ok(())
}

View File

@@ -1,165 +0,0 @@
use crate::entry::EntryHandle;
use crate::handle::Filetype;
use crate::sched::{
ClockEventData, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, FdEventData,
};
use crate::sys::AsFile;
use crate::{Error, Result};
use std::io;
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
use yanix::file::fionread;
use yanix::poll::{poll, PollFd, PollFlags};
pub(crate) fn oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<Event>,
) -> Result<()> {
if fd_events.is_empty() && timeout.is_none() {
return Ok(());
}
let poll_fds: Result<Vec<_>> = fd_events
.iter()
.map(|event| {
let mut flags = PollFlags::empty();
match event.r#type {
Eventtype::FdRead => flags.insert(PollFlags::POLLIN),
Eventtype::FdWrite => flags.insert(PollFlags::POLLOUT),
// An event on a file descriptor can currently only be of type FD_READ or FD_WRITE
// Nothing else has been defined in the specification, and these are also the only two
// events we filtered before. If we get something else here, the code has a serious bug.
_ => unreachable!(),
};
let file = event.handle.as_file()?;
unsafe { Ok(PollFd::new(file.as_raw_fd(), flags)) }
})
.collect();
let mut poll_fds = poll_fds?;
let poll_timeout = timeout.map_or(-1, |timeout| {
let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds
delay.try_into().unwrap_or(libc::c_int::max_value())
});
tracing::debug!(
poll_timeout = tracing::field::debug(poll_timeout),
"poll_oneoff"
);
let ready = loop {
match poll(&mut poll_fds, poll_timeout) {
Err(_) => {
let last_err = io::Error::last_os_error();
if last_err.raw_os_error().unwrap() == libc::EINTR {
continue;
}
return Err(last_err.into());
}
Ok(ready) => break ready,
}
};
Ok(if ready == 0 {
handle_timeout_event(timeout.expect("timeout should not be None"), events)
} else {
let ready_events = fd_events.into_iter().zip(poll_fds.into_iter()).take(ready);
handle_fd_event(ready_events, events)?
})
}
fn handle_timeout_event(timeout: ClockEventData, events: &mut Vec<Event>) {
events.push(Event {
userdata: timeout.userdata,
error: Errno::Success,
type_: Eventtype::Clock,
fd_readwrite: EventFdReadwrite {
flags: Eventrwflags::empty(),
nbytes: 0,
},
});
}
fn handle_fd_event(
ready_events: impl Iterator<Item = (FdEventData, yanix::poll::PollFd)>,
events: &mut Vec<Event>,
) -> Result<()> {
fn query_nbytes(handle: EntryHandle) -> Result<u64> {
let file = handle.as_file()?;
if handle.get_file_type() == Filetype::RegularFile {
// fionread may overflow for large files, so use another way for regular files.
use yanix::file::tell;
let meta = file.metadata()?;
let len = meta.len();
let host_offset = unsafe { tell(file.as_raw_fd())? };
return Ok(len - host_offset);
}
Ok(unsafe { fionread(file.as_raw_fd())?.into() })
}
for (fd_event, poll_fd) in ready_events {
tracing::debug!(
poll_fd = tracing::field::debug(poll_fd),
poll_event = tracing::field::debug(&fd_event),
"poll_oneoff handle_fd_event"
);
let revents = match poll_fd.revents() {
Some(revents) => revents,
None => continue,
};
let nbytes = if fd_event.r#type == Eventtype::FdRead {
query_nbytes(fd_event.handle)?
} else {
0
};
let output_event = if revents.contains(PollFlags::POLLNVAL) {
Event {
userdata: fd_event.userdata,
error: Error::Badf.try_into().unwrap(),
type_: fd_event.r#type,
fd_readwrite: EventFdReadwrite {
nbytes: 0,
flags: Eventrwflags::FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLERR) {
Event {
userdata: fd_event.userdata,
error: Error::Io.try_into().unwrap(),
type_: fd_event.r#type,
fd_readwrite: EventFdReadwrite {
nbytes: 0,
flags: Eventrwflags::FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLHUP) {
Event {
userdata: fd_event.userdata,
error: Errno::Success,
type_: fd_event.r#type,
fd_readwrite: EventFdReadwrite {
nbytes: 0,
flags: Eventrwflags::FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) {
Event {
userdata: fd_event.userdata,
error: Errno::Success,
type_: fd_event.r#type,
fd_readwrite: EventFdReadwrite {
nbytes: nbytes.try_into()?,
flags: Eventrwflags::empty(),
},
}
} else {
continue;
};
events.push(output_event);
}
Ok(())
}

View File

@@ -1,59 +0,0 @@
use super::{get_file_type, get_rights};
use crate::handle::Handle;
use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt};
use std::cell::Cell;
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::os::unix::prelude::{AsRawFd, FromRawFd, RawFd};
impl AsRawFd for Stdin {
fn as_raw_fd(&self) -> RawFd {
io::stdin().as_raw_fd()
}
}
impl AsRawFd for Stdout {
fn as_raw_fd(&self) -> RawFd {
io::stdout().as_raw_fd()
}
}
impl AsRawFd for Stderr {
fn as_raw_fd(&self) -> RawFd {
io::stderr().as_raw_fd()
}
}
impl StdinExt for Stdin {
fn stdin() -> io::Result<Box<dyn Handle>> {
let file = unsafe { File::from_raw_fd(io::stdin().as_raw_fd()) };
let file = ManuallyDrop::new(file);
let file_type = get_file_type(&file)?;
let rights = get_rights(&file, &file_type)?;
let rights = Cell::new(rights);
Ok(Box::new(Self { file_type, rights }))
}
}
impl StdoutExt for Stdout {
fn stdout() -> io::Result<Box<dyn Handle>> {
let file = unsafe { File::from_raw_fd(io::stdout().as_raw_fd()) };
let file = ManuallyDrop::new(file);
let file_type = get_file_type(&file)?;
let rights = get_rights(&file, &file_type)?;
let rights = Cell::new(rights);
Ok(Box::new(Self { file_type, rights }))
}
}
impl StderrExt for Stderr {
fn stderr() -> io::Result<Box<dyn Handle>> {
let file = unsafe { File::from_raw_fd(io::stderr().as_raw_fd()) };
let file = ManuallyDrop::new(file);
let file_type = get_file_type(&file)?;
let rights = get_rights(&file, &file_type)?;
let rights = Cell::new(rights);
Ok(Box::new(Self { file_type, rights }))
}
}

View File

@@ -1,105 +0,0 @@
use crate::sched::{Clockid, Timestamp};
use crate::{Error, Result};
use cpu_time::{ProcessTime, ThreadTime};
use lazy_static::lazy_static;
use std::convert::TryInto;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
lazy_static! {
static ref START_MONOTONIC: Instant = Instant::now();
static ref PERF_COUNTER_RES: u64 = get_perf_counter_resolution_ns();
}
// Timer resolution on Windows is really hard. We may consider exposing the resolution of the respective
// timers as an associated function in the future.
pub(crate) fn res_get(clock_id: Clockid) -> Result<Timestamp> {
let ts = match clock_id {
// This is the best that we can do with std::time::SystemTime.
// Rust uses GetSystemTimeAsFileTime, which is said to have the resolution of
// 10ms or 55ms, [1] but MSDN doesn't confirm this in any way.
// Even the MSDN article on high resolution timestamps doesn't even mention the precision
// for this method. [3]
//
// The timer resolution can be queried using one of the functions: [2, 5]
// * NtQueryTimerResolution, which is undocumented and thus not exposed by the winapi crate
// * timeGetDevCaps, which returns the upper and lower bound for the precision, in ms.
// While the upper bound seems like something we could use, it's typically too high to be meaningful.
// For instance, the intervals return by the syscall are:
// * [1, 65536] on Wine
// * [1, 1000000] on Windows 10, which is up to (sic) 1000 seconds.
//
// It's possible to manually set the timer resolution, but this sounds like something which should
// only be done temporarily. [5]
//
// Alternatively, we could possibly use GetSystemTimePreciseAsFileTime in clock_time_get, but
// this syscall is only available starting from Windows 8.
// (we could possibly emulate it on earlier versions of Windows, see [4])
// The MSDN are not clear on the resolution of GetSystemTimePreciseAsFileTime either, but a
// Microsoft devblog entry [1] suggests that it kind of combines GetSystemTimeAsFileTime with
// QueryPeformanceCounter, which probably means that those two should have the same resolution.
//
// See also this discussion about the use of GetSystemTimePreciseAsFileTime in Python stdlib,
// which in particular contains some resolution benchmarks.
//
// [1] https://devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057
// [2] http://www.windowstimestamp.com/description
// [3] https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps?redirectedfrom=MSDN
// [4] https://www.codeproject.com/Tips/1011902/High-Resolution-Time-For-Windows
// [5] https://stackoverflow.com/questions/7685762/windows-7-timing-functions-how-to-use-getsystemtimeadjustment-correctly
// [6] https://bugs.python.org/issue19007
Clockid::Realtime => 55_000_000,
// std::time::Instant uses QueryPerformanceCounter & QueryPerformanceFrequency internally
Clockid::Monotonic => *PERF_COUNTER_RES,
// The best we can do is to hardcode the value from the docs.
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes
Clockid::ProcessCputimeId => 100,
// The best we can do is to hardcode the value from the docs.
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes
Clockid::ThreadCputimeId => 100,
};
Ok(ts)
}
pub(crate) fn time_get(clock_id: Clockid) -> Result<Timestamp> {
let duration = match clock_id {
Clockid::Realtime => get_monotonic_time(),
Clockid::Monotonic => get_realtime_time()?,
Clockid::ProcessCputimeId => get_proc_cputime()?,
Clockid::ThreadCputimeId => get_thread_cputime()?,
};
let duration = duration.as_nanos().try_into()?;
Ok(duration)
}
fn get_monotonic_time() -> Duration {
// We're circumventing the fact that we can't get a Duration from an Instant
// The epoch of __WASI_CLOCKID_MONOTONIC is undefined, so we fix a time point once
// and count relative to this time point.
//
// The alternative would be to copy over the implementation of std::time::Instant
// to our source tree and add a conversion to std::time::Duration
START_MONOTONIC.elapsed()
}
fn get_realtime_time() -> Result<Duration> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| Error::Fault)
}
fn get_proc_cputime() -> Result<Duration> {
Ok(ProcessTime::try_now()?.as_duration())
}
fn get_thread_cputime() -> Result<Duration> {
Ok(ThreadTime::try_now()?.as_duration())
}
fn get_perf_counter_resolution_ns() -> u64 {
use winx::time::perf_counter_frequency;
const NANOS_PER_SEC: u64 = 1_000_000_000;
// This should always succeed starting from Windows XP, so it's fine to panic in case of an error.
let freq = perf_counter_frequency().expect("QueryPerformanceFrequency returned an error");
let epsilon = NANOS_PER_SEC / freq;
epsilon
}

View File

@@ -1,195 +0,0 @@
use super::file_serial_no;
use super::oshandle::RawOsHandle;
use crate::handle::{Advice, Dircookie, Dirent, Fdflags, Filesize, Filestat};
use crate::path;
use crate::sys::osdir::OsDir;
use crate::sys::osfile::OsFile;
use crate::sys::AsFile;
use crate::Result;
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::Path;
use tracing::trace;
use winx::file::{AccessMode, FileModeInformation, Flags};
pub(crate) fn fdstat_get(file: &File) -> Result<Fdflags> {
let mut fdflags = Fdflags::empty();
let handle = file.as_raw_handle();
let access_mode = winx::file::query_access_information(handle)?;
let mode = winx::file::query_mode_information(handle)?;
// Append without write implies append-only (__WASI_FDFLAGS_APPEND)
if access_mode.contains(AccessMode::FILE_APPEND_DATA)
&& !access_mode.contains(AccessMode::FILE_WRITE_DATA)
{
fdflags |= Fdflags::APPEND;
}
if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
// Only report __WASI_FDFLAGS_SYNC
// This is technically the only one of the O_?SYNC flags Windows supports.
fdflags |= Fdflags::SYNC;
}
// Files do not support the `__WASI_FDFLAGS_NONBLOCK` flag
Ok(fdflags)
}
// TODO Investigate further for Stdio handles. `ReOpenFile` requires the file
// handle came from `CreateFile`, but the Rust's libstd will use `GetStdHandle`
// rather than `CreateFile`. Relevant discussion can be found in:
// https://github.com/rust-lang/rust/issues/40490
pub(crate) fn fdstat_set_flags(file: &File, fdflags: Fdflags) -> Result<Option<RawOsHandle>> {
let handle = file.as_raw_handle();
let access_mode = winx::file::query_access_information(handle)?;
let new_access_mode = file_access_mode_from_fdflags(
fdflags,
access_mode.contains(AccessMode::FILE_READ_DATA),
access_mode.contains(AccessMode::FILE_WRITE_DATA)
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
);
unsafe {
Ok(Some(RawOsHandle::from_raw_handle(winx::file::reopen_file(
handle,
new_access_mode,
fdflags.into(),
)?)))
}
}
pub(crate) fn advise(
_file: &OsFile,
_advice: Advice,
_offset: Filesize,
_len: Filesize,
) -> Result<()> {
Ok(())
}
fn file_access_mode_from_fdflags(fdflags: Fdflags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
// Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode
// The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead
// These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below)
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
// This makes the handle "append only".
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
if fdflags.contains(&Fdflags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}
// On Windows there is apparently no support for seeking the directory stream in the OS.
// cf. https://github.com/WebAssembly/WASI/issues/61
//
// The implementation here may perform in O(n^2) if the host buffer is O(1)
// and the number of directory entries is O(n).
// TODO: Add a heuristic optimization to achieve O(n) time in the most common case
// where fd_readdir is resumed where it previously finished
//
// Correctness of this approach relies upon one assumption: that the order of entries
// returned by `FindNextFileW` is stable, i.e. doesn't change if the directory
// contents stay the same. This invariant is crucial to be able to implement
// any kind of seeking whatsoever without having to read the whole directory at once
// and then return the data from cache. (which leaks memory)
//
// The MSDN documentation explicitly says that the order in which the search returns the files
// is not guaranteed, and is dependent on the file system.
// cf. https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew
//
// This stackoverflow post suggests that `FindNextFileW` is indeed stable and that
// the order of directory entries depends **only** on the filesystem used, but the
// MSDN documentation is not clear about this.
// cf. https://stackoverflow.com/questions/47380739/is-findfirstfile-and-findnextfile-order-random-even-for-dvd
//
// Implementation details:
// Cookies for the directory entries start from 1. (0 is reserved by wasi::__WASI_DIRCOOKIE_START)
// . gets cookie = 1
// .. gets cookie = 2
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
pub(crate) fn readdir(
dirfd: &OsDir,
cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>>>> {
use winx::file::get_file_path;
let cookie = cookie.try_into()?;
let path = get_file_path(&*dirfd.as_file()?)?;
// std::fs::ReadDir doesn't return . and .., so we need to emulate it
let path = Path::new(&path);
// The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too
let parent = path.parent().unwrap_or(path);
let dot = dirent_from_path(path, ".", 1)?;
let dotdot = dirent_from_path(parent, "..", 2)?;
trace!(" | fd_readdir impl: executing std::fs::ReadDir");
let iter = path.read_dir()?.zip(3..).map(|(dir, no)| {
let dir: std::fs::DirEntry = dir?;
let ftype = dir.file_type()?;
let name = path::from_host(dir.file_name())?;
let d_ino = File::open(dir.path()).and_then(|f| file_serial_no(&f))?;
let dirent = Dirent {
d_namlen: name.len().try_into()?,
d_type: ftype.into(),
d_ino,
d_next: no,
};
Ok((dirent, name))
});
// into_iter for arrays is broken and returns references instead of values,
// so we need to use vec![...] and do heap allocation
// See https://github.com/rust-lang/rust/issues/25725
let iter = vec![dot, dotdot].into_iter().map(Ok).chain(iter);
// Emulate seekdir(). This may give O(n^2) complexity if used with a
// small host_buf, but this is difficult to implement efficiently.
//
// See https://github.com/WebAssembly/WASI/issues/61 for more details.
Ok(Box::new(iter.skip(cookie)))
}
fn dirent_from_path<P: AsRef<Path>>(
path: P,
name: &str,
cookie: Dircookie,
) -> Result<(Dirent, String)> {
let path = path.as_ref();
trace!("dirent_from_path: opening {}", path.to_string_lossy());
// To open a directory on Windows, FILE_FLAG_BACKUP_SEMANTICS flag must be used
let file = OpenOptions::new()
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
.read(true)
.open(path)?;
let ty = file.metadata()?.file_type();
let name = name.to_owned();
let dirent = Dirent {
d_namlen: name.len().try_into()?,
d_next: cookie,
d_type: ty.into(),
d_ino: file_serial_no(&file)?,
};
Ok((dirent, name))
}
pub(crate) fn filestat_get(file: &File) -> Result<Filestat> {
let filestat = file.try_into()?;
Ok(filestat)
}

View File

@@ -1,183 +0,0 @@
pub(crate) mod clock;
pub(crate) mod fd;
pub(crate) mod osdir;
pub(crate) mod osfile;
pub(crate) mod oshandle;
pub(crate) mod osother;
pub(crate) mod path;
pub(crate) mod poll;
pub(crate) mod stdio;
use crate::handle::{Fdflags, Filestat, Filetype, HandleRights, Oflags, Rights, RightsExt};
use crate::sys::AsFile;
use crate::{Error, Result};
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::mem::ManuallyDrop;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{io, string};
use winx::file::{CreationDisposition, Flags};
impl<T: AsRawHandle> AsFile for T {
fn as_file(&self) -> io::Result<ManuallyDrop<File>> {
let file = unsafe { File::from_raw_handle(self.as_raw_handle()) };
Ok(ManuallyDrop::new(file))
}
}
pub(super) fn get_file_type(file: &File) -> io::Result<Filetype> {
let file_type = unsafe { winx::file::get_file_type(file.as_raw_handle())? };
let file_type = if file_type.is_char() {
// character file: LPT device or console
// TODO: rule out LPT device
Filetype::CharacterDevice
} else if file_type.is_disk() {
// disk file: file, dir or disk device
let meta = file.metadata()?;
if meta.is_dir() {
Filetype::Directory
} else if meta.is_file() {
Filetype::RegularFile
} else {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
} else if file_type.is_pipe() {
// pipe object: socket, named pipe or anonymous pipe
// TODO: what about pipes, etc?
Filetype::SocketStream
} else {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
};
Ok(file_type)
}
pub(super) fn get_rights(file_type: &Filetype) -> io::Result<HandleRights> {
let (base, inheriting) = match file_type {
Filetype::BlockDevice => (
Rights::block_device_base(),
Rights::block_device_inheriting(),
),
Filetype::CharacterDevice => (Rights::tty_base(), Rights::tty_base()),
Filetype::SocketDgram | Filetype::SocketStream => {
(Rights::socket_base(), Rights::socket_inheriting())
}
Filetype::SymbolicLink | Filetype::Unknown => (
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
),
Filetype::Directory => (Rights::directory_base(), Rights::directory_inheriting()),
Filetype::RegularFile => (
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
),
};
let rights = HandleRights::new(base, inheriting);
Ok(rights)
}
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
// To open a directory using CreateFile, specify the
// FILE_FLAG_BACKUP_SEMANTICS flag as part of dwFileFlags...
// cf. https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfile2
OpenOptions::new()
.create(false)
.write(true)
.read(true)
.attributes(FILE_FLAG_BACKUP_SEMANTICS)
.open(path)
}
pub(crate) fn file_serial_no(file: &File) -> io::Result<u64> {
let info = winx::file::get_fileinfo(file)?;
let high = info.nFileIndexHigh;
let low = info.nFileIndexLow;
let no = (u64::from(high) << 32) | u64::from(low);
Ok(no)
}
impl From<string::FromUtf16Error> for Error {
fn from(_err: string::FromUtf16Error) -> Self {
Self::Ilseq
}
}
fn num_hardlinks(file: &File) -> io::Result<u64> {
Ok(winx::file::get_fileinfo(file)?.nNumberOfLinks.into())
}
fn device_id(file: &File) -> io::Result<u64> {
Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into())
}
fn change_time(file: &File) -> io::Result<i64> {
winx::file::change_time(file)
}
fn systemtime_to_timestamp(st: SystemTime) -> Result<u64> {
st.duration_since(UNIX_EPOCH)
.map_err(|_| Error::Inval)? // date earlier than UNIX_EPOCH
.as_nanos()
.try_into()
.map_err(Into::into) // u128 doesn't fit into u64
}
impl TryFrom<&File> for Filestat {
type Error = Error;
fn try_from(file: &File) -> Result<Self> {
let metadata = file.metadata()?;
Ok(Filestat {
dev: device_id(file)?,
ino: file_serial_no(file)?,
nlink: num_hardlinks(file)?.try_into()?, // u64 doesn't fit into u32
size: metadata.len(),
atim: systemtime_to_timestamp(metadata.accessed()?)?,
ctim: change_time(file)?.try_into()?, // i64 doesn't fit into u64
mtim: systemtime_to_timestamp(metadata.modified()?)?,
filetype: metadata.file_type().into(),
})
}
}
impl From<Oflags> for CreationDisposition {
fn from(oflags: Oflags) -> Self {
if oflags.contains(&Oflags::CREAT) {
if oflags.contains(&Oflags::EXCL) {
CreationDisposition::CREATE_NEW
} else {
CreationDisposition::CREATE_ALWAYS
}
} else if oflags.contains(&Oflags::TRUNC) {
CreationDisposition::TRUNCATE_EXISTING
} else {
CreationDisposition::OPEN_EXISTING
}
}
}
impl From<Fdflags> for Flags {
fn from(fdflags: Fdflags) -> Self {
// Enable backup semantics so directories can be opened as files
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;
// Note: __WASI_FDFLAGS_NONBLOCK is purposely being ignored for files
// While Windows does inherently support a non-blocking mode on files, the WASI API will
// treat I/O operations on files as synchronous. WASI might have an async-io API in the future.
// Technically, Windows only supports __WASI_FDFLAGS_SYNC, but treat all the flags as the same.
if fdflags.contains(&Fdflags::DSYNC)
|| fdflags.contains(&Fdflags::RSYNC)
|| fdflags.contains(&Fdflags::SYNC)
{
flags.insert(Flags::FILE_FLAG_WRITE_THROUGH);
}
flags
}
}

View File

@@ -1,66 +0,0 @@
use super::oshandle::RawOsHandle;
use crate::handle::{HandleRights, Rights, RightsExt};
use std::cell::Cell;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle};
#[derive(Debug)]
/// A directory in the operating system's file system. Its impl of `Handle` is
/// in `sys::osdir`. This type is exposed to all other modules as
/// `sys::osdir::OsDir` when configured.
///
/// # Constructing `OsDir`
///
/// `OsDir` can currently only be constructed from `std::fs::File` using
/// the `std::convert::TryFrom` trait:
///
/// ```rust,no_run
/// use std::fs::OpenOptions;
/// use std::convert::TryFrom;
/// use std::os::windows::fs::OpenOptionsExt;
/// use wasi_common::OsDir;
/// use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
///
/// let dir = OpenOptions::new().read(true).attributes(FILE_FLAG_BACKUP_SEMANTICS).open("some_dir").unwrap();
/// let os_dir = OsDir::try_from(dir).unwrap();
/// ```
pub struct OsDir {
pub(crate) rights: Cell<HandleRights>,
pub(crate) handle: RawOsHandle,
}
impl OsDir {
pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result<Self> {
let rights = Cell::new(rights);
Ok(Self { rights, handle })
}
}
impl TryFrom<File> for OsDir {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let ft = file.metadata()?.file_type();
if !ft.is_dir() {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let rights = get_rights(&file)?;
let handle = unsafe { RawOsHandle::from_raw_handle(file.into_raw_handle()) };
Self::new(rights, handle)
}
}
fn get_rights(file: &File) -> io::Result<HandleRights> {
use winx::file::{query_access_information, AccessMode};
let mut rights = HandleRights::new(Rights::directory_base(), Rights::directory_inheriting());
let mode = query_access_information(file.as_raw_handle())?;
if mode.contains(AccessMode::FILE_GENERIC_READ) {
rights.base |= Rights::FD_READ;
}
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
rights.base |= Rights::FD_WRITE;
}
Ok(rights)
}

View File

@@ -1,37 +0,0 @@
use super::oshandle::RawOsHandle;
use crate::handle::{HandleRights, Rights, RightsExt};
use crate::sys::osfile::OsFile;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle};
impl TryFrom<File> for OsFile {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let ft = file.metadata()?.file_type();
if !ft.is_file() {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let rights = get_rights(&file)?;
let handle = unsafe { RawOsHandle::from_raw_handle(file.into_raw_handle()) };
Ok(Self::new(rights, handle))
}
}
fn get_rights(file: &File) -> io::Result<HandleRights> {
use winx::file::{query_access_information, AccessMode};
let mut rights = HandleRights::new(
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
);
let mode = query_access_information(file.as_raw_handle())?;
if mode.contains(AccessMode::FILE_GENERIC_READ) {
rights.base |= Rights::FD_READ;
}
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
rights.base |= Rights::FD_WRITE;
}
Ok(rights)
}

View File

@@ -1,56 +0,0 @@
use crate::sys::AsFile;
use std::cell::Cell;
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
#[derive(Debug)]
pub struct RawOsHandle(Cell<RawHandle>);
impl RawOsHandle {
/// Tries cloning `self`.
pub(crate) fn try_clone(&self) -> io::Result<Self> {
let handle = self.as_file()?.try_clone()?;
Ok(Self(Cell::new(handle.into_raw_handle())))
}
/// Consumes `other` taking the ownership of the underlying
/// `RawHandle` file handle.
pub(crate) fn update_from(&self, other: Self) {
let new_handle = other.into_raw_handle();
let old_handle = self.0.get();
self.0.set(new_handle);
// We need to remember to close the old_handle.
unsafe {
File::from_raw_handle(old_handle);
}
}
}
impl Drop for RawOsHandle {
fn drop(&mut self) {
unsafe {
File::from_raw_handle(self.as_raw_handle());
}
}
}
impl AsRawHandle for RawOsHandle {
fn as_raw_handle(&self) -> RawHandle {
self.0.get()
}
}
impl FromRawHandle for RawOsHandle {
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
Self(Cell::new(handle))
}
}
impl IntoRawHandle for RawOsHandle {
fn into_raw_handle(self) -> RawHandle {
// We need to prevent dropping of the OsFile
let wrapped = ManuallyDrop::new(self);
wrapped.0.get()
}
}

View File

@@ -1,22 +0,0 @@
use super::oshandle::RawOsHandle;
use super::{get_file_type, get_rights};
use crate::handle::Filetype;
use crate::sys::osother::OsOther;
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::os::windows::prelude::{FromRawHandle, IntoRawHandle};
impl TryFrom<File> for OsOther {
type Error = io::Error;
fn try_from(file: File) -> io::Result<Self> {
let file_type = get_file_type(&file)?;
if file_type == Filetype::RegularFile || file_type == Filetype::Directory {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let rights = get_rights(&file_type)?;
let handle = unsafe { RawOsHandle::from_raw_handle(file.into_raw_handle()) };
Ok(Self::new(file_type, rights, handle))
}
}

View File

@@ -1,539 +0,0 @@
use crate::handle::{Fdflags, Filestat, Fstflags, Handle, HandleRights, Oflags, Rights};
use crate::sched::Timestamp;
use crate::sys::osdir::OsDir;
use crate::sys::{fd, AsFile};
use crate::{Error, Result};
use std::convert::TryFrom;
use std::ffi::{OsStr, OsString};
use std::fs::{self, Metadata, OpenOptions};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use winapi::shared::winerror;
use winx::file::AccessMode;
fn strip_trailing_slashes_and_concatenate(dirfd: &OsDir, path: &str) -> Result<Option<PathBuf>> {
if path.ends_with('/') {
let suffix = path.trim_end_matches('/');
concatenate(dirfd, Path::new(suffix)).map(Some)
} else {
Ok(None)
}
}
fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
let path: Vec<u16> = path.as_ref().encode_wide().collect();
if &[92, 92, 63, 92] == &path[0..4] {
OsString::from_wide(&path[4..])
} else {
OsString::from_wide(&path)
}
}
fn concatenate<P: AsRef<Path>>(file: &OsDir, path: P) -> Result<PathBuf> {
use winx::file::get_file_path;
// WASI is not able to deal with absolute paths
// so error out if absolute
if path.as_ref().is_absolute() {
return Err(Error::Notcapable);
}
let dir_path = get_file_path(&*file.as_file()?)?;
// concatenate paths
let mut out_path = PathBuf::from(dir_path);
out_path.push(path.as_ref());
// strip extended prefix; otherwise we will error out on any relative
// components with `out_path`
let out_path = PathBuf::from(strip_extended_prefix(out_path));
tracing::debug!(out_path = tracing::field::debug(&out_path));
Ok(out_path)
}
fn file_access_mode_from_fdflags(fdflags: Fdflags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
// We always need `FILE_WRITE_ATTRIBUTES` so that we can set attributes such as filetimes, etc.
access_mode.insert(AccessMode::FILE_WRITE_ATTRIBUTES);
// Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode
// The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead
// These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below)
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
// This makes the handle "append only".
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
if fdflags.contains(&Fdflags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
/// `__WASI_ERRNO_ILSEQ` error is returned.
pub(crate) fn from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
let vec: Vec<u16> = s.as_ref().encode_wide().collect();
let s = String::from_utf16(&vec)?;
Ok(s)
}
pub(crate) fn open_rights(
input_rights: &HandleRights,
oflags: Oflags,
fdflags: Fdflags,
) -> HandleRights {
// which rights are needed on the dirfd?
let mut needed_base = Rights::PATH_OPEN;
let mut needed_inheriting = input_rights.base | input_rights.inheriting;
// convert open flags
if oflags.contains(&Oflags::CREAT) {
needed_base |= Rights::PATH_CREATE_FILE;
} else if oflags.contains(&Oflags::TRUNC) {
needed_base |= Rights::PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
if fdflags.contains(&Fdflags::DSYNC)
|| fdflags.contains(&Fdflags::RSYNC)
|| fdflags.contains(&Fdflags::SYNC)
{
needed_inheriting |= Rights::FD_DATASYNC;
needed_inheriting |= Rights::FD_SYNC;
}
HandleRights::new(needed_base, needed_inheriting)
}
pub(crate) fn readlinkat(dirfd: &OsDir, s_path: &str) -> Result<String> {
use winx::file::get_file_path;
let path = concatenate(dirfd, Path::new(s_path))?;
let err = match path.read_link() {
Ok(target_path) => {
// since on Windows we are effectively emulating 'at' syscalls
// we need to strip the prefix from the absolute path
// as otherwise we will error out since WASI is not capable
// of dealing with absolute paths
let dir_path = get_file_path(&*dirfd.as_file()?)?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
.map_err(|_| Error::Notcapable)?;
let target_path = target_path.to_str().ok_or(Error::Ilseq)?;
return Ok(target_path.to_owned());
}
Err(e) => e,
};
if let Some(code) = err.raw_os_error() {
tracing::debug!("readlinkat error={:?}", code);
if code as u32 == winerror::ERROR_INVALID_NAME {
if s_path.ends_with('/') {
// strip "/" and check if exists
let path = concatenate(dirfd, Path::new(s_path.trim_end_matches('/')))?;
if path.exists() && !path.is_dir() {
return Err(Error::Notdir);
}
}
}
}
Err(err.into())
}
pub(crate) fn create_directory(file: &OsDir, path: &str) -> Result<()> {
let path = concatenate(file, path)?;
std::fs::create_dir(&path)?;
Ok(())
}
pub(crate) fn link(
old_dirfd: &OsDir,
old_path: &str,
new_dirfd: &OsDir,
new_path: &str,
follow_symlinks: bool,
) -> Result<()> {
use std::fs;
let mut old_path = concatenate(old_dirfd, old_path)?;
let new_path = concatenate(new_dirfd, new_path)?;
if follow_symlinks {
// in particular, this will return an error if the target path doesn't exist
tracing::debug!(
old_path = tracing::field::display(old_path.display()),
"Following symlinks"
);
old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() {
// fs::canonicalize under Windows will return:
// * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink
// * ERROR_CANT_RESOLVE_FILENAME, if it encounters a symlink loop
Some(code) if code as u32 == winerror::ERROR_CANT_RESOLVE_FILENAME => Error::Loop,
_ => e.into(),
})?;
}
let err = match fs::hard_link(&old_path, &new_path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(code) = err.raw_os_error() {
tracing::debug!("path_link at fs::hard_link error code={:?}", code);
if code as u32 == winerror::ERROR_ACCESS_DENIED {
// If an attempt is made to create a hard link to a directory, POSIX-compliant
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
// to `EACCES`. We detect and correct this case here.
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
return Err(Error::Perm);
}
}
}
Err(err.into())
}
pub(crate) fn open(
dirfd: &OsDir,
path: &str,
read: bool,
write: bool,
oflags: Oflags,
fdflags: Fdflags,
) -> Result<Box<dyn Handle>> {
use winx::file::{AccessMode, CreationDisposition, Flags};
let is_trunc = oflags.contains(&Oflags::TRUNC);
if is_trunc {
// Windows does not support append mode when opening for truncation
// This is because truncation requires `GENERIC_WRITE` access, which will override the removal
// of the `FILE_WRITE_DATA` permission.
if fdflags.contains(&Fdflags::APPEND) {
return Err(Error::Notsup);
}
}
// convert open flags
// note: the calls to `write(true)` are to bypass an internal OpenOption check
// the write flag will ultimately be ignored when `access_mode` is calculated below.
let mut opts = OpenOptions::new();
match oflags.into() {
CreationDisposition::CREATE_ALWAYS => {
opts.create(true).truncate(true).write(true);
}
CreationDisposition::CREATE_NEW => {
opts.create_new(true).write(true);
}
CreationDisposition::TRUNCATE_EXISTING => {
opts.truncate(true).write(true);
}
_ => {}
}
let path = concatenate(dirfd, path)?;
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
Ok(file_type) => {
// check if we are trying to open a symlink
if file_type.is_symlink() {
return Err(Error::Loop);
}
// check if we are trying to open a file as a dir
if file_type.is_file() && oflags.contains(&Oflags::DIRECTORY) {
return Err(Error::Notdir);
}
}
Err(err) => match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_open at symlink_metadata error code={:?}", code);
match code as u32 {
winerror::ERROR_FILE_NOT_FOUND => {
// file not found, let it proceed to actually
// trying to open it
}
winerror::ERROR_INVALID_NAME => {
// TODO rethink this. For now, migrate how we handled
// it in `path::openat` on Windows.
return Err(Error::Notdir);
}
_ => return Err(err.into()),
};
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
return Err(Error::Io);
}
},
}
let mut access_mode = file_access_mode_from_fdflags(fdflags, read, write);
// Truncation requires the special `GENERIC_WRITE` bit set (this is why it doesn't work with append-only mode)
if is_trunc {
access_mode |= AccessMode::GENERIC_WRITE;
}
let flags: Flags = fdflags.into();
let file = opts
.access_mode(access_mode.bits())
.custom_flags(flags.bits())
.open(&path)?;
let handle = <Box<dyn Handle>>::try_from(file)?;
Ok(handle)
}
pub(crate) fn readlink(dirfd: &OsDir, path: &str, buf: &mut [u8]) -> Result<usize> {
use winx::file::get_file_path;
let path = concatenate(dirfd, path)?;
let target_path = path.read_link()?;
// since on Windows we are effectively emulating 'at' syscalls
// we need to strip the prefix from the absolute path
// as otherwise we will error out since WASI is not capable
// of dealing with absolute paths
let dir_path = get_file_path(&*dirfd.as_file()?)?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
.map_err(|_| Error::Notcapable)
.and_then(|path| path.to_str().map(String::from).ok_or(Error::Ilseq))?;
if buf.len() > 0 {
let mut chars = target_path.chars();
let mut nread = 0usize;
for i in 0..buf.len() {
match chars.next() {
Some(ch) => {
buf[i] = ch as u8;
nread += 1;
}
None => break,
}
}
Ok(nread)
} else {
Ok(0)
}
}
pub(crate) fn rename(
old_dirfd: &OsDir,
old_path_: &str,
new_dirfd: &OsDir,
new_path_: &str,
) -> Result<()> {
use std::fs;
let old_path = concatenate(old_dirfd, old_path_)?;
let new_path = concatenate(new_dirfd, new_path_)?;
// First sanity check: check we're not trying to rename dir to file or vice versa.
// NB on Windows, the former is actually permitted [std::fs::rename].
//
// [std::fs::rename]: https://doc.rust-lang.org/std/fs/fn.rename.html
if old_path.is_dir() && new_path.is_file() {
return Err(Error::Notdir);
}
// Second sanity check: check we're not trying to rename a file into a path
// ending in a trailing slash.
if old_path.is_file() && new_path_.ends_with('/') {
return Err(Error::Notdir);
}
// TODO handle symlinks
let err = match fs::rename(&old_path, &new_path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_rename at rename error code={:?}", code);
match code as u32 {
winerror::ERROR_ACCESS_DENIED => {
// So most likely dealing with new_path == dir.
// Eliminate case old_path == file first.
if old_path.is_file() {
return Err(Error::Isdir);
} else {
// Ok, let's try removing an empty dir at new_path if it exists
// and is a nonempty dir.
fs::remove_dir(&new_path)?;
fs::rename(old_path, new_path)?;
return Ok(());
}
}
winerror::ERROR_INVALID_NAME => {
// If source contains trailing slashes, check if we are dealing with
// a file instead of a dir, and if so, throw ENOTDIR.
if let Some(path) =
strip_trailing_slashes_and_concatenate(old_dirfd, old_path_)?
{
if path.is_file() {
return Err(Error::Notdir);
}
}
}
_ => {}
}
Err(err.into())
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
Err(Error::Io)
}
}
}
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path_: &str) -> Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file};
let old_path = concatenate(new_dirfd, Path::new(old_path))?;
let new_path = concatenate(new_dirfd, new_path_)?;
// Windows distinguishes between file and directory symlinks.
// If the source doesn't exist or is an exotic file type, we fall back
// to regular file symlinks.
let use_dir_symlink = fs::metadata(&new_path)
.as_ref()
.map(Metadata::is_dir)
.unwrap_or(false);
let res = if use_dir_symlink {
symlink_dir(&old_path, &new_path)
} else {
symlink_file(&old_path, &new_path)
};
let err = match res {
Ok(()) => return Ok(()),
Err(e) => e,
};
match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_symlink at symlink_file error code={:?}", code);
match code as u32 {
// If the target contains a trailing slash, the Windows API returns
// ERROR_INVALID_NAME (which corresponds to ENOENT) instead of
// ERROR_ALREADY_EXISTS (which corresponds to EEXIST)
//
// This concerns only trailing slashes (not backslashes) and
// only symbolic links (not hard links).
//
// Since POSIX will return EEXIST in such case, we simulate this behavior
winerror::ERROR_INVALID_NAME => {
if let Some(path) =
strip_trailing_slashes_and_concatenate(new_dirfd, new_path_)?
{
if path.exists() {
return Err(Error::Exist);
}
}
}
_ => {}
}
Err(err.into())
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
Err(Error::Io)
}
}
}
pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> {
use std::fs;
let path = concatenate(dirfd, path)?;
let file_type = path
.symlink_metadata()
.map(|metadata| metadata.file_type())?;
// check if we're unlinking a symlink
// NB this will get cleaned up a lot when [std::os::windows::fs::FileTypeExt]
// stabilises
//
// [std::os::windows::fs::FileTypeExt]: https://doc.rust-lang.org/std/os/windows/fs/trait.FileTypeExt.html
if file_type.is_symlink() {
let err = match fs::remove_file(&path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
match err.raw_os_error() {
Some(code) => {
tracing::debug!("path_unlink_file at symlink_file error code={:?}", code);
if code as u32 == winerror::ERROR_ACCESS_DENIED {
// try unlinking a dir symlink instead
return fs::remove_dir(path).map_err(Into::into);
}
Err(err.into())
}
None => {
tracing::debug!("Inconvertible OS error: {}", err);
Err(Error::Io)
}
}
} else if file_type.is_dir() {
Err(Error::Isdir)
} else if file_type.is_file() {
fs::remove_file(path).map_err(Into::into)
} else {
Err(Error::Inval)
}
}
pub(crate) fn remove_directory(dirfd: &OsDir, path: &str) -> Result<()> {
let path = concatenate(dirfd, path)?;
std::fs::remove_dir(&path).map_err(Into::into)
}
pub(crate) fn filestat_get_at(dirfd: &OsDir, path: &str, follow: bool) -> Result<Filestat> {
use winx::file::Flags;
let path = concatenate(dirfd, path)?;
let mut opts = OpenOptions::new();
if !follow {
// By specifying FILE_FLAG_OPEN_REPARSE_POINT, we force Windows to *not* dereference symlinks.
opts.custom_flags(Flags::FILE_FLAG_OPEN_REPARSE_POINT.bits());
}
let file = opts.read(true).open(path)?;
let stat = fd::filestat_get(&file)?;
Ok(stat)
}
pub(crate) fn filestat_set_times_at(
dirfd: &OsDir,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
follow: bool,
) -> Result<()> {
use winx::file::{AccessMode, Flags};
let path = concatenate(dirfd, path)?;
let mut opts = OpenOptions::new();
if !follow {
// By specifying FILE_FLAG_OPEN_REPARSE_POINT, we force Windows to *not* dereference symlinks.
opts.custom_flags(Flags::FILE_FLAG_OPEN_REPARSE_POINT.bits());
}
let file = opts
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
.open(path)?;
fd::filestat_set_times(&file, atim, mtim, fst_flags)?;
Ok(())
}

View File

@@ -1,317 +0,0 @@
use crate::handle::{Filetype, Handle};
use crate::sched::{
ClockEventData, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, FdEventData,
};
use crate::sys::osdir::OsDir;
use crate::sys::osfile::OsFile;
use crate::sys::osother::OsOther;
use crate::sys::stdio::{Stderr, Stdin, Stdout};
use crate::sys::AsFile;
use crate::{Error, Result};
use lazy_static::lazy_static;
use std::convert::{TryFrom, TryInto};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use tracing::{debug, error, trace, warn};
struct StdinPoll {
request_tx: Sender<()>,
notify_rx: Receiver<PollState>,
}
enum PollState {
Ready,
NotReady, // it's not ready, but we didn't wait
TimedOut, // it's not ready and a timeout has occurred
Error(Errno),
}
enum WaitMode {
Timeout(Duration),
Infinite,
Immediate,
}
impl StdinPoll {
// This function should not be used directly
// Correctness of this function crucially depends on the fact that
// mpsc::Receiver is !Sync.
fn poll(&self, wait_mode: WaitMode) -> PollState {
// Clean up possible unread result from the previous poll
match self.notify_rx.try_recv() {
Ok(_) | Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => panic!("notify_rx channel closed"),
}
// Notify the worker thread that we want to poll stdin
self.request_tx.send(()).expect("request_tx channel closed");
// Wait for the worker thread to send a readiness notification
let pollret = match wait_mode {
WaitMode::Timeout(timeout) => {
self.notify_rx
.recv_timeout(timeout)
.unwrap_or_else(|e| match e {
RecvTimeoutError::Disconnected => panic!("notify_rx channel closed"),
RecvTimeoutError::Timeout => PollState::TimedOut,
})
}
WaitMode::Infinite => self.notify_rx.recv().expect("notify_rx channel closed"),
WaitMode::Immediate => self.notify_rx.try_recv().unwrap_or_else(|e| match e {
TryRecvError::Disconnected => panic!("notify_rx channel closed"),
TryRecvError::Empty => PollState::NotReady,
}),
};
pollret
}
fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {
use std::io::BufRead;
loop {
// Wait for the request to poll stdin
request_rx.recv().expect("request_rx channel closed");
// Wait for data to appear in stdin.
// If `fill_buf` returns any slice, then it means that either
// (a) there some data in stdin, if it's non-empty
// (b) EOF was received, if it's empty
// Linux returns `POLLIN` in both cases, and we imitate this behavior.
let resp = match std::io::stdin().lock().fill_buf() {
Ok(_) => PollState::Ready,
Err(e) => {
PollState::Error(Errno::try_from(Error::from(e)).expect("non-trapping error"))
}
};
// Notify the requestor about data in stdin. They may have already timed out,
// then the next requestor will have to clean the channel.
notify_tx.send(resp).expect("notify_tx channel closed");
}
}
}
lazy_static! {
static ref STDIN_POLL: Mutex<StdinPoll> = {
let (request_tx, request_rx) = mpsc::channel();
let (notify_tx, notify_rx) = mpsc::channel();
thread::spawn(move || StdinPoll::event_loop(request_rx, notify_tx));
Mutex::new(StdinPoll {
request_tx,
notify_rx,
})
};
}
fn make_rw_event(event: &FdEventData, nbytes: std::result::Result<u64, Errno>) -> Event {
let (nbytes, error) = match nbytes {
Ok(nbytes) => (nbytes, Errno::Success),
Err(e) => (u64::default(), e),
};
Event {
userdata: event.userdata,
type_: event.r#type,
error,
fd_readwrite: EventFdReadwrite {
nbytes,
flags: Eventrwflags::empty(),
},
}
}
fn make_timeout_event(timeout: &ClockEventData) -> Event {
Event {
userdata: timeout.userdata,
type_: Eventtype::Clock,
error: Errno::Success,
fd_readwrite: EventFdReadwrite {
nbytes: 0,
flags: Eventrwflags::empty(),
},
}
}
fn handle_timeout(timeout_event: ClockEventData, timeout: Duration, events: &mut Vec<Event>) {
thread::sleep(timeout);
handle_timeout_event(timeout_event, events);
}
fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec<Event>) {
let new_event = make_timeout_event(&timeout_event);
events.push(new_event);
}
fn handle_rw_event(event: FdEventData, out_events: &mut Vec<Event>) {
let handle = &event.handle;
let size = if let Some(_) = handle.as_any().downcast_ref::<Stdin>() {
// We return the only universally correct lower bound, see the comment later in the function.
Ok(1)
} else if let Some(_) = handle.as_any().downcast_ref::<Stdout>() {
// On Unix, ioctl(FIONREAD) will return 0 for stdout. Emulate the same behavior on Windows.
Ok(0)
} else if let Some(_) = handle.as_any().downcast_ref::<Stderr>() {
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
Ok(0)
} else {
if event.r#type == Eventtype::FdRead {
handle
.as_file()
.and_then(|f| f.metadata())
.map(|m| m.len())
.map_err(|ioerror| {
Errno::try_from(Error::from(ioerror)).expect("non-trapping error")
})
} else {
// The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and
// the implementation on Unix just returns 0 here, so it's probably fine
// to do the same on Windows for now.
// cf. https://github.com/WebAssembly/WASI/issues/148
Ok(0)
}
};
let new_event = make_rw_event(&event, size);
out_events.push(new_event);
}
fn handle_error_event(event: FdEventData, error: Errno, out_events: &mut Vec<Event>) {
let new_event = make_rw_event(&event, Err(error));
out_events.push(new_event);
}
pub(crate) fn oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<Event>,
) -> Result<()> {
let timeout = timeout
.map(|event| {
event
.delay
.try_into()
.map(Duration::from_nanos)
.map(|dur| (event, dur))
})
.transpose()?;
// With no events to listen, poll_oneoff just becomes a sleep.
if fd_events.is_empty() {
match timeout {
Some((event, dur)) => return Ok(handle_timeout(event, dur, events)),
// The implementation has to return Ok(()) in this case,
// cf. the comment in src/hostcalls_impl/misc.rs
None => return Ok(()),
}
}
let mut stdin_events = vec![];
let mut immediate_events = vec![];
let mut pipe_events = vec![];
for event in fd_events {
let handle = &event.handle;
if let Some(_) = handle.as_any().downcast_ref::<OsFile>() {
immediate_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<OsDir>() {
immediate_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<Stdin>() {
stdin_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<Stdout>() {
// stdout are always considered ready to write because there seems to
// be no way of checking if a write to stdout would block.
//
// If stdin is polled for anything else then reading, then it is also
// considered immediately ready, following the behavior on Linux.
immediate_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<Stderr>() {
// stderr are always considered ready to write because there seems to
// be no way of checking if a write to stdout would block.
//
// If stdin is polled for anything else then reading, then it is also
// considered immediately ready, following the behavior on Linux.
immediate_events.push(event);
} else if let Some(other) = handle.as_any().downcast_ref::<OsOther>() {
if other.get_file_type() == Filetype::SocketStream {
// We map pipe to SocketStream
pipe_events.push(event);
} else {
debug!(
"poll_oneoff: unsupported file type: {}",
other.get_file_type()
);
handle_error_event(event, Errno::Notsup, events);
}
} else {
tracing::error!("can poll FdEvent for OS resources only");
return Err(Error::Badf);
}
}
let immediate = !immediate_events.is_empty();
// Process all the events that do not require waiting.
if immediate {
trace!(" | have immediate events, will return immediately");
for event in immediate_events {
handle_rw_event(event, events);
}
}
if !stdin_events.is_empty() {
// waiting for data to arrive on stdin. This thread will not terminate.
//
// We'd like to do the following:
// (1) wait in a non-blocking way for data to be available in stdin, with timeout
// (2) find out, how many bytes are there available to be read.
//
// One issue is that we are currently relying on the Rust libstd for interaction
// with stdin. More precisely, `io::stdin` is used via the `BufRead` trait,
// in the `fd_read` function, which always does buffering on the libstd side. [1]
// This means that even if there's still some unread data in stdin,
// the lower-level Windows system calls may return false negatives,
// claiming that stdin is empty.
//
// Theoretically, one could use `WaitForSingleObject` on the stdin handle
// to achieve (1). Unfortunately, this function doesn't seem to honor the
// requested timeout and to misbehaves after the stdin is closed.
//
// There appears to be no way of achieving (2) on Windows.
// [1]: https://github.com/rust-lang/rust/pull/12422
let waitmode = if immediate {
trace!(" | tentatively checking stdin");
WaitMode::Immediate
} else {
trace!(" | passively waiting on stdin");
match timeout {
Some((_event, dur)) => WaitMode::Timeout(dur),
None => WaitMode::Infinite,
}
};
let state = STDIN_POLL.lock().unwrap().poll(waitmode);
for event in stdin_events {
match state {
PollState::Ready => handle_rw_event(event, events),
PollState::NotReady => {} // not immediately available, so just ignore
PollState::TimedOut => handle_timeout_event(timeout.unwrap().0, events),
PollState::Error(e) => handle_error_event(event, e, events),
}
}
}
if !immediate && !pipe_events.is_empty() {
trace!(" | actively polling pipes");
match timeout {
Some((event, dur)) => {
// In the tests stdin is replaced with a dummy pipe, so for now
// we just time out. Support for pipes will be decided later on.
warn!("Polling pipes not supported on Windows, will just time out.");
handle_timeout(event, dur, events);
}
None => {
error!("Polling only pipes with no timeout not supported on Windows.");
return Err(Error::Notsup);
}
}
}
Ok(())
}

View File

@@ -1,59 +0,0 @@
use super::{get_file_type, get_rights};
use crate::handle::Handle;
use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt};
use std::cell::Cell;
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle};
impl AsRawHandle for Stdin {
fn as_raw_handle(&self) -> RawHandle {
io::stdin().as_raw_handle()
}
}
impl AsRawHandle for Stdout {
fn as_raw_handle(&self) -> RawHandle {
io::stdout().as_raw_handle()
}
}
impl AsRawHandle for Stderr {
fn as_raw_handle(&self) -> RawHandle {
io::stderr().as_raw_handle()
}
}
impl StdinExt for Stdin {
fn stdin() -> io::Result<Box<dyn Handle>> {
let file = unsafe { File::from_raw_handle(io::stdin().as_raw_handle()) };
let file = ManuallyDrop::new(file);
let file_type = get_file_type(&file)?;
let rights = get_rights(&file_type)?;
let rights = Cell::new(rights);
Ok(Box::new(Self { file_type, rights }))
}
}
impl StdoutExt for Stdout {
fn stdout() -> io::Result<Box<dyn Handle>> {
let file = unsafe { File::from_raw_handle(io::stdin().as_raw_handle()) };
let file = ManuallyDrop::new(file);
let file_type = get_file_type(&file)?;
let rights = get_rights(&file_type)?;
let rights = Cell::new(rights);
Ok(Box::new(Self { file_type, rights }))
}
}
impl StderrExt for Stderr {
fn stderr() -> io::Result<Box<dyn Handle>> {
let file = unsafe { File::from_raw_handle(io::stdin().as_raw_handle()) };
let file = ManuallyDrop::new(file);
let file_type = get_file_type(&file)?;
let rights = get_rights(&file_type)?;
let rights = Cell::new(rights);
Ok(Box::new(Self { file_type, rights }))
}
}

View File

@@ -1,781 +0,0 @@
use crate::handle::{
Advice, Dircookie, Dirent, Fdflags, Filesize, Filestat, Filetype, Fstflags, Handle,
HandleRights, Oflags, Rights, RightsExt, Size, DIRCOOKIE_START,
};
use crate::sched::Timestamp;
use crate::{Error, Result};
use std::any::Any;
use std::cell::{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;
use tracing::trace;
pub mod pipe;
/// An entry in a virtual filesystem
pub enum VirtualDirEntry {
/// The contents of a child directory
Directory(HashMap<String, VirtualDirEntry>),
/// A file
File(Box<dyn FileContents>),
}
impl VirtualDirEntry {
/// Construct an empty directory
pub fn empty_directory() -> Self {
Self::Directory(HashMap::new())
}
}
/// Files and directories may be moved, and for implementation reasons retain a reference to their
/// parent Handle, 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 Handle>>);
}
pub trait FileContents {
/// The implementation-defined maximum size of the store corresponding to a `FileContents`
/// implementation.
fn max_size(&self) -> Filesize;
/// The current number of bytes this `FileContents` describes.
fn size(&self) -> Filesize;
/// Resize to hold `new_size` number of bytes, or error if this is not possible.
fn resize(&mut self, new_size: Filesize) -> 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: Filesize) -> 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: Filesize) -> 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: Filesize) -> 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: Filesize) -> Result<usize>;
}
impl FileContents for VecFileContents {
fn max_size(&self) -> Filesize {
std::usize::MAX as Filesize
}
fn size(&self) -> Filesize {
self.content.len() as Filesize
}
fn resize(&mut self, new_size: Filesize) -> Result<()> {
let new_size: usize = new_size.try_into().map_err(|_| Error::Inval)?;
self.content.resize(new_size, 0);
Ok(())
}
fn preadv(&self, iovs: &mut [io::IoSliceMut], offset: Filesize) -> Result<usize> {
let mut read_total = 0usize;
for iov in iovs.iter_mut() {
let skip: u64 = read_total.try_into().map_err(|_| Error::Inval)?;
let read = self.pread(iov, offset + skip)?;
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: Filesize) -> Result<usize> {
let mut write_total = 0usize;
for iov in iovs.iter() {
let skip: u64 = write_total.try_into().map_err(|_| Error::Inval)?;
let written = self.pwrite(iov, offset + skip)?;
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: Filesize) -> Result<usize> {
trace!(buffer_length = buf.len(), offset = offset, "pread");
let offset: usize = offset.try_into().map_err(|_| Error::Inval)?;
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]);
Ok(read_count)
}
fn pwrite(&mut self, buf: &[u8], offset: Filesize) -> Result<usize> {
let offset: usize = offset.try_into().map_err(|_| Error::Inval)?;
let write_end = offset.checked_add(buf.len()).ok_or(Error::Fbig)?;
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())
}
}
pub struct VecFileContents {
content: Vec<u8>,
}
impl VecFileContents {
pub fn new() -> Self {
Self {
content: Vec::new(),
}
}
pub fn with_content(content: Vec<u8>) -> Self {
Self { content }
}
}
/// 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 {
rights: Cell<HandleRights>,
cursor: Cell<Filesize>,
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
fd_flags: Cell<Fdflags>,
data: Rc<RefCell<Box<dyn FileContents>>>,
}
impl InMemoryFile {
pub fn memory_backed() -> Self {
Self::new(Box::new(VecFileContents::new()))
}
pub fn new(contents: Box<dyn FileContents>) -> Self {
let rights = HandleRights::new(
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
);
let rights = Cell::new(rights);
Self {
rights,
cursor: Cell::new(0),
fd_flags: Cell::new(Fdflags::empty()),
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 Handle>>) {
*self.parent.borrow_mut() = new_parent;
}
}
impl Handle for InMemoryFile {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(Self {
rights: self.rights.clone(),
cursor: Cell::new(0),
fd_flags: self.fd_flags.clone(),
parent: Rc::clone(&self.parent),
data: Rc::clone(&self.data),
}))
}
fn get_file_type(&self) -> Filetype {
Filetype::RegularFile
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> {
// we'll just ignore advice for now, unless it's totally invalid
Ok(())
}
fn allocate(&self, offset: Filesize, len: Filesize) -> Result<()> {
let new_limit = offset.checked_add(len).ok_or(Error::Fbig)?;
let mut data = self.data.borrow_mut();
if new_limit > data.max_size() {
return Err(Error::Fbig);
}
if new_limit > data.size() {
data.resize(new_limit)?;
}
Ok(())
}
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(self.fd_flags.get())
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
self.fd_flags.set(fdflags);
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
let stat = Filestat {
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 filestat_set_size(&self, st_size: Filesize) -> Result<()> {
let mut data = self.data.borrow_mut();
if st_size > data.max_size() {
return Err(Error::Fbig);
}
data.resize(st_size)
}
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: Filesize) -> Result<usize> {
self.data.borrow_mut().preadv(buf, offset)
}
fn pwritev(&self, buf: &[io::IoSlice], offset: Filesize) -> Result<usize> {
self.data.borrow_mut().pwritev(buf, offset)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
trace!("read_vectored(iovs={:?})", iovs);
trace!(" | *read_start={:?}", self.cursor.get());
let read = self.data.borrow_mut().preadv(iovs, self.cursor.get())?;
let offset: u64 = read.try_into().map_err(|_| Error::Inval)?;
let update = self.cursor.get().checked_add(offset).ok_or(Error::Inval)?;
self.cursor.set(update);
Ok(read)
}
fn seek(&self, offset: SeekFrom) -> Result<Filesize> {
let content_len = self.data.borrow().size();
match offset {
SeekFrom::Current(offset) => {
let new_cursor = if offset < 0 {
self.cursor
.get()
.checked_sub(offset.wrapping_neg() as u64)
.ok_or(Error::Inval)?
} else {
self.cursor
.get()
.checked_add(offset as u64)
.ok_or(Error::Inval)?
};
self.cursor.set(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::Inval)?;
self.cursor.set(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::Inval)?;
self.cursor.set(std::cmp::min(content_len, offset));
}
}
Ok(self.cursor.get())
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
trace!("write_vectored(iovs={:?})", iovs);
let mut data = self.data.borrow_mut();
let append_mode = self.fd_flags.get().contains(Fdflags::APPEND);
trace!(" | fd_flags={}", self.fd_flags.get());
// If this file is in append mode, we write to the end.
let write_start = if append_mode {
data.size()
} else {
self.cursor.get()
};
let max_size = iovs
.iter()
.map(|iov| {
let cast_iovlen: Size = 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 Filesize) {
if end > data.max_size() {
return Err(Error::Fbig);
}
} else {
return Err(Error::Fbig);
}
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 {
let update = self.cursor.get() + written as u64;
self.cursor.set(update);
}
Ok(written)
}
// PathOps
fn create_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn openat(
&self,
path: &str,
_read: bool,
_write: bool,
oflags: Oflags,
_fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
if oflags.contains(Oflags::DIRECTORY) {
tracing::trace!(
"InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.",
path
);
return Err(Error::Notdir);
}
if path == "." {
return self.try_clone().map_err(Into::into);
} else if path == ".." {
match &*self.parent.borrow() {
Some(file) => file.try_clone().map_err(Into::into),
None => self.try_clone().map_err(Into::into),
}
} else {
Err(Error::Acces)
}
}
fn link(
&self,
_old_path: &str,
_new_handle: Box<dyn Handle>,
_new_path: &str,
_follow: bool,
) -> Result<()> {
Err(Error::Notdir)
}
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
Err(Error::Notdir)
}
fn readlinkat(&self, _path: &str) -> Result<String> {
Err(Error::Notdir)
}
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
}
/// A clonable read/write directory.
pub struct VirtualDir {
rights: Cell<HandleRights>,
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 Handle>>>>,
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
}
impl VirtualDir {
pub fn new(writable: bool) -> Self {
let rights = HandleRights::new(Rights::directory_base(), Rights::directory_inheriting());
let rights = Cell::new(rights);
Self {
rights,
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: Self, path: P) -> Self {
self.add_dir(dir, path);
self
}
#[allow(dead_code)]
pub fn add_dir<P: AsRef<Path>>(&mut self, dir: Self, 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 Handle>>) {
*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 Handle for VirtualDir {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(Self {
rights: self.rights.clone(),
writable: self.writable,
parent: Rc::clone(&self.parent),
entries: Rc::clone(&self.entries),
}))
}
fn get_file_type(&self) -> Filetype {
Filetype::Directory
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn filestat_get(&self) -> Result<Filestat> {
let stat = Filestat {
dev: 0,
ino: 0,
nlink: 0,
size: 0,
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn readdir(
&self,
cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>>>> {
struct VirtualDirIter {
start: u32,
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
}
impl Iterator for VirtualDirIter {
type Item = Result<(Dirent, String)>;
fn next(&mut self) -> Option<Self::Item> {
tracing::trace!("VirtualDirIter::next continuing from {}", self.start);
if self.start == SELF_DIR_COOKIE {
self.start += 1;
let name = ".".to_owned();
let dirent = Dirent {
d_next: self.start as u64,
d_ino: 0,
d_namlen: name.len() as _,
d_type: Filetype::Directory,
};
return Some(Ok((dirent, name)));
}
if self.start == PARENT_DIR_COOKIE {
self.start += 1;
let name = "..".to_owned();
let dirent = Dirent {
d_next: self.start as u64,
d_ino: 0,
d_namlen: name.len() as _,
d_type: Filetype::Directory,
};
return Some(Ok((dirent, name)));
}
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 name = path
.to_str()
.expect("wasi paths are valid utf8 strings")
.to_owned();
let dirent = || -> Result<Dirent> {
let dirent = Dirent {
d_namlen: name.len().try_into()?,
d_type: file.get_file_type(),
d_ino: 0,
d_next: self.start as u64,
};
Ok(dirent)
};
Some(dirent().map(|dirent| (dirent, name)))
}
}
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),
}))
}
// PathOps
fn create_directory(&self, path: &str) -> Result<()> {
let mut entries = self.entries.borrow_mut();
match entries.entry(PathBuf::from(path)) {
Entry::Occupied(_) => Err(Error::Exist),
Entry::Vacant(v) => {
if self.writable {
let new_dir = Box::new(Self::new(true));
new_dir.set_parent(Some(self.try_clone()?));
v.insert(new_dir);
Ok(())
} else {
Err(Error::Acces)
}
}
}
}
fn filestat_get_at(&self, path: &str, _follow: bool) -> Result<Filestat> {
let stat = self
.openat(path, false, false, Oflags::empty(), Fdflags::empty())?
.filestat_get()?;
Ok(stat)
}
fn filestat_set_times_at(
&self,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
_follow: bool,
) -> Result<()> {
self.openat(path, false, false, Oflags::empty(), Fdflags::empty())?
.filestat_set_times(atim, mtim, fst_flags)?;
Ok(())
}
fn openat(
&self,
path: &str,
_read: bool,
_write: bool,
oflags: Oflags,
fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
if path == "." {
return self.try_clone().map_err(Into::into);
} else if path == ".." {
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::new(path).file_name().ok_or(Error::Inval)?;
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 = Oflags::CREAT | Oflags::EXCL;
if (oflags & creat_excl_mask) == creat_excl_mask {
tracing::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name);
return Err(Error::Exist);
}
if oflags.contains(Oflags::DIRECTORY)
&& e.get().get_file_type() != Filetype::Directory
{
tracing::trace!(
"VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.",
file_name
);
return Err(Error::Notdir);
}
e.get().try_clone().map_err(Into::into)
}
Entry::Vacant(v) => {
if oflags.contains(Oflags::CREAT) {
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::Nospc);
}
tracing::trace!(
"VirtualDir::openat creating an InMemoryFile named {}",
path
);
let file = Box::new(InMemoryFile::memory_backed());
file.fd_flags.set(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::Acces)
}
} else {
Err(Error::Noent)
}
}
}
}
fn readlinkat(&self, _path: &str) -> Result<String> {
// Files are not symbolic links or directories, faithfully report Notdir.
Err(Error::Notdir)
}
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() != Filetype::Directory {
return Err(Error::Notdir);
}
// Okay, but is the directory empty?
let iter = e.get().readdir(DIRCOOKIE_START)?;
if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() {
return Err(Error::Notempty);
}
// Alright, it's an empty directory. We can remove it.
let removed = e.remove_entry();
// TODO refactor
// And sever the file's parent ref to avoid Rc cycles.
if let Some(dir) = removed.1.as_any().downcast_ref::<Self>() {
dir.set_parent(None);
} else if let Some(file) = removed.1.as_any().downcast_ref::<InMemoryFile>() {
file.set_parent(None);
} else {
panic!("neither VirtualDir nor InMemoryFile");
}
Ok(())
}
Entry::Vacant(_) => {
tracing::trace!(
"VirtualDir::remove_directory failed to remove {}, no such entry",
trimmed_path
);
Err(Error::Noent)
}
}
}
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 Isdir, 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::Isdir);
}
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() == Filetype::Directory {
return Err(Error::Isdir);
}
let removed = e.remove_entry();
// TODO refactor
// Sever the file's parent ref to avoid Rc cycles.
if let Some(dir) = removed.1.as_any().downcast_ref::<Self>() {
dir.set_parent(None);
} else if let Some(file) = removed.1.as_any().downcast_ref::<InMemoryFile>() {
file.set_parent(None);
} else {
panic!("neither VirtualDir nor InMemoryFile");
}
Ok(())
}
Entry::Vacant(_) => {
tracing::trace!(
"VirtualDir::unlink_file failed to remove {}, no such entry",
trimmed_path
);
Err(Error::Noent)
}
}
}
}

View File

@@ -1,403 +0,0 @@
//! Virtual pipes.
//!
//! These types provide easy implementations of `Handle` that mimic much of the behavior of Unix
//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other
//! than OS files.
//!
//! Some convenience constructors are included for common backing types like `Vec<u8>` and `String`,
//! but the virtual pipes can be instantiated with any `Read` or `Write` type.
//!
//! Note that `poll_oneoff` is not supported for these types, so they do not match the behavior of
//! real pipes exactly.
use crate::handle::{
Advice, Fdflags, Filesize, Filestat, Filetype, Handle, HandleRights, Oflags, Rights,
};
use crate::{Error, Result};
use std::any::Any;
use std::io::{self, Read, Write};
use std::sync::{Arc, RwLock};
/// A virtual pipe read end.
///
/// A variety of `From` impls are provided so that common pipe types are easy to create. For example:
///
/// ```
/// # use wasi_common::WasiCtxBuilder;
/// # use wasi_common::virtfs::pipe::ReadPipe;
/// let mut ctx = WasiCtxBuilder::new();
/// let stdin = ReadPipe::from("hello from stdin!");
/// ctx.stdin(stdin);
/// ```
#[derive(Debug)]
pub struct ReadPipe<R: Read + Any> {
rights: RwLock<HandleRights>,
reader: Arc<RwLock<R>>,
}
impl<R: Read + Any> Clone for ReadPipe<R> {
fn clone(&self) -> Self {
Self {
rights: RwLock::new(*self.rights.read().unwrap()),
reader: self.reader.clone(),
}
}
}
impl<R: Read + Any> ReadPipe<R> {
/// Create a new pipe from a `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn new(r: R) -> Self {
Self::from_shared(Arc::new(RwLock::new(r)))
}
/// Create a new pipe from a shareable `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn from_shared(reader: Arc<RwLock<R>>) -> Self {
Self {
rights: RwLock::new(HandleRights::from_base(
Rights::FD_DATASYNC
| Rights::FD_FDSTAT_SET_FLAGS
| Rights::FD_READ
| Rights::FD_SYNC
| Rights::FD_FILESTAT_GET
| Rights::POLL_FD_READWRITE,
)),
reader,
}
}
/// Try to convert this `ReadPipe<R>` back to the underlying `R` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `R` exist.
pub fn try_into_inner(mut self) -> std::result::Result<R, Self> {
match Arc::try_unwrap(self.reader) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(reader) => {
self.reader = reader;
Err(self)
}
}
}
}
impl From<Vec<u8>> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: Vec<u8>) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&[u8]> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: &[u8]) -> Self {
Self::from(r.to_vec())
}
}
impl From<String> for ReadPipe<io::Cursor<String>> {
fn from(r: String) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&str> for ReadPipe<io::Cursor<String>> {
fn from(r: &str) -> Self {
Self::from(r.to_string())
}
}
impl<R: Read + Any> Handle for ReadPipe<R> {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
Filetype::Unknown
}
fn get_rights(&self) -> HandleRights {
*self.rights.read().unwrap()
}
fn set_rights(&self, rights: HandleRights) {
*self.rights.write().unwrap() = rights;
}
fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Spipe)
}
fn allocate(&self, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Spipe)
}
fn fdstat_set_flags(&self, _fdflags: Fdflags) -> Result<()> {
// do nothing for now
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
let stat = Filestat {
dev: 0,
ino: 0,
nlink: 0,
size: 0,
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn filestat_set_size(&self, _st_size: Filesize) -> Result<()> {
Err(Error::Spipe)
}
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: Filesize) -> Result<usize> {
if offset != 0 {
return Err(Error::Spipe);
}
Ok(self.reader.write().unwrap().read_vectored(buf)?)
}
fn seek(&self, _offset: io::SeekFrom) -> Result<Filesize> {
Err(Error::Spipe)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Ok(self.reader.write().unwrap().read_vectored(iovs)?)
}
fn create_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn openat(
&self,
_path: &str,
_read: bool,
_write: bool,
_oflags: Oflags,
_fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
Err(Error::Notdir)
}
fn link(
&self,
_old_path: &str,
_new_handle: Box<dyn Handle>,
_new_path: &str,
_follow: bool,
) -> Result<()> {
Err(Error::Notdir)
}
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
Err(Error::Notdir)
}
fn readlinkat(&self, _path: &str) -> Result<String> {
Err(Error::Notdir)
}
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
}
/// A virtual pipe write end.
#[derive(Debug)]
pub struct WritePipe<W: Write + Any> {
rights: RwLock<HandleRights>,
writer: Arc<RwLock<W>>,
}
impl<W: Write + Any> Clone for WritePipe<W> {
fn clone(&self) -> Self {
Self {
rights: RwLock::new(*self.rights.read().unwrap()),
writer: self.writer.clone(),
}
}
}
impl<W: Write + Any> WritePipe<W> {
/// Create a new pipe from a `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn new(w: W) -> Self {
Self::from_shared(Arc::new(RwLock::new(w)))
}
/// Create a new pipe from a shareable `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn from_shared(writer: Arc<RwLock<W>>) -> Self {
Self {
rights: RwLock::new(HandleRights::from_base(
Rights::FD_DATASYNC
| Rights::FD_FDSTAT_SET_FLAGS
| Rights::FD_SYNC
| Rights::FD_WRITE
| Rights::FD_FILESTAT_GET
| Rights::POLL_FD_READWRITE,
)),
writer,
}
}
/// Try to convert this `WritePipe<W>` back to the underlying `W` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `W` exist.
pub fn try_into_inner(mut self) -> std::result::Result<W, Self> {
match Arc::try_unwrap(self.writer) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(writer) => {
self.writer = writer;
Err(self)
}
}
}
}
impl WritePipe<io::Cursor<Vec<u8>>> {
/// Create a new writable virtual pipe backed by a `Vec<u8>` buffer.
pub fn new_in_memory() -> Self {
Self::new(io::Cursor::new(vec![]))
}
}
impl<W: Write + Any> Handle for WritePipe<W> {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
Filetype::Unknown
}
fn get_rights(&self) -> HandleRights {
*self.rights.read().unwrap()
}
fn set_rights(&self, rights: HandleRights) {
*self.rights.write().unwrap() = rights;
}
fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Spipe)
}
fn allocate(&self, _offset: Filesize, _len: Filesize) -> Result<()> {
Err(Error::Spipe)
}
fn fdstat_set_flags(&self, _fdflags: Fdflags) -> Result<()> {
// do nothing for now
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
let stat = Filestat {
dev: 0,
ino: 0,
nlink: 0,
size: 0,
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn filestat_set_size(&self, _st_size: Filesize) -> Result<()> {
Err(Error::Spipe)
}
fn pwritev(&self, buf: &[io::IoSlice], offset: Filesize) -> Result<usize> {
if offset != 0 {
return Err(Error::Spipe);
}
Ok(self.writer.write().unwrap().write_vectored(buf)?)
}
fn seek(&self, _offset: io::SeekFrom) -> Result<Filesize> {
Err(Error::Spipe)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
Ok(self.writer.write().unwrap().write_vectored(iovs)?)
}
fn create_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn openat(
&self,
_path: &str,
_read: bool,
_write: bool,
_oflags: Oflags,
_fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
Err(Error::Notdir)
}
fn link(
&self,
_old_path: &str,
_new_handle: Box<dyn Handle>,
_new_path: &str,
_follow: bool,
) -> Result<()> {
Err(Error::Notdir)
}
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
Err(Error::Notdir)
}
fn readlinkat(&self, _path: &str) -> Result<String> {
Err(Error::Notdir)
}
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
}

View File

@@ -1,103 +0,0 @@
use crate::{Error, WasiCtx};
use std::convert::{TryFrom, TryInto};
use tracing::debug;
wiggle::from_witx!({
witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
ctx: WasiCtx,
errors: { errno => Error },
});
use types::Errno;
impl wiggle::GuestErrorType for Errno {
fn success() -> Self {
Self::Success
}
}
impl types::GuestErrorConversion for WasiCtx {
fn into_errno(&self, e: wiggle::GuestError) -> Errno {
debug!("Guest error: {:?}", e);
e.into()
}
}
impl types::UserErrorConversion for WasiCtx {
fn errno_from_error(&self, e: Error) -> Result<Errno, wiggle::Trap> {
debug!("Error: {:?}", e);
e.try_into()
}
}
impl TryFrom<Error> for Errno {
type Error = wiggle::Trap;
fn try_from(e: Error) -> Result<Errno, wiggle::Trap> {
match e {
Error::Guest(e) => Ok(e.into()),
Error::TryFromInt(_) => Ok(Errno::Overflow),
Error::Utf8(_) => Ok(Errno::Ilseq),
Error::UnexpectedIo(_) => Ok(Errno::Io),
Error::GetRandom(_) => Ok(Errno::Io),
Error::TooBig => Ok(Errno::TooBig),
Error::Acces => Ok(Errno::Acces),
Error::Badf => Ok(Errno::Badf),
Error::Busy => Ok(Errno::Busy),
Error::Exist => Ok(Errno::Exist),
Error::Fault => Ok(Errno::Fault),
Error::Fbig => Ok(Errno::Fbig),
Error::Ilseq => Ok(Errno::Ilseq),
Error::Inval => Ok(Errno::Inval),
Error::Io => Ok(Errno::Io),
Error::Isdir => Ok(Errno::Isdir),
Error::Loop => Ok(Errno::Loop),
Error::Mfile => Ok(Errno::Mfile),
Error::Mlink => Ok(Errno::Mlink),
Error::Nametoolong => Ok(Errno::Nametoolong),
Error::Nfile => Ok(Errno::Nfile),
Error::Noent => Ok(Errno::Noent),
Error::Nomem => Ok(Errno::Nomem),
Error::Nospc => Ok(Errno::Nospc),
Error::Notdir => Ok(Errno::Notdir),
Error::Notempty => Ok(Errno::Notempty),
Error::Notsup => Ok(Errno::Notsup),
Error::Overflow => Ok(Errno::Overflow),
Error::Pipe => Ok(Errno::Pipe),
Error::Perm => Ok(Errno::Perm),
Error::Spipe => Ok(Errno::Spipe),
Error::Notcapable => Ok(Errno::Notcapable),
Error::Unsupported(feature) => {
Err(wiggle::Trap::String(format!("unsupported: {}", feature)))
}
}
}
}
impl From<wiggle::GuestError> for Errno {
fn from(err: wiggle::GuestError) -> Self {
use wiggle::GuestError::*;
match err {
InvalidFlagValue { .. } => Self::Inval,
InvalidEnumValue { .. } => Self::Inval,
PtrOverflow { .. } => Self::Fault,
PtrOutOfBounds { .. } => Self::Fault,
PtrNotAligned { .. } => Self::Inval,
PtrBorrowed { .. } => Self::Fault,
InvalidUtf8 { .. } => Self::Ilseq,
TryFromIntError { .. } => Self::Overflow,
InFunc { err, .. } => Errno::from(*err),
InDataField { err, .. } => Errno::from(*err),
SliceLengthsDiffer { .. } => Self::Fault,
BorrowCheckerOutOfHandles { .. } => Self::Fault,
}
}
}
impl crate::fdpool::Fd for types::Fd {
fn as_raw(&self) -> u32 {
(*self).into()
}
fn from_raw(raw_fd: u32) -> Self {
Self::from(raw_fd)
}
}