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:
335
wasi-common/src/ctx.rs
Normal file
335
wasi-common/src/ctx.rs
Normal 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
280
wasi-common/src/error.rs
Normal 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
186
wasi-common/src/fdentry.rs
Normal 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
216
wasi-common/src/fs/dir.rs
Normal 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
|
||||
49
wasi-common/src/fs/dir_builder.rs
Normal file
49
wasi-common/src/fs/dir_builder.rs
Normal 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
|
||||
53
wasi-common/src/fs/dir_entry.rs
Normal file
53
wasi-common/src/fs/dir_entry.rs
Normal 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
265
wasi-common/src/fs/error.rs
Normal 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
107
wasi-common/src/fs/file.rs
Normal 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
|
||||
49
wasi-common/src/fs/file_type.rs
Normal file
49
wasi-common/src/fs/file_type.rs
Normal 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
|
||||
106
wasi-common/src/fs/metadata.rs
Normal file
106
wasi-common/src/fs/metadata.rs
Normal 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
51
wasi-common/src/fs/mod.rs
Normal 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::*;
|
||||
99
wasi-common/src/fs/open_options.rs
Normal file
99
wasi-common/src/fs/open_options.rs
Normal 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?
|
||||
37
wasi-common/src/fs/permissions.rs
Normal file
37
wasi-common/src/fs/permissions.rs
Normal 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
|
||||
32
wasi-common/src/fs/readdir.rs
Normal file
32
wasi-common/src/fs/readdir.rs
Normal 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
|
||||
20
wasi-common/src/helpers.rs
Normal file
20
wasi-common/src/helpers.rs
Normal 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
109
wasi-common/src/host.rs
Normal 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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
256
wasi-common/src/hostcalls/fs.rs
Normal file
256
wasi-common/src/hostcalls/fs.rs
Normal 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;
|
||||
}
|
||||
83
wasi-common/src/hostcalls/misc.rs
Normal file
83
wasi-common/src/hostcalls/misc.rs
Normal 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;
|
||||
}
|
||||
7
wasi-common/src/hostcalls/mod.rs
Normal file
7
wasi-common/src/hostcalls/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod fs;
|
||||
mod misc;
|
||||
mod sock;
|
||||
|
||||
pub use self::fs::*;
|
||||
pub use self::misc::*;
|
||||
pub use self::sock::*;
|
||||
43
wasi-common/src/hostcalls/sock.rs
Normal file
43
wasi-common/src/hostcalls/sock.rs
Normal 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")
|
||||
}
|
||||
1091
wasi-common/src/hostcalls_impl/fs.rs
Normal file
1091
wasi-common/src/hostcalls_impl/fs.rs
Normal file
File diff suppressed because it is too large
Load Diff
213
wasi-common/src/hostcalls_impl/fs_helpers.rs
Normal file
213
wasi-common/src/hostcalls_impl/fs_helpers.rs
Normal 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("."),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
317
wasi-common/src/hostcalls_impl/misc.rs
Normal file
317
wasi-common/src/hostcalls_impl/misc.rs
Normal 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,
|
||||
}
|
||||
7
wasi-common/src/hostcalls_impl/mod.rs
Normal file
7
wasi-common/src/hostcalls_impl/mod.rs
Normal 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
42
wasi-common/src/lib.rs
Normal 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
13
wasi-common/src/macros.rs
Normal 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
480
wasi-common/src/memory.rs
Normal 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);
|
||||
24
wasi-common/src/sys/mod.rs
Normal file
24
wasi-common/src/sys/mod.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
289
wasi-common/src/sys/unix/bsd/hostcalls_impl.rs
Normal file
289
wasi-common/src/sys/unix/bsd/hostcalls_impl.rs
Normal 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(())
|
||||
}
|
||||
82
wasi-common/src/sys/unix/bsd/mod.rs
Normal file
82
wasi-common/src/sys/unix/bsd/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
wasi-common/src/sys/unix/bsd/osfile.rs
Normal file
52
wasi-common/src/sys/unix/bsd/osfile.rs
Normal 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
|
||||
}
|
||||
}
|
||||
216
wasi-common/src/sys/unix/dir.rs
Normal file
216
wasi-common/src/sys/unix/dir.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
135
wasi-common/src/sys/unix/fdentry_impl.rs
Normal file
135
wasi-common/src/sys/unix/fdentry_impl.rs
Normal 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))
|
||||
}
|
||||
246
wasi-common/src/sys/unix/host_impl.rs
Normal file
246
wasi-common/src/sys/unix/host_impl.rs
Normal 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)
|
||||
}
|
||||
357
wasi-common/src/sys/unix/hostcalls_impl/fs.rs
Normal file
357
wasi-common/src/sys/unix/hostcalls_impl/fs.rs
Normal 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())),
|
||||
}
|
||||
}
|
||||
83
wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs
Normal file
83
wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs
Normal 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)
|
||||
}
|
||||
226
wasi-common/src/sys/unix/hostcalls_impl/misc.rs
Normal file
226
wasi-common/src/sys/unix/hostcalls_impl/misc.rs
Normal 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(())
|
||||
}
|
||||
8
wasi-common/src/sys/unix/hostcalls_impl/mod.rs
Normal file
8
wasi-common/src/sys/unix/hostcalls_impl/mod.rs
Normal 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::*;
|
||||
165
wasi-common/src/sys/unix/linux/hostcalls_impl.rs
Normal file
165
wasi-common/src/sys/unix/linux/hostcalls_impl.rs
Normal 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(())
|
||||
}
|
||||
42
wasi-common/src/sys/unix/linux/mod.rs
Normal file
42
wasi-common/src/sys/unix/linux/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
32
wasi-common/src/sys/unix/linux/osfile.rs
Normal file
32
wasi-common/src/sys/unix/linux/osfile.rs
Normal 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
|
||||
}
|
||||
}
|
||||
38
wasi-common/src/sys/unix/mod.rs
Normal file
38
wasi-common/src/sys/unix/mod.rs
Normal 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)
|
||||
}
|
||||
129
wasi-common/src/sys/windows/fdentry_impl.rs
Normal file
129
wasi-common/src/sys/windows/fdentry_impl.rs
Normal 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))
|
||||
}
|
||||
108
wasi-common/src/sys/windows/host_impl.rs
Normal file
108
wasi-common/src/sys/windows/host_impl.rs
Normal 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)
|
||||
}
|
||||
550
wasi-common/src/sys/windows/hostcalls_impl/fs.rs
Normal file
550
wasi-common/src/sys/windows/hostcalls_impl/fs.rs
Normal 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)
|
||||
}
|
||||
149
wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs
Normal file
149
wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs
Normal 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)
|
||||
}
|
||||
119
wasi-common/src/sys/windows/hostcalls_impl/misc.rs
Normal file
119
wasi-common/src/sys/windows/hostcalls_impl/misc.rs
Normal 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
|
||||
}
|
||||
8
wasi-common/src/sys/windows/hostcalls_impl/mod.rs
Normal file
8
wasi-common/src/sys/windows/hostcalls_impl/mod.rs
Normal 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::*;
|
||||
32
wasi-common/src/sys/windows/mod.rs
Normal file
32
wasi-common/src/sys/windows/mod.rs
Normal 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
957
wasi-common/src/wasi.rs
Normal 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
173
wasi-common/src/wasi32.rs
Normal 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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user