Merge wasi-common into wasmtime

This commit merges [CraneStation/wasi-common] repo as a subdir of
this repo while preserving **all** of git history. There is an
initiative to pull `wasi-common` into [CraneStation/wasmtime], and
[CraneStation/wasmtime] becoming a monorepo. This came about for
several reasons with a common theme of convenience, namely,
having a monorepo:
1. cleans up the problem of dependencies (as we have seen first
   hand with dependabot enabled, it can cause some grief)
2. completely removes the problem of syncing the closely dependent
   repos (e.g., updating `wasi-common` with say a bugfix generally
   implies creating a "sync" commit for pulling in the changes into
   the "parent" repo, in this case, `wasmtime`)
3. mainly for the two reasons above, makes publishing to crates.io
   easier
4. hopefully streamlines the process of getting the community
   involved in contributing to `wasi-common` as now everything
   is one place

[CraneStation/wasi-common]: https://github.com/CraneStation/wasi-common
[CraneStation/wasmtime]: https://github.com/CraneStation/wasmtime
This commit is contained in:
Jakub Konka
2019-11-07 15:16:34 +01:00
120 changed files with 14853 additions and 5 deletions

335
wasi-common/src/ctx.rs Normal file
View File

@@ -0,0 +1,335 @@
use crate::fdentry::FdEntry;
use crate::{wasi, Error, Result};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::env;
use std::ffi::{CString, OsString};
use std::fs::File;
use std::path::{Path, PathBuf};
enum PendingFdEntry {
Thunk(fn() -> Result<FdEntry>),
File(File),
}
impl std::fmt::Debug for PendingFdEntry {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PendingFdEntry::Thunk(f) => write!(
fmt,
"PendingFdEntry::Thunk({:p})",
f as *const fn() -> Result<FdEntry>
),
PendingFdEntry::File(f) => write!(fmt, "PendingFdEntry::File({:?})", f),
}
}
}
#[derive(Debug, Eq, Hash, PartialEq)]
enum PendingCString {
Bytes(Vec<u8>),
OsString(OsString),
}
impl From<Vec<u8>> for PendingCString {
fn from(bytes: Vec<u8>) -> Self {
Self::Bytes(bytes)
}
}
impl From<OsString> for PendingCString {
fn from(s: OsString) -> Self {
Self::OsString(s)
}
}
impl PendingCString {
fn into_string(self) -> Result<String> {
match self {
PendingCString::Bytes(v) => String::from_utf8(v).map_err(|_| Error::EILSEQ),
PendingCString::OsString(s) => s.into_string().map_err(|_| Error::EILSEQ),
}
}
/// Create a `CString` containing valid UTF-8, or fail with `Error::EILSEQ`.
fn into_utf8_cstring(self) -> Result<CString> {
self.into_string()
.and_then(|s| CString::new(s).map_err(|_| Error::EILSEQ))
}
}
/// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder {
fds: HashMap<wasi::__wasi_fd_t, PendingFdEntry>,
preopens: Vec<(PathBuf, File)>,
args: Vec<PendingCString>,
env: HashMap<PendingCString, PendingCString>,
}
impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`.
pub fn new() -> Self {
let mut builder = Self {
fds: HashMap::new(),
preopens: Vec::new(),
args: vec![],
env: HashMap::new(),
};
builder.fds.insert(0, PendingFdEntry::Thunk(FdEntry::null));
builder.fds.insert(1, PendingFdEntry::Thunk(FdEntry::null));
builder.fds.insert(2, PendingFdEntry::Thunk(FdEntry::null));
builder
}
/// Add arguments to the command-line arguments list.
///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
/// with `Error::EILSEQ`.
pub fn args<S: AsRef<[u8]>>(mut self, args: impl IntoIterator<Item = S>) -> Self {
self.args = args
.into_iter()
.map(|arg| arg.as_ref().to_vec().into())
.collect();
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
/// with `Error::EILSEQ`.
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self {
self.args.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 with `Error::EILSEQ`.
pub fn inherit_args(mut self) -> Self {
self.args = env::args_os().map(PendingCString::OsString).collect();
self
}
/// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(mut self) -> Self {
self.fds
.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
self.fds
.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
self.fds
.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_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 with
/// `Error::EILSEQ`.
pub fn inherit_env(mut self) -> Self {
self.env = std::env::vars_os()
.map(|(k, v)| (k.into(), v.into()))
.collect();
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 with `Error::EILSEQ`.
pub fn env<S: AsRef<[u8]>>(mut self, k: S, v: S) -> Self {
self.env
.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 with `Error::EILSEQ`.
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
mut self,
envs: impl IntoIterator<Item = T>,
) -> Self {
self.env = envs
.into_iter()
.map(|t| {
let (k, v) = t.borrow();
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
})
.collect();
self
}
/// Provide a File to use as stdin
pub fn stdin(mut self, file: File) -> Self {
self.fds.insert(0, PendingFdEntry::File(file));
self
}
/// Provide a File to use as stdout
pub fn stdout(mut self, file: File) -> Self {
self.fds.insert(1, PendingFdEntry::File(file));
self
}
/// Provide a File to use as stderr
pub fn stderr(mut self, file: File) -> Self {
self.fds.insert(2, PendingFdEntry::File(file));
self
}
/// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self {
self.preopens.push((guest_path.as_ref().to_owned(), dir));
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 returns `Error::EILSEQ`.
pub fn build(self) -> Result<WasiCtx> {
// Process arguments and environment variables into `CString`s, failing quickly if they
// contain any NUL bytes, or if conversion from `OsString` fails.
let args = self
.args
.into_iter()
.map(|arg| arg.into_utf8_cstring())
.collect::<Result<Vec<CString>>>()?;
let env = self
.env
.into_iter()
.map(|(k, v)| {
k.into_string().and_then(|mut pair| {
v.into_string().and_then(|v| {
pair.push('=');
pair.push_str(v.as_str());
// We have valid UTF-8, but the keys and values have not yet been checked
// for NULs, so we do a final check here.
CString::new(pair).map_err(|_| Error::EILSEQ)
})
})
})
.collect::<Result<Vec<CString>>>()?;
let mut fds: HashMap<wasi::__wasi_fd_t, FdEntry> = HashMap::new();
// Populate the non-preopen fds.
for (fd, pending) in self.fds {
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending {
PendingFdEntry::Thunk(f) => {
fds.insert(fd, f()?);
}
PendingFdEntry::File(f) => {
fds.insert(fd, FdEntry::from(f)?);
}
}
}
// Then add the preopen fds. Startup code in the guest starts looking at fd 3 for preopens,
// so we start from there. This variable is initially 2, though, because the loop
// immediately does the increment and check for overflow.
let mut preopen_fd: wasi::__wasi_fd_t = 2;
for (guest_path, dir) in self.preopens {
// We do the increment at the beginning of the loop body, so that we don't overflow
// unnecessarily if we have exactly the maximum number of file descriptors.
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
if !dir.metadata()?.is_dir() {
return Err(Error::EBADF);
}
// We don't currently allow setting file descriptors other than 0-2, but this will avoid
// collisions if we restore that functionality in the future.
while fds.contains_key(&preopen_fd) {
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
}
let mut fe = FdEntry::from(dir)?;
fe.preopen_path = Some(guest_path);
log::debug!("WasiCtx inserting ({:?}, {:?})", preopen_fd, fe);
fds.insert(preopen_fd, fe);
log::debug!("WasiCtx fds = {:?}", fds);
}
Ok(WasiCtx { args, env, fds })
}
}
#[derive(Debug)]
pub struct WasiCtx {
fds: HashMap<wasi::__wasi_fd_t, FdEntry>,
pub(crate) args: Vec<CString>,
pub(crate) env: Vec<CString>,
}
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>) -> Result<Self> {
WasiCtxBuilder::new()
.args(args)
.inherit_stdio()
.inherit_env()
.build()
}
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
pub(crate) unsafe fn contains_fd_entry(&self, fd: wasi::__wasi_fd_t) -> bool {
self.fds.contains_key(&fd)
}
/// Get an immutable `FdEntry` corresponding to the specified raw WASI `fd`.
pub(crate) unsafe fn get_fd_entry(&self, fd: wasi::__wasi_fd_t) -> Result<&FdEntry> {
self.fds.get(&fd).ok_or(Error::EBADF)
}
/// Get a mutable `FdEntry` corresponding to the specified raw WASI `fd`.
pub(crate) unsafe fn get_fd_entry_mut(
&mut self,
fd: wasi::__wasi_fd_t,
) -> Result<&mut FdEntry> {
self.fds.get_mut(&fd).ok_or(Error::EBADF)
}
/// Insert the specified `FdEntry` into the `WasiCtx` object.
///
/// The `FdEntry` 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_fd_entry(&mut self, fe: FdEntry) -> Result<wasi::__wasi_fd_t> {
// Never insert where stdio handles are expected to be.
let mut fd = 3;
while self.fds.contains_key(&fd) {
if let Some(next_fd) = fd.checked_add(1) {
fd = next_fd;
} else {
return Err(Error::EMFILE);
}
}
self.fds.insert(fd, fe);
Ok(fd)
}
/// Insert the specified `FdEntry` with the specified raw WASI `fd` key into the `WasiCtx`
/// object.
pub(crate) fn insert_fd_entry_at(
&mut self,
fd: wasi::__wasi_fd_t,
fe: FdEntry,
) -> Option<FdEntry> {
self.fds.insert(fd, fe)
}
/// Remove `FdEntry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object.
pub(crate) fn remove_fd_entry(&mut self, fd: wasi::__wasi_fd_t) -> Result<FdEntry> {
self.fds.remove(&fd).ok_or(Error::EBADF)
}
}

280
wasi-common/src/error.rs Normal file
View File

@@ -0,0 +1,280 @@
// Due to https://github.com/rust-lang/rust/issues/64247
#![allow(clippy::use_self)]
use crate::wasi;
use failure::Fail;
use std::convert::Infallible;
use std::fmt;
use std::num::TryFromIntError;
use std::str;
#[derive(Clone, Copy, Debug, Fail, Eq, PartialEq)]
#[repr(u16)]
pub enum WasiError {
ESUCCESS = wasi::__WASI_ESUCCESS,
E2BIG = wasi::__WASI_E2BIG,
EACCES = wasi::__WASI_EACCES,
EADDRINUSE = wasi::__WASI_EADDRINUSE,
EADDRNOTAVAIL = wasi::__WASI_EADDRNOTAVAIL,
EAFNOSUPPORT = wasi::__WASI_EAFNOSUPPORT,
EAGAIN = wasi::__WASI_EAGAIN,
EALREADY = wasi::__WASI_EALREADY,
EBADF = wasi::__WASI_EBADF,
EBADMSG = wasi::__WASI_EBADMSG,
EBUSY = wasi::__WASI_EBUSY,
ECANCELED = wasi::__WASI_ECANCELED,
ECHILD = wasi::__WASI_ECHILD,
ECONNABORTED = wasi::__WASI_ECONNABORTED,
ECONNREFUSED = wasi::__WASI_ECONNREFUSED,
ECONNRESET = wasi::__WASI_ECONNRESET,
EDEADLK = wasi::__WASI_EDEADLK,
EDESTADDRREQ = wasi::__WASI_EDESTADDRREQ,
EDOM = wasi::__WASI_EDOM,
EDQUOT = wasi::__WASI_EDQUOT,
EEXIST = wasi::__WASI_EEXIST,
EFAULT = wasi::__WASI_EFAULT,
EFBIG = wasi::__WASI_EFBIG,
EHOSTUNREACH = wasi::__WASI_EHOSTUNREACH,
EIDRM = wasi::__WASI_EIDRM,
EILSEQ = wasi::__WASI_EILSEQ,
EINPROGRESS = wasi::__WASI_EINPROGRESS,
EINTR = wasi::__WASI_EINTR,
EINVAL = wasi::__WASI_EINVAL,
EIO = wasi::__WASI_EIO,
EISCONN = wasi::__WASI_EISCONN,
EISDIR = wasi::__WASI_EISDIR,
ELOOP = wasi::__WASI_ELOOP,
EMFILE = wasi::__WASI_EMFILE,
EMLINK = wasi::__WASI_EMLINK,
EMSGSIZE = wasi::__WASI_EMSGSIZE,
EMULTIHOP = wasi::__WASI_EMULTIHOP,
ENAMETOOLONG = wasi::__WASI_ENAMETOOLONG,
ENETDOWN = wasi::__WASI_ENETDOWN,
ENETRESET = wasi::__WASI_ENETRESET,
ENETUNREACH = wasi::__WASI_ENETUNREACH,
ENFILE = wasi::__WASI_ENFILE,
ENOBUFS = wasi::__WASI_ENOBUFS,
ENODEV = wasi::__WASI_ENODEV,
ENOENT = wasi::__WASI_ENOENT,
ENOEXEC = wasi::__WASI_ENOEXEC,
ENOLCK = wasi::__WASI_ENOLCK,
ENOLINK = wasi::__WASI_ENOLINK,
ENOMEM = wasi::__WASI_ENOMEM,
ENOMSG = wasi::__WASI_ENOMSG,
ENOPROTOOPT = wasi::__WASI_ENOPROTOOPT,
ENOSPC = wasi::__WASI_ENOSPC,
ENOSYS = wasi::__WASI_ENOSYS,
ENOTCONN = wasi::__WASI_ENOTCONN,
ENOTDIR = wasi::__WASI_ENOTDIR,
ENOTEMPTY = wasi::__WASI_ENOTEMPTY,
ENOTRECOVERABLE = wasi::__WASI_ENOTRECOVERABLE,
ENOTSOCK = wasi::__WASI_ENOTSOCK,
ENOTSUP = wasi::__WASI_ENOTSUP,
ENOTTY = wasi::__WASI_ENOTTY,
ENXIO = wasi::__WASI_ENXIO,
EOVERFLOW = wasi::__WASI_EOVERFLOW,
EOWNERDEAD = wasi::__WASI_EOWNERDEAD,
EPERM = wasi::__WASI_EPERM,
EPIPE = wasi::__WASI_EPIPE,
EPROTO = wasi::__WASI_EPROTO,
EPROTONOSUPPORT = wasi::__WASI_EPROTONOSUPPORT,
EPROTOTYPE = wasi::__WASI_EPROTOTYPE,
ERANGE = wasi::__WASI_ERANGE,
EROFS = wasi::__WASI_EROFS,
ESPIPE = wasi::__WASI_ESPIPE,
ESRCH = wasi::__WASI_ESRCH,
ESTALE = wasi::__WASI_ESTALE,
ETIMEDOUT = wasi::__WASI_ETIMEDOUT,
ETXTBSY = wasi::__WASI_ETXTBSY,
EXDEV = wasi::__WASI_EXDEV,
ENOTCAPABLE = wasi::__WASI_ENOTCAPABLE,
}
impl WasiError {
pub fn as_raw_errno(self) -> wasi::__wasi_errno_t {
self as wasi::__wasi_errno_t
}
}
impl fmt::Display for WasiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
_ => write!(f, "{:?}", self),
}
}
}
#[derive(Debug, Fail)]
pub enum Error {
Wasi(WasiError),
Io(std::io::Error),
#[cfg(unix)]
Nix(nix::Error),
#[cfg(windows)]
Win(winx::winerror::WinError),
}
impl From<WasiError> for Error {
fn from(err: WasiError) -> Self {
Self::Wasi(err)
}
}
#[cfg(unix)]
impl From<nix::Error> for Error {
fn from(err: nix::Error) -> Self {
Self::Nix(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<TryFromIntError> for Error {
fn from(_: TryFromIntError) -> Self {
Self::Wasi(WasiError::EOVERFLOW)
}
}
impl From<Infallible> for Error {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl From<str::Utf8Error> for Error {
fn from(_: str::Utf8Error) -> Self {
Self::Wasi(WasiError::EILSEQ)
}
}
#[cfg(windows)]
impl From<winx::winerror::WinError> for Error {
fn from(err: winx::winerror::WinError) -> Self {
Self::Win(err)
}
}
impl Error {
pub(crate) fn as_wasi_errno(&self) -> wasi::__wasi_errno_t {
match self {
Self::Wasi(no) => no.as_raw_errno(),
Self::Io(e) => errno_from_ioerror(e.to_owned()),
#[cfg(unix)]
Self::Nix(err) => err
.as_errno()
.map_or_else(
|| {
log::debug!("Unknown nix errno: {}", err);
Self::ENOSYS
},
crate::sys::host_impl::errno_from_nix,
)
.as_wasi_errno(),
#[cfg(windows)]
Self::Win(err) => crate::sys::host_impl::errno_from_win(*err),
}
}
pub const ESUCCESS: Self = Error::Wasi(WasiError::ESUCCESS);
pub const E2BIG: Self = Error::Wasi(WasiError::E2BIG);
pub const EACCES: Self = Error::Wasi(WasiError::EACCES);
pub const EADDRINUSE: Self = Error::Wasi(WasiError::EADDRINUSE);
pub const EADDRNOTAVAIL: Self = Error::Wasi(WasiError::EADDRNOTAVAIL);
pub const EAFNOSUPPORT: Self = Error::Wasi(WasiError::EAFNOSUPPORT);
pub const EAGAIN: Self = Error::Wasi(WasiError::EAGAIN);
pub const EALREADY: Self = Error::Wasi(WasiError::EALREADY);
pub const EBADF: Self = Error::Wasi(WasiError::EBADF);
pub const EBADMSG: Self = Error::Wasi(WasiError::EBADMSG);
pub const EBUSY: Self = Error::Wasi(WasiError::EBUSY);
pub const ECANCELED: Self = Error::Wasi(WasiError::ECANCELED);
pub const ECHILD: Self = Error::Wasi(WasiError::ECHILD);
pub const ECONNABORTED: Self = Error::Wasi(WasiError::ECONNABORTED);
pub const ECONNREFUSED: Self = Error::Wasi(WasiError::ECONNREFUSED);
pub const ECONNRESET: Self = Error::Wasi(WasiError::ECONNRESET);
pub const EDEADLK: Self = Error::Wasi(WasiError::EDEADLK);
pub const EDESTADDRREQ: Self = Error::Wasi(WasiError::EDESTADDRREQ);
pub const EDOM: Self = Error::Wasi(WasiError::EDOM);
pub const EDQUOT: Self = Error::Wasi(WasiError::EDQUOT);
pub const EEXIST: Self = Error::Wasi(WasiError::EEXIST);
pub const EFAULT: Self = Error::Wasi(WasiError::EFAULT);
pub const EFBIG: Self = Error::Wasi(WasiError::EFBIG);
pub const EHOSTUNREACH: Self = Error::Wasi(WasiError::EHOSTUNREACH);
pub const EIDRM: Self = Error::Wasi(WasiError::EIDRM);
pub const EILSEQ: Self = Error::Wasi(WasiError::EILSEQ);
pub const EINPROGRESS: Self = Error::Wasi(WasiError::EINPROGRESS);
pub const EINTR: Self = Error::Wasi(WasiError::EINTR);
pub const EINVAL: Self = Error::Wasi(WasiError::EINVAL);
pub const EIO: Self = Error::Wasi(WasiError::EIO);
pub const EISCONN: Self = Error::Wasi(WasiError::EISCONN);
pub const EISDIR: Self = Error::Wasi(WasiError::EISDIR);
pub const ELOOP: Self = Error::Wasi(WasiError::ELOOP);
pub const EMFILE: Self = Error::Wasi(WasiError::EMFILE);
pub const EMLINK: Self = Error::Wasi(WasiError::EMLINK);
pub const EMSGSIZE: Self = Error::Wasi(WasiError::EMSGSIZE);
pub const EMULTIHOP: Self = Error::Wasi(WasiError::EMULTIHOP);
pub const ENAMETOOLONG: Self = Error::Wasi(WasiError::ENAMETOOLONG);
pub const ENETDOWN: Self = Error::Wasi(WasiError::ENETDOWN);
pub const ENETRESET: Self = Error::Wasi(WasiError::ENETRESET);
pub const ENETUNREACH: Self = Error::Wasi(WasiError::ENETUNREACH);
pub const ENFILE: Self = Error::Wasi(WasiError::ENFILE);
pub const ENOBUFS: Self = Error::Wasi(WasiError::ENOBUFS);
pub const ENODEV: Self = Error::Wasi(WasiError::ENODEV);
pub const ENOENT: Self = Error::Wasi(WasiError::ENOENT);
pub const ENOEXEC: Self = Error::Wasi(WasiError::ENOEXEC);
pub const ENOLCK: Self = Error::Wasi(WasiError::ENOLCK);
pub const ENOLINK: Self = Error::Wasi(WasiError::ENOLINK);
pub const ENOMEM: Self = Error::Wasi(WasiError::ENOMEM);
pub const ENOMSG: Self = Error::Wasi(WasiError::ENOMSG);
pub const ENOPROTOOPT: Self = Error::Wasi(WasiError::ENOPROTOOPT);
pub const ENOSPC: Self = Error::Wasi(WasiError::ENOSPC);
pub const ENOSYS: Self = Error::Wasi(WasiError::ENOSYS);
pub const ENOTCONN: Self = Error::Wasi(WasiError::ENOTCONN);
pub const ENOTDIR: Self = Error::Wasi(WasiError::ENOTDIR);
pub const ENOTEMPTY: Self = Error::Wasi(WasiError::ENOTEMPTY);
pub const ENOTRECOVERABLE: Self = Error::Wasi(WasiError::ENOTRECOVERABLE);
pub const ENOTSOCK: Self = Error::Wasi(WasiError::ENOTSOCK);
pub const ENOTSUP: Self = Error::Wasi(WasiError::ENOTSUP);
pub const ENOTTY: Self = Error::Wasi(WasiError::ENOTTY);
pub const ENXIO: Self = Error::Wasi(WasiError::ENXIO);
pub const EOVERFLOW: Self = Error::Wasi(WasiError::EOVERFLOW);
pub const EOWNERDEAD: Self = Error::Wasi(WasiError::EOWNERDEAD);
pub const EPERM: Self = Error::Wasi(WasiError::EPERM);
pub const EPIPE: Self = Error::Wasi(WasiError::EPIPE);
pub const EPROTO: Self = Error::Wasi(WasiError::EPROTO);
pub const EPROTONOSUPPORT: Self = Error::Wasi(WasiError::EPROTONOSUPPORT);
pub const EPROTOTYPE: Self = Error::Wasi(WasiError::EPROTOTYPE);
pub const ERANGE: Self = Error::Wasi(WasiError::ERANGE);
pub const EROFS: Self = Error::Wasi(WasiError::EROFS);
pub const ESPIPE: Self = Error::Wasi(WasiError::ESPIPE);
pub const ESRCH: Self = Error::Wasi(WasiError::ESRCH);
pub const ESTALE: Self = Error::Wasi(WasiError::ESTALE);
pub const ETIMEDOUT: Self = Error::Wasi(WasiError::ETIMEDOUT);
pub const ETXTBSY: Self = Error::Wasi(WasiError::ETXTBSY);
pub const EXDEV: Self = Error::Wasi(WasiError::EXDEV);
pub const ENOTCAPABLE: Self = Error::Wasi(WasiError::ENOTCAPABLE);
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Io(e) => e.fmt(f),
Self::Wasi(e) => e.fmt(f),
#[cfg(unix)]
Self::Nix(e) => e.fmt(f),
#[cfg(windows)]
Self::Win(e) => e.fmt(f),
}
}
}
fn errno_from_ioerror(e: &std::io::Error) -> wasi::__wasi_errno_t {
match e.raw_os_error() {
Some(code) => crate::sys::errno_from_host(code),
None => {
log::debug!("Inconvertible OS error: {}", e);
wasi::__WASI_EIO
}
}
}

186
wasi-common/src/fdentry.rs Normal file
View File

@@ -0,0 +1,186 @@
use crate::sys::dev_null;
use crate::sys::fdentry_impl::{determine_type_and_access_rights, OsFile};
use crate::{wasi, Error, Result};
use std::path::PathBuf;
use std::{fs, io};
#[derive(Debug)]
pub(crate) enum Descriptor {
OsFile(OsFile),
Stdin,
Stdout,
Stderr,
}
impl Descriptor {
pub(crate) fn as_file(&self) -> Result<&OsFile> {
match self {
Self::OsFile(file) => Ok(file),
_ => Err(Error::EBADF),
}
}
pub(crate) fn as_file_mut(&mut self) -> Result<&mut OsFile> {
match self {
Self::OsFile(file) => Ok(file),
_ => Err(Error::EBADF),
}
}
pub(crate) fn is_file(&self) -> bool {
match self {
Self::OsFile(_) => true,
_ => false,
}
}
#[allow(unused)]
pub(crate) fn is_stdin(&self) -> bool {
match self {
Self::Stdin => true,
_ => false,
}
}
#[allow(unused)]
pub(crate) fn is_stdout(&self) -> bool {
match self {
Self::Stdout => true,
_ => false,
}
}
#[allow(unused)]
pub(crate) fn is_stderr(&self) -> bool {
match self {
Self::Stderr => true,
_ => false,
}
}
}
/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
/// certain base rights `rights_base` and inheriting rights `rights_inheriting` in order to be
/// accessed correctly.
///
/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or
/// stdin handle), and accessing it can only be done via the provided `FdEntry::as_descriptor` and
/// `FdEntry::as_descriptor_mut` methods which require a set of base and inheriting rights to be
/// specified, verifying whether the stored `Descriptor` object is valid for the rights specified.
#[derive(Debug)]
pub(crate) struct FdEntry {
pub(crate) file_type: wasi::__wasi_filetype_t,
descriptor: Descriptor,
pub(crate) rights_base: wasi::__wasi_rights_t,
pub(crate) rights_inheriting: wasi::__wasi_rights_t,
pub(crate) preopen_path: Option<PathBuf>,
// TODO: directories
}
impl FdEntry {
pub(crate) fn from(file: fs::File) -> Result<Self> {
unsafe { determine_type_and_access_rights(&file) }.map(
|(file_type, rights_base, rights_inheriting)| Self {
file_type,
descriptor: Descriptor::OsFile(OsFile::from(file)),
rights_base,
rights_inheriting,
preopen_path: None,
},
)
}
pub(crate) fn duplicate(file: &fs::File) -> Result<Self> {
Self::from(file.try_clone()?)
}
pub(crate) fn duplicate_stdin() -> Result<Self> {
unsafe { determine_type_and_access_rights(&io::stdin()) }.map(
|(file_type, rights_base, rights_inheriting)| Self {
file_type,
descriptor: Descriptor::Stdin,
rights_base,
rights_inheriting,
preopen_path: None,
},
)
}
pub(crate) fn duplicate_stdout() -> Result<Self> {
unsafe { determine_type_and_access_rights(&io::stdout()) }.map(
|(file_type, rights_base, rights_inheriting)| Self {
file_type,
descriptor: Descriptor::Stdout,
rights_base,
rights_inheriting,
preopen_path: None,
},
)
}
pub(crate) fn duplicate_stderr() -> Result<Self> {
unsafe { determine_type_and_access_rights(&io::stderr()) }.map(
|(file_type, rights_base, rights_inheriting)| Self {
file_type,
descriptor: Descriptor::Stderr,
rights_base,
rights_inheriting,
preopen_path: None,
},
)
}
pub(crate) fn null() -> Result<Self> {
Self::from(dev_null()?)
}
/// Convert this `FdEntry` into a host `Descriptor` object provided the specified
/// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object.
///
/// The `FdEntry` can only be converted into a valid `Descriptor` object if
/// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting`
/// is a subset of rights attached to this `FdEntry`. The check is performed using
/// `FdEntry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned.
pub(crate) fn as_descriptor(
&self,
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
) -> Result<&Descriptor> {
self.validate_rights(rights_base, rights_inheriting)?;
Ok(&self.descriptor)
}
/// Convert this `FdEntry` into a mutable host `Descriptor` object provided the specified
/// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object.
///
/// The `FdEntry` can only be converted into a valid `Descriptor` object if
/// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting`
/// is a subset of rights attached to this `FdEntry`. The check is performed using
/// `FdEntry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned.
pub(crate) fn as_descriptor_mut(
&mut self,
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
) -> Result<&mut Descriptor> {
self.validate_rights(rights_base, rights_inheriting)?;
Ok(&mut self.descriptor)
}
/// Check if this `FdEntry` object satisfies the specified base rights `rights_base`, and
/// inheriting rights `rights_inheriting`; i.e., if rights attached to this `FdEntry` object
/// are a superset.
///
/// Upon unsuccessful check, `Error::ENOTCAPABLE` is returned.
fn validate_rights(
&self,
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
) -> Result<()> {
if !self.rights_base & rights_base != 0 || !self.rights_inheriting & rights_inheriting != 0
{
Err(Error::ENOTCAPABLE)
} else {
Ok(())
}
}
}

216
wasi-common/src/fs/dir.rs Normal file
View File

@@ -0,0 +1,216 @@
use crate::fs::{error::wasi_errno_to_io_error, File, OpenOptions, ReadDir};
use crate::{host, hostcalls, wasi, 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 mut WasiCtx,
fd: wasi::__wasi_fd_t,
}
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 mut WasiCtx, fd: wasi::__wasi_fd_t) -> 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 = 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_LOOKUP_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 = 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_LOOKUP_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
wasi::__WASI_O_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 = 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_LOOKUP_SYMLINK_FOLLOW,
path.as_os_str().as_bytes(),
path.as_os_str().len(),
wasi::__WASI_O_CREAT | wasi::__WASI_O_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 _ = unsafe { hostcalls::fd_close(self.ctx, 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

@@ -0,0 +1,49 @@
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

@@ -0,0 +1,53 @@
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

265
wasi-common/src/fs/error.rs Normal file
View File

@@ -0,0 +1,265 @@
use crate::wasi;
use std::io;
/// Translate a WASI errno code into an `io::Result<()>`.
///
/// TODO: Would it be better to have our own version of `io::Error` (and
/// `io::Result`), rather than trying to shoehorn WASI errors into the
/// libstd version?
pub(crate) fn wasi_errno_to_io_error(errno: wasi::__wasi_errno_t) -> io::Result<()> {
#[cfg(unix)]
let raw_os_error = match errno {
wasi::__WASI_ESUCCESS => return Ok(()),
wasi::__WASI_EIO => libc::EIO,
wasi::__WASI_EPERM => libc::EPERM,
wasi::__WASI_EINVAL => libc::EINVAL,
wasi::__WASI_EPIPE => libc::EPIPE,
wasi::__WASI_ENOTCONN => libc::ENOTCONN,
wasi::__WASI_E2BIG => libc::E2BIG,
wasi::__WASI_EACCES => libc::EACCES,
wasi::__WASI_EADDRINUSE => libc::EADDRINUSE,
wasi::__WASI_EADDRNOTAVAIL => libc::EADDRNOTAVAIL,
wasi::__WASI_EAFNOSUPPORT => libc::EAFNOSUPPORT,
wasi::__WASI_EAGAIN => libc::EAGAIN,
wasi::__WASI_EALREADY => libc::EALREADY,
wasi::__WASI_EBADF => libc::EBADF,
wasi::__WASI_EBADMSG => libc::EBADMSG,
wasi::__WASI_EBUSY => libc::EBUSY,
wasi::__WASI_ECANCELED => libc::ECANCELED,
wasi::__WASI_ECHILD => libc::ECHILD,
wasi::__WASI_ECONNABORTED => libc::ECONNABORTED,
wasi::__WASI_ECONNREFUSED => libc::ECONNREFUSED,
wasi::__WASI_ECONNRESET => libc::ECONNRESET,
wasi::__WASI_EDEADLK => libc::EDEADLK,
wasi::__WASI_EDESTADDRREQ => libc::EDESTADDRREQ,
wasi::__WASI_EDOM => libc::EDOM,
wasi::__WASI_EDQUOT => libc::EDQUOT,
wasi::__WASI_EEXIST => libc::EEXIST,
wasi::__WASI_EFAULT => libc::EFAULT,
wasi::__WASI_EFBIG => libc::EFBIG,
wasi::__WASI_EHOSTUNREACH => libc::EHOSTUNREACH,
wasi::__WASI_EIDRM => libc::EIDRM,
wasi::__WASI_EILSEQ => libc::EILSEQ,
wasi::__WASI_EINPROGRESS => libc::EINPROGRESS,
wasi::__WASI_EINTR => libc::EINTR,
wasi::__WASI_EISCONN => libc::EISCONN,
wasi::__WASI_EISDIR => libc::EISDIR,
wasi::__WASI_ELOOP => libc::ELOOP,
wasi::__WASI_EMFILE => libc::EMFILE,
wasi::__WASI_EMLINK => libc::EMLINK,
wasi::__WASI_EMSGSIZE => libc::EMSGSIZE,
wasi::__WASI_EMULTIHOP => libc::EMULTIHOP,
wasi::__WASI_ENAMETOOLONG => libc::ENAMETOOLONG,
wasi::__WASI_ENETDOWN => libc::ENETDOWN,
wasi::__WASI_ENETRESET => libc::ENETRESET,
wasi::__WASI_ENETUNREACH => libc::ENETUNREACH,
wasi::__WASI_ENFILE => libc::ENFILE,
wasi::__WASI_ENOBUFS => libc::ENOBUFS,
wasi::__WASI_ENODEV => libc::ENODEV,
wasi::__WASI_ENOENT => libc::ENOENT,
wasi::__WASI_ENOEXEC => libc::ENOEXEC,
wasi::__WASI_ENOLCK => libc::ENOLCK,
wasi::__WASI_ENOLINK => libc::ENOLINK,
wasi::__WASI_ENOMEM => libc::ENOMEM,
wasi::__WASI_ENOMSG => libc::ENOMSG,
wasi::__WASI_ENOPROTOOPT => libc::ENOPROTOOPT,
wasi::__WASI_ENOSPC => libc::ENOSPC,
wasi::__WASI_ENOSYS => libc::ENOSYS,
wasi::__WASI_ENOTDIR => libc::ENOTDIR,
wasi::__WASI_ENOTEMPTY => libc::ENOTEMPTY,
wasi::__WASI_ENOTRECOVERABLE => libc::ENOTRECOVERABLE,
wasi::__WASI_ENOTSOCK => libc::ENOTSOCK,
wasi::__WASI_ENOTSUP => libc::ENOTSUP,
wasi::__WASI_ENOTTY => libc::ENOTTY,
wasi::__WASI_ENXIO => libc::ENXIO,
wasi::__WASI_EOVERFLOW => libc::EOVERFLOW,
wasi::__WASI_EOWNERDEAD => libc::EOWNERDEAD,
wasi::__WASI_EPROTO => libc::EPROTO,
wasi::__WASI_EPROTONOSUPPORT => libc::EPROTONOSUPPORT,
wasi::__WASI_EPROTOTYPE => libc::EPROTOTYPE,
wasi::__WASI_ERANGE => libc::ERANGE,
wasi::__WASI_EROFS => libc::EROFS,
wasi::__WASI_ESPIPE => libc::ESPIPE,
wasi::__WASI_ESRCH => libc::ESRCH,
wasi::__WASI_ESTALE => libc::ESTALE,
wasi::__WASI_ETIMEDOUT => libc::ETIMEDOUT,
wasi::__WASI_ETXTBSY => libc::ETXTBSY,
wasi::__WASI_EXDEV => libc::EXDEV,
#[cfg(target_os = "wasi")]
wasi::__WASI_ENOTCAPABLE => libc::ENOTCAPABLE,
#[cfg(not(target_os = "wasi"))]
wasi::__WASI_ENOTCAPABLE => libc::EIO,
_ => panic!("unexpected wasi errno value"),
};
#[cfg(windows)]
use winapi::shared::winerror::*;
#[cfg(windows)]
let raw_os_error = match errno {
wasi::__WASI_ESUCCESS => return Ok(()),
wasi::__WASI_EINVAL => WSAEINVAL,
wasi::__WASI_EPIPE => ERROR_BROKEN_PIPE,
wasi::__WASI_ENOTCONN => WSAENOTCONN,
wasi::__WASI_EPERM | wasi::__WASI_EACCES => ERROR_ACCESS_DENIED,
wasi::__WASI_EADDRINUSE => WSAEADDRINUSE,
wasi::__WASI_EADDRNOTAVAIL => WSAEADDRNOTAVAIL,
wasi::__WASI_EAGAIN => WSAEWOULDBLOCK,
wasi::__WASI_ECONNABORTED => WSAECONNABORTED,
wasi::__WASI_ECONNREFUSED => WSAECONNREFUSED,
wasi::__WASI_ECONNRESET => WSAECONNRESET,
wasi::__WASI_EEXIST => ERROR_ALREADY_EXISTS,
wasi::__WASI_ENOENT => ERROR_FILE_NOT_FOUND,
wasi::__WASI_ETIMEDOUT => WSAETIMEDOUT,
wasi::__WASI_EAFNOSUPPORT => WSAEAFNOSUPPORT,
wasi::__WASI_EALREADY => WSAEALREADY,
wasi::__WASI_EBADF => WSAEBADF,
wasi::__WASI_EDESTADDRREQ => WSAEDESTADDRREQ,
wasi::__WASI_EDQUOT => WSAEDQUOT,
wasi::__WASI_EFAULT => WSAEFAULT,
wasi::__WASI_EHOSTUNREACH => WSAEHOSTUNREACH,
wasi::__WASI_EINPROGRESS => WSAEINPROGRESS,
wasi::__WASI_EINTR => WSAEINTR,
wasi::__WASI_EISCONN => WSAEISCONN,
wasi::__WASI_ELOOP => WSAELOOP,
wasi::__WASI_EMFILE => WSAEMFILE,
wasi::__WASI_EMSGSIZE => WSAEMSGSIZE,
wasi::__WASI_ENAMETOOLONG => WSAENAMETOOLONG,
wasi::__WASI_ENETDOWN => WSAENETDOWN,
wasi::__WASI_ENETRESET => WSAENETRESET,
wasi::__WASI_ENETUNREACH => WSAENETUNREACH,
wasi::__WASI_ENOBUFS => WSAENOBUFS,
wasi::__WASI_ENOPROTOOPT => WSAENOPROTOOPT,
wasi::__WASI_ENOTEMPTY => WSAENOTEMPTY,
wasi::__WASI_ENOTSOCK => WSAENOTSOCK,
wasi::__WASI_EPROTONOSUPPORT => WSAEPROTONOSUPPORT,
wasi::__WASI_EPROTOTYPE => WSAEPROTOTYPE,
wasi::__WASI_ESTALE => WSAESTALE,
wasi::__WASI_EIO
| wasi::__WASI_EISDIR
| wasi::__WASI_E2BIG
| wasi::__WASI_EBADMSG
| wasi::__WASI_EBUSY
| wasi::__WASI_ECANCELED
| wasi::__WASI_ECHILD
| wasi::__WASI_EDEADLK
| wasi::__WASI_EDOM
| wasi::__WASI_EFBIG
| wasi::__WASI_EIDRM
| wasi::__WASI_EILSEQ
| wasi::__WASI_EMLINK
| wasi::__WASI_EMULTIHOP
| wasi::__WASI_ENFILE
| wasi::__WASI_ENODEV
| wasi::__WASI_ENOEXEC
| wasi::__WASI_ENOLCK
| wasi::__WASI_ENOLINK
| wasi::__WASI_ENOMEM
| wasi::__WASI_ENOMSG
| wasi::__WASI_ENOSPC
| wasi::__WASI_ENOSYS
| wasi::__WASI_ENOTDIR
| wasi::__WASI_ENOTRECOVERABLE
| wasi::__WASI_ENOTSUP
| wasi::__WASI_ENOTTY
| wasi::__WASI_ENXIO
| wasi::__WASI_EOVERFLOW
| wasi::__WASI_EOWNERDEAD
| wasi::__WASI_EPROTO
| wasi::__WASI_ERANGE
| wasi::__WASI_EROFS
| wasi::__WASI_ESPIPE
| wasi::__WASI_ESRCH
| wasi::__WASI_ETXTBSY
| wasi::__WASI_EXDEV
| wasi::__WASI_ENOTCAPABLE => {
return Err(io::Error::new(io::ErrorKind::Other, error_str(errno)))
}
_ => panic!("unrecognized WASI errno value"),
} as i32;
Err(io::Error::from_raw_os_error(raw_os_error))
}
#[cfg(windows)]
fn error_str(errno: wasi::__wasi_errno_t) -> &'static str {
match errno {
wasi::__WASI_E2BIG => "Argument list too long",
wasi::__WASI_EACCES => "Permission denied",
wasi::__WASI_EADDRINUSE => "Address in use",
wasi::__WASI_EADDRNOTAVAIL => "Address not available",
wasi::__WASI_EAFNOSUPPORT => "Address family not supported by protocol",
wasi::__WASI_EAGAIN => "Resource temporarily unavailable",
wasi::__WASI_EALREADY => "Operation already in progress",
wasi::__WASI_EBADF => "Bad file descriptor",
wasi::__WASI_EBADMSG => "Bad message",
wasi::__WASI_EBUSY => "Resource busy",
wasi::__WASI_ECANCELED => "Operation canceled",
wasi::__WASI_ECHILD => "No child process",
wasi::__WASI_ECONNABORTED => "Connection aborted",
wasi::__WASI_ECONNREFUSED => "Connection refused",
wasi::__WASI_ECONNRESET => "Connection reset by peer",
wasi::__WASI_EDEADLK => "Resource deadlock would occur",
wasi::__WASI_EDESTADDRREQ => "Destination address required",
wasi::__WASI_EDOM => "Domain error",
wasi::__WASI_EDQUOT => "Quota exceeded",
wasi::__WASI_EEXIST => "File exists",
wasi::__WASI_EFAULT => "Bad address",
wasi::__WASI_EFBIG => "File too large",
wasi::__WASI_EHOSTUNREACH => "Host is unreachable",
wasi::__WASI_EIDRM => "Identifier removed",
wasi::__WASI_EILSEQ => "Illegal byte sequence",
wasi::__WASI_EINPROGRESS => "Operation in progress",
wasi::__WASI_EINTR => "Interrupted system call",
wasi::__WASI_EINVAL => "Invalid argument",
wasi::__WASI_EIO => "Remote I/O error",
wasi::__WASI_EISCONN => "Socket is connected",
wasi::__WASI_EISDIR => "Is a directory",
wasi::__WASI_ELOOP => "Symbolic link loop",
wasi::__WASI_EMFILE => "No file descriptors available",
wasi::__WASI_EMLINK => "Too many links",
wasi::__WASI_EMSGSIZE => "Message too large",
wasi::__WASI_EMULTIHOP => "Multihop attempted",
wasi::__WASI_ENAMETOOLONG => "Filename too long",
wasi::__WASI_ENETDOWN => "Network is down",
wasi::__WASI_ENETRESET => "Connection reset by network",
wasi::__WASI_ENETUNREACH => "Network unreachable",
wasi::__WASI_ENFILE => "Too many open files in system",
wasi::__WASI_ENOBUFS => "No buffer space available",
wasi::__WASI_ENODEV => "No such device",
wasi::__WASI_ENOENT => "No such file or directory",
wasi::__WASI_ENOEXEC => "Exec format error",
wasi::__WASI_ENOLCK => "No locks available",
wasi::__WASI_ENOLINK => "Link has been severed",
wasi::__WASI_ENOMEM => "Out of memory",
wasi::__WASI_ENOMSG => "No message of desired type",
wasi::__WASI_ENOPROTOOPT => "Protocol not available",
wasi::__WASI_ENOSPC => "No space left on device",
wasi::__WASI_ENOSYS => "Function not implemented",
wasi::__WASI_ENOTCONN => "Socket not connected",
wasi::__WASI_ENOTDIR => "Not a directory",
wasi::__WASI_ENOTEMPTY => "Directory not empty",
wasi::__WASI_ENOTRECOVERABLE => "State not recoverable",
wasi::__WASI_ENOTSOCK => "Not a socket",
wasi::__WASI_ENOTSUP => "Not supported",
wasi::__WASI_ENOTTY => "Not a tty",
wasi::__WASI_ENXIO => "No such device or address",
wasi::__WASI_EOVERFLOW => "Value too large for data type",
wasi::__WASI_EOWNERDEAD => "Previous owner died",
wasi::__WASI_EPERM => "Operation not permitted",
wasi::__WASI_EPIPE => "Broken pipe",
wasi::__WASI_EPROTO => "Protocol error",
wasi::__WASI_EPROTONOSUPPORT => "Protocol not supported",
wasi::__WASI_EPROTOTYPE => "Protocol wrong type for socket",
wasi::__WASI_ERANGE => "Result not representable",
wasi::__WASI_EROFS => "Read-only file system",
wasi::__WASI_ESPIPE => "Invalid seek",
wasi::__WASI_ESRCH => "No such process",
wasi::__WASI_ESTALE => "Stale file handle",
wasi::__WASI_ETIMEDOUT => "Operation timed out",
wasi::__WASI_ETXTBSY => "Text file busy",
wasi::__WASI_EXDEV => "Cross-device link",
wasi::__WASI_ENOTCAPABLE => "Capabilities insufficient",
_ => panic!("unrecognized WASI errno value"),
}
}

107
wasi-common/src/fs/file.rs Normal file
View File

@@ -0,0 +1,107 @@
use crate::fs::{error::wasi_errno_to_io_error, Metadata};
use crate::{host, hostcalls, wasi, 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 mut WasiCtx,
fd: wasi::__wasi_fd_t,
}
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 mut WasiCtx, fd: wasi::__wasi_fd_t) -> 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) -> io::Result<()> {
wasi_errno_to_io_error(unsafe { hostcalls::fd_sync(self.ctx, self.fd) })
}
/// 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) -> io::Result<()> {
wasi_errno_to_io_error(unsafe { hostcalls::fd_datasync(self.ctx, self.fd) })
}
/// 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) -> io::Result<()> {
wasi_errno_to_io_error(unsafe { hostcalls::fd_filestat_set_size(self.ctx, self.fd, size) })
}
/// 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) -> io::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 _ = unsafe { hostcalls::fd_close(self.ctx, 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> {
let iov = [host::__wasi_iovec_t {
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

@@ -0,0 +1,49 @@
/// 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

@@ -0,0 +1,106 @@
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

51
wasi-common/src/fs/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
//! 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 error;
mod file;
mod file_type;
mod metadata;
mod open_options;
mod permissions;
mod readdir;
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

@@ -0,0 +1,99 @@
/// 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

@@ -0,0 +1,37 @@
/// 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

@@ -0,0 +1,32 @@
use crate::fs::DirEntry;
use crate::{hostcalls, wasi};
/// 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: wasi::__wasi_fd_t,
}
impl ReadDir {
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
pub unsafe fn from_raw_wasi_fd(fd: wasi::__wasi_fd_t) -> 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

@@ -0,0 +1,20 @@
use crate::{Error, Result};
use std::convert::TryInto;
use std::str;
use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) fn systemtime_to_timestamp(st: SystemTime) -> Result<u64> {
st.duration_since(UNIX_EPOCH)
.map_err(|_| Error::EINVAL)? // date earlier than UNIX_EPOCH
.as_nanos()
.try_into()
.map_err(Into::into) // u128 doesn't fit into u64
}
/// Creates not-owned WASI path from byte slice.
///
/// NB WASI spec requires bytes to be valid UTF-8. Otherwise,
/// `__WASI_EILSEQ` error is returned.
pub(crate) fn path_from_slice<'a>(s: &'a [u8]) -> Result<&'a str> {
str::from_utf8(s).map_err(|_| Error::EILSEQ)
}

109
wasi-common/src/host.rs Normal file
View File

@@ -0,0 +1,109 @@
//! WASI host types. These are types that contain raw pointers and `usize`
//! values, and so are platform-specific.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::wasi::*;
use std::{io, slice};
use wig::witx_host_types;
witx_host_types!("unstable" "wasi_unstable_preview0");
pub(crate) unsafe fn ciovec_to_host(ciovec: &__wasi_ciovec_t) -> io::IoSlice {
let slice = slice::from_raw_parts(ciovec.buf as *const u8, ciovec.buf_len);
io::IoSlice::new(slice)
}
pub(crate) unsafe fn iovec_to_host_mut(iovec: &mut __wasi_iovec_t) -> io::IoSliceMut {
let slice = slice::from_raw_parts_mut(iovec.buf as *mut u8, iovec.buf_len);
io::IoSliceMut::new(slice)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn bindgen_test_layout___wasi_prestat_t() {
assert_eq!(
::std::mem::size_of::<__wasi_prestat_t>(),
16usize,
concat!("Size of: ", stringify!(__wasi_prestat_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_prestat_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_prestat_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).pr_type as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_t),
"::",
stringify!(pr_type)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).u as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_t),
"::",
stringify!(u)
)
);
}
#[test]
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t() {
assert_eq!(
::std::mem::size_of::<__wasi_prestat_dir>(),
8usize,
concat!("Size of: ", stringify!(__wasi_prestat_dir))
);
assert_eq!(
::std::mem::align_of::<__wasi_prestat_dir>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_prestat_dir))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_prestat_dir>())).pr_name_len as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_dir),
"::",
stringify!(pr_name_len)
)
);
}
#[test]
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u() {
assert_eq!(
::std::mem::size_of::<__wasi_prestat_u>(),
8usize,
concat!("Size of: ", stringify!(__wasi_prestat_u))
);
assert_eq!(
::std::mem::align_of::<__wasi_prestat_u>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_prestat_u))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_prestat_u>())).dir as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_u),
"::",
stringify!(dir)
)
);
}
}

View File

@@ -0,0 +1,256 @@
#![allow(non_camel_case_types)]
use crate::ctx::WasiCtx;
use crate::{wasi, wasi32};
hostcalls! {
pub unsafe fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasi::__wasi_fd_t,) -> wasi::__wasi_errno_t;
pub unsafe fn fd_datasync(wasi_ctx: &WasiCtx, fd: wasi::__wasi_fd_t,) -> wasi::__wasi_errno_t;
pub unsafe fn fd_pread(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
iovs_ptr: wasi32::uintptr_t,
iovs_len: wasi32::size_t,
offset: wasi::__wasi_filesize_t,
nread: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_pwrite(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
iovs_ptr: wasi32::uintptr_t,
iovs_len: wasi32::size_t,
offset: wasi::__wasi_filesize_t,
nwritten: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_read(
wasi_ctx: &mut WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
iovs_ptr: wasi32::uintptr_t,
iovs_len: wasi32::size_t,
nread: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_renumber(
wasi_ctx: &mut WasiCtx,
from: wasi::__wasi_fd_t,
to: wasi::__wasi_fd_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_seek(
wasi_ctx: &mut WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
offset: wasi::__wasi_filedelta_t,
whence: wasi::__wasi_whence_t,
newoffset: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_tell(
wasi_ctx: &mut WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
newoffset: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_fdstat_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
fdstat_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_fdstat_set_flags(
wasi_ctx: &WasiCtx,
fd: wasi::__wasi_fd_t,
fdflags: wasi::__wasi_fdflags_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_fdstat_set_rights(
wasi_ctx: &mut WasiCtx,
fd: wasi::__wasi_fd_t,
fs_rights_base: wasi::__wasi_rights_t,
fs_rights_inheriting: wasi::__wasi_rights_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_sync(wasi_ctx: &WasiCtx, fd: wasi::__wasi_fd_t,) -> wasi::__wasi_errno_t;
pub unsafe fn fd_write(
wasi_ctx: &mut WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
iovs_ptr: wasi32::uintptr_t,
iovs_len: wasi32::size_t,
nwritten: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_advise(
wasi_ctx: &WasiCtx,
fd: wasi::__wasi_fd_t,
offset: wasi::__wasi_filesize_t,
len: wasi::__wasi_filesize_t,
advice: wasi::__wasi_advice_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_allocate(
wasi_ctx: &WasiCtx,
fd: wasi::__wasi_fd_t,
offset: wasi::__wasi_filesize_t,
len: wasi::__wasi_filesize_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_create_directory(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_link(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
old_dirfd: wasi::__wasi_fd_t,
old_flags: wasi::__wasi_lookupflags_t,
old_path_ptr: wasi32::uintptr_t,
old_path_len: wasi32::size_t,
new_dirfd: wasi::__wasi_fd_t,
new_path_ptr: wasi32::uintptr_t,
new_path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_open(
wasi_ctx: &mut WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
dirflags: wasi::__wasi_lookupflags_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
oflags: wasi::__wasi_oflags_t,
fs_rights_base: wasi::__wasi_rights_t,
fs_rights_inheriting: wasi::__wasi_rights_t,
fs_flags: wasi::__wasi_fdflags_t,
fd_out_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_readdir(
wasi_ctx: &mut WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
buf: wasi32::uintptr_t,
buf_len: wasi32::size_t,
cookie: wasi::__wasi_dircookie_t,
buf_used: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_readlink(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
buf_ptr: wasi32::uintptr_t,
buf_len: wasi32::size_t,
buf_used: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_rename(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
old_dirfd: wasi::__wasi_fd_t,
old_path_ptr: wasi32::uintptr_t,
old_path_len: wasi32::size_t,
new_dirfd: wasi::__wasi_fd_t,
new_path_ptr: wasi32::uintptr_t,
new_path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_filestat_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
filestat_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_filestat_set_times(
wasi_ctx: &WasiCtx,
fd: wasi::__wasi_fd_t,
st_atim: wasi::__wasi_timestamp_t,
st_mtim: wasi::__wasi_timestamp_t,
fst_flags: wasi::__wasi_fstflags_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_filestat_set_size(
wasi_ctx: &WasiCtx,
fd: wasi::__wasi_fd_t,
st_size: wasi::__wasi_filesize_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_filestat_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
dirflags: wasi::__wasi_lookupflags_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
filestat_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_filestat_set_times(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
dirflags: wasi::__wasi_lookupflags_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
st_atim: wasi::__wasi_timestamp_t,
st_mtim: wasi::__wasi_timestamp_t,
fst_flags: wasi::__wasi_fstflags_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_symlink(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
old_path_ptr: wasi32::uintptr_t,
old_path_len: wasi32::size_t,
dirfd: wasi::__wasi_fd_t,
new_path_ptr: wasi32::uintptr_t,
new_path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_unlink_file(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn path_remove_directory(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
dirfd: wasi::__wasi_fd_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_prestat_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
prestat_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn fd_prestat_dir_name(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
fd: wasi::__wasi_fd_t,
path_ptr: wasi32::uintptr_t,
path_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
}

View File

@@ -0,0 +1,83 @@
#![allow(non_camel_case_types)]
use crate::ctx::WasiCtx;
use crate::{wasi, wasi32};
use log::trace;
use wasi_common_cbindgen::wasi_common_cbindgen;
#[wasi_common_cbindgen]
pub unsafe fn proc_exit(rval: wasi::__wasi_exitcode_t) {
trace!("proc_exit(rval={:?})", rval);
// TODO: Rather than call std::process::exit here, we should trigger a
// stack unwind similar to a trap.
std::process::exit(rval as i32);
}
#[wasi_common_cbindgen]
pub unsafe fn proc_raise(
_wasi_ctx: &WasiCtx,
_memory: &mut [u8],
_sig: wasi::__wasi_signal_t,
) -> wasi::__wasi_errno_t {
unimplemented!("proc_raise")
}
hostcalls! {
pub unsafe fn args_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
argv_ptr: wasi32::uintptr_t,
argv_buf: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn args_sizes_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
argc_ptr: wasi32::uintptr_t,
argv_buf_size_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn environ_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
environ_ptr: wasi32::uintptr_t,
environ_buf: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn environ_sizes_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
environ_count_ptr: wasi32::uintptr_t,
environ_size_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn random_get(
memory: &mut [u8],
buf_ptr: wasi32::uintptr_t,
buf_len: wasi32::size_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn clock_res_get(
memory: &mut [u8],
clock_id: wasi::__wasi_clockid_t,
resolution_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn clock_time_get(
memory: &mut [u8],
clock_id: wasi::__wasi_clockid_t,
precision: wasi::__wasi_timestamp_t,
time_ptr: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn poll_oneoff(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
input: wasi32::uintptr_t,
output: wasi32::uintptr_t,
nsubscriptions: wasi32::size_t,
nevents: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t;
pub unsafe fn sched_yield() -> wasi::__wasi_errno_t;
}

View File

@@ -0,0 +1,7 @@
mod fs;
mod misc;
mod sock;
pub use self::fs::*;
pub use self::misc::*;
pub use self::sock::*;

View File

@@ -0,0 +1,43 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
#![allow(unused)]
use crate::ctx::WasiCtx;
use crate::{wasi, wasi32};
use wasi_common_cbindgen::wasi_common_cbindgen;
#[wasi_common_cbindgen]
pub unsafe fn sock_recv(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
sock: wasi::__wasi_fd_t,
ri_data: wasi32::uintptr_t,
ri_data_len: wasi32::size_t,
ri_flags: wasi::__wasi_riflags_t,
ro_datalen: wasi32::uintptr_t,
ro_flags: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t {
unimplemented!("sock_recv")
}
#[wasi_common_cbindgen]
pub unsafe fn sock_send(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
sock: wasi::__wasi_fd_t,
si_data: wasi32::uintptr_t,
si_data_len: wasi32::size_t,
si_flags: wasi::__wasi_siflags_t,
so_datalen: wasi32::uintptr_t,
) -> wasi::__wasi_errno_t {
unimplemented!("sock_send")
}
#[wasi_common_cbindgen]
pub unsafe fn sock_shutdown(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
sock: wasi::__wasi_fd_t,
how: wasi::__wasi_sdflags_t,
) -> wasi::__wasi_errno_t {
unimplemented!("sock_shutdown")
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
#![allow(non_camel_case_types)]
use crate::sys::host_impl;
use crate::sys::hostcalls_impl::fs_helpers::*;
use crate::{fdentry::FdEntry, wasi, Error, Result};
use std::fs::File;
use std::path::{Component, Path};
#[derive(Debug)]
pub(crate) struct PathGet {
dirfd: File,
path: String,
}
impl PathGet {
pub(crate) fn dirfd(&self) -> &File {
&self.dirfd
}
pub(crate) fn path(&self) -> &str {
&self.path
}
}
/// 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 path_get(
fe: &FdEntry,
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
dirflags: wasi::__wasi_lookupflags_t,
path: &str,
needs_final_component: bool,
) -> Result<PathGet> {
const MAX_SYMLINK_EXPANSIONS: usize = 128;
if path.contains('\0') {
// if contains NUL, return EILSEQ
return Err(Error::EILSEQ);
}
if fe.file_type != wasi::__WASI_FILETYPE_DIRECTORY {
// if `dirfd` doesn't refer to a directory, return `ENOTDIR`.
return Err(Error::ENOTDIR);
}
let dirfd = fe
.as_descriptor(rights_base, rights_inheriting)?
.as_file()?
.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) => {
log::debug!("path_get cur_path = {:?}", cur_path);
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::ENOENT),
Some(p) => p,
};
let tail = components.as_path();
if tail.components().next().is_some() {
let mut tail = host_impl::path_from_host(tail.as_os_str())?;
if ends_with_slash {
tail.push('/');
}
path_stack.push(tail);
}
log::debug!("path_get path_stack = {:?}", path_stack);
match head {
Component::Prefix(_) | Component::RootDir => {
// path is absolute!
return Err(Error::ENOTCAPABLE);
}
Component::CurDir => {
// "." so skip
}
Component::ParentDir => {
// ".." so pop a dir
let _ = dir_stack.pop().ok_or(Error::ENOTCAPABLE)?;
// we're not allowed to pop past the original directory
if dir_stack.is_empty() {
return Err(Error::ENOTCAPABLE);
}
}
Component::Normal(head) => {
let mut head = host_impl::path_from_host(head)?;
if ends_with_slash {
// preserve trailing slash
head.push('/');
}
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
match openat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) {
Ok(new_dir) => {
dir_stack.push(new_dir);
}
Err(e) => {
match e.as_wasi_errno() {
wasi::__WASI_ELOOP
| wasi::__WASI_EMLINK
| wasi::__WASI_ENOTDIR =>
// Check to see if it was a symlink. Linux indicates
// this with ENOTDIR because of the O_DIRECTORY flag.
{
// attempt symlink expansion
let mut link_path = readlinkat(
dir_stack.last().ok_or(Error::ENOTCAPABLE)?,
&head,
)?;
symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
return Err(Error::ELOOP);
}
if head.ends_with('/') {
link_path.push('/');
}
log::debug!(
"attempted symlink expansion link_path={:?}",
link_path
);
path_stack.push(link_path);
}
_ => {
return Err(e);
}
}
}
}
continue;
} else if ends_with_slash
|| (dirflags & wasi::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0
{
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
// symlink expansion
match readlinkat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) {
Ok(mut link_path) => {
symlink_expansions += 1;
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
return Err(Error::ELOOP);
}
if head.ends_with('/') {
link_path.push('/');
}
log::debug!(
"attempted symlink expansion link_path={:?}",
link_path
);
path_stack.push(link_path);
continue;
}
Err(e) => {
if e.as_wasi_errno() != wasi::__WASI_EINVAL
&& e.as_wasi_errno() != wasi::__WASI_ENOENT
// this handles the cases when trying to link to
// a destination that already exists, and the target
// path contains a slash
&& e.as_wasi_errno() != wasi::__WASI_ENOTDIR
{
return Err(e);
}
}
}
}
// not a symlink, so we're done;
return Ok(PathGet {
dirfd: dir_stack.pop().ok_or(Error::ENOTCAPABLE)?,
path: 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(PathGet {
dirfd: dir_stack.pop().ok_or(Error::ENOTCAPABLE)?,
path: String::from("."),
});
}
}
}
}

View File

@@ -0,0 +1,317 @@
#![allow(non_camel_case_types)]
use crate::ctx::WasiCtx;
use crate::fdentry::Descriptor;
use crate::memory::*;
use crate::sys::hostcalls_impl;
use crate::{wasi, wasi32, Error, Result};
use log::trace;
use std::convert::TryFrom;
pub(crate) fn args_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
argv_ptr: wasi32::uintptr_t,
argv_buf: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"args_get(argv_ptr={:#x?}, argv_buf={:#x?})",
argv_ptr,
argv_buf,
);
let mut argv_buf_offset = 0;
let mut argv = vec![];
for arg in &wasi_ctx.args {
let arg_bytes = arg.as_bytes_with_nul();
let arg_ptr = argv_buf + argv_buf_offset;
enc_slice_of_u8(memory, arg_bytes, arg_ptr)?;
argv.push(arg_ptr);
let len = wasi32::uintptr_t::try_from(arg_bytes.len())?;
argv_buf_offset = argv_buf_offset.checked_add(len).ok_or(Error::EOVERFLOW)?;
}
enc_slice_of_wasi32_uintptr(memory, argv.as_slice(), argv_ptr)
}
pub(crate) fn args_sizes_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
argc_ptr: wasi32::uintptr_t,
argv_buf_size_ptr: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"args_sizes_get(argc_ptr={:#x?}, argv_buf_size_ptr={:#x?})",
argc_ptr,
argv_buf_size_ptr,
);
let argc = wasi_ctx.args.len();
let argv_size = wasi_ctx
.args
.iter()
.map(|arg| arg.as_bytes_with_nul().len())
.sum();
trace!(" | *argc_ptr={:?}", argc);
enc_usize_byref(memory, argc_ptr, argc)?;
trace!(" | *argv_buf_size_ptr={:?}", argv_size);
enc_usize_byref(memory, argv_buf_size_ptr, argv_size)
}
pub(crate) fn environ_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
environ_ptr: wasi32::uintptr_t,
environ_buf: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"environ_get(environ_ptr={:#x?}, environ_buf={:#x?})",
environ_ptr,
environ_buf,
);
let mut environ_buf_offset = 0;
let mut environ = vec![];
for pair in &wasi_ctx.env {
let env_bytes = pair.as_bytes_with_nul();
let env_ptr = environ_buf + environ_buf_offset;
enc_slice_of_u8(memory, env_bytes, env_ptr)?;
environ.push(env_ptr);
let len = wasi32::uintptr_t::try_from(env_bytes.len())?;
environ_buf_offset = environ_buf_offset
.checked_add(len)
.ok_or(Error::EOVERFLOW)?;
}
enc_slice_of_wasi32_uintptr(memory, environ.as_slice(), environ_ptr)
}
pub(crate) fn environ_sizes_get(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
environ_count_ptr: wasi32::uintptr_t,
environ_size_ptr: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"environ_sizes_get(environ_count_ptr={:#x?}, environ_size_ptr={:#x?})",
environ_count_ptr,
environ_size_ptr,
);
let environ_count = wasi_ctx.env.len();
let environ_size = wasi_ctx
.env
.iter()
.try_fold(0, |acc: u32, pair| {
acc.checked_add(pair.as_bytes_with_nul().len() as u32)
})
.ok_or(Error::EOVERFLOW)?;
trace!(" | *environ_count_ptr={:?}", environ_count);
enc_usize_byref(memory, environ_count_ptr, environ_count)?;
trace!(" | *environ_size_ptr={:?}", environ_size);
enc_usize_byref(memory, environ_size_ptr, environ_size as usize)
}
pub(crate) fn random_get(
memory: &mut [u8],
buf_ptr: wasi32::uintptr_t,
buf_len: wasi32::size_t,
) -> Result<()> {
use rand::{thread_rng, RngCore};
trace!("random_get(buf_ptr={:#x?}, buf_len={:?})", buf_ptr, buf_len);
let buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?;
thread_rng().fill_bytes(buf);
Ok(())
}
pub(crate) fn clock_res_get(
memory: &mut [u8],
clock_id: wasi::__wasi_clockid_t,
resolution_ptr: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"clock_res_get(clock_id={:?}, resolution_ptr={:#x?})",
clock_id,
resolution_ptr,
);
let resolution = hostcalls_impl::clock_res_get(clock_id)?;
trace!(" | *resolution_ptr={:?}", resolution);
enc_timestamp_byref(memory, resolution_ptr, resolution)
}
pub(crate) fn clock_time_get(
memory: &mut [u8],
clock_id: wasi::__wasi_clockid_t,
precision: wasi::__wasi_timestamp_t,
time_ptr: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"clock_time_get(clock_id={:?}, precision={:?}, time_ptr={:#x?})",
clock_id,
precision,
time_ptr,
);
let time = hostcalls_impl::clock_time_get(clock_id)?;
trace!(" | *time_ptr={:?}", time);
enc_timestamp_byref(memory, time_ptr, time)
}
pub(crate) fn sched_yield() -> Result<()> {
trace!("sched_yield()");
std::thread::yield_now();
Ok(())
}
pub(crate) fn poll_oneoff(
wasi_ctx: &WasiCtx,
memory: &mut [u8],
input: wasi32::uintptr_t,
output: wasi32::uintptr_t,
nsubscriptions: wasi32::size_t,
nevents: wasi32::uintptr_t,
) -> Result<()> {
trace!(
"poll_oneoff(input={:#x?}, output={:#x?}, nsubscriptions={}, nevents={:#x?})",
input,
output,
nsubscriptions,
nevents,
);
if u64::from(nsubscriptions) > wasi::__wasi_filesize_t::max_value() {
return Err(Error::EINVAL);
}
enc_int_byref(memory, nevents, 0)?;
let subscriptions = dec_subscriptions(memory, input, nsubscriptions)?;
let mut events = Vec::new();
let mut timeout: Option<ClockEventData> = None;
let mut fd_events = Vec::new();
for subscription in subscriptions {
match subscription.r#type {
wasi::__WASI_EVENTTYPE_CLOCK => {
let clock = unsafe { subscription.u.clock };
let delay = wasi_clock_to_relative_ns_delay(clock)?;
log::debug!("poll_oneoff event.u.clock = {:?}", clock);
log::debug!("poll_oneoff delay = {:?}ns", delay);
let current = ClockEventData {
delay,
userdata: subscription.userdata,
};
let timeout = timeout.get_or_insert(current);
if current.delay < timeout.delay {
*timeout = current;
}
}
r#type
if r#type == wasi::__WASI_EVENTTYPE_FD_READ
|| r#type == wasi::__WASI_EVENTTYPE_FD_WRITE =>
{
let wasi_fd = unsafe { subscription.u.fd_readwrite.file_descriptor };
let rights = if r#type == wasi::__WASI_EVENTTYPE_FD_READ {
wasi::__WASI_RIGHT_FD_READ
} else {
wasi::__WASI_RIGHT_FD_WRITE
};
match unsafe {
wasi_ctx
.get_fd_entry(wasi_fd)
.and_then(|fe| fe.as_descriptor(rights, 0))
} {
Ok(descriptor) => fd_events.push(FdEventData {
descriptor,
r#type: subscription.r#type,
userdata: subscription.userdata,
}),
Err(err) => {
let event = wasi::__wasi_event_t {
userdata: subscription.userdata,
r#type,
error: err.as_wasi_errno(),
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: 0,
},
},
};
events.push(event);
}
};
}
_ => unreachable!(),
}
}
log::debug!("poll_oneoff timeout = {:?}", timeout);
log::debug!("poll_oneoff fd_events = {:?}", fd_events);
hostcalls_impl::poll_oneoff(timeout, fd_events, &mut events)?;
let events_count = u32::try_from(events.len()).map_err(|_| Error::EOVERFLOW)?;
enc_events(memory, output, nsubscriptions, events)?;
trace!(" | *nevents={:?}", events_count);
enc_int_byref(memory, nevents, events_count)
}
fn wasi_clock_to_relative_ns_delay(wasi_clock: wasi::__wasi_subscription_clock_t) -> Result<u128> {
use std::time::SystemTime;
if wasi_clock.flags != wasi::__WASI_SUBSCRIPTION_CLOCK_ABSTIME {
return Ok(u128::from(wasi_clock.timeout));
}
let now: u128 = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| Error::ENOTCAPABLE)?
.as_nanos();
let deadline = u128::from(wasi_clock.timeout);
Ok(deadline.saturating_sub(now))
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct ClockEventData {
pub(crate) delay: u128, // delay is expressed in nanoseconds
pub(crate) userdata: wasi::__wasi_userdata_t,
}
#[derive(Debug)]
pub(crate) struct FdEventData<'a> {
pub(crate) descriptor: &'a Descriptor,
pub(crate) r#type: wasi::__wasi_eventtype_t,
pub(crate) userdata: wasi::__wasi_userdata_t,
}

View File

@@ -0,0 +1,7 @@
mod fs;
mod fs_helpers;
mod misc;
pub(crate) use self::fs::*;
pub(crate) use self::fs_helpers::PathGet;
pub(crate) use self::misc::*;

42
wasi-common/src/lib.rs Normal file
View File

@@ -0,0 +1,42 @@
#![deny(
// missing_docs,
trivial_numeric_casts,
unused_extern_crates,
unstable_features
)]
#![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::option_map_unwrap_or,
clippy::option_map_unwrap_or_else,
clippy::unicode_not_nfc,
clippy::use_self
)
)]
mod ctx;
mod error;
mod fdentry;
mod helpers;
mod hostcalls_impl;
mod sys;
#[macro_use]
mod macros;
pub mod fs;
mod host;
pub mod hostcalls;
mod memory;
pub mod wasi;
pub mod wasi32;
pub use ctx::{WasiCtx, WasiCtxBuilder};
pub use sys::preopen_dir;
pub type Error = error::Error;
pub(crate) type Result<T> = std::result::Result<T, Error>;

13
wasi-common/src/macros.rs Normal file
View File

@@ -0,0 +1,13 @@
macro_rules! hostcalls {
($(pub unsafe fn $name:ident($($arg:ident: $ty:ty,)*) -> $ret:ty;)*) => ($(
#[wasi_common_cbindgen::wasi_common_cbindgen]
pub unsafe fn $name($($arg: $ty,)*) -> $ret {
let ret = match crate::hostcalls_impl::$name($($arg,)*) {
Ok(()) => crate::wasi::__WASI_ESUCCESS,
Err(e) => e.as_wasi_errno(),
};
ret
}
)*)
}

480
wasi-common/src/memory.rs Normal file
View File

@@ -0,0 +1,480 @@
//! Functions to store and load data to and from wasm linear memory,
//! transforming them from and to host data types.
//!
//! Endianness concerns are completely encapsulated in this file, so
//! that users outside this file holding a `wasi::*` value never need
//! to consider what endianness it's in. Inside this file,
//! wasm linear-memory-ordered values are called "raw" values, and
//! are not held for long durations.
#![allow(unused)]
use crate::{host, wasi, wasi32, Error, Result};
use num::PrimInt;
use std::convert::TryFrom;
use std::mem::{align_of, size_of};
use std::{ptr, slice};
fn dec_ptr(memory: &[u8], ptr: wasi32::uintptr_t, len: usize) -> Result<*const u8> {
// check for overflow
let checked_len = (ptr as usize).checked_add(len).ok_or(Error::EFAULT)?;
// translate the pointer
memory
.get(ptr as usize..checked_len)
.ok_or(Error::EFAULT)
.map(|mem| mem.as_ptr())
}
fn dec_ptr_mut(memory: &mut [u8], ptr: wasi32::uintptr_t, len: usize) -> Result<*mut u8> {
// check for overflow
let checked_len = (ptr as usize).checked_add(len).ok_or(Error::EFAULT)?;
// translate the pointer
memory
.get_mut(ptr as usize..checked_len)
.ok_or(Error::EFAULT)
.map(|mem| mem.as_mut_ptr())
}
fn dec_ptr_to<'memory, T>(memory: &'memory [u8], ptr: wasi32::uintptr_t) -> Result<&'memory T> {
// check that the ptr is aligned
if ptr as usize % align_of::<T>() != 0 {
return Err(Error::EINVAL);
}
dec_ptr(memory, ptr, size_of::<T>()).map(|p| unsafe { &*(p as *const T) })
}
fn dec_ptr_to_mut<'memory, T>(
memory: &'memory mut [u8],
ptr: wasi32::uintptr_t,
) -> Result<&'memory mut T> {
// check that the ptr is aligned
if ptr as usize % align_of::<T>() != 0 {
return Err(Error::EINVAL);
}
dec_ptr_mut(memory, ptr, size_of::<T>()).map(|p| unsafe { &mut *(p as *mut T) })
}
/// This function does not perform endianness conversions!
fn dec_raw_byref<T>(memory: &[u8], ptr: wasi32::uintptr_t) -> Result<T> {
dec_ptr_to::<T>(memory, ptr).map(|p| unsafe { ptr::read(p) })
}
/// This function does not perform endianness conversions!
fn enc_raw_byref<T>(memory: &mut [u8], ptr: wasi32::uintptr_t, t: T) -> Result<()> {
dec_ptr_to_mut::<T>(memory, ptr).map(|p| unsafe { ptr::write(p, t) })
}
pub(crate) fn dec_int_byref<T>(memory: &[u8], ptr: wasi32::uintptr_t) -> Result<T>
where
T: PrimInt,
{
dec_raw_byref::<T>(memory, ptr).map(|i| PrimInt::from_le(i))
}
pub(crate) fn enc_int_byref<T>(memory: &mut [u8], ptr: wasi32::uintptr_t, t: T) -> Result<()>
where
T: PrimInt,
{
enc_raw_byref::<T>(memory, ptr, PrimInt::to_le(t))
}
fn check_slice_of<T>(ptr: wasi32::uintptr_t, len: wasi32::size_t) -> Result<(usize, usize)> {
// check alignment, and that length doesn't overflow
if ptr as usize % align_of::<T>() != 0 {
return Err(Error::EINVAL);
}
let len = dec_usize(len);
let len_bytes = if let Some(len) = size_of::<T>().checked_mul(len) {
len
} else {
return Err(Error::EOVERFLOW);
};
Ok((len, len_bytes))
}
fn dec_raw_slice_of<'memory, T>(
memory: &'memory [u8],
ptr: wasi32::uintptr_t,
len: wasi32::size_t,
) -> Result<&'memory [T]> {
let (len, len_bytes) = check_slice_of::<T>(ptr, len)?;
let ptr = dec_ptr(memory, ptr, len_bytes)? as *const T;
Ok(unsafe { slice::from_raw_parts(ptr, len) })
}
fn dec_raw_slice_of_mut<'memory, T>(
memory: &'memory mut [u8],
ptr: wasi32::uintptr_t,
len: wasi32::size_t,
) -> Result<&'memory mut [T]> {
let (len, len_bytes) = check_slice_of::<T>(ptr, len)?;
let ptr = dec_ptr_mut(memory, ptr, len_bytes)? as *mut T;
Ok(unsafe { slice::from_raw_parts_mut(ptr, len) })
}
fn raw_slice_for_enc<'memory, T>(
memory: &'memory mut [u8],
slice: &[T],
ptr: wasi32::uintptr_t,
) -> Result<&'memory mut [T]> {
// check alignment
if ptr as usize % align_of::<T>() != 0 {
return Err(Error::EINVAL);
}
// check that length doesn't overflow
let len_bytes = if let Some(len) = size_of::<T>().checked_mul(slice.len()) {
len
} else {
return Err(Error::EOVERFLOW);
};
// get the pointer into guest memory
let ptr = dec_ptr_mut(memory, ptr, len_bytes)? as *mut T;
Ok(unsafe { slice::from_raw_parts_mut(ptr, slice.len()) })
}
pub(crate) fn dec_slice_of_u8<'memory>(
memory: &'memory [u8],
ptr: wasi32::uintptr_t,
len: wasi32::size_t,
) -> Result<&'memory [u8]> {
dec_raw_slice_of::<u8>(memory, ptr, len)
}
pub(crate) fn dec_slice_of_mut_u8<'memory>(
memory: &'memory mut [u8],
ptr: wasi32::uintptr_t,
len: wasi32::size_t,
) -> Result<&'memory mut [u8]> {
dec_raw_slice_of_mut::<u8>(memory, ptr, len)
}
pub(crate) fn enc_slice_of_u8(
memory: &mut [u8],
slice: &[u8],
ptr: wasi32::uintptr_t,
) -> Result<()> {
let output = raw_slice_for_enc::<u8>(memory, slice, ptr)?;
output.copy_from_slice(slice);
Ok(())
}
pub(crate) fn enc_slice_of_wasi32_uintptr(
memory: &mut [u8],
slice: &[wasi32::uintptr_t],
ptr: wasi32::uintptr_t,
) -> Result<()> {
let mut output_iter = raw_slice_for_enc::<wasi32::uintptr_t>(memory, slice, ptr)?.into_iter();
for p in slice {
*output_iter.next().unwrap() = PrimInt::to_le(*p);
}
Ok(())
}
macro_rules! dec_enc_scalar {
( $ty:ident, $dec_byref:ident, $enc_byref:ident) => {
pub(crate) fn $dec_byref(memory: &mut [u8], ptr: wasi32::uintptr_t) -> Result<wasi::$ty> {
dec_int_byref::<wasi::$ty>(memory, ptr)
}
pub(crate) fn $enc_byref(
memory: &mut [u8],
ptr: wasi32::uintptr_t,
x: wasi::$ty,
) -> Result<()> {
enc_int_byref::<wasi::$ty>(memory, ptr, x)
}
};
}
pub(crate) fn dec_ciovec_slice(
memory: &[u8],
ptr: wasi32::uintptr_t,
len: wasi32::size_t,
) -> Result<Vec<host::__wasi_ciovec_t>> {
let raw_slice = dec_raw_slice_of::<wasi32::__wasi_ciovec_t>(memory, ptr, len)?;
raw_slice
.iter()
.map(|raw_iov| {
let len = dec_usize(PrimInt::from_le(raw_iov.buf_len));
let buf = PrimInt::from_le(raw_iov.buf);
Ok(host::__wasi_ciovec_t {
buf: dec_ptr(memory, buf, len)? as *const u8,
buf_len: len,
})
})
.collect()
}
pub(crate) fn dec_iovec_slice(
memory: &[u8],
ptr: wasi32::uintptr_t,
len: wasi32::size_t,
) -> Result<Vec<host::__wasi_iovec_t>> {
let raw_slice = dec_raw_slice_of::<wasi32::__wasi_iovec_t>(memory, ptr, len)?;
raw_slice
.iter()
.map(|raw_iov| {
let len = dec_usize(PrimInt::from_le(raw_iov.buf_len));
let buf = PrimInt::from_le(raw_iov.buf);
Ok(host::__wasi_iovec_t {
buf: dec_ptr(memory, buf, len)? as *mut u8,
buf_len: len,
})
})
.collect()
}
dec_enc_scalar!(__wasi_clockid_t, dec_clockid_byref, enc_clockid_byref);
dec_enc_scalar!(__wasi_errno_t, dec_errno_byref, enc_errno_byref);
dec_enc_scalar!(__wasi_exitcode_t, dec_exitcode_byref, enc_exitcode_byref);
dec_enc_scalar!(__wasi_fd_t, dec_fd_byref, enc_fd_byref);
dec_enc_scalar!(__wasi_fdflags_t, dec_fdflags_byref, enc_fdflags_byref);
dec_enc_scalar!(__wasi_device_t, dev_device_byref, enc_device_byref);
dec_enc_scalar!(__wasi_inode_t, dev_inode_byref, enc_inode_byref);
dec_enc_scalar!(__wasi_linkcount_t, dev_linkcount_byref, enc_linkcount_byref);
pub(crate) fn dec_filestat_byref(
memory: &mut [u8],
filestat_ptr: wasi32::uintptr_t,
) -> Result<wasi::__wasi_filestat_t> {
let raw = dec_raw_byref::<wasi::__wasi_filestat_t>(memory, filestat_ptr)?;
Ok(wasi::__wasi_filestat_t {
st_dev: PrimInt::from_le(raw.st_dev),
st_ino: PrimInt::from_le(raw.st_ino),
st_filetype: PrimInt::from_le(raw.st_filetype),
st_nlink: PrimInt::from_le(raw.st_nlink),
st_size: PrimInt::from_le(raw.st_size),
st_atim: PrimInt::from_le(raw.st_atim),
st_mtim: PrimInt::from_le(raw.st_mtim),
st_ctim: PrimInt::from_le(raw.st_ctim),
})
}
pub(crate) fn enc_filestat_byref(
memory: &mut [u8],
filestat_ptr: wasi32::uintptr_t,
filestat: wasi::__wasi_filestat_t,
) -> Result<()> {
let raw = wasi::__wasi_filestat_t {
st_dev: PrimInt::to_le(filestat.st_dev),
st_ino: PrimInt::to_le(filestat.st_ino),
st_filetype: PrimInt::to_le(filestat.st_filetype),
st_nlink: PrimInt::to_le(filestat.st_nlink),
st_size: PrimInt::to_le(filestat.st_size),
st_atim: PrimInt::to_le(filestat.st_atim),
st_mtim: PrimInt::to_le(filestat.st_mtim),
st_ctim: PrimInt::to_le(filestat.st_ctim),
};
enc_raw_byref::<wasi::__wasi_filestat_t>(memory, filestat_ptr, raw)
}
pub(crate) fn dec_fdstat_byref(
memory: &mut [u8],
fdstat_ptr: wasi32::uintptr_t,
) -> Result<wasi::__wasi_fdstat_t> {
let raw = dec_raw_byref::<wasi::__wasi_fdstat_t>(memory, fdstat_ptr)?;
Ok(wasi::__wasi_fdstat_t {
fs_filetype: PrimInt::from_le(raw.fs_filetype),
fs_flags: PrimInt::from_le(raw.fs_flags),
fs_rights_base: PrimInt::from_le(raw.fs_rights_base),
fs_rights_inheriting: PrimInt::from_le(raw.fs_rights_inheriting),
})
}
pub(crate) fn enc_fdstat_byref(
memory: &mut [u8],
fdstat_ptr: wasi32::uintptr_t,
fdstat: wasi::__wasi_fdstat_t,
) -> Result<()> {
let raw = wasi::__wasi_fdstat_t {
fs_filetype: PrimInt::to_le(fdstat.fs_filetype),
fs_flags: PrimInt::to_le(fdstat.fs_flags),
fs_rights_base: PrimInt::to_le(fdstat.fs_rights_base),
fs_rights_inheriting: PrimInt::to_le(fdstat.fs_rights_inheriting),
};
enc_raw_byref::<wasi::__wasi_fdstat_t>(memory, fdstat_ptr, raw)
}
dec_enc_scalar!(__wasi_filedelta_t, dec_filedelta_byref, enc_filedelta_byref);
dec_enc_scalar!(__wasi_filesize_t, dec_filesize_byref, enc_filesize_byref);
dec_enc_scalar!(__wasi_filetype_t, dec_filetype_byref, enc_filetype_byref);
dec_enc_scalar!(
__wasi_lookupflags_t,
dec_lookupflags_byref,
enc_lookupflags_byref
);
dec_enc_scalar!(__wasi_oflags_t, dec_oflags_byref, enc_oflags_byref);
pub(crate) fn dec_prestat_byref(
memory: &mut [u8],
prestat_ptr: wasi32::uintptr_t,
) -> Result<host::__wasi_prestat_t> {
let raw = dec_raw_byref::<wasi32::__wasi_prestat_t>(memory, prestat_ptr)?;
match PrimInt::from_le(raw.pr_type) {
wasi::__WASI_PREOPENTYPE_DIR => Ok(host::__wasi_prestat_t {
pr_type: wasi::__WASI_PREOPENTYPE_DIR,
u: host::__wasi_prestat_u {
dir: host::__wasi_prestat_dir {
pr_name_len: dec_usize(PrimInt::from_le(unsafe { raw.u.dir.pr_name_len })),
},
},
}),
_ => Err(Error::EINVAL),
}
}
pub(crate) fn enc_prestat_byref(
memory: &mut [u8],
prestat_ptr: wasi32::uintptr_t,
prestat: host::__wasi_prestat_t,
) -> Result<()> {
let raw = match prestat.pr_type {
wasi::__WASI_PREOPENTYPE_DIR => Ok(wasi32::__wasi_prestat_t {
pr_type: PrimInt::to_le(wasi::__WASI_PREOPENTYPE_DIR),
u: wasi32::__wasi_prestat_u {
dir: wasi32::__wasi_prestat_dir {
pr_name_len: enc_usize(unsafe { prestat.u.dir.pr_name_len }),
},
},
}),
_ => Err(Error::EINVAL),
}?;
enc_raw_byref::<wasi32::__wasi_prestat_t>(memory, prestat_ptr, raw)
}
dec_enc_scalar!(__wasi_rights_t, dec_rights_byref, enc_rights_byref);
dec_enc_scalar!(__wasi_timestamp_t, dec_timestamp_byref, enc_timestamp_byref);
pub(crate) fn dec_usize(size: wasi32::size_t) -> usize {
usize::try_from(size).unwrap()
}
pub(crate) fn enc_usize(size: usize) -> wasi32::size_t {
wasi32::size_t::try_from(size).unwrap()
}
pub(crate) fn enc_usize_byref(
memory: &mut [u8],
usize_ptr: wasi32::uintptr_t,
host_usize: usize,
) -> Result<()> {
enc_int_byref::<wasi32::size_t>(memory, usize_ptr, enc_usize(host_usize))
}
dec_enc_scalar!(__wasi_whence_t, dec_whence_byref, enc_whence_byref);
dec_enc_scalar!(
__wasi_subclockflags_t,
dec_subclockflags_byref,
enc_subclockflags_byref
);
dec_enc_scalar!(
__wasi_eventrwflags_t,
dec_eventrwflags_byref,
enc_eventrwflags_byref
);
dec_enc_scalar!(__wasi_eventtype_t, dec_eventtype_byref, enc_eventtype_byref);
dec_enc_scalar!(__wasi_userdata_t, dec_userdata_byref, enc_userdata_byref);
pub(crate) fn dec_subscriptions(
memory: &mut [u8],
input: wasi32::uintptr_t,
nsubscriptions: wasi32::size_t,
) -> Result<Vec<wasi::__wasi_subscription_t>> {
let raw_input_slice =
dec_raw_slice_of::<wasi::__wasi_subscription_t>(memory, input, nsubscriptions)?;
raw_input_slice
.into_iter()
.map(|raw_subscription| {
let userdata = PrimInt::from_le(raw_subscription.userdata);
let r#type = PrimInt::from_le(raw_subscription.r#type);
let raw_u = raw_subscription.u;
let u = match r#type {
wasi::__WASI_EVENTTYPE_CLOCK => wasi::__wasi_subscription_u {
clock: unsafe {
wasi::__wasi_subscription_clock_t {
identifier: PrimInt::from_le(raw_u.clock.identifier),
clock_id: PrimInt::from_le(raw_u.clock.clock_id),
timeout: PrimInt::from_le(raw_u.clock.timeout),
precision: PrimInt::from_le(raw_u.clock.precision),
flags: PrimInt::from_le(raw_u.clock.flags),
}
},
},
wasi::__WASI_EVENTTYPE_FD_READ | wasi::__WASI_EVENTTYPE_FD_WRITE => {
wasi::__wasi_subscription_u {
fd_readwrite: wasi::__wasi_subscription_fd_readwrite_t {
file_descriptor: PrimInt::from_le(unsafe {
raw_u.fd_readwrite.file_descriptor
}),
},
}
}
_ => return Err(Error::EINVAL),
};
Ok(wasi::__wasi_subscription_t {
userdata,
r#type,
u,
})
})
.collect::<Result<Vec<_>>>()
}
pub(crate) fn enc_events(
memory: &mut [u8],
output: wasi32::uintptr_t,
nsubscriptions: wasi32::size_t,
events: Vec<wasi::__wasi_event_t>,
) -> Result<()> {
let mut raw_output_iter =
dec_raw_slice_of_mut::<wasi::__wasi_event_t>(memory, output, nsubscriptions)?.into_iter();
for event in events.iter() {
*raw_output_iter
.next()
.expect("the number of events cannot exceed the number of subscriptions") = {
let fd_readwrite = unsafe { event.u.fd_readwrite };
wasi::__wasi_event_t {
userdata: PrimInt::to_le(event.userdata),
r#type: PrimInt::to_le(event.r#type),
error: PrimInt::to_le(event.error),
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: PrimInt::to_le(fd_readwrite.nbytes),
flags: PrimInt::to_le(fd_readwrite.flags),
},
},
}
};
}
Ok(())
}
dec_enc_scalar!(__wasi_advice_t, dec_advice_byref, enc_advice_byref);
dec_enc_scalar!(__wasi_fstflags_t, dec_fstflags_byref, enc_fstflags_byref);
dec_enc_scalar!(__wasi_dircookie_t, dec_dircookie_byref, enc_dircookie_byref);

View File

@@ -0,0 +1,24 @@
use crate::wasi;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(unix)] {
mod unix;
pub(crate) use self::unix::*;
pub use self::unix::preopen_dir;
pub(crate) fn errno_from_host(err: i32) -> wasi::__wasi_errno_t {
host_impl::errno_from_nix(nix::errno::from_i32(err)).as_wasi_errno()
}
} else if #[cfg(windows)] {
mod windows;
pub(crate) use self::windows::*;
pub use self::windows::preopen_dir;
pub(crate) fn errno_from_host(err: i32) -> wasi::__wasi_errno_t {
host_impl::errno_from_win(winx::winerror::WinError::from_u32(err as u32))
}
} else {
compile_error!("wasi-common doesn't compile for this platform yet");
}
}

View File

@@ -0,0 +1,289 @@
use super::osfile::OsFile;
use crate::hostcalls_impl::PathGet;
use crate::sys::host_impl;
use crate::sys::unix::str_to_cstring;
use crate::{wasi, Error, Result};
use nix::libc::{self, c_long, c_void};
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
use nix::errno;
use nix::libc::unlinkat;
let path_cstr = str_to_cstring(resolved.path())?;
// nix doesn't expose unlinkat() yet
match unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) } {
0 => Ok(()),
_ => {
let mut e = errno::Errno::last();
// 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 nix::fcntl::AtFlags;
use nix::sys::stat::{fstatat, SFlag};
if e == errno::Errno::EPERM {
if let Ok(stat) = fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlags::AT_SYMLINK_NOFOLLOW,
) {
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR) {
e = errno::Errno::EISDIR;
}
} else {
e = errno::Errno::last();
}
}
Err(host_impl::errno_from_nix(e))
}
}
}
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use nix::{errno::Errno, fcntl::AtFlags, libc::symlinkat, sys::stat::fstatat};
let old_path_cstr = str_to_cstring(old_path)?;
let new_path_cstr = str_to_cstring(resolved.path())?;
log::debug!("path_symlink old_path = {:?}", old_path);
log::debug!("path_symlink resolved = {:?}", resolved);
let res = unsafe {
symlinkat(
old_path_cstr.as_ptr(),
resolved.dirfd().as_raw_fd(),
new_path_cstr.as_ptr(),
)
};
if res != 0 {
match Errno::last() {
Errno::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 = resolved.path().trim_end_matches('/');
if let Ok(_) = fstatat(
resolved.dirfd().as_raw_fd(),
new_path,
AtFlags::AT_SYMLINK_NOFOLLOW,
) {
Err(Error::EEXIST)
} else {
Err(Error::ENOTDIR)
}
}
x => Err(host_impl::errno_from_nix(x)),
}
} else {
Ok(())
}
}
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use nix::{errno::Errno, fcntl::AtFlags, libc::renameat, sys::stat::fstatat};
let old_path_cstr = str_to_cstring(resolved_old.path())?;
let new_path_cstr = str_to_cstring(resolved_new.path())?;
let res = unsafe {
renameat(
resolved_old.dirfd().as_raw_fd(),
old_path_cstr.as_ptr(),
resolved_new.dirfd().as_raw_fd(),
new_path_cstr.as_ptr(),
)
};
if res != 0 {
// 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.
match Errno::last() {
Errno::ENOENT => {
// check if the source path exists
if let Ok(_) = fstatat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
AtFlags::AT_SYMLINK_NOFOLLOW,
) {
// check if destination contains a trailing slash
if resolved_new.path().contains('/') {
Err(Error::ENOTDIR)
} else {
Err(Error::ENOENT)
}
} else {
Err(Error::ENOENT)
}
}
x => Err(host_impl::errno_from_nix(x)),
}
} else {
Ok(())
}
}
pub(crate) fn fd_readdir(
os_file: &mut OsFile,
host_buf: &mut [u8],
cookie: wasi::__wasi_dircookie_t,
) -> Result<usize> {
use crate::sys::unix::bsd::osfile::DirStream;
use libc::{fdopendir, readdir, rewinddir, seekdir, telldir};
use nix::errno::Errno;
use std::mem::ManuallyDrop;
use std::sync::Mutex;
let dir_stream = match os_file.dir_stream {
Some(ref mut dir_stream) => dir_stream,
None => {
let file = os_file.file.try_clone()?;
let dir_ptr = unsafe { fdopendir(file.as_raw_fd()) };
os_file.dir_stream.get_or_insert(Mutex::new(DirStream {
file: ManuallyDrop::new(file),
dir_ptr,
}))
}
};
let dir_stream = dir_stream.lock().unwrap();
let host_buf_ptr = host_buf.as_mut_ptr();
let host_buf_len = host_buf.len();
if cookie != wasi::__WASI_DIRCOOKIE_START {
unsafe { seekdir(dir_stream.dir_ptr, cookie as c_long) };
} else {
unsafe { rewinddir(dir_stream.dir_ptr) };
}
let mut left = host_buf_len;
let mut host_buf_offset: usize = 0;
loop {
let host_entry = unsafe { readdir(dir_stream.dir_ptr) };
if host_entry.is_null() {
// FIXME
// Currently, these are verified to be correct on macOS.
// Need to still verify these on other BSD-based OSes.
match Errno::last() {
Errno::EBADF => return Err(Error::EBADF),
Errno::EFAULT => return Err(Error::EFAULT),
Errno::EIO => return Err(Error::EIO),
_ => break, // not an error
}
}
let mut entry: wasi::__wasi_dirent_t =
host_impl::dirent_from_host(&unsafe { *host_entry })?;
// Set d_next manually:
// * on macOS d_seekoff is not set for some reason
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
// not equivalent to the value read from telldir call
entry.d_next = unsafe { telldir(dir_stream.dir_ptr) } as wasi::__wasi_dircookie_t;
log::debug!("fd_readdir entry = {:?}", entry);
let name_len = entry.d_namlen.try_into()?;
let required_space = std::mem::size_of_val(&entry) + name_len;
if required_space > left {
break;
}
unsafe {
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
as *mut wasi::__wasi_dirent_t;
*ptr = entry;
}
host_buf_offset += std::mem::size_of_val(&entry);
let name_ptr = unsafe { *host_entry }.d_name.as_ptr();
unsafe {
std::ptr::copy_nonoverlapping(
name_ptr as *const _,
host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut _,
name_len,
)
};
host_buf_offset += name_len;
left -= required_space;
}
Ok(host_buf_len - left)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub(crate) fn fd_advise(
file: &File,
advice: wasi::__wasi_advice_t,
offset: wasi::__wasi_filesize_t,
len: wasi::__wasi_filesize_t,
) -> Result<()> {
use nix::errno::Errno;
match advice {
wasi::__WASI_ADVICE_DONTNEED => return Ok(()),
// unfortunately, the advisory syscall in macOS doesn't take any flags of this
// sort (unlike on Linux), hence, they are left here as a noop
wasi::__WASI_ADVICE_SEQUENTIAL
| wasi::__WASI_ADVICE_WILLNEED
| wasi::__WASI_ADVICE_NOREUSE
| wasi::__WASI_ADVICE_RANDOM
| wasi::__WASI_ADVICE_NORMAL => {}
_ => return Err(Error::EINVAL),
}
// From macOS man pages:
// F_RDADVISE Issue an advisory read async with no copy to user.
//
// The F_RDADVISE command operates on the following structure which holds information passed from
// the user to the system:
//
// struct radvisory {
// off_t ra_offset; /* offset into the file */
// int ra_count; /* size of the read */
// };
let advisory = libc::radvisory {
ra_offset: offset.try_into()?,
ra_count: len.try_into()?,
};
let res = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_RDADVISE, &advisory) };
Errno::result(res).map(|_| ()).map_err(Error::from)
}
// TODO
// It seems that at least some BSDs do support `posix_fadvise`,
// so we should investigate further.
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub(crate) fn fd_advise(
_file: &File,
advice: wasi::__wasi_advice_t,
_offset: wasi::__wasi_filesize_t,
_len: wasi::__wasi_filesize_t,
) -> Result<()> {
match advice {
wasi::__WASI_ADVICE_DONTNEED
| wasi::__WASI_ADVICE_SEQUENTIAL
| wasi::__WASI_ADVICE_WILLNEED
| wasi::__WASI_ADVICE_NOREUSE
| wasi::__WASI_ADVICE_RANDOM
| wasi::__WASI_ADVICE_NORMAL => {}
_ => return Err(Error::EINVAL),
}
Ok(())
}

View File

@@ -0,0 +1,82 @@
pub(crate) mod hostcalls_impl;
pub(crate) mod osfile;
pub(crate) mod fdentry_impl {
use crate::{sys::host_impl, Result};
use std::os::unix::prelude::AsRawFd;
pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result<bool> {
let res = libc::isatty(fd.as_raw_fd());
if res == 1 {
// isatty() returns 1 if fd is an open file descriptor referring to a terminal...
Ok(true)
} else {
// ... otherwise 0 is returned, and errno is set to indicate the error.
match nix::errno::Errno::last() {
nix::errno::Errno::ENOTTY => Ok(false),
x => Err(host_impl::errno_from_nix(x)),
}
}
}
}
pub(crate) mod host_impl {
use super::super::host_impl::dirent_filetype_from_host;
use crate::{wasi, Result};
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC;
pub(crate) fn dirent_from_host(
host_entry: &nix::libc::dirent,
) -> Result<wasi::__wasi_dirent_t> {
let mut entry = unsafe { std::mem::zeroed::<wasi::__wasi_dirent_t>() };
let d_type = dirent_filetype_from_host(host_entry)?;
entry.d_ino = host_entry.d_ino;
entry.d_next = host_entry.d_seekoff;
entry.d_namlen = u32::from(host_entry.d_namlen);
entry.d_type = d_type;
Ok(entry)
}
}
pub(crate) mod fs_helpers {
use cfg_if::cfg_if;
pub(crate) fn utime_now() -> libc::c_long {
cfg_if! {
if #[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "ios",
target_os = "dragonfly"
))] {
-1
} else if #[cfg(target_os = "openbsd")] {
// https://github.com/openbsd/src/blob/master/sys/sys/stat.h#L187
-2
} else if #[cfg(target_os = "netbsd" )] {
// http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/sys/stat.h?rev=1.69&content-type=text/x-cvsweb-markup&only_with_tag=MAIN
1_073_741_823
}
}
}
pub(crate) fn utime_omit() -> libc::c_long {
cfg_if! {
if #[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "ios",
target_os = "dragonfly"
))] {
-2
} else if #[cfg(target_os = "openbsd")] {
// https://github.com/openbsd/src/blob/master/sys/sys/stat.h#L187
-1
} else if #[cfg(target_os = "netbsd")] {
// http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/sys/stat.h?rev=1.69&content-type=text/x-cvsweb-markup&only_with_tag=MAIN
1_073_741_822
}
}
}
}

View File

@@ -0,0 +1,52 @@
use std::fs;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::os::unix::prelude::{AsRawFd, RawFd};
use std::sync::Mutex;
#[derive(Debug)]
pub(crate) struct DirStream {
pub(crate) file: ManuallyDrop<fs::File>,
pub(crate) dir_ptr: *mut libc::DIR,
}
impl Drop for DirStream {
fn drop(&mut self) {
unsafe { libc::closedir(self.dir_ptr) };
}
}
#[derive(Debug)]
pub(crate) struct OsFile {
pub(crate) file: fs::File,
pub(crate) dir_stream: Option<Mutex<DirStream>>,
}
impl From<fs::File> for OsFile {
fn from(file: fs::File) -> Self {
Self {
file,
dir_stream: None,
}
}
}
impl AsRawFd for OsFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl Deref for OsFile {
type Target = fs::File;
fn deref(&self) -> &Self::Target {
&self.file
}
}
impl DerefMut for OsFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.file
}
}

View File

@@ -0,0 +1,216 @@
// Based on src/dir.rs from nix
#![allow(unused)] // temporarily, until BSD catches up with this change
use crate::hostcalls_impl::FileType;
use libc;
use nix::{errno::Errno, Error, Result};
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::{ffi, ptr};
#[cfg(target_os = "linux")]
use libc::{dirent64 as dirent, readdir64_r as readdir_r};
#[cfg(not(target_os = "linux"))]
use libc::{dirent, readdir_r};
/// An open directory.
///
/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences:
/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing
/// if the path represents a file or directory).
/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc.
/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd`
/// after the `Dir` is dropped.
/// * can be iterated through multiple times without closing and reopening the file
/// descriptor. Each iteration rewinds when finished.
/// * returns entries for `.` (current directory) and `..` (parent directory).
/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
/// does).
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct Dir(ptr::NonNull<libc::DIR>);
impl Dir {
/// Converts from a descriptor-based object, closing the descriptor on success or failure.
#[inline]
pub(crate) fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
unsafe { Self::from_fd(fd.into_raw_fd()) }
}
/// Converts from a file descriptor, closing it on success or failure.
unsafe fn from_fd(fd: RawFd) -> Result<Self> {
let d = libc::fdopendir(fd);
if d.is_null() {
let e = Error::last();
libc::close(fd);
return Err(e);
};
// Always guaranteed to be non-null by the previous check
Ok(Self(ptr::NonNull::new(d).unwrap()))
}
/// Set the position of the directory stream, see `seekdir(3)`.
#[cfg(not(target_os = "android"))]
pub(crate) fn seek(&mut self, loc: SeekLoc) {
unsafe { libc::seekdir(self.0.as_ptr(), loc.0) }
}
/// Reset directory stream, see `rewinddir(3)`.
pub(crate) fn rewind(&mut self) {
unsafe { libc::rewinddir(self.0.as_ptr()) }
}
/// Get the current position in the directory stream.
///
/// If this location is given to `Dir::seek`, the entries up to the previously returned
/// will be omitted and the iteration will start from the currently pending directory entry.
#[cfg(not(target_os = "android"))]
#[allow(dead_code)]
pub(crate) fn tell(&self) -> SeekLoc {
let loc = unsafe { libc::telldir(self.0.as_ptr()) };
SeekLoc(loc)
}
}
// `Dir` is not `Sync`. With the current implementation, it could be, but according to
// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
// call `readdir` simultaneously from multiple threads.
//
// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
unsafe impl Send for Dir {}
impl AsRawFd for Dir {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.0.as_ptr()) }
}
}
impl Drop for Dir {
fn drop(&mut self) {
unsafe { libc::closedir(self.0.as_ptr()) };
}
}
pub(crate) struct IntoIter(Dir);
impl Iterator for IntoIter {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
// Note: POSIX specifies that portable applications should dynamically allocate a
// buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
// for the NUL byte. It doesn't look like the std library does this; it just uses
// fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
// Probably fine here too then.
//
// See `impl Iterator for ReadDir` [1] for more details.
// [1] https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs
let mut ent = std::mem::MaybeUninit::<dirent>::uninit();
let mut result = ptr::null_mut();
if let Err(e) = Errno::result(readdir_r(
(self.0).0.as_ptr(),
ent.as_mut_ptr(),
&mut result,
)) {
return Some(Err(e));
}
if result.is_null() {
None
} else {
assert_eq!(result, ent.as_mut_ptr(), "readdir_r specification violated");
Some(Ok(Entry(ent.assume_init())))
}
}
}
}
impl IntoIterator for Dir {
type IntoIter = IntoIter;
type Item = Result<Entry>;
fn into_iter(self) -> IntoIter {
IntoIter(self)
}
}
/// A directory entry, similar to `std::fs::DirEntry`.
///
/// Note that unlike the std version, this may represent the `.` or `..` entries.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub(crate) struct Entry(dirent);
pub(crate) type Type = FileType;
impl Entry {
/// Returns the inode number (`d_ino`) of the underlying `dirent`.
#[cfg(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "ios",
target_os = "l4re",
target_os = "linux",
target_os = "macos",
target_os = "solaris"
))]
pub(crate) fn ino(&self) -> u64 {
self.0.d_ino.into()
}
/// Returns the inode number (`d_fileno`) of the underlying `dirent`.
#[cfg(not(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "ios",
target_os = "l4re",
target_os = "linux",
target_os = "macos",
target_os = "solaris"
)))]
pub(crate) fn ino(&self) -> u64 {
u64::from(self.0.d_fileno)
}
/// Returns the bare file name of this directory entry without any other leading path component.
pub(crate) fn file_name(&self) -> &ffi::CStr {
unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) }
}
/// Returns the type of this directory entry, if known.
///
/// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
/// notably, some Linux filesystems don't implement this. The caller should use `stat` or
/// `fstat` if this returns `None`.
pub(crate) fn file_type(&self) -> FileType {
match self.0.d_type {
libc::DT_CHR => Type::CharacterDevice,
libc::DT_DIR => Type::Directory,
libc::DT_BLK => Type::BlockDevice,
libc::DT_REG => Type::RegularFile,
libc::DT_LNK => Type::Symlink,
/* libc::DT_UNKNOWN | libc::DT_SOCK | libc::DT_FIFO */ _ => Type::Unknown,
}
}
#[cfg(target_os = "linux")]
pub(crate) fn seek_loc(&self) -> SeekLoc {
unsafe { SeekLoc::from_raw(self.0.d_off) }
}
}
#[cfg(not(target_os = "android"))]
#[derive(Clone, Copy, Debug)]
pub(crate) struct SeekLoc(libc::c_long);
#[cfg(not(target_os = "android"))]
impl SeekLoc {
pub(crate) unsafe fn from_raw(loc: i64) -> Self {
Self(loc.into())
}
pub(crate) fn to_raw(&self) -> i64 {
self.0.into()
}
}

View File

@@ -0,0 +1,135 @@
use crate::fdentry::Descriptor;
use crate::{wasi, Error, Result};
use std::io;
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd};
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub(crate) use super::linux::osfile::*;
pub(crate) use super::linux::fdentry_impl::*;
} else if #[cfg(any(
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "ios",
target_os = "dragonfly"
))] {
pub(crate) use super::bsd::osfile::*;
pub(crate) use super::bsd::fdentry_impl::*;
}
}
impl AsRawFd for Descriptor {
fn as_raw_fd(&self) -> RawFd {
match self {
Self::OsFile(file) => file.as_raw_fd(),
Self::Stdin => io::stdin().as_raw_fd(),
Self::Stdout => io::stdout().as_raw_fd(),
Self::Stderr => io::stderr().as_raw_fd(),
}
}
}
/// This function is unsafe because it operates on a raw file descriptor.
pub(crate) unsafe fn determine_type_and_access_rights<Fd: AsRawFd>(
fd: &Fd,
) -> Result<(
wasi::__wasi_filetype_t,
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(fd)?;
use nix::fcntl::{fcntl, OFlag, F_GETFL};
let flags_bits = fcntl(fd.as_raw_fd(), F_GETFL)?;
let flags = OFlag::from_bits_truncate(flags_bits);
let accmode = flags & OFlag::O_ACCMODE;
if accmode == OFlag::O_RDONLY {
rights_base &= !wasi::__WASI_RIGHT_FD_WRITE;
} else if accmode == OFlag::O_WRONLY {
rights_base &= !wasi::__WASI_RIGHT_FD_READ;
}
Ok((file_type, rights_base, rights_inheriting))
}
/// This function is unsafe because it operates on a raw file descriptor.
pub(crate) unsafe fn determine_type_rights<Fd: AsRawFd>(
fd: &Fd,
) -> Result<(
wasi::__wasi_filetype_t,
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
let (file_type, rights_base, rights_inheriting) = {
// we just make a `File` here for convenience; we don't want it to close when it drops
let file = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd.as_raw_fd()));
let ft = file.metadata()?.file_type();
if ft.is_block_device() {
log::debug!("Host fd {:?} is a block device", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_BLOCK_DEVICE,
wasi::RIGHTS_BLOCK_DEVICE_BASE,
wasi::RIGHTS_BLOCK_DEVICE_INHERITING,
)
} else if ft.is_char_device() {
log::debug!("Host fd {:?} is a char device", fd.as_raw_fd());
if isatty(fd)? {
(
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
wasi::RIGHTS_TTY_BASE,
wasi::RIGHTS_TTY_BASE,
)
} else {
(
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
wasi::RIGHTS_CHARACTER_DEVICE_BASE,
wasi::RIGHTS_CHARACTER_DEVICE_INHERITING,
)
}
} else if ft.is_dir() {
log::debug!("Host fd {:?} is a directory", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_DIRECTORY,
wasi::RIGHTS_DIRECTORY_BASE,
wasi::RIGHTS_DIRECTORY_INHERITING,
)
} else if ft.is_file() {
log::debug!("Host fd {:?} is a file", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_REGULAR_FILE,
wasi::RIGHTS_REGULAR_FILE_BASE,
wasi::RIGHTS_REGULAR_FILE_INHERITING,
)
} else if ft.is_socket() {
log::debug!("Host fd {:?} is a socket", fd.as_raw_fd());
use nix::sys::socket;
match socket::getsockopt(fd.as_raw_fd(), socket::sockopt::SockType)? {
socket::SockType::Datagram => (
wasi::__WASI_FILETYPE_SOCKET_DGRAM,
wasi::RIGHTS_SOCKET_BASE,
wasi::RIGHTS_SOCKET_INHERITING,
),
socket::SockType::Stream => (
wasi::__WASI_FILETYPE_SOCKET_STREAM,
wasi::RIGHTS_SOCKET_BASE,
wasi::RIGHTS_SOCKET_INHERITING,
),
_ => return Err(Error::EINVAL),
}
} else if ft.is_fifo() {
log::debug!("Host fd {:?} is a fifo", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_UNKNOWN,
wasi::RIGHTS_REGULAR_FILE_BASE,
wasi::RIGHTS_REGULAR_FILE_INHERITING,
)
} else {
log::debug!("Host fd {:?} is unknown", fd.as_raw_fd());
return Err(Error::EINVAL);
}
};
Ok((file_type, rights_base, rights_inheriting))
}

View File

@@ -0,0 +1,246 @@
//! WASI host types specific to *nix host.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
use crate::hostcalls_impl::FileType;
use crate::{helpers, wasi, Error, Result};
use log::warn;
use std::ffi::OsStr;
use std::os::unix::prelude::OsStrExt;
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub(crate) use super::linux::host_impl::*;
} else if #[cfg(any(
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "ios",
target_os = "dragonfly"
))] {
pub(crate) use super::bsd::host_impl::*;
}
}
pub(crate) fn errno_from_nix(errno: nix::errno::Errno) -> Error {
match errno {
nix::errno::Errno::EPERM => Error::EPERM,
nix::errno::Errno::ENOENT => Error::ENOENT,
nix::errno::Errno::ESRCH => Error::ESRCH,
nix::errno::Errno::EINTR => Error::EINTR,
nix::errno::Errno::EIO => Error::EIO,
nix::errno::Errno::ENXIO => Error::ENXIO,
nix::errno::Errno::E2BIG => Error::E2BIG,
nix::errno::Errno::ENOEXEC => Error::ENOEXEC,
nix::errno::Errno::EBADF => Error::EBADF,
nix::errno::Errno::ECHILD => Error::ECHILD,
nix::errno::Errno::EAGAIN => Error::EAGAIN,
nix::errno::Errno::ENOMEM => Error::ENOMEM,
nix::errno::Errno::EACCES => Error::EACCES,
nix::errno::Errno::EFAULT => Error::EFAULT,
nix::errno::Errno::EBUSY => Error::EBUSY,
nix::errno::Errno::EEXIST => Error::EEXIST,
nix::errno::Errno::EXDEV => Error::EXDEV,
nix::errno::Errno::ENODEV => Error::ENODEV,
nix::errno::Errno::ENOTDIR => Error::ENOTDIR,
nix::errno::Errno::EISDIR => Error::EISDIR,
nix::errno::Errno::EINVAL => Error::EINVAL,
nix::errno::Errno::ENFILE => Error::ENFILE,
nix::errno::Errno::EMFILE => Error::EMFILE,
nix::errno::Errno::ENOTTY => Error::ENOTTY,
nix::errno::Errno::ETXTBSY => Error::ETXTBSY,
nix::errno::Errno::EFBIG => Error::EFBIG,
nix::errno::Errno::ENOSPC => Error::ENOSPC,
nix::errno::Errno::ESPIPE => Error::ESPIPE,
nix::errno::Errno::EROFS => Error::EROFS,
nix::errno::Errno::EMLINK => Error::EMLINK,
nix::errno::Errno::EPIPE => Error::EPIPE,
nix::errno::Errno::EDOM => Error::EDOM,
nix::errno::Errno::ERANGE => Error::ERANGE,
nix::errno::Errno::EDEADLK => Error::EDEADLK,
nix::errno::Errno::ENAMETOOLONG => Error::ENAMETOOLONG,
nix::errno::Errno::ENOLCK => Error::ENOLCK,
nix::errno::Errno::ENOSYS => Error::ENOSYS,
nix::errno::Errno::ENOTEMPTY => Error::ENOTEMPTY,
nix::errno::Errno::ELOOP => Error::ELOOP,
nix::errno::Errno::ENOMSG => Error::ENOMSG,
nix::errno::Errno::EIDRM => Error::EIDRM,
nix::errno::Errno::ENOLINK => Error::ENOLINK,
nix::errno::Errno::EPROTO => Error::EPROTO,
nix::errno::Errno::EMULTIHOP => Error::EMULTIHOP,
nix::errno::Errno::EBADMSG => Error::EBADMSG,
nix::errno::Errno::EOVERFLOW => Error::EOVERFLOW,
nix::errno::Errno::EILSEQ => Error::EILSEQ,
nix::errno::Errno::ENOTSOCK => Error::ENOTSOCK,
nix::errno::Errno::EDESTADDRREQ => Error::EDESTADDRREQ,
nix::errno::Errno::EMSGSIZE => Error::EMSGSIZE,
nix::errno::Errno::EPROTOTYPE => Error::EPROTOTYPE,
nix::errno::Errno::ENOPROTOOPT => Error::ENOPROTOOPT,
nix::errno::Errno::EPROTONOSUPPORT => Error::EPROTONOSUPPORT,
nix::errno::Errno::EAFNOSUPPORT => Error::EAFNOSUPPORT,
nix::errno::Errno::EADDRINUSE => Error::EADDRINUSE,
nix::errno::Errno::EADDRNOTAVAIL => Error::EADDRNOTAVAIL,
nix::errno::Errno::ENETDOWN => Error::ENETDOWN,
nix::errno::Errno::ENETUNREACH => Error::ENETUNREACH,
nix::errno::Errno::ENETRESET => Error::ENETRESET,
nix::errno::Errno::ECONNABORTED => Error::ECONNABORTED,
nix::errno::Errno::ECONNRESET => Error::ECONNRESET,
nix::errno::Errno::ENOBUFS => Error::ENOBUFS,
nix::errno::Errno::EISCONN => Error::EISCONN,
nix::errno::Errno::ENOTCONN => Error::ENOTCONN,
nix::errno::Errno::ETIMEDOUT => Error::ETIMEDOUT,
nix::errno::Errno::ECONNREFUSED => Error::ECONNREFUSED,
nix::errno::Errno::EHOSTUNREACH => Error::EHOSTUNREACH,
nix::errno::Errno::EALREADY => Error::EALREADY,
nix::errno::Errno::EINPROGRESS => Error::EINPROGRESS,
nix::errno::Errno::ESTALE => Error::ESTALE,
nix::errno::Errno::EDQUOT => Error::EDQUOT,
nix::errno::Errno::ECANCELED => Error::ECANCELED,
nix::errno::Errno::EOWNERDEAD => Error::EOWNERDEAD,
nix::errno::Errno::ENOTRECOVERABLE => Error::ENOTRECOVERABLE,
other => {
warn!("Unknown error from nix: {}", other);
Error::ENOSYS
}
}
}
pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> nix::fcntl::OFlag {
use nix::fcntl::OFlag;
let mut nix_flags = OFlag::empty();
if fdflags & wasi::__WASI_FDFLAG_APPEND != 0 {
nix_flags.insert(OFlag::O_APPEND);
}
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0 {
nix_flags.insert(OFlag::O_DSYNC);
}
if fdflags & wasi::__WASI_FDFLAG_NONBLOCK != 0 {
nix_flags.insert(OFlag::O_NONBLOCK);
}
if fdflags & wasi::__WASI_FDFLAG_RSYNC != 0 {
nix_flags.insert(O_RSYNC);
}
if fdflags & wasi::__WASI_FDFLAG_SYNC != 0 {
nix_flags.insert(OFlag::O_SYNC);
}
nix_flags
}
pub(crate) fn fdflags_from_nix(oflags: nix::fcntl::OFlag) -> wasi::__wasi_fdflags_t {
use nix::fcntl::OFlag;
let mut fdflags = 0;
if oflags.contains(OFlag::O_APPEND) {
fdflags |= wasi::__WASI_FDFLAG_APPEND;
}
if oflags.contains(OFlag::O_DSYNC) {
fdflags |= wasi::__WASI_FDFLAG_DSYNC;
}
if oflags.contains(OFlag::O_NONBLOCK) {
fdflags |= wasi::__WASI_FDFLAG_NONBLOCK;
}
if oflags.contains(O_RSYNC) {
fdflags |= wasi::__WASI_FDFLAG_RSYNC;
}
if oflags.contains(OFlag::O_SYNC) {
fdflags |= wasi::__WASI_FDFLAG_SYNC;
}
fdflags
}
pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> nix::fcntl::OFlag {
use nix::fcntl::OFlag;
let mut nix_flags = OFlag::empty();
if oflags & wasi::__WASI_O_CREAT != 0 {
nix_flags.insert(OFlag::O_CREAT);
}
if oflags & wasi::__WASI_O_DIRECTORY != 0 {
nix_flags.insert(OFlag::O_DIRECTORY);
}
if oflags & wasi::__WASI_O_EXCL != 0 {
nix_flags.insert(OFlag::O_EXCL);
}
if oflags & wasi::__WASI_O_TRUNC != 0 {
nix_flags.insert(OFlag::O_TRUNC);
}
nix_flags
}
pub(crate) fn filetype_from_nix(sflags: nix::sys::stat::SFlag) -> FileType {
use nix::sys::stat::SFlag;
if sflags.contains(SFlag::S_IFCHR) {
FileType::CharacterDevice
} else if sflags.contains(SFlag::S_IFBLK) {
FileType::BlockDevice
} else if sflags.contains(SFlag::S_IFSOCK) {
FileType::SocketStream
} else if sflags.contains(SFlag::S_IFDIR) {
FileType::Directory
} else if sflags.contains(SFlag::S_IFREG) {
FileType::RegularFile
} else if sflags.contains(SFlag::S_IFLNK) {
FileType::Symlink
} else {
FileType::Unknown
}
}
pub(crate) fn filestat_from_nix(
filestat: nix::sys::stat::FileStat,
) -> Result<wasi::__wasi_filestat_t> {
use std::convert::TryFrom;
fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result<wasi::__wasi_timestamp_t> {
secs.checked_mul(1_000_000_000)
.and_then(|sec_nsec| sec_nsec.checked_add(nsecs))
.ok_or(Error::EOVERFLOW)
}
let filetype = nix::sys::stat::SFlag::from_bits_truncate(filestat.st_mode);
let dev = wasi::__wasi_device_t::try_from(filestat.st_dev)?;
let ino = wasi::__wasi_inode_t::try_from(filestat.st_ino)?;
let st_atim = filestat_to_timestamp(filestat.st_atime as u64, filestat.st_atime_nsec as u64)?;
let st_ctim = filestat_to_timestamp(filestat.st_ctime as u64, filestat.st_ctime_nsec as u64)?;
let st_mtim = filestat_to_timestamp(filestat.st_mtime as u64, filestat.st_mtime_nsec as u64)?;
Ok(wasi::__wasi_filestat_t {
st_dev: dev,
st_ino: ino,
st_nlink: filestat.st_nlink as wasi::__wasi_linkcount_t,
st_size: filestat.st_size as wasi::__wasi_filesize_t,
st_atim,
st_ctim,
st_mtim,
st_filetype: filetype_from_nix(filetype).to_wasi(),
})
}
pub(crate) fn dirent_filetype_from_host(
host_entry: &nix::libc::dirent,
) -> Result<wasi::__wasi_filetype_t> {
match host_entry.d_type {
libc::DT_FIFO => Ok(wasi::__WASI_FILETYPE_UNKNOWN),
libc::DT_CHR => Ok(wasi::__WASI_FILETYPE_CHARACTER_DEVICE),
libc::DT_DIR => Ok(wasi::__WASI_FILETYPE_DIRECTORY),
libc::DT_BLK => Ok(wasi::__WASI_FILETYPE_BLOCK_DEVICE),
libc::DT_REG => Ok(wasi::__WASI_FILETYPE_REGULAR_FILE),
libc::DT_LNK => Ok(wasi::__WASI_FILETYPE_SYMBOLIC_LINK),
libc::DT_SOCK => {
// 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?
Ok(wasi::__WASI_FILETYPE_UNKNOWN)
}
libc::DT_UNKNOWN => Ok(wasi::__WASI_FILETYPE_UNKNOWN),
_ => Err(Error::EINVAL),
}
}
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
/// `__WASI_EILSEQ` error is returned.
pub(crate) fn path_from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
helpers::path_from_slice(s.as_ref().as_bytes()).map(String::from)
}

View File

@@ -0,0 +1,357 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use super::fs_helpers::*;
use crate::helpers::systemtime_to_timestamp;
use crate::hostcalls_impl::{FileType, PathGet};
use crate::sys::host_impl;
use crate::sys::unix::str_to_cstring;
use crate::{wasi, Error, Result};
use nix::libc;
use std::convert::TryInto;
use std::fs::{File, Metadata};
use std::os::unix::fs::FileExt;
use std::os::unix::prelude::{AsRawFd, FromRawFd};
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub(crate) use super::super::linux::hostcalls_impl::*;
} else if #[cfg(any(
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "ios",
target_os = "dragonfly"
))] {
pub(crate) use super::super::bsd::hostcalls_impl::*;
}
}
pub(crate) fn fd_pread(
file: &File,
buf: &mut [u8],
offset: wasi::__wasi_filesize_t,
) -> Result<usize> {
file.read_at(buf, offset).map_err(Into::into)
}
pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
file.write_at(buf, offset).map_err(Into::into)
}
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
use nix::fcntl::{fcntl, OFlag, F_GETFL};
match fcntl(fd.as_raw_fd(), F_GETFL).map(OFlag::from_bits_truncate) {
Ok(flags) => Ok(host_impl::fdflags_from_nix(flags)),
Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())),
}
}
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
use nix::fcntl::{fcntl, F_SETFL};
let nix_flags = host_impl::nix_from_fdflags(fdflags);
match fcntl(fd.as_raw_fd(), F_SETFL(nix_flags)) {
Ok(_) => Ok(()),
Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())),
}
}
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> {
use nix::libc::mkdirat;
let path_cstr = str_to_cstring(resolved.path())?;
// nix doesn't expose mkdirat() yet
match unsafe { mkdirat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0o777) } {
0 => Ok(()),
_ => Err(host_impl::errno_from_nix(nix::errno::Errno::last())),
}
}
pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use nix::libc::linkat;
let old_path_cstr = str_to_cstring(resolved_old.path())?;
let new_path_cstr = str_to_cstring(resolved_new.path())?;
// Not setting AT_SYMLINK_FOLLOW fails on most filesystems
let atflags = libc::AT_SYMLINK_FOLLOW;
let res = unsafe {
linkat(
resolved_old.dirfd().as_raw_fd(),
old_path_cstr.as_ptr(),
resolved_new.dirfd().as_raw_fd(),
new_path_cstr.as_ptr(),
atflags,
)
};
if res != 0 {
Err(host_impl::errno_from_nix(nix::errno::Errno::last()))
} else {
Ok(())
}
}
pub(crate) fn path_open(
resolved: PathGet,
read: bool,
write: bool,
oflags: wasi::__wasi_oflags_t,
fs_flags: wasi::__wasi_fdflags_t,
) -> Result<File> {
use nix::errno::Errno;
use nix::fcntl::{openat, AtFlags, OFlag};
use nix::sys::stat::{fstatat, Mode, SFlag};
let mut nix_all_oflags = if read && write {
OFlag::O_RDWR
} else if write {
OFlag::O_WRONLY
} else {
OFlag::O_RDONLY
};
// on non-Capsicum systems, we always want nofollow
nix_all_oflags.insert(OFlag::O_NOFOLLOW);
// convert open flags
nix_all_oflags.insert(host_impl::nix_from_oflags(oflags));
// convert file descriptor flags
nix_all_oflags.insert(host_impl::nix_from_fdflags(fs_flags));
// 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.
log::debug!("path_open resolved = {:?}", resolved);
log::debug!("path_open oflags = {:?}", nix_all_oflags);
let new_fd = match openat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
nix_all_oflags,
Mode::from_bits_truncate(0o666),
) {
Ok(fd) => fd,
Err(e) => {
match e.as_errno() {
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
Some(Errno::ENXIO) => {
if let Ok(stat) = fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlags::AT_SYMLINK_NOFOLLOW,
) {
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) {
return Err(Error::ENOTSUP);
} else {
return Err(Error::ENXIO);
}
} else {
return Err(Error::ENXIO);
}
}
// Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
// on a symlink.
Some(Errno::ENOTDIR)
if !(nix_all_oflags & (OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY)).is_empty() =>
{
if let Ok(stat) = fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlags::AT_SYMLINK_NOFOLLOW,
) {
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFLNK) {
return Err(Error::ELOOP);
}
}
return Err(Error::ENOTDIR);
}
// FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
// a symlink.
Some(Errno::EMLINK) if !(nix_all_oflags & OFlag::O_NOFOLLOW).is_empty() => {
return Err(Error::ELOOP);
}
Some(e) => return Err(host_impl::errno_from_nix(e)),
None => return Err(Error::ENOSYS),
}
}
};
log::debug!("path_open (host) new_fd = {:?}", new_fd);
// Determine the type of the new file descriptor and which rights contradict with this type
Ok(unsafe { File::from_raw_fd(new_fd) })
}
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
use nix::errno::Errno;
let path_cstr = str_to_cstring(resolved.path())?;
// Linux requires that the buffer size is positive, whereas POSIX does not.
// Use a fake buffer to store the results if the size is zero.
// TODO: instead of using raw libc::readlinkat call here, this should really
// be fixed in `nix` crate
let fakebuf: &mut [u8] = &mut [0];
let buf_len = buf.len();
let len = unsafe {
libc::readlinkat(
resolved.dirfd().as_raw_fd(),
path_cstr.as_ptr() as *const libc::c_char,
if buf_len == 0 {
fakebuf.as_mut_ptr()
} else {
buf.as_mut_ptr()
} as *mut libc::c_char,
if buf_len == 0 { fakebuf.len() } else { buf_len },
)
};
if len < 0 {
Err(host_impl::errno_from_nix(Errno::last()))
} else {
let len = len as usize;
Ok(if len < buf_len { len } else { buf_len })
}
}
pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result<wasi::__wasi_filestat_t> {
use std::os::unix::fs::MetadataExt;
let metadata = file.metadata()?;
Ok(wasi::__wasi_filestat_t {
st_dev: metadata.dev(),
st_ino: metadata.ino(),
st_nlink: metadata.nlink().try_into()?, // u64 doesn't fit into u32
st_size: metadata.len(),
st_atim: systemtime_to_timestamp(metadata.accessed()?)?,
st_ctim: metadata.ctime().try_into()?, // i64 doesn't fit into u64
st_mtim: systemtime_to_timestamp(metadata.modified()?)?,
st_filetype: filetype(file, &metadata)?.to_wasi(),
})
}
fn filetype(file: &File, metadata: &Metadata) -> Result<FileType> {
use nix::sys::socket::{self, SockType};
use std::os::unix::fs::FileTypeExt;
let ftype = metadata.file_type();
if ftype.is_file() {
Ok(FileType::RegularFile)
} else if ftype.is_dir() {
Ok(FileType::Directory)
} else if ftype.is_symlink() {
Ok(FileType::Symlink)
} else if ftype.is_char_device() {
Ok(FileType::CharacterDevice)
} else if ftype.is_block_device() {
Ok(FileType::BlockDevice)
} else if ftype.is_socket() {
match socket::getsockopt(file.as_raw_fd(), socket::sockopt::SockType)
.map_err(|err| err.as_errno().unwrap())
.map_err(host_impl::errno_from_nix)?
{
SockType::Datagram => Ok(FileType::SocketDgram),
SockType::Stream => Ok(FileType::SocketStream),
_ => Ok(FileType::Unknown),
}
} else {
Ok(FileType::Unknown)
}
}
pub(crate) fn path_filestat_get(
resolved: PathGet,
dirflags: wasi::__wasi_lookupflags_t,
) -> Result<wasi::__wasi_filestat_t> {
use nix::fcntl::AtFlags;
use nix::sys::stat::fstatat;
let atflags = match dirflags {
0 => AtFlags::empty(),
_ => AtFlags::AT_SYMLINK_NOFOLLOW,
};
let filestat = fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags)
.map_err(|err| host_impl::errno_from_nix(err.as_errno().unwrap()))?;
host_impl::filestat_from_nix(filestat)
}
pub(crate) fn path_filestat_set_times(
resolved: PathGet,
dirflags: wasi::__wasi_lookupflags_t,
st_atim: wasi::__wasi_timestamp_t,
st_mtim: wasi::__wasi_timestamp_t,
fst_flags: wasi::__wasi_fstflags_t,
) -> Result<()> {
use nix::sys::stat::{utimensat, UtimensatFlags};
use nix::sys::time::{TimeSpec, TimeValLike};
// FIXME this should be a part of nix
fn timespec_omit() -> TimeSpec {
let raw_ts = libc::timespec {
tv_sec: 0,
tv_nsec: utime_omit(),
};
unsafe { std::mem::transmute(raw_ts) }
};
fn timespec_now() -> TimeSpec {
let raw_ts = libc::timespec {
tv_sec: 0,
tv_nsec: utime_now(),
};
unsafe { std::mem::transmute(raw_ts) }
};
let set_atim = fst_flags & wasi::__WASI_FILESTAT_SET_ATIM != 0;
let set_atim_now = fst_flags & wasi::__WASI_FILESTAT_SET_ATIM_NOW != 0;
let set_mtim = fst_flags & wasi::__WASI_FILESTAT_SET_MTIM != 0;
let set_mtim_now = fst_flags & wasi::__WASI_FILESTAT_SET_MTIM_NOW != 0;
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
return Err(Error::EINVAL);
}
let atflags = match dirflags {
wasi::__WASI_LOOKUP_SYMLINK_FOLLOW => UtimensatFlags::FollowSymlink,
_ => UtimensatFlags::NoFollowSymlink,
};
let atim = if set_atim {
let st_atim = st_atim.try_into()?;
TimeSpec::nanoseconds(st_atim)
} else if set_atim_now {
timespec_now()
} else {
timespec_omit()
};
let mtim = if set_mtim {
let st_mtim = st_mtim.try_into()?;
TimeSpec::nanoseconds(st_mtim)
} else if set_mtim_now {
timespec_now()
} else {
timespec_omit()
};
let fd = resolved.dirfd().as_raw_fd().into();
utimensat(fd, resolved.path(), &atim, &mtim, atflags).map_err(Into::into)
}
pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> {
use nix::errno;
use nix::libc::{unlinkat, AT_REMOVEDIR};
let path_cstr = str_to_cstring(resolved.path())?;
// nix doesn't expose unlinkat() yet
match unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
path_cstr.as_ptr(),
AT_REMOVEDIR,
)
} {
0 => Ok(()),
_ => Err(host_impl::errno_from_nix(errno::Errno::last())),
}
}

View File

@@ -0,0 +1,83 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use crate::sys::host_impl;
use crate::{wasi, Result};
use std::fs::File;
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub(crate) use super::super::linux::fs_helpers::*;
} else if #[cfg(any(
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "ios",
target_os = "dragonfly"
))] {
pub(crate) use super::super::bsd::fs_helpers::*;
}
}
pub(crate) fn path_open_rights(
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
oflags: wasi::__wasi_oflags_t,
fs_flags: wasi::__wasi_fdflags_t,
) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) {
use nix::fcntl::OFlag;
// which rights are needed on the dirfd?
let mut needed_base = wasi::__WASI_RIGHT_PATH_OPEN;
let mut needed_inheriting = rights_base | rights_inheriting;
// convert open flags
let oflags = host_impl::nix_from_oflags(oflags);
if oflags.contains(OFlag::O_CREAT) {
needed_base |= wasi::__WASI_RIGHT_PATH_CREATE_FILE;
}
if oflags.contains(OFlag::O_TRUNC) {
needed_base |= wasi::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
let fdflags = host_impl::nix_from_fdflags(fs_flags);
if fdflags.contains(OFlag::O_DSYNC) {
needed_inheriting |= wasi::__WASI_RIGHT_FD_DATASYNC;
}
if fdflags.intersects(host_impl::O_RSYNC | OFlag::O_SYNC) {
needed_inheriting |= wasi::__WASI_RIGHT_FD_SYNC;
}
(needed_base, needed_inheriting)
}
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
use nix::fcntl::{self, OFlag};
use nix::sys::stat::Mode;
use std::os::unix::prelude::{AsRawFd, FromRawFd};
log::debug!("path_get openat path = {:?}", path);
fcntl::openat(
dirfd.as_raw_fd(),
path,
OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW,
Mode::empty(),
)
.map(|new_fd| unsafe { File::from_raw_fd(new_fd) })
.map_err(Into::into)
}
pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result<String> {
use nix::fcntl;
use std::os::unix::prelude::AsRawFd;
log::debug!("path_get readlinkat path = {:?}", path);
let readlink_buf = &mut [0u8; libc::PATH_MAX as usize + 1];
fcntl::readlinkat(dirfd.as_raw_fd(), path, readlink_buf)
.map_err(Into::into)
.and_then(host_impl::path_from_host)
}

View File

@@ -0,0 +1,226 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use crate::hostcalls_impl::{ClockEventData, FdEventData};
use crate::sys::host_impl;
use crate::{wasi, Error, Result};
use nix::libc::{self, c_int};
use std::mem::MaybeUninit;
pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
// convert the supported clocks to the libc types, or return EINVAL
let clock_id = match clock_id {
wasi::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME,
wasi::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC,
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID,
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID,
_ => return Err(Error::EINVAL),
};
// no `nix` wrapper for clock_getres, so we do it ourselves
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
let res = unsafe { libc::clock_getres(clock_id, timespec.as_mut_ptr()) };
if res != 0 {
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
}
let timespec = unsafe { timespec.assume_init() };
// 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 wasi::__wasi_timestamp_t)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t))
.map_or(Err(Error::EOVERFLOW), |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::EINVAL)
} else {
Ok(resolution)
}
})
}
pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
// convert the supported clocks to the libc types, or return EINVAL
let clock_id = match clock_id {
wasi::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME,
wasi::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC,
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID,
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID,
_ => return Err(Error::EINVAL),
};
// no `nix` wrapper for clock_getres, so we do it ourselves
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
let res = unsafe { libc::clock_gettime(clock_id, timespec.as_mut_ptr()) };
if res != 0 {
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
}
let timespec = unsafe { timespec.assume_init() };
// 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 wasi::__wasi_timestamp_t)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t))
.map_or(Err(Error::EOVERFLOW), Ok)
}
pub(crate) fn poll_oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<wasi::__wasi_event_t>,
) -> Result<()> {
use nix::{
errno::Errno,
poll::{poll, PollFd, PollFlags},
};
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
if fd_events.is_empty() && timeout.is_none() {
return Ok(());
}
let mut poll_fds: Vec<_> = fd_events
.iter()
.map(|event| {
let mut flags = PollFlags::empty();
match event.r#type {
wasi::__WASI_EVENTTYPE_FD_READ => flags.insert(PollFlags::POLLIN),
wasi::__WASI_EVENTTYPE_FD_WRITE => 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!(),
};
PollFd::new(event.descriptor.as_raw_fd(), flags)
})
.collect();
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(c_int::max_value())
});
log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout);
let ready = loop {
match poll(&mut poll_fds, poll_timeout) {
Err(_) => {
if Errno::last() == Errno::EINTR {
continue;
}
return Err(host_impl::errno_from_nix(Errno::last()));
}
Ok(ready) => break ready as usize,
}
};
Ok(if ready == 0 {
poll_oneoff_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);
poll_oneoff_handle_fd_event(ready_events, events)?
})
}
// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)`
nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int);
fn poll_oneoff_handle_timeout_event(
timeout: ClockEventData,
events: &mut Vec<wasi::__wasi_event_t>,
) {
events.push(wasi::__wasi_event_t {
userdata: timeout.userdata,
r#type: wasi::__WASI_EVENTTYPE_CLOCK,
error: wasi::__WASI_ESUCCESS,
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: 0,
},
},
});
}
fn poll_oneoff_handle_fd_event<'a>(
ready_events: impl Iterator<Item = (FdEventData<'a>, nix::poll::PollFd)>,
events: &mut Vec<wasi::__wasi_event_t>,
) -> Result<()> {
use nix::poll::PollFlags;
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
for (fd_event, poll_fd) in ready_events {
log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event);
log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd);
let revents = match poll_fd.revents() {
Some(revents) => revents,
None => continue,
};
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
let mut nbytes = 0;
if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ {
let _ = unsafe { fionread(fd_event.descriptor.as_raw_fd(), &mut nbytes) };
}
let output_event = if revents.contains(PollFlags::POLLNVAL) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
r#type: fd_event.r#type,
error: wasi::__WASI_EBADF,
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: wasi::__WASI_EVENT_FD_READWRITE_HANGUP,
},
},
}
} else if revents.contains(PollFlags::POLLERR) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
r#type: fd_event.r#type,
error: wasi::__WASI_EIO,
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: wasi::__WASI_EVENT_FD_READWRITE_HANGUP,
},
},
}
} else if revents.contains(PollFlags::POLLHUP) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
r#type: fd_event.r#type,
error: wasi::__WASI_ESUCCESS,
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: wasi::__WASI_EVENT_FD_READWRITE_HANGUP,
},
},
}
} else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
r#type: fd_event.r#type,
error: wasi::__WASI_ESUCCESS,
u: wasi::__wasi_event_u {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: nbytes.try_into()?,
flags: 0,
},
},
}
} else {
continue;
};
events.push(output_event);
}
Ok(())
}

View File

@@ -0,0 +1,8 @@
//! Unix-specific hostcalls that implement
//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md).
mod fs;
pub(crate) mod fs_helpers;
mod misc;
pub(crate) use self::fs::*;
pub(crate) use self::misc::*;

View File

@@ -0,0 +1,165 @@
use super::super::dir::{Dir, Entry, SeekLoc};
use super::osfile::OsFile;
use crate::hostcalls_impl::{Dirent, PathGet};
use crate::sys::host_impl;
use crate::sys::unix::str_to_cstring;
use crate::{wasi, Error, Result};
use log::trace;
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
use nix::errno;
use nix::libc::unlinkat;
let path_cstr = str_to_cstring(resolved.path())?;
// nix doesn't expose unlinkat() yet
let res = unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) };
if res == 0 {
Ok(())
} else {
Err(host_impl::errno_from_nix(errno::Errno::last()))
}
}
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use nix::{errno::Errno, libc::symlinkat};
let old_path_cstr = str_to_cstring(old_path)?;
let new_path_cstr = str_to_cstring(resolved.path())?;
log::debug!("path_symlink old_path = {:?}", old_path);
log::debug!("path_symlink resolved = {:?}", resolved);
let res = unsafe {
symlinkat(
old_path_cstr.as_ptr(),
resolved.dirfd().as_raw_fd(),
new_path_cstr.as_ptr(),
)
};
if res != 0 {
Err(host_impl::errno_from_nix(Errno::last()))
} else {
Ok(())
}
}
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use nix::libc::renameat;
let old_path_cstr = str_to_cstring(resolved_old.path())?;
let new_path_cstr = str_to_cstring(resolved_new.path())?;
let res = unsafe {
renameat(
resolved_old.dirfd().as_raw_fd(),
old_path_cstr.as_ptr(),
resolved_new.dirfd().as_raw_fd(),
new_path_cstr.as_ptr(),
)
};
if res != 0 {
Err(host_impl::errno_from_nix(nix::errno::Errno::last()))
} else {
Ok(())
}
}
pub(crate) fn fd_readdir_impl(
fd: &File,
cookie: wasi::__wasi_dircookie_t,
) -> Result<impl Iterator<Item = Result<Dirent>>> {
// We need to duplicate the fd, 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 fd = fd.try_clone()?;
let mut dir = Dir::from(fd)?;
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
// new items may not be returned to the caller.
//
// According to `opendir(3p)`:
// If a file is removed from or added to the directory after the most recent call
// to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry
// for that file is unspecified.
if cookie == wasi::__WASI_DIRCOOKIE_START {
trace!(" | fd_readdir: doing rewinddir");
dir.rewind();
} else {
trace!(" | fd_readdir: doing seekdir to {}", cookie);
let loc = unsafe { SeekLoc::from_raw(cookie as i64) };
dir.seek(loc);
}
Ok(dir.into_iter().map(|entry| {
let entry: Entry = entry?;
Ok(Dirent {
name: entry // TODO can we reuse path_from_host for CStr?
.file_name()
.to_str()?
.to_owned(),
ino: entry.ino(),
ftype: entry.file_type().into(),
cookie: entry.seek_loc().to_raw().try_into()?,
})
}))
}
// This should actually be common code with Windows,
// but there's BSD stuff remaining
pub(crate) fn fd_readdir(
os_file: &mut OsFile,
mut host_buf: &mut [u8],
cookie: wasi::__wasi_dircookie_t,
) -> Result<usize> {
let iter = fd_readdir_impl(os_file, cookie)?;
let mut used = 0;
for dirent in iter {
let dirent_raw = dirent?.to_wasi_raw()?;
let offset = dirent_raw.len();
if host_buf.len() < offset {
break;
} else {
host_buf[0..offset].copy_from_slice(&dirent_raw);
used += offset;
host_buf = &mut host_buf[offset..];
}
}
trace!(" | *buf_used={:?}", used);
Ok(used)
}
pub(crate) fn fd_advise(
file: &File,
advice: wasi::__wasi_advice_t,
offset: wasi::__wasi_filesize_t,
len: wasi::__wasi_filesize_t,
) -> Result<()> {
{
use nix::fcntl::{posix_fadvise, PosixFadviseAdvice};
let offset = offset.try_into()?;
let len = len.try_into()?;
let host_advice = match advice {
wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::POSIX_FADV_DONTNEED,
wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::POSIX_FADV_SEQUENTIAL,
wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::POSIX_FADV_WILLNEED,
wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::POSIX_FADV_NOREUSE,
wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::POSIX_FADV_RANDOM,
wasi::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::POSIX_FADV_NORMAL,
_ => return Err(Error::EINVAL),
};
posix_fadvise(file.as_raw_fd(), offset, len, host_advice)?;
}
Ok(())
}

View File

@@ -0,0 +1,42 @@
pub(crate) mod hostcalls_impl;
pub(crate) mod osfile;
pub(crate) mod fdentry_impl {
use crate::{sys::host_impl, Result};
use std::os::unix::prelude::AsRawFd;
pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result<bool> {
use nix::errno::Errno;
let res = libc::isatty(fd.as_raw_fd());
if res == 1 {
// isatty() returns 1 if fd is an open file descriptor referring to a terminal...
Ok(true)
} else {
// ... otherwise 0 is returned, and errno is set to indicate the error.
match Errno::last() {
// While POSIX specifies ENOTTY if the passed
// fd is *not* a tty, on Linux, some implementations
// may return EINVAL instead.
//
// https://linux.die.net/man/3/isatty
Errno::ENOTTY | Errno::EINVAL => Ok(false),
x => Err(host_impl::errno_from_nix(x)),
}
}
}
}
pub(crate) mod host_impl {
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC;
}
pub(crate) mod fs_helpers {
pub(crate) fn utime_now() -> libc::c_long {
libc::UTIME_NOW
}
pub(crate) fn utime_omit() -> libc::c_long {
libc::UTIME_OMIT
}
}

View File

@@ -0,0 +1,32 @@
use std::fs;
use std::ops::{Deref, DerefMut};
use std::os::unix::prelude::{AsRawFd, RawFd};
#[derive(Debug)]
pub(crate) struct OsFile(fs::File);
impl From<fs::File> for OsFile {
fn from(file: fs::File) -> Self {
Self(file)
}
}
impl AsRawFd for OsFile {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl Deref for OsFile {
type Target = fs::File;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for OsFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View File

@@ -0,0 +1,38 @@
pub(crate) mod fdentry_impl;
pub(crate) mod host_impl;
pub(crate) mod hostcalls_impl;
mod dir;
#[cfg(any(
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "ios",
target_os = "dragonfly"
))]
mod bsd;
#[cfg(target_os = "linux")]
mod linux;
use crate::{Error, Result};
use std::ffi::CString;
use std::fs::{File, OpenOptions};
use std::path::Path;
pub(crate) fn dev_null() -> Result<File> {
OpenOptions::new()
.read(true)
.write(true)
.open("/dev/null")
.map_err(Into::into)
}
pub(crate) fn str_to_cstring(s: &str) -> Result<CString> {
CString::new(s.as_bytes()).map_err(|_| Error::EILSEQ)
}
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> Result<File> {
File::open(path).map_err(Into::into)
}

View File

@@ -0,0 +1,129 @@
use crate::fdentry::Descriptor;
use crate::{wasi, Error, Result};
use std::fs::File;
use std::io;
use std::ops::{Deref, DerefMut};
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle};
#[derive(Debug)]
pub(crate) struct OsFile(File);
impl From<File> for OsFile {
fn from(file: File) -> Self {
Self(file)
}
}
impl AsRawHandle for OsFile {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
impl Deref for OsFile {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for OsFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl AsRawHandle for Descriptor {
fn as_raw_handle(&self) -> RawHandle {
match self {
Self::OsFile(file) => file.as_raw_handle(),
Self::Stdin => io::stdin().as_raw_handle(),
Self::Stdout => io::stdout().as_raw_handle(),
Self::Stderr => io::stderr().as_raw_handle(),
}
}
}
/// This function is unsafe because it operates on a raw file handle.
pub(crate) unsafe fn determine_type_and_access_rights<Handle: AsRawHandle>(
handle: &Handle,
) -> Result<(
wasi::__wasi_filetype_t,
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
use winx::file::{get_file_access_mode, AccessMode};
let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(handle)?;
match file_type {
wasi::__WASI_FILETYPE_DIRECTORY | wasi::__WASI_FILETYPE_REGULAR_FILE => {
let mode = get_file_access_mode(handle.as_raw_handle())?;
if mode.contains(AccessMode::FILE_GENERIC_READ) {
rights_base |= wasi::__WASI_RIGHT_FD_READ;
}
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
rights_base |= wasi::__WASI_RIGHT_FD_WRITE;
}
}
_ => {
// TODO: is there a way around this? On windows, it seems
// we cannot check access rights for anything but dirs and regular files
}
}
Ok((file_type, rights_base, rights_inheriting))
}
/// This function is unsafe because it operates on a raw file handle.
pub(crate) unsafe fn determine_type_rights<Handle: AsRawHandle>(
handle: &Handle,
) -> Result<(
wasi::__wasi_filetype_t,
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
let (file_type, rights_base, rights_inheriting) = {
let file_type = winx::file::get_file_type(handle.as_raw_handle())?;
if file_type.is_char() {
// character file: LPT device or console
// TODO: rule out LPT device
(
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
wasi::RIGHTS_TTY_BASE,
wasi::RIGHTS_TTY_BASE,
)
} else if file_type.is_disk() {
// disk file: file, dir or disk device
let file = std::mem::ManuallyDrop::new(File::from_raw_handle(handle.as_raw_handle()));
let meta = file.metadata().map_err(|_| Error::EINVAL)?;
if meta.is_dir() {
(
wasi::__WASI_FILETYPE_DIRECTORY,
wasi::RIGHTS_DIRECTORY_BASE,
wasi::RIGHTS_DIRECTORY_INHERITING,
)
} else if meta.is_file() {
(
wasi::__WASI_FILETYPE_REGULAR_FILE,
wasi::RIGHTS_REGULAR_FILE_BASE,
wasi::RIGHTS_REGULAR_FILE_INHERITING,
)
} else {
return Err(Error::EINVAL);
}
} else if file_type.is_pipe() {
// pipe object: socket, named pipe or anonymous pipe
// TODO: what about pipes, etc?
(
wasi::__WASI_FILETYPE_SOCKET_STREAM,
wasi::RIGHTS_SOCKET_BASE,
wasi::RIGHTS_SOCKET_INHERITING,
)
} else {
return Err(Error::EINVAL);
}
};
Ok((file_type, rights_base, rights_inheriting))
}

View File

@@ -0,0 +1,108 @@
//! WASI host types specific to Windows host.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
use crate::{wasi, Error, Result};
use std::ffi::OsStr;
use std::fs::OpenOptions;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::fs::OpenOptionsExt;
use winx::file::{AccessMode, Attributes, CreationDisposition, Flags};
pub(crate) fn errno_from_win(error: winx::winerror::WinError) -> wasi::__wasi_errno_t {
// TODO: implement error mapping between Windows and WASI
use winx::winerror::WinError::*;
match error {
ERROR_SUCCESS => wasi::__WASI_ESUCCESS,
ERROR_BAD_ENVIRONMENT => wasi::__WASI_E2BIG,
ERROR_FILE_NOT_FOUND => wasi::__WASI_ENOENT,
ERROR_PATH_NOT_FOUND => wasi::__WASI_ENOENT,
ERROR_TOO_MANY_OPEN_FILES => wasi::__WASI_ENFILE,
ERROR_ACCESS_DENIED => wasi::__WASI_EACCES,
ERROR_SHARING_VIOLATION => wasi::__WASI_EACCES,
ERROR_PRIVILEGE_NOT_HELD => wasi::__WASI_ENOTCAPABLE, // TODO is this the correct mapping?
ERROR_INVALID_HANDLE => wasi::__WASI_EBADF,
ERROR_INVALID_NAME => wasi::__WASI_ENOENT,
ERROR_NOT_ENOUGH_MEMORY => wasi::__WASI_ENOMEM,
ERROR_OUTOFMEMORY => wasi::__WASI_ENOMEM,
ERROR_DIR_NOT_EMPTY => wasi::__WASI_ENOTEMPTY,
ERROR_NOT_READY => wasi::__WASI_EBUSY,
ERROR_BUSY => wasi::__WASI_EBUSY,
ERROR_NOT_SUPPORTED => wasi::__WASI_ENOTSUP,
ERROR_FILE_EXISTS => wasi::__WASI_EEXIST,
ERROR_BROKEN_PIPE => wasi::__WASI_EPIPE,
ERROR_BUFFER_OVERFLOW => wasi::__WASI_ENAMETOOLONG,
ERROR_NOT_A_REPARSE_POINT => wasi::__WASI_EINVAL,
ERROR_NEGATIVE_SEEK => wasi::__WASI_EINVAL,
ERROR_DIRECTORY => wasi::__WASI_ENOTDIR,
ERROR_ALREADY_EXISTS => wasi::__WASI_EEXIST,
_ => wasi::__WASI_ENOTSUP,
}
}
pub(crate) fn fdflags_from_win(mode: AccessMode) -> wasi::__wasi_fdflags_t {
let mut fdflags = 0;
// TODO verify this!
if mode.contains(AccessMode::FILE_APPEND_DATA) {
fdflags |= wasi::__WASI_FDFLAG_APPEND;
}
if mode.contains(AccessMode::SYNCHRONIZE) {
fdflags |= wasi::__WASI_FDFLAG_DSYNC;
fdflags |= wasi::__WASI_FDFLAG_RSYNC;
fdflags |= wasi::__WASI_FDFLAG_SYNC;
}
// The NONBLOCK equivalent is FILE_FLAG_OVERLAPPED
// but it seems winapi doesn't provide a mechanism
// for checking whether the handle supports async IO.
// On the contrary, I've found some dicsussion online
// which suggests that on Windows all handles should
// generally be assumed to be opened with async support
// and then the program should fallback should that **not**
// be the case at the time of the operation.
// TODO: this requires further investigation
fdflags
}
pub(crate) fn win_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> (AccessMode, Flags) {
let mut access_mode = AccessMode::empty();
let mut flags = Flags::empty();
// TODO verify this!
if fdflags & wasi::__WASI_FDFLAG_NONBLOCK != 0 {
flags.insert(Flags::FILE_FLAG_OVERLAPPED);
}
if fdflags & wasi::__WASI_FDFLAG_APPEND != 0 {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
}
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0
|| fdflags & wasi::__WASI_FDFLAG_RSYNC != 0
|| fdflags & wasi::__WASI_FDFLAG_SYNC != 0
{
access_mode.insert(AccessMode::SYNCHRONIZE);
}
(access_mode, flags)
}
pub(crate) fn win_from_oflags(oflags: wasi::__wasi_oflags_t) -> CreationDisposition {
if oflags & wasi::__WASI_O_CREAT != 0 {
if oflags & wasi::__WASI_O_EXCL != 0 {
CreationDisposition::CREATE_NEW
} else {
CreationDisposition::CREATE_ALWAYS
}
} else if oflags & wasi::__WASI_O_TRUNC != 0 {
CreationDisposition::TRUNCATE_EXISTING
} else {
CreationDisposition::OPEN_EXISTING
}
}
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
/// `__WASI_EILSEQ` error is returned.
pub(crate) fn path_from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
let vec: Vec<u16> = s.as_ref().encode_wide().collect();
String::from_utf16(&vec).map_err(|_| Error::EILSEQ)
}

View File

@@ -0,0 +1,550 @@
#![allow(non_camel_case_types)]
#![allow(unused)]
use super::fs_helpers::*;
use crate::ctx::WasiCtx;
use crate::fdentry::FdEntry;
use crate::helpers::systemtime_to_timestamp;
use crate::hostcalls_impl::{fd_filestat_set_times_impl, Dirent, FileType, PathGet};
use crate::sys::fdentry_impl::{determine_type_rights, OsFile};
use crate::sys::host_impl::{self, path_from_host};
use crate::sys::hostcalls_impl::fs_helpers::PathGetExt;
use crate::{wasi, Error, Result};
use log::{debug, trace};
use std::convert::TryInto;
use std::fs::{File, Metadata, OpenOptions};
use std::io::{self, Seek, SeekFrom};
use std::os::windows::fs::{FileExt, OpenOptionsExt};
use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::{Path, PathBuf};
use winx::file::{AccessMode, Flags};
fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
// get current cursor position
let cur_pos = file.seek(SeekFrom::Current(0))?;
// perform a seek read by a specified offset
let nread = file.seek_read(buf, offset)?;
// rewind the cursor back to the original position
file.seek(SeekFrom::Start(cur_pos))?;
Ok(nread)
}
fn write_at(mut file: &File, buf: &[u8], offset: u64) -> io::Result<usize> {
// get current cursor position
let cur_pos = file.seek(SeekFrom::Current(0))?;
// perform a seek write by a specified offset
let nwritten = file.seek_write(buf, offset)?;
// rewind the cursor back to the original position
file.seek(SeekFrom::Start(cur_pos))?;
Ok(nwritten)
}
// TODO refactor common code with unix
pub(crate) fn fd_pread(
file: &File,
buf: &mut [u8],
offset: wasi::__wasi_filesize_t,
) -> Result<usize> {
read_at(file, buf, offset).map_err(Into::into)
}
// TODO refactor common code with unix
pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
write_at(file, buf, offset).map_err(Into::into)
}
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
use winx::file::AccessMode;
unsafe { winx::file::get_file_access_mode(fd.as_raw_handle()) }
.map(host_impl::fdflags_from_win)
.map_err(Into::into)
}
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
unimplemented!("fd_fdstat_set_flags")
}
pub(crate) fn fd_advise(
_file: &File,
advice: wasi::__wasi_advice_t,
_offset: wasi::__wasi_filesize_t,
_len: wasi::__wasi_filesize_t,
) -> Result<()> {
match advice {
wasi::__WASI_ADVICE_DONTNEED
| wasi::__WASI_ADVICE_SEQUENTIAL
| wasi::__WASI_ADVICE_WILLNEED
| wasi::__WASI_ADVICE_NOREUSE
| wasi::__WASI_ADVICE_RANDOM
| wasi::__WASI_ADVICE_NORMAL => {}
_ => return Err(Error::EINVAL),
}
Ok(())
}
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> {
let path = resolved.concatenate()?;
std::fs::create_dir(&path).map_err(Into::into)
}
pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
unimplemented!("path_link")
}
pub(crate) fn path_open(
resolved: PathGet,
read: bool,
write: bool,
oflags: wasi::__wasi_oflags_t,
fdflags: wasi::__wasi_fdflags_t,
) -> Result<File> {
use winx::file::{AccessMode, CreationDisposition, Flags};
let mut access_mode = AccessMode::READ_CONTROL;
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;
// convert open flags
let mut opts = OpenOptions::new();
match host_impl::win_from_oflags(oflags) {
CreationDisposition::CREATE_ALWAYS => {
opts.create(true).append(true);
}
CreationDisposition::CREATE_NEW => {
opts.create_new(true).write(true);
}
CreationDisposition::TRUNCATE_EXISTING => {
opts.truncate(true);
}
_ => {}
}
// convert file descriptor flags
let (add_access_mode, add_flags) = host_impl::win_from_fdflags(fdflags);
access_mode.insert(add_access_mode);
flags.insert(add_flags);
let path = resolved.concatenate()?;
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::ELOOP);
}
// check if we are trying to open a file as a dir
if file_type.is_file() && oflags & wasi::__WASI_O_DIRECTORY != 0 {
return Err(Error::ENOTDIR);
}
}
Err(e) => match e.raw_os_error() {
Some(e) => {
use winx::winerror::WinError;
log::debug!("path_open at symlink_metadata error code={:?}", e);
let e = WinError::from_u32(e as u32);
if e != WinError::ERROR_FILE_NOT_FOUND {
return Err(e.into());
}
// file not found, let it proceed to actually
// trying to open it
}
None => {
log::debug!("Inconvertible OS error: {}", e);
return Err(Error::EIO);
}
},
}
opts.access_mode(access_mode.bits())
.custom_flags(flags.bits())
.open(&path)
.map_err(Into::into)
}
fn dirent_from_path<P: AsRef<Path>>(
path: P,
name: &str,
cookie: wasi::__wasi_dircookie_t,
) -> Result<Dirent> {
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();
Ok(Dirent {
ftype: filetype_from_std(&ty),
name: name.to_owned(),
cookie,
ino: file_serial_no(&file)?,
})
}
// 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 fd_readdir_impl(
fd: &File,
cookie: wasi::__wasi_dircookie_t,
) -> Result<impl Iterator<Item = Result<Dirent>>> {
use winx::file::get_file_path;
let cookie = cookie.try_into()?;
let path = get_file_path(fd)?;
// 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?;
Ok(Dirent {
name: path_from_host(dir.file_name())?,
ftype: filetype_from_std(&dir.file_type()?),
ino: File::open(dir.path()).and_then(|f| file_serial_no(&f))?,
cookie: no,
})
});
// 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(iter.skip(cookie))
}
// This should actually be common code with Linux
pub(crate) fn fd_readdir(
os_file: &mut OsFile,
mut host_buf: &mut [u8],
cookie: wasi::__wasi_dircookie_t,
) -> Result<usize> {
let iter = fd_readdir_impl(os_file, cookie)?;
let mut used = 0;
for dirent in iter {
let dirent_raw = dirent?.to_wasi_raw()?;
let offset = dirent_raw.len();
if host_buf.len() < offset {
break;
} else {
host_buf[0..offset].copy_from_slice(&dirent_raw);
used += offset;
host_buf = &mut host_buf[offset..];
}
}
trace!(" | *buf_used={:?}", used);
Ok(used)
}
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
use winx::file::get_file_path;
let path = resolved.concatenate()?;
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(resolved.dirfd())?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
.map_err(|_| Error::ENOTCAPABLE)
.and_then(|path| path.to_str().map(String::from).ok_or(Error::EILSEQ))?;
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)
}
}
fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result<Option<PathBuf>> {
if resolved.path().ends_with('/') {
let suffix = resolved.path().trim_end_matches('/');
concatenate(resolved.dirfd(), Path::new(suffix)).map(Some)
} else {
Ok(None)
}
}
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use std::fs;
let old_path = resolved_old.concatenate()?;
let new_path = resolved_new.concatenate()?;
// 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::ENOTDIR);
}
// 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() && resolved_new.path().ends_with('/') {
return Err(Error::ENOTDIR);
}
// TODO handle symlinks
fs::rename(&old_path, &new_path).or_else(|e| match e.raw_os_error() {
Some(e) => {
use winx::winerror::WinError;
log::debug!("path_rename at rename error code={:?}", e);
match WinError::from_u32(e 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() {
Err(Error::EISDIR)
} 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)
.and_then(|()| fs::rename(old_path, new_path))
.map_err(Into::into)
}
}
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(&resolved_old)? {
if path.is_file() {
return Err(Error::ENOTDIR);
}
}
Err(WinError::ERROR_INVALID_NAME.into())
}
e => Err(e.into()),
}
}
None => {
log::debug!("Inconvertible OS error: {}", e);
Err(Error::EIO)
}
})
}
pub(crate) fn num_hardlinks(file: &File, _metadata: &Metadata) -> io::Result<u64> {
Ok(winx::file::get_fileinfo(file)?.nNumberOfLinks.into())
}
pub(crate) fn device_id(file: &File, _metadata: &Metadata) -> io::Result<u64> {
Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into())
}
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 = ((high as u64) << 32) | (low as u64);
Ok(no)
}
pub(crate) fn change_time(file: &File, _metadata: &Metadata) -> io::Result<i64> {
winx::file::change_time(file)
}
pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result<wasi::__wasi_filestat_t> {
let metadata = file.metadata()?;
Ok(wasi::__wasi_filestat_t {
st_dev: device_id(file, &metadata)?,
st_ino: file_serial_no(file)?,
st_nlink: num_hardlinks(file, &metadata)?.try_into()?, // u64 doesn't fit into u32
st_size: metadata.len(),
st_atim: systemtime_to_timestamp(metadata.accessed()?)?,
st_ctim: change_time(file, &metadata)?.try_into()?, // i64 doesn't fit into u64
st_mtim: systemtime_to_timestamp(metadata.modified()?)?,
st_filetype: filetype_from_std(&metadata.file_type()).to_wasi(),
})
}
pub(crate) fn filetype_from_std(ftype: &std::fs::FileType) -> FileType {
if ftype.is_file() {
FileType::RegularFile
} else if ftype.is_dir() {
FileType::Directory
} else if ftype.is_symlink() {
FileType::Symlink
} else {
FileType::Unknown
}
}
pub(crate) fn path_filestat_get(
resolved: PathGet,
dirflags: wasi::__wasi_lookupflags_t,
) -> Result<wasi::__wasi_filestat_t> {
let path = resolved.concatenate()?;
let file = File::open(path)?;
fd_filestat_get_impl(&file)
}
pub(crate) fn path_filestat_set_times(
resolved: PathGet,
dirflags: wasi::__wasi_lookupflags_t,
st_atim: wasi::__wasi_timestamp_t,
mut st_mtim: wasi::__wasi_timestamp_t,
fst_flags: wasi::__wasi_fstflags_t,
) -> Result<()> {
use winx::file::AccessMode;
let path = resolved.concatenate()?;
let file = OpenOptions::new()
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
.open(path)?;
fd_filestat_set_times_impl(&file, st_atim, st_mtim, fst_flags)
}
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file};
use winx::winerror::WinError;
let old_path = concatenate(resolved.dirfd(), Path::new(old_path))?;
let new_path = resolved.concatenate()?;
// try creating a file symlink
symlink_file(&old_path, &new_path).or_else(|e| {
match e.raw_os_error() {
Some(e) => {
log::debug!("path_symlink at symlink_file error code={:?}", e);
match WinError::from_u32(e as u32) {
WinError::ERROR_NOT_A_REPARSE_POINT => {
// try creating a dir symlink instead
symlink_dir(old_path, new_path).map_err(Into::into)
}
WinError::ERROR_ACCESS_DENIED => {
// does the target exist?
if new_path.exists() {
Err(Error::EEXIST)
} else {
Err(WinError::ERROR_ACCESS_DENIED.into())
}
}
WinError::ERROR_INVALID_NAME => {
// does the target without trailing slashes exist?
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? {
if path.exists() {
return Err(Error::EEXIST);
}
}
Err(WinError::ERROR_INVALID_NAME.into())
}
e => Err(e.into()),
}
}
None => {
log::debug!("Inconvertible OS error: {}", e);
Err(Error::EIO)
}
}
})
}
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
use std::fs;
use winx::winerror::WinError;
let path = resolved.concatenate()?;
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() {
fs::remove_file(&path).or_else(|e| {
match e.raw_os_error() {
Some(e) => {
log::debug!("path_unlink_file at symlink_file error code={:?}", e);
match WinError::from_u32(e as u32) {
WinError::ERROR_ACCESS_DENIED => {
// try unlinking a dir symlink instead
fs::remove_dir(path).map_err(Into::into)
}
e => Err(e.into()),
}
}
None => {
log::debug!("Inconvertible OS error: {}", e);
Err(Error::EIO)
}
}
})
} else if file_type.is_dir() {
Err(Error::EISDIR)
} else if file_type.is_file() {
fs::remove_file(path).map_err(Into::into)
} else {
Err(Error::EINVAL)
}
}
pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> {
let path = resolved.concatenate()?;
std::fs::remove_dir(&path).map_err(Into::into)
}

View File

@@ -0,0 +1,149 @@
#![allow(non_camel_case_types)]
use crate::hostcalls_impl::PathGet;
use crate::{wasi, Error, Result};
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
pub(crate) trait PathGetExt {
fn concatenate(&self) -> Result<PathBuf>;
}
impl PathGetExt for PathGet {
fn concatenate(&self) -> Result<PathBuf> {
concatenate(self.dirfd(), Path::new(self.path()))
}
}
pub(crate) fn path_open_rights(
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
oflags: wasi::__wasi_oflags_t,
fdflags: wasi::__wasi_fdflags_t,
) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) {
// which rights are needed on the dirfd?
let mut needed_base = wasi::__WASI_RIGHT_PATH_OPEN;
let mut needed_inheriting = rights_base | rights_inheriting;
// convert open flags
if oflags & wasi::__WASI_O_CREAT != 0 {
needed_base |= wasi::__WASI_RIGHT_PATH_CREATE_FILE;
} else if oflags & wasi::__WASI_O_TRUNC != 0 {
needed_base |= wasi::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0
|| fdflags & wasi::__WASI_FDFLAG_RSYNC != 0
|| fdflags & wasi::__WASI_FDFLAG_SYNC != 0
{
needed_inheriting |= wasi::__WASI_RIGHT_FD_DATASYNC;
needed_inheriting |= wasi::__WASI_RIGHT_FD_SYNC;
}
(needed_base, needed_inheriting)
}
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use winx::file::Flags;
use winx::winerror::WinError;
let path = concatenate(dirfd, Path::new(path))?;
OpenOptions::new()
.read(true)
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
.open(&path)
.map_err(|e| match e.raw_os_error() {
Some(e) => {
log::debug!("openat error={:?}", e);
match WinError::from_u32(e as u32) {
WinError::ERROR_INVALID_NAME => Error::ENOTDIR,
e => e.into(),
}
}
None => {
log::debug!("Inconvertible OS error: {}", e);
Error::EIO
}
})
}
pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
use winx::file::get_file_path;
use winx::winerror::WinError;
let path = concatenate(dirfd, Path::new(s_path))?;
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)?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
target_path
.strip_prefix(dir_path)
.map_err(|_| Error::ENOTCAPABLE)
.and_then(|path| path.to_str().map(String::from).ok_or(Error::EILSEQ))
}
Err(e) => match e.raw_os_error() {
Some(e) => {
log::debug!("readlinkat error={:?}", e);
match WinError::from_u32(e 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() {
Err(Error::ENOTDIR)
} else {
Err(Error::ENOENT)
}
} else {
Err(Error::ENOENT)
}
}
e => Err(e.into()),
}
}
None => {
log::debug!("Inconvertible OS error: {}", e);
Err(Error::EIO)
}
},
}
}
pub(crate) 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)
}
}
pub(crate) fn concatenate<P: AsRef<Path>>(dirfd: &File, 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::ENOTCAPABLE);
}
let dir_path = get_file_path(dirfd)?;
// 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));
log::debug!("out_path={:?}", out_path);
Ok(out_path)
}

View File

@@ -0,0 +1,119 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
#![allow(unused)]
use crate::helpers::systemtime_to_timestamp;
use crate::hostcalls_impl::{ClockEventData, FdEventData};
use crate::memory::*;
use crate::sys::host_impl;
use crate::{wasi, wasi32, 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 clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
Ok(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
wasi::__WASI_CLOCK_REALTIME => 55_000_000,
// std::time::Instant uses QueryPerformanceCounter & QueryPerformanceFrequency internally
wasi::__WASI_CLOCK_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
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => 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
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => 100,
_ => return Err(Error::EINVAL),
})
}
pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
let duration = match clock_id {
wasi::__WASI_CLOCK_REALTIME => get_monotonic_time(),
wasi::__WASI_CLOCK_MONOTONIC => get_realtime_time()?,
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => get_proc_cputime()?,
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => get_thread_cputime()?,
_ => return Err(Error::EINVAL),
};
duration.as_nanos().try_into().map_err(Into::into)
}
pub(crate) fn poll_oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<wasi::__wasi_event_t>,
) -> Result<Vec<wasi::__wasi_event_t>> {
unimplemented!("poll_oneoff")
}
fn get_monotonic_time() -> Duration {
// We're circumventing the fact that we can't get a Duration from an Instant
// The epoch of __WASI_CLOCK_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::EFAULT)
}
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

@@ -0,0 +1,8 @@
//! Windows-specific hostcalls that implement
//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md).
mod fs;
pub(crate) mod fs_helpers;
mod misc;
pub(crate) use self::fs::*;
pub(crate) use self::misc::*;

View File

@@ -0,0 +1,32 @@
pub(crate) mod fdentry_impl;
pub(crate) mod host_impl;
pub(crate) mod hostcalls_impl;
use crate::Result;
use std::fs::{File, OpenOptions};
use std::path::Path;
pub(crate) fn dev_null() -> Result<File> {
OpenOptions::new()
.read(true)
.write(true)
.open("NUL")
.map_err(Into::into)
}
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> 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)
.map_err(Into::into)
}

957
wasi-common/src/wasi.rs Normal file
View File

@@ -0,0 +1,957 @@
//! Types and constants shared between 32-bit and 64-bit wasi. Types involving
//! pointer or `usize`-sized data are excluded here, so this file only contains
//! fixed-size types, so it's host/target independent.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
use wig::witx_wasi_types;
witx_wasi_types!("unstable" "wasi_unstable_preview0");
pub(crate) const RIGHTS_ALL: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC
| __WASI_RIGHT_FD_READ
| __WASI_RIGHT_FD_SEEK
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
| __WASI_RIGHT_FD_SYNC
| __WASI_RIGHT_FD_TELL
| __WASI_RIGHT_FD_WRITE
| __WASI_RIGHT_FD_ADVISE
| __WASI_RIGHT_FD_ALLOCATE
| __WASI_RIGHT_PATH_CREATE_DIRECTORY
| __WASI_RIGHT_PATH_CREATE_FILE
| __WASI_RIGHT_PATH_LINK_SOURCE
| __WASI_RIGHT_PATH_LINK_TARGET
| __WASI_RIGHT_PATH_OPEN
| __WASI_RIGHT_FD_READDIR
| __WASI_RIGHT_PATH_READLINK
| __WASI_RIGHT_PATH_RENAME_SOURCE
| __WASI_RIGHT_PATH_RENAME_TARGET
| __WASI_RIGHT_PATH_FILESTAT_GET
| __WASI_RIGHT_PATH_FILESTAT_SET_SIZE
| __WASI_RIGHT_PATH_FILESTAT_SET_TIMES
| __WASI_RIGHT_FD_FILESTAT_GET
| __WASI_RIGHT_FD_FILESTAT_SET_SIZE
| __WASI_RIGHT_FD_FILESTAT_SET_TIMES
| __WASI_RIGHT_PATH_SYMLINK
| __WASI_RIGHT_PATH_UNLINK_FILE
| __WASI_RIGHT_PATH_REMOVE_DIRECTORY
| __WASI_RIGHT_POLL_FD_READWRITE
| __WASI_RIGHT_SOCK_SHUTDOWN;
// Block and character device interaction is outside the scope of
// WASI. Simply allow everything.
pub(crate) const RIGHTS_BLOCK_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL;
pub(crate) const RIGHTS_BLOCK_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL;
pub(crate) const RIGHTS_CHARACTER_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL;
pub(crate) const RIGHTS_CHARACTER_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL;
// Only allow directory operations on directories. Directories can only
// yield file descriptors to other directories and files.
pub(crate) const RIGHTS_DIRECTORY_BASE: __wasi_rights_t = __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
| __WASI_RIGHT_FD_SYNC
| __WASI_RIGHT_FD_ADVISE
| __WASI_RIGHT_PATH_CREATE_DIRECTORY
| __WASI_RIGHT_PATH_CREATE_FILE
| __WASI_RIGHT_PATH_LINK_SOURCE
| __WASI_RIGHT_PATH_LINK_TARGET
| __WASI_RIGHT_PATH_OPEN
| __WASI_RIGHT_FD_READDIR
| __WASI_RIGHT_PATH_READLINK
| __WASI_RIGHT_PATH_RENAME_SOURCE
| __WASI_RIGHT_PATH_RENAME_TARGET
| __WASI_RIGHT_PATH_FILESTAT_GET
| __WASI_RIGHT_PATH_FILESTAT_SET_SIZE
| __WASI_RIGHT_PATH_FILESTAT_SET_TIMES
| __WASI_RIGHT_FD_FILESTAT_GET
| __WASI_RIGHT_FD_FILESTAT_SET_TIMES
| __WASI_RIGHT_PATH_SYMLINK
| __WASI_RIGHT_PATH_UNLINK_FILE
| __WASI_RIGHT_PATH_REMOVE_DIRECTORY
| __WASI_RIGHT_POLL_FD_READWRITE;
pub(crate) const RIGHTS_DIRECTORY_INHERITING: __wasi_rights_t =
RIGHTS_DIRECTORY_BASE | RIGHTS_REGULAR_FILE_BASE;
// Operations that apply to regular files.
pub(crate) const RIGHTS_REGULAR_FILE_BASE: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC
| __WASI_RIGHT_FD_READ
| __WASI_RIGHT_FD_SEEK
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
| __WASI_RIGHT_FD_SYNC
| __WASI_RIGHT_FD_TELL
| __WASI_RIGHT_FD_WRITE
| __WASI_RIGHT_FD_ADVISE
| __WASI_RIGHT_FD_ALLOCATE
| __WASI_RIGHT_FD_FILESTAT_GET
| __WASI_RIGHT_FD_FILESTAT_SET_SIZE
| __WASI_RIGHT_FD_FILESTAT_SET_TIMES
| __WASI_RIGHT_POLL_FD_READWRITE;
pub(crate) const RIGHTS_REGULAR_FILE_INHERITING: __wasi_rights_t = 0;
// Operations that apply to sockets and socket pairs.
pub(crate) const RIGHTS_SOCKET_BASE: __wasi_rights_t = __WASI_RIGHT_FD_READ
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
| __WASI_RIGHT_FD_WRITE
| __WASI_RIGHT_FD_FILESTAT_GET
| __WASI_RIGHT_POLL_FD_READWRITE
| __WASI_RIGHT_SOCK_SHUTDOWN;
pub(crate) const RIGHTS_SOCKET_INHERITING: __wasi_rights_t = RIGHTS_ALL;
// Operations that apply to TTYs.
pub(crate) const RIGHTS_TTY_BASE: __wasi_rights_t = __WASI_RIGHT_FD_READ
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
| __WASI_RIGHT_FD_WRITE
| __WASI_RIGHT_FD_FILESTAT_GET
| __WASI_RIGHT_POLL_FD_READWRITE;
#[allow(unused)]
pub(crate) const RIGHTS_TTY_INHERITING: __wasi_rights_t = 0;
pub fn strerror(errno: __wasi_errno_t) -> &'static str {
match errno {
__WASI_ESUCCESS => "__WASI_ESUCCESS",
__WASI_E2BIG => "__WASI_E2BIG",
__WASI_EACCES => "__WASI_EACCES",
__WASI_EADDRINUSE => "__WASI_EADDRINUSE",
__WASI_EADDRNOTAVAIL => "__WASI_EADDRNOTAVAIL",
__WASI_EAFNOSUPPORT => "__WASI_EAFNOSUPPORT",
__WASI_EAGAIN => "__WASI_EAGAIN",
__WASI_EALREADY => "__WASI_EALREADY",
__WASI_EBADF => "__WASI_EBADF",
__WASI_EBADMSG => "__WASI_EBADMSG",
__WASI_EBUSY => "__WASI_EBUSY",
__WASI_ECANCELED => "__WASI_ECANCELED",
__WASI_ECHILD => "__WASI_ECHILD",
__WASI_ECONNABORTED => "__WASI_ECONNABORTED",
__WASI_ECONNREFUSED => "__WASI_ECONNREFUSED",
__WASI_ECONNRESET => "__WASI_ECONNRESET",
__WASI_EDEADLK => "__WASI_EDEADLK",
__WASI_EDESTADDRREQ => "__WASI_EDESTADDRREQ",
__WASI_EDOM => "__WASI_EDOM",
__WASI_EDQUOT => "__WASI_EDQUOT",
__WASI_EEXIST => "__WASI_EEXIST",
__WASI_EFAULT => "__WASI_EFAULT",
__WASI_EFBIG => "__WASI_EFBIG",
__WASI_EHOSTUNREACH => "__WASI_EHOSTUNREACH",
__WASI_EIDRM => "__WASI_EIDRM",
__WASI_EILSEQ => "__WASI_EILSEQ",
__WASI_EINPROGRESS => "__WASI_EINPROGRESS",
__WASI_EINTR => "__WASI_EINTR",
__WASI_EINVAL => "__WASI_EINVAL",
__WASI_EIO => "__WASI_EIO",
__WASI_EISCONN => "__WASI_EISCONN",
__WASI_EISDIR => "__WASI_EISDIR",
__WASI_ELOOP => "__WASI_ELOOP",
__WASI_EMFILE => "__WASI_EMFILE",
__WASI_EMLINK => "__WASI_EMLINK",
__WASI_EMSGSIZE => "__WASI_EMSGSIZE",
__WASI_EMULTIHOP => "__WASI_EMULTIHOP",
__WASI_ENAMETOOLONG => "__WASI_ENAMETOOLONG",
__WASI_ENETDOWN => "__WASI_ENETDOWN",
__WASI_ENETRESET => "__WASI_ENETRESET",
__WASI_ENETUNREACH => "__WASI_ENETUNREACH",
__WASI_ENFILE => "__WASI_ENFILE",
__WASI_ENOBUFS => "__WASI_ENOBUFS",
__WASI_ENODEV => "__WASI_ENODEV",
__WASI_ENOENT => "__WASI_ENOENT",
__WASI_ENOEXEC => "__WASI_ENOEXEC",
__WASI_ENOLCK => "__WASI_ENOLCK",
__WASI_ENOLINK => "__WASI_ENOLINK",
__WASI_ENOMEM => "__WASI_ENOMEM",
__WASI_ENOMSG => "__WASI_ENOMSG",
__WASI_ENOPROTOOPT => "__WASI_ENOPROTOOPT",
__WASI_ENOSPC => "__WASI_ENOSPC",
__WASI_ENOSYS => "__WASI_ENOSYS",
__WASI_ENOTCONN => "__WASI_ENOTCONN",
__WASI_ENOTDIR => "__WASI_ENOTDIR",
__WASI_ENOTEMPTY => "__WASI_ENOTEMPTY",
__WASI_ENOTRECOVERABLE => "__WASI_ENOTRECOVERABLE",
__WASI_ENOTSOCK => "__WASI_ENOTSOCK",
__WASI_ENOTSUP => "__WASI_ENOTSUP",
__WASI_ENOTTY => "__WASI_ENOTTY",
__WASI_ENXIO => "__WASI_ENXIO",
__WASI_EOVERFLOW => "__WASI_EOVERFLOW",
__WASI_EOWNERDEAD => "__WASI_EOWNERDEAD",
__WASI_EPERM => "__WASI_EPERM",
__WASI_EPIPE => "__WASI_EPIPE",
__WASI_EPROTO => "__WASI_EPROTO",
__WASI_EPROTONOSUPPORT => "__WASI_EPROTONOSUPPORT",
__WASI_EPROTOTYPE => "__WASI_EPROTOTYPE",
__WASI_ERANGE => "__WASI_ERANGE",
__WASI_EROFS => "__WASI_EROFS",
__WASI_ESPIPE => "__WASI_ESPIPE",
__WASI_ESRCH => "__WASI_ESRCH",
__WASI_ESTALE => "__WASI_ESTALE",
__WASI_ETIMEDOUT => "__WASI_ETIMEDOUT",
__WASI_ETXTBSY => "__WASI_ETXTBSY",
__WASI_EXDEV => "__WASI_EXDEV",
__WASI_ENOTCAPABLE => "__WASI_ENOTCAPABLE",
other => panic!("Undefined errno value {:?}", other),
}
}
pub fn whence_to_str(whence: __wasi_whence_t) -> &'static str {
match whence {
__WASI_WHENCE_CUR => "__WASI_WHENCE_CUR",
__WASI_WHENCE_END => "__WASI_WHENCE_END",
__WASI_WHENCE_SET => "__WASI_WHENCE_SET",
other => panic!("Undefined whence value {:?}", other),
}
}
pub const __WASI_DIRCOOKIE_START: __wasi_dircookie_t = 0;
#[cfg(test)]
mod test {
use super::*;
#[test]
fn bindgen_test_layout_wasi_dirent_t() {
assert_eq!(
::std::mem::size_of::<__wasi_dirent_t>(),
24usize,
concat!("Size of: ", stringify!(__wasi_dirent_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_next as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_next)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_ino as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_ino)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_namlen as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_namlen)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_type as *const _ as usize },
20usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_type)
)
);
}
#[test]
fn bindgen_test_layout___wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t() {
assert_eq!(
::std::mem::size_of::<__wasi_event_fd_readwrite_t>(),
16usize,
concat!("Size of: ", stringify!(__wasi_event_fd_readwrite_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_event_fd_readwrite_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_event_fd_readwrite_t))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_event_fd_readwrite_t>())).nbytes as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_fd_readwrite_t),
"::",
stringify!(nbytes)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_event_fd_readwrite_t>())).flags as *const _ as usize
},
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_fd_readwrite_t),
"::",
stringify!(flags)
)
);
}
#[test]
fn bindgen_test_layout___wasi_event_t___wasi_event_u() {
assert_eq!(
::std::mem::size_of::<__wasi_event_u>(),
16usize,
concat!("Size of: ", stringify!(__wasi_event_u))
);
assert_eq!(
::std::mem::align_of::<__wasi_event_u>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_event_u))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_u>())).fd_readwrite as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_u),
"::",
stringify!(fd_readwrite)
)
);
}
#[test]
fn bindgen_test_layout___wasi_event_t() {
assert_eq!(
::std::mem::size_of::<__wasi_event_t>(),
32usize,
concat!("Size of: ", stringify!(__wasi_event_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_event_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_event_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).userdata as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(userdata)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).error as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(error)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).r#type as *const _ as usize },
10usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(r#type)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).u as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(u)
)
);
}
#[test]
fn bindgen_test_layout_wasi_event_t() {
assert_eq!(
::std::mem::size_of::<__wasi_event_t>(),
32usize,
concat!("Size of: ", stringify!(__wasi_event_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).userdata as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(userdata)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).error as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(error)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).r#type as *const _ as usize },
10usize,
concat!(
"Offset of field: ",
stringify!(__wasi_event_t),
"::",
stringify!(r#type)
)
);
}
#[test]
fn bindgen_test_layout_wasi_fdstat_t() {
assert_eq!(
::std::mem::size_of::<__wasi_fdstat_t>(),
24usize,
concat!("Size of: ", stringify!(__wasi_fdstat_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_filetype as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_filetype)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_flags as *const _ as usize },
2usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_flags)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_base as *const _ as usize
},
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_rights_base)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_inheriting as *const _
as usize
},
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_rights_inheriting)
)
);
}
#[test]
fn bindgen_test_layout_wasi_filestat_t() {
assert_eq!(
::std::mem::size_of::<__wasi_filestat_t>(),
56usize,
concat!("Size of: ", stringify!(__wasi_filestat_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_dev as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_dev)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ino as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_ino)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_filestat_t>())).st_filetype as *const _ as usize
},
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_filetype)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_nlink as *const _ as usize },
20usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_nlink)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_size as *const _ as usize },
24usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_size)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_atim as *const _ as usize },
32usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_atim)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_mtim as *const _ as usize },
40usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_mtim)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ctim as *const _ as usize },
48usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_ctim)
)
);
}
#[test]
fn bindgen_test_layout___wasi_subscription_clock_t() {
assert_eq!(
::std::mem::size_of::<__wasi_subscription_clock_t>(),
40usize,
concat!("Size of: ", stringify!(__wasi_subscription_clock_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_subscription_clock_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_subscription_clock_t))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).identifier as *const _
as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_clock_t),
"::",
stringify!(identifier)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).clock_id as *const _
as usize
},
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_clock_t),
"::",
stringify!(clock_id)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).timeout as *const _ as usize
},
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_clock_t),
"::",
stringify!(timeout)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).precision as *const _
as usize
},
24usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_clock_t),
"::",
stringify!(precision)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).flags as *const _ as usize
},
32usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_clock_t),
"::",
stringify!(flags)
)
);
}
#[test]
fn bindgen_test_layout___wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_fd_readwrite_t(
) {
assert_eq!(
::std::mem::size_of::<__wasi_subscription_fd_readwrite_t>(),
4usize,
concat!("Size of: ", stringify!(__wasi_subscription_fd_readwrite_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_subscription_fd_readwrite_t>(),
4usize,
concat!(
"Alignment of ",
stringify!(__wasi_subscription_fd_readwrite_t)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_fd_readwrite_t>())).file_descriptor
as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_fd_readwrite_t),
"::",
stringify!(fd)
)
);
}
#[test]
fn bindgen_test_layout___wasi_subscription_t___wasi_subscription_u() {
assert_eq!(
::std::mem::size_of::<__wasi_subscription_u>(),
40usize,
concat!("Size of: ", stringify!(__wasi_subscription_u))
);
assert_eq!(
::std::mem::align_of::<__wasi_subscription_u>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_subscription_u))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_subscription_u>())).clock as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_u),
"::",
stringify!(clock)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_u>())).fd_readwrite as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_u),
"::",
stringify!(fd_readwrite)
)
);
}
#[test]
fn bindgen_test_layout___wasi_subscription_t() {
assert_eq!(
::std::mem::size_of::<__wasi_subscription_t>(),
56usize,
concat!("Size of: ", stringify!(__wasi_subscription_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_subscription_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_subscription_t))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_t>())).userdata as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_t),
"::",
stringify!(userdata)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_subscription_t>())).r#type as *const _ as usize
},
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_t),
"::",
stringify!(r#type)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_subscription_t>())).u as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_subscription_t),
"::",
stringify!(u)
)
);
}
#[test]
fn bindgen_test_layout___wasi_filestat_t() {
assert_eq!(
::std::mem::size_of::<__wasi_filestat_t>(),
56usize,
concat!("Size of: ", stringify!(__wasi_filestat_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_filestat_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_filestat_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_dev as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_dev)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ino as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_ino)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_filestat_t>())).st_filetype as *const _ as usize
},
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_filetype)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_nlink as *const _ as usize },
20usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_nlink)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_size as *const _ as usize },
24usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_size)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_atim as *const _ as usize },
32usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_atim)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_mtim as *const _ as usize },
40usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_mtim)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ctim as *const _ as usize },
48usize,
concat!(
"Offset of field: ",
stringify!(__wasi_filestat_t),
"::",
stringify!(st_ctim)
)
);
}
#[test]
fn bindgen_test_layout___wasi_fdstat_t() {
assert_eq!(
::std::mem::size_of::<__wasi_fdstat_t>(),
24usize,
concat!("Size of: ", stringify!(__wasi_fdstat_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_fdstat_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_fdstat_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_filetype as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_filetype)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_flags as *const _ as usize },
2usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_flags)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_base as *const _ as usize
},
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_rights_base)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_inheriting as *const _
as usize
},
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_fdstat_t),
"::",
stringify!(fs_rights_inheriting)
)
);
}
#[test]
fn bindgen_test_layout___wasi_dirent_t() {
assert_eq!(
::std::mem::size_of::<__wasi_dirent_t>(),
24usize,
concat!("Size of: ", stringify!(__wasi_dirent_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_dirent_t>(),
8usize,
concat!("Alignment of ", stringify!(__wasi_dirent_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_next as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_next)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_ino as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_ino)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_namlen as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_namlen)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_type as *const _ as usize },
20usize,
concat!(
"Offset of field: ",
stringify!(__wasi_dirent_t),
"::",
stringify!(d_type)
)
);
}
}

173
wasi-common/src/wasi32.rs Normal file
View File

@@ -0,0 +1,173 @@
//! Types and constants specific to 32-bit wasi. These are similar to the types
//! in the `host` module, but pointers and `usize` values are replaced with
//! `u32`-sized types.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
use wig::witx_wasi32_types;
use crate::wasi::*;
pub type uintptr_t = u32;
pub type size_t = u32;
witx_wasi32_types!("unstable" "wasi_unstable_preview0");
#[cfg(test)]
mod test {
use super::*;
#[test]
fn bindgen_test_layout_wasi_ciovec_t() {
assert_eq!(
::std::mem::size_of::<__wasi_ciovec_t>(),
8usize,
concat!("Size of: ", stringify!(__wasi_ciovec_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_ciovec_t>(),
4usize,
concat!("Alignment of ", stringify!(__wasi_ciovec_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_ciovec_t>())).buf as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_ciovec_t),
"::",
stringify!(buf)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_ciovec_t>())).buf_len as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(__wasi_ciovec_t),
"::",
stringify!(buf_len)
)
);
}
#[test]
fn bindgen_test_layout_wasi_iovec_t() {
assert_eq!(
::std::mem::size_of::<__wasi_iovec_t>(),
8usize,
concat!("Size of: ", stringify!(__wasi_iovec_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_iovec_t>(),
4usize,
concat!("Alignment of ", stringify!(__wasi_iovec_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_iovec_t>())).buf as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_iovec_t),
"::",
stringify!(buf)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_iovec_t>())).buf_len as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(__wasi_iovec_t),
"::",
stringify!(buf_len)
)
);
}
#[test]
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t() {
assert_eq!(
::std::mem::size_of::<__wasi_prestat_dir>(),
4usize,
concat!("Size of: ", stringify!(__wasi_prestat_dir))
);
assert_eq!(
::std::mem::align_of::<__wasi_prestat_dir>(),
4usize,
concat!("Alignment of ", stringify!(__wasi_prestat_dir))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<__wasi_prestat_dir>())).pr_name_len as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_dir),
"::",
stringify!(pr_name_len)
)
);
}
#[test]
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u() {
assert_eq!(
::std::mem::size_of::<__wasi_prestat_u>(),
4usize,
concat!("Size of: ", stringify!(__wasi_prestat_u))
);
assert_eq!(
::std::mem::align_of::<__wasi_prestat_u>(),
4usize,
concat!("Alignment of ", stringify!(__wasi_prestat_u))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_prestat_u>())).dir as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_u),
"::",
stringify!(dir)
)
);
}
#[test]
fn bindgen_test_layout___wasi_prestat_t() {
assert_eq!(
::std::mem::size_of::<__wasi_prestat_t>(),
8usize,
concat!("Size of: ", stringify!(__wasi_prestat_t))
);
assert_eq!(
::std::mem::align_of::<__wasi_prestat_t>(),
4usize,
concat!("Alignment of ", stringify!(__wasi_prestat_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).pr_type as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_t),
"::",
stringify!(pr_type)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).u as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(__wasi_prestat_t),
"::",
stringify!(u)
)
);
}
}