delete wasi-common, yanix, winx

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

View File

@@ -1,49 +0,0 @@
[package]
name = "wasi-common"
version = "0.22.0"
authors = ["The Wasmtime Project Developers"]
description = "WASI implementation in Rust"
license = "Apache-2.0 WITH LLVM-exception"
categories = ["wasm"]
keywords = ["webassembly", "wasm"]
repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md"
edition = "2018"
include = ["src/**/*", "LICENSE", "WASI/phases", "build.rs"]
# This doesn't actually link to a native library, but it allows us to set env
# vars like `DEP_WASI_COMMON_19_*` for crates that have build scripts and depend
# on this crate, allowing other crates to use the same witx files.
links = "wasi-common-19"
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
libc = "0.2"
getrandom = { version = "0.2.0", features = ["std"] }
cfg-if = "1.0"
filetime = "0.2.7"
lazy_static = "1.4.0"
wiggle = { path = "../wiggle", default-features = false, version = "0.22.0" }
tracing = "0.1.19"
[target.'cfg(unix)'.dependencies]
yanix = { path = "yanix", version = "0.22.0" }
[target.'cfg(windows)'.dependencies]
winx = { path = "winx", version = "0.22.0" }
winapi = "0.3"
cpu-time = "1.0"
[badges]
maintenance = { status = "actively-developed" }
[features]
default = ["trace_log"]
# This feature enables the `tracing` logs in the calls to target the `log`
# ecosystem of backends (e.g. `env_logger`. Disable this if you want to use
# `tracing-subscriber`.
trace_log = [ "wiggle/tracing_log", "tracing/log" ]
# Need to make the wiggle_metadata feature available to consumers of this
# crate if they want the snapshots to have metadata available.
wiggle_metadata = ["wiggle/wiggle_metadata"]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,145 +0,0 @@
use crate::Error;
use std::collections::HashMap;
use std::convert::TryInto;
use std::ffi::{CString, OsString};
use wiggle::GuestPtr;
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum PendingString {
Bytes(Vec<u8>),
OsString(OsString),
}
impl From<Vec<u8>> for PendingString {
fn from(bytes: Vec<u8>) -> Self {
Self::Bytes(bytes)
}
}
impl From<OsString> for PendingString {
fn from(s: OsString) -> Self {
Self::OsString(s)
}
}
impl PendingString {
pub fn into_string(self) -> Result<String, StringArrayError> {
let res = match self {
Self::Bytes(v) => String::from_utf8(v)?,
#[cfg(unix)]
Self::OsString(s) => {
use std::os::unix::ffi::OsStringExt;
String::from_utf8(s.into_vec())?
}
#[cfg(windows)]
Self::OsString(s) => {
use std::os::windows::ffi::OsStrExt;
let bytes: Vec<u16> = s.encode_wide().collect();
String::from_utf16(&bytes)?
}
};
Ok(res)
}
}
#[derive(Debug, thiserror::Error)]
pub enum StringArrayError {
/// Provided sequence of bytes contained an unexpected NUL byte.
#[error("provided sequence contained an unexpected NUL byte")]
Nul(#[from] std::ffi::NulError),
/// Too many elements: must fit into u32
#[error("too many elements")]
NumElements,
/// Element size: must fit into u32
#[error("element too big")]
ElemSize,
/// Cumulative element size: must fit into u32
#[error("cumulative element size too big")]
CumElemSize,
/// Provided sequence of bytes was not a valid UTF-8.
#[error("provided sequence is not valid UTF-8: {0}")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
/// Provided sequence of bytes was not a valid UTF-16.
///
/// This error is expected to only occur on Windows hosts.
#[error("provided sequence is not valid UTF-16: {0}")]
InvalidUtf16(#[from] std::string::FromUtf16Error),
}
pub struct StringArray {
elems: Vec<CString>,
pub number_elements: u32,
pub cumulative_size: u32,
}
impl StringArray {
pub fn from_pending_vec(elems: Vec<PendingString>) -> Result<Self, StringArrayError> {
let elems = elems
.into_iter()
.map(|arg| arg.into_string())
.collect::<Result<Vec<String>, StringArrayError>>()?;
Self::from_strings(elems)
}
pub fn from_pending_map(
elems: HashMap<PendingString, PendingString>,
) -> Result<Self, StringArrayError> {
let mut pairs = Vec::new();
for (k, v) in elems.into_iter() {
let mut pair = k.into_string()?;
pair.push('=');
pair.push_str(&v.into_string()?);
pairs.push(pair);
}
Self::from_strings(pairs)
}
pub fn from_strings(elems: Vec<String>) -> Result<Self, StringArrayError> {
let elems = elems
.into_iter()
.map(|s| CString::new(s))
.collect::<Result<Vec<CString>, _>>()?;
let number_elements = elems
.len()
.try_into()
.map_err(|_| StringArrayError::NumElements)?;
let mut cumulative_size: u32 = 0;
for elem in elems.iter() {
let elem_bytes = elem
.as_bytes_with_nul()
.len()
.try_into()
.map_err(|_| StringArrayError::ElemSize)?;
cumulative_size = cumulative_size
.checked_add(elem_bytes)
.ok_or(StringArrayError::CumElemSize)?;
}
Ok(Self {
elems,
number_elements,
cumulative_size,
})
}
pub fn write_to_guest<'a>(
&self,
buffer: &GuestPtr<'a, u8>,
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
) -> Result<(), Error> {
let element_heads = element_heads.as_array(self.number_elements);
let buffer = buffer.as_array(self.cumulative_size);
let mut cursor = 0;
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
let bytes = elem.as_bytes_with_nul();
let len: u32 = bytes.len().try_into()?;
let elem_buffer = buffer
.get_range(cursor..(cursor + len))
.ok_or(Error::Inval)?; // Elements don't fit in buffer provided
elem_buffer.copy_from_slice(bytes)?;
head?.write(
elem_buffer
.get(0)
.expect("all elem buffers at least length 1"),
)?;
cursor += len;
}
Ok(())
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,781 +0,0 @@
use crate::handle::{
Advice, Dircookie, Dirent, Fdflags, Filesize, Filestat, Filetype, Fstflags, Handle,
HandleRights, Oflags, Rights, RightsExt, Size, DIRCOOKIE_START,
};
use crate::sched::Timestamp;
use crate::{Error, Result};
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::convert::TryInto;
use std::io;
use std::io::SeekFrom;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use tracing::trace;
pub mod pipe;
/// An entry in a virtual filesystem
pub enum VirtualDirEntry {
/// The contents of a child directory
Directory(HashMap<String, VirtualDirEntry>),
/// A file
File(Box<dyn FileContents>),
}
impl VirtualDirEntry {
/// Construct an empty directory
pub fn empty_directory() -> Self {
Self::Directory(HashMap::new())
}
}
/// Files and directories may be moved, and for implementation reasons retain a reference to their
/// parent Handle, so files that can be moved must provide an interface to update their parent
/// reference.
pub(crate) trait MovableFile {
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>);
}
pub trait FileContents {
/// The implementation-defined maximum size of the store corresponding to a `FileContents`
/// implementation.
fn max_size(&self) -> Filesize;
/// The current number of bytes this `FileContents` describes.
fn size(&self) -> Filesize;
/// Resize to hold `new_size` number of bytes, or error if this is not possible.
fn resize(&mut self, new_size: Filesize) -> Result<()>;
/// Write a list of `IoSlice` starting at `offset`. `offset` plus the total size of all `iovs`
/// is guaranteed to not exceed `max_size`. Implementations must not indicate more bytes have
/// been written than can be held by `iovs`.
fn pwritev(&mut self, iovs: &[io::IoSlice], offset: Filesize) -> Result<usize>;
/// Read from the file from `offset`, filling a list of `IoSlice`. The returend size must not
/// be more than the capactiy of `iovs`, and must not exceed the limit reported by
/// `self.max_size()`.
fn preadv(&self, iovs: &mut [io::IoSliceMut], offset: Filesize) -> Result<usize>;
/// Write contents from `buf` to this file starting at `offset`. `offset` plus the length of
/// `buf` is guaranteed to not exceed `max_size`. Implementations must not indicate more bytes
/// have been written than the size of `buf`.
fn pwrite(&mut self, buf: &[u8], offset: Filesize) -> Result<usize>;
/// Read from the file at `offset`, filling `buf`. The returned size must not be more than the
/// capacity of `buf`, and `offset` plus the returned size must not exceed `self.max_size()`.
fn pread(&self, buf: &mut [u8], offset: Filesize) -> Result<usize>;
}
impl FileContents for VecFileContents {
fn max_size(&self) -> Filesize {
std::usize::MAX as Filesize
}
fn size(&self) -> Filesize {
self.content.len() as Filesize
}
fn resize(&mut self, new_size: Filesize) -> Result<()> {
let new_size: usize = new_size.try_into().map_err(|_| Error::Inval)?;
self.content.resize(new_size, 0);
Ok(())
}
fn preadv(&self, iovs: &mut [io::IoSliceMut], offset: Filesize) -> Result<usize> {
let mut read_total = 0usize;
for iov in iovs.iter_mut() {
let skip: u64 = read_total.try_into().map_err(|_| Error::Inval)?;
let read = self.pread(iov, offset + skip)?;
read_total = read_total.checked_add(read).expect("FileContents::preadv must not be called when reads could total to more bytes than the return value can hold");
}
Ok(read_total)
}
fn pwritev(&mut self, iovs: &[io::IoSlice], offset: Filesize) -> Result<usize> {
let mut write_total = 0usize;
for iov in iovs.iter() {
let skip: u64 = write_total.try_into().map_err(|_| Error::Inval)?;
let written = self.pwrite(iov, offset + skip)?;
write_total = write_total.checked_add(written).expect("FileContents::pwritev must not be called when writes could total to more bytes than the return value can hold");
}
Ok(write_total)
}
fn pread(&self, buf: &mut [u8], offset: Filesize) -> Result<usize> {
trace!(buffer_length = buf.len(), offset = offset, "pread");
let offset: usize = offset.try_into().map_err(|_| Error::Inval)?;
let data_remaining = self.content.len().saturating_sub(offset);
let read_count = std::cmp::min(buf.len(), data_remaining);
(&mut buf[..read_count]).copy_from_slice(&self.content[offset..][..read_count]);
Ok(read_count)
}
fn pwrite(&mut self, buf: &[u8], offset: Filesize) -> Result<usize> {
let offset: usize = offset.try_into().map_err(|_| Error::Inval)?;
let write_end = offset.checked_add(buf.len()).ok_or(Error::Fbig)?;
if write_end > self.content.len() {
self.content.resize(write_end, 0);
}
(&mut self.content[offset..][..buf.len()]).copy_from_slice(buf);
Ok(buf.len())
}
}
pub struct VecFileContents {
content: Vec<u8>,
}
impl VecFileContents {
pub fn new() -> Self {
Self {
content: Vec::new(),
}
}
pub fn with_content(content: Vec<u8>) -> Self {
Self { content }
}
}
/// An `InMemoryFile` is a shared handle to some underlying data. The relationship is analagous to
/// a filesystem wherein a file descriptor is one view into a possibly-shared underlying collection
/// of data and permissions on a filesystem.
pub struct InMemoryFile {
rights: Cell<HandleRights>,
cursor: Cell<Filesize>,
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
fd_flags: Cell<Fdflags>,
data: Rc<RefCell<Box<dyn FileContents>>>,
}
impl InMemoryFile {
pub fn memory_backed() -> Self {
Self::new(Box::new(VecFileContents::new()))
}
pub fn new(contents: Box<dyn FileContents>) -> Self {
let rights = HandleRights::new(
Rights::regular_file_base(),
Rights::regular_file_inheriting(),
);
let rights = Cell::new(rights);
Self {
rights,
cursor: Cell::new(0),
fd_flags: Cell::new(Fdflags::empty()),
parent: Rc::new(RefCell::new(None)),
data: Rc::new(RefCell::new(contents)),
}
}
}
impl MovableFile for InMemoryFile {
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>) {
*self.parent.borrow_mut() = new_parent;
}
}
impl Handle for InMemoryFile {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(Self {
rights: self.rights.clone(),
cursor: Cell::new(0),
fd_flags: self.fd_flags.clone(),
parent: Rc::clone(&self.parent),
data: Rc::clone(&self.data),
}))
}
fn get_file_type(&self) -> Filetype {
Filetype::RegularFile
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> {
// we'll just ignore advice for now, unless it's totally invalid
Ok(())
}
fn allocate(&self, offset: Filesize, len: Filesize) -> Result<()> {
let new_limit = offset.checked_add(len).ok_or(Error::Fbig)?;
let mut data = self.data.borrow_mut();
if new_limit > data.max_size() {
return Err(Error::Fbig);
}
if new_limit > data.size() {
data.resize(new_limit)?;
}
Ok(())
}
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(self.fd_flags.get())
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
self.fd_flags.set(fdflags);
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
let stat = Filestat {
dev: 0,
ino: 0,
nlink: 0,
size: self.data.borrow().size(),
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn filestat_set_size(&self, st_size: Filesize) -> Result<()> {
let mut data = self.data.borrow_mut();
if st_size > data.max_size() {
return Err(Error::Fbig);
}
data.resize(st_size)
}
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: Filesize) -> Result<usize> {
self.data.borrow_mut().preadv(buf, offset)
}
fn pwritev(&self, buf: &[io::IoSlice], offset: Filesize) -> Result<usize> {
self.data.borrow_mut().pwritev(buf, offset)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
trace!("read_vectored(iovs={:?})", iovs);
trace!(" | *read_start={:?}", self.cursor.get());
let read = self.data.borrow_mut().preadv(iovs, self.cursor.get())?;
let offset: u64 = read.try_into().map_err(|_| Error::Inval)?;
let update = self.cursor.get().checked_add(offset).ok_or(Error::Inval)?;
self.cursor.set(update);
Ok(read)
}
fn seek(&self, offset: SeekFrom) -> Result<Filesize> {
let content_len = self.data.borrow().size();
match offset {
SeekFrom::Current(offset) => {
let new_cursor = if offset < 0 {
self.cursor
.get()
.checked_sub(offset.wrapping_neg() as u64)
.ok_or(Error::Inval)?
} else {
self.cursor
.get()
.checked_add(offset as u64)
.ok_or(Error::Inval)?
};
self.cursor.set(std::cmp::min(content_len, new_cursor));
}
SeekFrom::End(offset) => {
// A negative offset from the end would be past the end of the file,
let offset: u64 = offset.try_into().map_err(|_| Error::Inval)?;
self.cursor.set(content_len.saturating_sub(offset));
}
SeekFrom::Start(offset) => {
// A negative offset from the end would be before the start of the file.
let offset: u64 = offset.try_into().map_err(|_| Error::Inval)?;
self.cursor.set(std::cmp::min(content_len, offset));
}
}
Ok(self.cursor.get())
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
trace!("write_vectored(iovs={:?})", iovs);
let mut data = self.data.borrow_mut();
let append_mode = self.fd_flags.get().contains(Fdflags::APPEND);
trace!(" | fd_flags={}", self.fd_flags.get());
// If this file is in append mode, we write to the end.
let write_start = if append_mode {
data.size()
} else {
self.cursor.get()
};
let max_size = iovs
.iter()
.map(|iov| {
let cast_iovlen: Size = iov
.len()
.try_into()
.expect("iovec are bounded by wasi max sizes");
cast_iovlen
})
.fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov)))
.expect("write_vectored will not be called with invalid iovs");
if let Some(end) = write_start.checked_add(max_size as Filesize) {
if end > data.max_size() {
return Err(Error::Fbig);
}
} else {
return Err(Error::Fbig);
}
trace!(" | *write_start={:?}", write_start);
let written = data.pwritev(iovs, write_start)?;
// If we are not appending, adjust the cursor appropriately for the write, too. This can't
// overflow, as we checked against that before writing any data.
if !append_mode {
let update = self.cursor.get() + written as u64;
self.cursor.set(update);
}
Ok(written)
}
// PathOps
fn create_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn openat(
&self,
path: &str,
_read: bool,
_write: bool,
oflags: Oflags,
_fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
if oflags.contains(Oflags::DIRECTORY) {
tracing::trace!(
"InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.",
path
);
return Err(Error::Notdir);
}
if path == "." {
return self.try_clone().map_err(Into::into);
} else if path == ".." {
match &*self.parent.borrow() {
Some(file) => file.try_clone().map_err(Into::into),
None => self.try_clone().map_err(Into::into),
}
} else {
Err(Error::Acces)
}
}
fn link(
&self,
_old_path: &str,
_new_handle: Box<dyn Handle>,
_new_path: &str,
_follow: bool,
) -> Result<()> {
Err(Error::Notdir)
}
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
Err(Error::Notdir)
}
fn readlinkat(&self, _path: &str) -> Result<String> {
Err(Error::Notdir)
}
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn remove_directory(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
Err(Error::Notdir)
}
fn unlink_file(&self, _path: &str) -> Result<()> {
Err(Error::Notdir)
}
}
/// A clonable read/write directory.
pub struct VirtualDir {
rights: Cell<HandleRights>,
writable: bool,
// All copies of this `VirtualDir` must share `parent`, and changes in one copy's `parent`
// must be reflected in all handles, so they share `Rc` of an underlying `parent`.
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
}
impl VirtualDir {
pub fn new(writable: bool) -> Self {
let rights = HandleRights::new(Rights::directory_base(), Rights::directory_inheriting());
let rights = Cell::new(rights);
Self {
rights,
writable,
parent: Rc::new(RefCell::new(None)),
entries: Rc::new(RefCell::new(HashMap::new())),
}
}
#[allow(dead_code)]
pub fn with_dir<P: AsRef<Path>>(mut self, dir: Self, path: P) -> Self {
self.add_dir(dir, path);
self
}
#[allow(dead_code)]
pub fn add_dir<P: AsRef<Path>>(&mut self, dir: Self, path: P) {
let entry = Box::new(dir);
entry.set_parent(Some(self.try_clone().expect("can clone self")));
self.entries
.borrow_mut()
.insert(path.as_ref().to_owned(), entry);
}
#[allow(dead_code)]
pub fn with_file<P: AsRef<Path>>(mut self, content: Box<dyn FileContents>, path: P) -> Self {
self.add_file(content, path);
self
}
#[allow(dead_code)]
pub fn add_file<P: AsRef<Path>>(&mut self, content: Box<dyn FileContents>, path: P) {
let entry = Box::new(InMemoryFile::new(content));
entry.set_parent(Some(self.try_clone().expect("can clone self")));
self.entries
.borrow_mut()
.insert(path.as_ref().to_owned(), entry);
}
}
impl MovableFile for VirtualDir {
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>) {
*self.parent.borrow_mut() = new_parent;
}
}
const SELF_DIR_COOKIE: u32 = 0;
const PARENT_DIR_COOKIE: u32 = 1;
// This MUST be the number of constants above. This limit is used to prevent allocation of files
// that would wrap and be mapped to the same dir cookies as `self` or `parent`.
const RESERVED_ENTRY_COUNT: u32 = 2;
impl Handle for VirtualDir {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(Self {
rights: self.rights.clone(),
writable: self.writable,
parent: Rc::clone(&self.parent),
entries: Rc::clone(&self.entries),
}))
}
fn get_file_type(&self) -> Filetype {
Filetype::Directory
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn filestat_get(&self) -> Result<Filestat> {
let stat = Filestat {
dev: 0,
ino: 0,
nlink: 0,
size: 0,
atim: 0,
ctim: 0,
mtim: 0,
filetype: self.get_file_type(),
};
Ok(stat)
}
fn readdir(
&self,
cookie: Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(Dirent, String)>>>> {
struct VirtualDirIter {
start: u32,
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
}
impl Iterator for VirtualDirIter {
type Item = Result<(Dirent, String)>;
fn next(&mut self) -> Option<Self::Item> {
tracing::trace!("VirtualDirIter::next continuing from {}", self.start);
if self.start == SELF_DIR_COOKIE {
self.start += 1;
let name = ".".to_owned();
let dirent = Dirent {
d_next: self.start as u64,
d_ino: 0,
d_namlen: name.len() as _,
d_type: Filetype::Directory,
};
return Some(Ok((dirent, name)));
}
if self.start == PARENT_DIR_COOKIE {
self.start += 1;
let name = "..".to_owned();
let dirent = Dirent {
d_next: self.start as u64,
d_ino: 0,
d_namlen: name.len() as _,
d_type: Filetype::Directory,
};
return Some(Ok((dirent, name)));
}
let entries = self.entries.borrow();
// Adjust `start` to be an appropriate number of HashMap entries.
let start = self.start - RESERVED_ENTRY_COUNT;
if start as usize >= entries.len() {
return None;
}
self.start += 1;
let (path, file) = entries
.iter()
.skip(start as usize)
.next()
.expect("seeked less than the length of entries");
let name = path
.to_str()
.expect("wasi paths are valid utf8 strings")
.to_owned();
let dirent = || -> Result<Dirent> {
let dirent = Dirent {
d_namlen: name.len().try_into()?,
d_type: file.get_file_type(),
d_ino: 0,
d_next: self.start as u64,
};
Ok(dirent)
};
Some(dirent().map(|dirent| (dirent, name)))
}
}
let cookie = match cookie.try_into() {
Ok(cookie) => cookie,
Err(_) => {
// Cookie is larger than u32. it doesn't seem like there's an explicit error
// condition in POSIX or WASI, so just start from the start?
0
}
};
Ok(Box::new(VirtualDirIter {
start: cookie,
entries: Rc::clone(&self.entries),
}))
}
// PathOps
fn create_directory(&self, path: &str) -> Result<()> {
let mut entries = self.entries.borrow_mut();
match entries.entry(PathBuf::from(path)) {
Entry::Occupied(_) => Err(Error::Exist),
Entry::Vacant(v) => {
if self.writable {
let new_dir = Box::new(Self::new(true));
new_dir.set_parent(Some(self.try_clone()?));
v.insert(new_dir);
Ok(())
} else {
Err(Error::Acces)
}
}
}
}
fn filestat_get_at(&self, path: &str, _follow: bool) -> Result<Filestat> {
let stat = self
.openat(path, false, false, Oflags::empty(), Fdflags::empty())?
.filestat_get()?;
Ok(stat)
}
fn filestat_set_times_at(
&self,
path: &str,
atim: Timestamp,
mtim: Timestamp,
fst_flags: Fstflags,
_follow: bool,
) -> Result<()> {
self.openat(path, false, false, Oflags::empty(), Fdflags::empty())?
.filestat_set_times(atim, mtim, fst_flags)?;
Ok(())
}
fn openat(
&self,
path: &str,
_read: bool,
_write: bool,
oflags: Oflags,
fd_flags: Fdflags,
) -> Result<Box<dyn Handle>> {
if path == "." {
return self.try_clone().map_err(Into::into);
} else if path == ".." {
match &*self.parent.borrow() {
Some(file) => {
return file.try_clone().map_err(Into::into);
}
None => {
return self.try_clone().map_err(Into::into);
}
}
}
// openat may have been passed a path with a trailing slash, but files are mapped to paths
// with trailing slashes normalized out.
let file_name = Path::new(path).file_name().ok_or(Error::Inval)?;
let mut entries = self.entries.borrow_mut();
let entry_count = entries.len();
match entries.entry(Path::new(file_name).to_path_buf()) {
Entry::Occupied(e) => {
let creat_excl_mask = Oflags::CREAT | Oflags::EXCL;
if (oflags & creat_excl_mask) == creat_excl_mask {
tracing::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name);
return Err(Error::Exist);
}
if oflags.contains(Oflags::DIRECTORY)
&& e.get().get_file_type() != Filetype::Directory
{
tracing::trace!(
"VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.",
file_name
);
return Err(Error::Notdir);
}
e.get().try_clone().map_err(Into::into)
}
Entry::Vacant(v) => {
if oflags.contains(Oflags::CREAT) {
if self.writable {
// Enforce a hard limit at `u32::MAX - 2` files.
// This is to have a constant limit (rather than target-dependent limit we
// would have with `usize`. The limit is the full `u32` range minus two so we
// can reserve "self" and "parent" cookie values.
if entry_count >= (std::u32::MAX - RESERVED_ENTRY_COUNT) as usize {
return Err(Error::Nospc);
}
tracing::trace!(
"VirtualDir::openat creating an InMemoryFile named {}",
path
);
let file = Box::new(InMemoryFile::memory_backed());
file.fd_flags.set(fd_flags);
file.set_parent(Some(self.try_clone().expect("can clone self")));
v.insert(file).try_clone().map_err(Into::into)
} else {
Err(Error::Acces)
}
} else {
Err(Error::Noent)
}
}
}
}
fn readlinkat(&self, _path: &str) -> Result<String> {
// Files are not symbolic links or directories, faithfully report Notdir.
Err(Error::Notdir)
}
fn remove_directory(&self, path: &str) -> Result<()> {
let trimmed_path = path.trim_end_matches('/');
let mut entries = self.entries.borrow_mut();
match entries.entry(Path::new(trimmed_path).to_path_buf()) {
Entry::Occupied(e) => {
// first, does this name a directory?
if e.get().get_file_type() != Filetype::Directory {
return Err(Error::Notdir);
}
// Okay, but is the directory empty?
let iter = e.get().readdir(DIRCOOKIE_START)?;
if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() {
return Err(Error::Notempty);
}
// Alright, it's an empty directory. We can remove it.
let removed = e.remove_entry();
// TODO refactor
// And sever the file's parent ref to avoid Rc cycles.
if let Some(dir) = removed.1.as_any().downcast_ref::<Self>() {
dir.set_parent(None);
} else if let Some(file) = removed.1.as_any().downcast_ref::<InMemoryFile>() {
file.set_parent(None);
} else {
panic!("neither VirtualDir nor InMemoryFile");
}
Ok(())
}
Entry::Vacant(_) => {
tracing::trace!(
"VirtualDir::remove_directory failed to remove {}, no such entry",
trimmed_path
);
Err(Error::Noent)
}
}
}
fn unlink_file(&self, path: &str) -> Result<()> {
let trimmed_path = path.trim_end_matches('/');
// Special case: we may be unlinking this directory itself if path is `"."`. In that case,
// fail with Isdir, since this is a directory. Alternatively, we may be unlinking `".."`,
// which is bound the same way, as this is by definition contained in a directory.
if trimmed_path == "." || trimmed_path == ".." {
return Err(Error::Isdir);
}
let mut entries = self.entries.borrow_mut();
match entries.entry(Path::new(trimmed_path).to_path_buf()) {
Entry::Occupied(e) => {
// Directories must be removed through `remove_directory`, not `unlink_file`.
if e.get().get_file_type() == Filetype::Directory {
return Err(Error::Isdir);
}
let removed = e.remove_entry();
// TODO refactor
// Sever the file's parent ref to avoid Rc cycles.
if let Some(dir) = removed.1.as_any().downcast_ref::<Self>() {
dir.set_parent(None);
} else if let Some(file) = removed.1.as_any().downcast_ref::<InMemoryFile>() {
file.set_parent(None);
} else {
panic!("neither VirtualDir nor InMemoryFile");
}
Ok(())
}
Entry::Vacant(_) => {
tracing::trace!(
"VirtualDir::unlink_file failed to remove {}, no such entry",
trimmed_path
);
Err(Error::Noent)
}
}
}
}

View File

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

View File

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

View File

@@ -1,29 +0,0 @@
[package]
name = "winx"
version = "0.22.0"
authors = ["Jakub Konka <kubkon@jakubkonka.com>"]
description = "Windows API helper library"
documentation = "https://docs.rs/winx"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasmtime"
edition = "2018"
[dependencies]
bitflags = "1.0"
cvt = "0.1"
winapi = { version = "^0.3", features = [
"std",
"errhandlingapi",
"handleapi",
"processthreadsapi",
"profileapi",
"securitybaseapi",
"winbase",
"winerror",
"ws2def",
"fileapi",
"aclapi",
] }
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -1,220 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

View File

@@ -1,455 +0,0 @@
#![allow(non_camel_case_types)]
use crate::ntdll::{
NtQueryInformationFile, RtlNtStatusToDosError, FILE_ACCESS_INFORMATION, FILE_INFORMATION_CLASS,
FILE_MODE_INFORMATION, IO_STATUS_BLOCK,
};
use bitflags::bitflags;
use cvt::cvt;
use std::ffi::{c_void, OsString};
use std::fs::File;
use std::io::{Error, Result};
use std::os::windows::prelude::{AsRawHandle, OsStringExt, RawHandle};
use winapi::shared::{
minwindef::{self, DWORD},
ntstatus, winerror,
};
use winapi::um::{fileapi, fileapi::GetFileType, minwinbase, winbase, winnt};
/// Maximum total path length for Unicode in Windows.
/// [Maximum path length limitation]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
pub const WIDE_MAX_PATH: DWORD = 0x7fff;
#[derive(Debug, Copy, Clone)]
pub struct FileType(minwindef::DWORD);
// possible types are:
// * FILE_TYPE_CHAR
// * FILE_TYPE_DISK
// * FILE_TYPE_PIPE
// * FILE_TYPE_REMOTE
// * FILE_TYPE_UNKNOWN
//
// FILE_TYPE_REMOTE is unused
// https://technet.microsoft.com/en-us/evalcenter/aa364960(v=vs.100)
impl FileType {
/// Returns true if character device such as LPT device or console
pub fn is_char(&self) -> bool {
self.0 == winbase::FILE_TYPE_CHAR
}
/// Returns true if disk device such as file or dir
pub fn is_disk(&self) -> bool {
self.0 == winbase::FILE_TYPE_DISK
}
/// Returns true if pipe device such as socket, named pipe or anonymous pipe
pub fn is_pipe(&self) -> bool {
self.0 == winbase::FILE_TYPE_PIPE
}
/// Returns true if unknown device
pub fn is_unknown(&self) -> bool {
self.0 == winbase::FILE_TYPE_UNKNOWN
}
}
pub unsafe fn get_file_type(handle: RawHandle) -> Result<FileType> {
let file_type = FileType(GetFileType(handle));
let err = Error::last_os_error();
if file_type.is_unknown() && err.raw_os_error().unwrap() as u32 != winerror::ERROR_SUCCESS {
Err(err)
} else {
Ok(file_type)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(u32)]
pub enum CreationDisposition {
NO_DISPOSITION = 0,
/// Creates a new file, only if it does not already exist.
/// If the specified file exists, the function fails and the last-error code is
/// set to ERROR_FILE_EXISTS (80).
///
/// If the specified file does not exist and is a valid path to a writable location,
/// a new file is created.
CREATE_NEW = fileapi::CREATE_NEW,
/// Creates a new file, always.
/// If the specified file exists and is writable, the function overwrites the file,
/// the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS (183).
///
/// If the specified file does not exist and is a valid path, a new file is created,
/// the function succeeds, and the last-error code is set to zero.
CREATE_ALWAYS = fileapi::CREATE_ALWAYS,
/// Opens a file or device, only if it exists.
/// If the specified file or device does not exist, the function fails and the
/// last-error code is set to ERROR_FILE_NOT_FOUND (2).
OPEN_EXISTING = fileapi::OPEN_EXISTING,
/// Opens a file, always.
/// If the specified file exists, the function succeeds and the last-error code is
/// set to ERROR_ALREADY_EXISTS (183).
///
/// If the specified file does not exist and is a valid path to a writable location,
/// the function creates a file and the last-error code is set to zero.
OPEN_ALWAYS = fileapi::OPEN_ALWAYS,
/// Opens a file and truncates it so that its size is zero bytes, only if it exists.
/// If the specified file does not exist, the function fails and the last-error code
/// is set to ERROR_FILE_NOT_FOUND (2).
///
/// The calling process must open the file with the GENERIC_WRITE bit set as part
/// of the dwDesiredAccess parameter.
TRUNCATE_EXISTING = fileapi::TRUNCATE_EXISTING,
}
impl CreationDisposition {
pub fn from_u32(disp: u32) -> Self {
use CreationDisposition::*;
match disp {
fileapi::CREATE_NEW => CREATE_NEW,
fileapi::CREATE_ALWAYS => CREATE_ALWAYS,
fileapi::OPEN_EXISTING => OPEN_EXISTING,
fileapi::OPEN_ALWAYS => OPEN_ALWAYS,
fileapi::TRUNCATE_EXISTING => TRUNCATE_EXISTING,
_ => NO_DISPOSITION,
}
}
}
bitflags! {
pub struct Attributes: minwindef::DWORD {
/// A file or directory that is an archive file or directory.
/// Applications typically use this attribute to mark files for backup or removal.
const FILE_ATTRIBUTE_ARCHIVE = winnt::FILE_ATTRIBUTE_ARCHIVE;
/// A file or directory that is compressed. For a file, all of the data in the file is compressed.
/// For a directory, compression is the default for newly created files and subdirectories.
const FILE_ATTRIBUTE_COMPRESSED = winnt::FILE_ATTRIBUTE_COMPRESSED;
/// This value is reserved for system use.
const FILE_ATTRIBUTE_DEVICE = winnt::FILE_ATTRIBUTE_DEVICE;
/// The handle that identifies a directory.
const FILE_ATTRIBUTE_DIRECTORY = winnt::FILE_ATTRIBUTE_DIRECTORY;
/// A file or directory that is encrypted. For a file, all data streams in the file are encrypted.
/// For a directory, encryption is the default for newly created files and subdirectories.
const FILE_ATTRIBUTE_ENCRYPTED = winnt::FILE_ATTRIBUTE_ENCRYPTED;
/// The file or directory is hidden. It is not included in an ordinary directory listing.
const FILE_ATTRIBUTE_HIDDEN = winnt::FILE_ATTRIBUTE_HIDDEN;
/// The directory or user data stream is configured with integrity (only supported on ReFS volumes).
/// It is not included in an ordinary directory listing. The integrity setting persists with the file if it's renamed.
/// If a file is copied the destination file will have integrity set if either the source file or destination directory have integrity set.
const FILE_ATTRIBUTE_INTEGRITY_STREAM = winnt::FILE_ATTRIBUTE_INTEGRITY_STREAM;
/// A file that does not have other attributes set. This attribute is valid only when used alone.
const FILE_ATTRIBUTE_NORMAL = winnt::FILE_ATTRIBUTE_NORMAL;
/// The file or directory is not to be indexed by the content indexing service.
const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = winnt::FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
/// The user data stream not to be read by the background data integrity scanner (AKA scrubber).
/// When set on a directory it only provides inheritance. This flag is only supported on Storage Spaces and ReFS volumes.
/// It is not included in an ordinary directory listing.
const FILE_ATTRIBUTE_NO_SCRUB_DATA = winnt::FILE_ATTRIBUTE_NO_SCRUB_DATA;
/// The data of a file is not available immediately.
/// This attribute indicates that the file data is physically moved to offline storage.
/// This attribute is used by Remote Storage, which is the hierarchical storage management software.
/// Applications should not arbitrarily change this attribute.
const FILE_ATTRIBUTE_OFFLINE = winnt::FILE_ATTRIBUTE_OFFLINE;
/// A file that is read-only. Applications can read the file, but cannot write to it or delete it.
/// This attribute is not honored on directories.
const FILE_ATTRIBUTE_READONLY = winnt::FILE_ATTRIBUTE_READONLY;
/// When this attribute is set, it means that the file or directory is not fully present locally.
/// For a file that means that not all of its data is on local storage (e.g. it may be sparse with some data still in remote storage).
/// For a directory it means that some of the directory contents are being virtualized from another location.
/// Reading the file / enumerating the directory will be more expensive than normal, e.g. it will cause at least some of the
/// file/directory content to be fetched from a remote store. Only kernel-mode callers can set this bit.
const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = winnt::FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS;
/// This attribute only appears in directory enumeration classes (FILE_DIRECTORY_INFORMATION, FILE_BOTH_DIR_INFORMATION, etc.).
/// When this attribute is set, it means that the file or directory has no physical representation on the local system; the item is virtual.
/// Opening the item will be more expensive than normal, e.g. it will cause at least some of it to be fetched from a remote store.
const FILE_ATTRIBUTE_RECALL_ON_OPEN = winnt::FILE_ATTRIBUTE_RECALL_ON_OPEN;
/// A file or directory that has an associated reparse point, or a file that is a symbolic link.
const FILE_ATTRIBUTE_REPARSE_POINT = winnt::FILE_ATTRIBUTE_REPARSE_POINT;
/// A file that is a sparse file.
const FILE_ATTRIBUTE_SPARSE_FILE = winnt::FILE_ATTRIBUTE_SPARSE_FILE;
/// A file or directory that the operating system uses a part of, or uses exclusively.
const FILE_ATTRIBUTE_SYSTEM = winnt::FILE_ATTRIBUTE_SYSTEM;
/// A file that is being used for temporary storage.
/// File systems avoid writing data back to mass storage if sufficient cache memory is available, because typically,
/// an application deletes a temporary file after the handle is closed. In that scenario, the system can entirely
/// avoid writing the data. Otherwise, the data is written after the handle is closed.
const FILE_ATTRIBUTE_TEMPORARY = winnt::FILE_ATTRIBUTE_TEMPORARY;
/// This value is reserved for system use.
const FILE_ATTRIBUTE_VIRTUAL = winnt::FILE_ATTRIBUTE_VIRTUAL;
}
}
bitflags! {
pub struct Flags: minwindef::DWORD {
/// The file is being opened or created for a backup or restore operation.
/// The system ensures that the calling process overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges.
/// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle.
const FILE_FLAG_BACKUP_SEMANTICS = winbase::FILE_FLAG_BACKUP_SEMANTICS;
/// The file is to be deleted immediately after all of its handles are closed, which includes the specified handle and any other open or duplicated handles.
/// If there are existing open handles to a file, the call fails unless they were all opened with the FILE_SHARE_DELETE share mode.
/// Subsequent open requests for the file fail, unless the FILE_SHARE_DELETE share mode is specified.
const FILE_FLAG_DELETE_ON_CLOSE = winbase::FILE_FLAG_DELETE_ON_CLOSE;
/// The file or device is being opened with no system caching for data reads and writes.
/// This flag does not affect hard disk caching or memory mapped files.
/// There are strict requirements for successfully working with files opened with
/// CreateFile using the FILE_FLAG_NO_BUFFERING flag.
const FILE_FLAG_NO_BUFFERING = winbase::FILE_FLAG_NO_BUFFERING;
/// The file data is requested, but it should continue to be located in remote storage.
/// It should not be transported back to local storage. This flag is for use by remote storage systems.
const FILE_FLAG_OPEN_NO_RECALL = winbase::FILE_FLAG_OPEN_NO_RECALL;
/// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point.
/// When a file is opened, a file handle is returned, whether or not the filter that controls the reparse point is operational.
/// This flag cannot be used with the CREATE_ALWAYS flag.
/// If the file is not a reparse point, then this flag is ignored.
const FILE_FLAG_OPEN_REPARSE_POINT = winbase::FILE_FLAG_OPEN_REPARSE_POINT;
/// The file or device is being opened or created for asynchronous I/O.
/// When subsequent I/O operations are completed on this handle, the event specified in the OVERLAPPED structure will be set to the signaled state.
/// If this flag is specified, the file can be used for simultaneous read and write operations.
/// If this flag is not specified, then I/O operations are serialized, even if the calls to the read and write functions specify an OVERLAPPED structure.
const FILE_FLAG_OVERLAPPED = winbase::FILE_FLAG_OVERLAPPED;
/// Access will occur according to POSIX rules. This includes allowing multiple files with names,
/// differing only in case, for file systems that support that naming. Use care when using this option,
/// because files created with this flag may not be accessible by applications that are written for MS-DOS or 16-bit Windows.
const FILE_FLAG_POSIX_SEMANTICS = winbase::FILE_FLAG_POSIX_SEMANTICS;
/// Access is intended to be random. The system can use this as a hint to optimize file caching.
/// This flag has no effect if the file system does not support cached I/O and FILE_FLAG_NO_BUFFERING.
const FILE_FLAG_RANDOM_ACCESS = winbase::FILE_FLAG_RANDOM_ACCESS;
/// The file or device is being opened with session awareness.
/// If this flag is not specified, then per-session devices (such as a device using RemoteFX USB Redirection)
/// cannot be opened by processes running in session 0. This flag has no effect for callers not in session 0.
/// This flag is supported only on server editions of Windows.
const FILE_FLAG_SESSION_AWARE = winbase::FILE_FLAG_SESSION_AWARE;
/// Access is intended to be sequential from beginning to end. The system can use this as a hint to optimize file caching.
/// This flag should not be used if read-behind (that is, reverse scans) will be used.
/// This flag has no effect if the file system does not support cached I/O and FILE_FLAG_NO_BUFFERING.
const FILE_FLAG_SEQUENTIAL_SCAN = winbase::FILE_FLAG_SEQUENTIAL_SCAN;
/// Write operations will not go through any intermediate cache, they will go directly to disk.
const FILE_FLAG_WRITE_THROUGH = winbase::FILE_FLAG_WRITE_THROUGH;
}
}
bitflags! {
/// [Access mask]: https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/access-mask
pub struct AccessMode: minwindef::DWORD {
/// For a file object, the right to read the corresponding file data.
/// For a directory object, the right to read the corresponding directory data.
const FILE_READ_DATA = winnt::FILE_READ_DATA;
const FILE_LIST_DIRECTORY = winnt::FILE_LIST_DIRECTORY;
/// For a file object, the right to write data to the file.
/// For a directory object, the right to create a file in the directory.
const FILE_WRITE_DATA = winnt::FILE_WRITE_DATA;
const FILE_ADD_FILE = winnt::FILE_ADD_FILE;
/// For a file object, the right to append data to the file.
/// (For local files, write operations will not overwrite existing data
/// if this flag is specified without FILE_WRITE_DATA.)
/// For a directory object, the right to create a subdirectory.
/// For a named pipe, the right to create a pipe.
const FILE_APPEND_DATA = winnt::FILE_APPEND_DATA;
const FILE_ADD_SUBDIRECTORY = winnt::FILE_ADD_SUBDIRECTORY;
const FILE_CREATE_PIPE_INSTANCE = winnt::FILE_CREATE_PIPE_INSTANCE;
/// The right to read extended file attributes.
const FILE_READ_EA = winnt::FILE_READ_EA;
/// The right to write extended file attributes.
const FILE_WRITE_EA = winnt::FILE_WRITE_EA;
/// For a file, the right to execute FILE_EXECUTE.
/// For a directory, the right to traverse the directory.
/// By default, users are assigned the BYPASS_TRAVERSE_CHECKING privilege,
/// which ignores the FILE_TRAVERSE access right.
const FILE_EXECUTE = winnt::FILE_EXECUTE;
const FILE_TRAVERSE = winnt::FILE_TRAVERSE;
/// For a directory, the right to delete a directory and all
/// the files it contains, including read-only files.
const FILE_DELETE_CHILD = winnt::FILE_DELETE_CHILD;
/// The right to read file attributes.
const FILE_READ_ATTRIBUTES = winnt::FILE_READ_ATTRIBUTES;
/// The right to write file attributes.
const FILE_WRITE_ATTRIBUTES = winnt::FILE_WRITE_ATTRIBUTES;
/// The right to delete the object.
const DELETE = winnt::DELETE;
/// The right to read the information in the object's security descriptor,
/// not including the information in the system access control list (SACL).
const READ_CONTROL = winnt::READ_CONTROL;
/// The right to use the object for synchronization. This enables a thread
/// to wait until the object is in the signaled state. Some object types
/// do not support this access right.
const SYNCHRONIZE = winnt::SYNCHRONIZE;
/// The right to modify the discretionary access control list (DACL) in
/// the object's security descriptor.
const WRITE_DAC = winnt::WRITE_DAC;
/// The right to change the owner in the object's security descriptor.
const WRITE_OWNER = winnt::WRITE_OWNER;
/// It is used to indicate access to a system access control list (SACL).
const ACCESS_SYSTEM_SECURITY = winnt::ACCESS_SYSTEM_SECURITY;
/// Maximum allowed.
const MAXIMUM_ALLOWED = winnt::MAXIMUM_ALLOWED;
/// Reserved
const RESERVED1 = 0x4000000;
/// Reserved
const RESERVED2 = 0x8000000;
/// Provides all possible access rights.
/// This is convenience flag which is translated by the OS into actual [`FILE_GENERIC_ALL`] union.
const GENERIC_ALL = winnt::GENERIC_ALL;
/// Provides execute access.
const GENERIC_EXECUTE = winnt::GENERIC_EXECUTE;
/// Provides write access.
/// This is convenience flag which is translated by the OS into actual [`FILE_GENERIC_WRITE`] union.
const GENERIC_WRITE = winnt::GENERIC_WRITE;
/// Provides read access.
/// This is convenience flag which is translated by the OS into actual [`FILE_GENERIC_READ`] union.
const GENERIC_READ = winnt::GENERIC_READ;
/// Provides read access.
const FILE_GENERIC_READ = winnt::FILE_GENERIC_READ;
/// Provides write access.
const FILE_GENERIC_WRITE = winnt::FILE_GENERIC_WRITE;
/// Provides execute access.
const FILE_GENERIC_EXECUTE = winnt::FILE_GENERIC_EXECUTE;
/// Provides all accesses.
const FILE_ALL_ACCESS = winnt::FILE_ALL_ACCESS;
}
}
bitflags! {
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/52df7798-8330-474b-ac31-9afe8075640c
pub struct FileModeInformation: minwindef::DWORD {
/// When set, any system services, file system drivers (FSDs), and drivers that write data to
/// the file are required to actually transfer the data into the file before any requested write
/// operation is considered complete.
const FILE_WRITE_THROUGH = 0x2;
/// This is a hint that informs the cache that it SHOULD optimize for sequential access.
/// Non-sequential access of the file can result in performance degradation.
const FILE_SEQUENTIAL_ONLY = 0x4;
/// When set, the file cannot be cached or buffered in a driver's internal buffers.
const FILE_NO_INTERMEDIATE_BUFFERING = 0x8;
/// When set, all operations on the file are performed synchronously.
/// Any wait on behalf of the caller is subject to premature termination from alerts.
/// This flag also causes the I/O system to maintain the file position context.
const FILE_SYNCHRONOUS_IO_ALERT = 0x10;
/// When set, all operations on the file are performed synchronously.
/// Wait requests in the system to synchronize I/O queuing and completion are not subject to alerts.
/// This flag also causes the I/O system to maintain the file position context.
const FILE_SYNCHRONOUS_IO_NONALERT = 0x20;
/// This flag is not implemented and is always returned as not set.
const FILE_DELETE_ON_CLOSE = 0x1000;
}
}
pub fn get_file_path(file: &File) -> Result<OsString> {
use winapi::um::fileapi::GetFinalPathNameByHandleW;
let mut raw_path: Vec<u16> = vec![0; WIDE_MAX_PATH as usize];
let handle = file.as_raw_handle();
let read_len =
cvt(unsafe { GetFinalPathNameByHandleW(handle, raw_path.as_mut_ptr(), WIDE_MAX_PATH, 0) })?;
// obtain a slice containing the written bytes, and check for it being too long
// (practically probably impossible)
let written_bytes = raw_path
.get(..read_len as usize)
.ok_or(Error::from_raw_os_error(
winerror::ERROR_BUFFER_OVERFLOW as i32,
))?;
Ok(OsString::from_wide(written_bytes))
}
pub fn get_fileinfo(file: &File) -> Result<fileapi::BY_HANDLE_FILE_INFORMATION> {
use fileapi::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION};
use std::mem;
let handle = file.as_raw_handle();
let info = unsafe {
let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
cvt(GetFileInformationByHandle(handle, &mut info))?;
info
};
Ok(info)
}
pub fn change_time(file: &File) -> Result<i64> {
use fileapi::FILE_BASIC_INFO;
use minwinbase::FileBasicInfo;
use std::mem;
use winbase::GetFileInformationByHandleEx;
let handle = file.as_raw_handle();
let tm = unsafe {
let mut info: FILE_BASIC_INFO = mem::zeroed();
let infosize = mem::size_of_val(&info);
cvt(GetFileInformationByHandleEx(
handle,
FileBasicInfo,
&mut info as *mut FILE_BASIC_INFO as *mut c_void,
infosize as u32,
))?;
*info.ChangeTime.QuadPart()
};
Ok(tm)
}
pub fn query_access_information(handle: RawHandle) -> Result<AccessMode> {
let mut io_status_block = IO_STATUS_BLOCK::default();
let mut info = FILE_ACCESS_INFORMATION::default();
unsafe {
let status = NtQueryInformationFile(
handle,
&mut io_status_block,
&mut info as *mut _ as *mut c_void,
std::mem::size_of::<FILE_ACCESS_INFORMATION>() as u32,
FILE_INFORMATION_CLASS::FileAccessInformation,
);
if status != ntstatus::STATUS_SUCCESS {
return Err(Error::from_raw_os_error(
RtlNtStatusToDosError(status) as i32
));
}
}
Ok(AccessMode::from_bits_truncate(info.AccessFlags))
}
pub fn query_mode_information(handle: RawHandle) -> Result<FileModeInformation> {
let mut io_status_block = IO_STATUS_BLOCK::default();
let mut info = FILE_MODE_INFORMATION::default();
unsafe {
let status = NtQueryInformationFile(
handle,
&mut io_status_block,
&mut info as *mut _ as *mut c_void,
std::mem::size_of::<FILE_MODE_INFORMATION>() as u32,
FILE_INFORMATION_CLASS::FileModeInformation,
);
if status != ntstatus::STATUS_SUCCESS {
return Err(Error::from_raw_os_error(
RtlNtStatusToDosError(status) as i32
));
}
}
Ok(FileModeInformation::from_bits_truncate(info.Mode))
}
pub fn reopen_file(handle: RawHandle, access_mode: AccessMode, flags: Flags) -> Result<RawHandle> {
// Files on Windows are opened with DELETE, READ, and WRITE share mode by default (see OpenOptions in stdlib)
// This keeps the same share mode when reopening the file handle
let new_handle = unsafe {
winbase::ReOpenFile(
handle,
access_mode.bits(),
winnt::FILE_SHARE_DELETE | winnt::FILE_SHARE_READ | winnt::FILE_SHARE_WRITE,
flags.bits(),
)
};
if new_handle == winapi::um::handleapi::INVALID_HANDLE_VALUE {
return Err(Error::last_os_error());
}
Ok(new_handle)
}

View File

@@ -1,25 +0,0 @@
#![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::map_unwrap_or,
clippy::clippy::unicode_not_nfc,
clippy::use_self
)
)]
#![cfg(windows)]
pub mod file;
mod ntdll;
pub mod time;

View File

@@ -1,90 +0,0 @@
//! Module for importing functions from ntdll.dll.
//! The winapi crate does not expose these Windows API functions.
#![allow(nonstandard_style)]
use std::ffi::c_void;
use std::os::raw::c_ulong;
use std::os::windows::prelude::RawHandle;
use std::sync::atomic::{AtomicUsize, Ordering};
use winapi::shared::ntdef::NTSTATUS;
use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress};
use winapi::um::winnt::ACCESS_MASK;
#[repr(C)]
#[derive(Copy, Clone)]
pub(crate) enum FILE_INFORMATION_CLASS {
FileAccessInformation = 8,
FileModeInformation = 16,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub(crate) union IO_STATUS_BLOCK_u {
pub Status: NTSTATUS,
pub Pointer: *mut c_void,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub(crate) struct IO_STATUS_BLOCK {
pub u: IO_STATUS_BLOCK_u,
pub Information: *mut c_void,
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub(crate) struct FILE_ACCESS_INFORMATION {
pub AccessFlags: ACCESS_MASK,
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub(crate) struct FILE_MODE_INFORMATION {
pub Mode: c_ulong,
}
impl Default for IO_STATUS_BLOCK {
#[inline]
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
macro_rules! ntdll_import {
{ fn $name:ident($($arg:ident: $argty:ty),*) -> $retty:ty; $($tail:tt)* } => {
pub(crate) unsafe fn $name($($arg: $argty),*) -> $retty {
static ADDRESS: AtomicUsize = AtomicUsize::new(0);
let address = match ADDRESS.load(Ordering::Relaxed) {
0 => {
let ntdll = GetModuleHandleA("ntdll\0".as_ptr() as *const i8);
let address = GetProcAddress(
ntdll,
concat!(stringify!($name), "\0").as_ptr() as *const i8,
) as usize;
assert!(address != 0);
ADDRESS.store(address, Ordering::Relaxed);
address
}
address => address
};
let func: unsafe fn($($argty),*) -> $retty = std::mem::transmute(address);
func($($arg),*)
}
ntdll_import! { $($tail)* }
};
{} => {};
}
ntdll_import! {
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile
fn NtQueryInformationFile(
FileHandle: RawHandle,
IoStatusBlock: *mut IO_STATUS_BLOCK,
FileInformation: *mut c_void,
Length: c_ulong,
FileInformationClass: FILE_INFORMATION_CLASS
) -> NTSTATUS;
// https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-rtlntstatustodoserror
fn RtlNtStatusToDosError(status: NTSTATUS) -> c_ulong;
}

View File

@@ -1,11 +0,0 @@
use cvt::cvt;
use std::io::Result;
use winapi::um::{profileapi::QueryPerformanceFrequency, winnt::LARGE_INTEGER};
pub fn perf_counter_frequency() -> Result<u64> {
unsafe {
let mut frequency: LARGE_INTEGER = std::mem::zeroed();
cvt(QueryPerformanceFrequency(&mut frequency))?;
Ok(*frequency.QuadPart() as u64)
}
}

View File

@@ -1,19 +0,0 @@
[package]
name = "yanix"
version = "0.22.0"
authors = ["The Wasmtime Project Developers"]
description = "Yet Another Nix crate: a Unix API helper library"
documentation = "https://docs.rs/yanix"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasmtime"
edition = "2018"
[dependencies]
tracing = "0.1.15"
libc = { version = "0.2", features = ["extra_traits"] }
bitflags = "1.2"
cfg-if = "1.0"
filetime = "0.2.7"
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -1,220 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

View File

@@ -1,34 +0,0 @@
use crate::from_success_code;
use std::io::Result;
use std::mem::MaybeUninit;
#[derive(Debug, Copy, Clone)]
pub enum ClockId {
Realtime,
Monotonic,
ProcessCPUTime,
ThreadCPUTime,
}
impl ClockId {
pub fn as_raw(&self) -> libc::clockid_t {
match self {
Self::Realtime => libc::CLOCK_REALTIME,
Self::Monotonic => libc::CLOCK_MONOTONIC,
Self::ProcessCPUTime => libc::CLOCK_PROCESS_CPUTIME_ID,
Self::ThreadCPUTime => libc::CLOCK_THREAD_CPUTIME_ID,
}
}
}
pub fn clock_getres(clock_id: ClockId) -> Result<libc::timespec> {
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
from_success_code(unsafe { libc::clock_getres(clock_id.as_raw(), timespec.as_mut_ptr()) })?;
Ok(unsafe { timespec.assume_init() })
}
pub fn clock_gettime(clock_id: ClockId) -> Result<libc::timespec> {
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
from_success_code(unsafe { libc::clock_gettime(clock_id.as_raw(), timespec.as_mut_ptr()) })?;
Ok(unsafe { timespec.assume_init() })
}

View File

@@ -1,150 +0,0 @@
use crate::{
file::FileType,
sys::dir::{iter_impl, EntryImpl},
};
use std::convert::TryInto;
use std::io::{Error, Result};
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
#[cfg(target_os = "wasi")]
use std::os::wasi::io::{AsRawFd, IntoRawFd, RawFd};
use std::{ffi::CStr, io, ops::Deref, ptr};
pub use crate::sys::EntryExt;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Dir(ptr::NonNull<libc::DIR>);
impl Dir {
/// Takes the ownership of the passed-in descriptor-based object,
/// and creates a new instance of `Dir`.
#[inline]
pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
let fd = fd.into_raw_fd();
unsafe { Self::from_fd(fd) }
}
unsafe fn from_fd(fd: RawFd) -> Result<Self> {
let d = libc::fdopendir(fd);
if let Some(d) = ptr::NonNull::new(d) {
Ok(Self(d))
} else {
let e = io::Error::last_os_error();
libc::close(fd);
Err(e.into())
}
}
/// Set the position of the directory stream, see `seekdir(3)`.
pub fn seek(&mut self, loc: SeekLoc) {
// https://github.com/rust-lang/libc/pull/1996
#[cfg(not(target_os = "android"))]
use libc::seekdir;
#[cfg(target_os = "android")]
extern "C" {
fn seekdir(dirp: *mut libc::DIR, loc: libc::c_long);
}
unsafe { seekdir(self.0.as_ptr(), loc.0) }
}
/// Reset directory stream, see `rewinddir(3)`.
pub 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.
#[allow(dead_code)]
pub fn tell(&self) -> SeekLoc {
#[cfg(not(target_os = "android"))]
use libc::telldir;
#[cfg(target_os = "android")]
extern "C" {
fn telldir(dirp: *mut libc::DIR) -> libc::c_long;
}
// https://github.com/rust-lang/libc/pull/1996
let loc = unsafe { telldir(self.0.as_ptr()) };
SeekLoc(loc)
}
/// For use by platform-specific implementation code. Returns the raw
/// underlying state.
pub(crate) fn as_raw(&self) -> ptr::NonNull<libc::DIR> {
self.0
}
}
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()) };
}
}
#[derive(Debug, Copy, Clone)]
pub struct Entry(pub(crate) EntryImpl);
impl Entry {
/// Returns the file name of this directory entry.
pub fn file_name(&self) -> &CStr {
unsafe { CStr::from_ptr(self.0.d_name.as_ptr()) }
}
/// Returns the type of this directory entry.
pub fn file_type(&self) -> FileType {
FileType::from_dirent_d_type(self.0.d_type)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SeekLoc(pub(crate) libc::c_long);
impl SeekLoc {
pub fn to_raw(&self) -> i64 {
self.0.into()
}
pub unsafe fn from_raw(loc: i64) -> Result<Self> {
// The cookie (or `loc`) is an opaque value, and applications aren't supposed to do
// arithmetic on them or pick their own values or have any awareness of the numeric
// range of the values. They're just supposed to pass back in the values that we
// give them. And any value we give them will be convertable back to `long`,
// because that's the type the OS gives them to us in. So return an `EINVAL`.
let loc = loc
.try_into()
.map_err(|_| Error::from_raw_os_error(libc::EINVAL))?;
Ok(Self(loc))
}
}
#[derive(Debug)]
pub struct DirIter<T: Deref<Target = Dir>>(T);
impl<T> DirIter<T>
where
T: Deref<Target = Dir>,
{
pub fn new(dir: T) -> Self {
Self(dir)
}
}
impl<T> Iterator for DirIter<T>
where
T: Deref<Target = Dir>,
{
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
iter_impl(&self.0).map(|x| x.map(Entry))
}
}

View File

@@ -1,37 +0,0 @@
use crate::{
file::{FdFlags, OFlags},
from_result, from_success_code,
};
use std::io::Result;
#[cfg(unix)]
use std::os::unix::prelude::*;
#[cfg(target_os = "wasi")]
use std::os::wasi::prelude::*;
pub unsafe fn dup_fd(fd: RawFd, close_on_exec: bool) -> Result<RawFd> {
// Both fcntl commands expect a RawFd arg which will specify
// the minimum duplicated RawFd number. In our case, I don't
// think we have to worry about this that much, so passing in
// the RawFd descriptor we want duplicated
from_result(if close_on_exec {
libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, fd)
} else {
libc::fcntl(fd, libc::F_DUPFD, fd)
})
}
pub unsafe fn get_fd_flags(fd: RawFd) -> Result<FdFlags> {
from_result(libc::fcntl(fd, libc::F_GETFD)).map(FdFlags::from_bits_truncate)
}
pub unsafe fn set_fd_flags(fd: RawFd, flags: FdFlags) -> Result<()> {
from_success_code(libc::fcntl(fd, libc::F_SETFD, flags.bits()))
}
pub unsafe fn get_status_flags(fd: RawFd) -> Result<OFlags> {
from_result(libc::fcntl(fd, libc::F_GETFL)).map(OFlags::from_bits_truncate)
}
pub unsafe fn set_status_flags(fd: RawFd, flags: OFlags) -> Result<()> {
from_success_code(libc::fcntl(fd, libc::F_SETFL, flags.bits()))
}

View File

@@ -1,304 +0,0 @@
use crate::{cstr, from_result, from_success_code};
use bitflags::bitflags;
use cfg_if::cfg_if;
#[cfg(unix)]
use std::os::unix::prelude::*;
#[cfg(target_os = "wasi")]
use std::os::wasi::prelude::*;
use std::{
convert::TryInto,
ffi::{OsStr, OsString},
io::Result,
path::Path,
};
pub use crate::sys::file::*;
bitflags! {
pub struct FdFlags: libc::c_int {
const CLOEXEC = libc::FD_CLOEXEC;
}
}
bitflags! {
pub struct AtFlags: libc::c_int {
const REMOVEDIR = libc::AT_REMOVEDIR;
const SYMLINK_FOLLOW = libc::AT_SYMLINK_FOLLOW;
const SYMLINK_NOFOLLOW = libc::AT_SYMLINK_NOFOLLOW;
#[cfg(any(target_os = "linux",
target_os = "fuchsia"))]
const EMPTY_PATH = libc::AT_EMPTY_PATH;
}
}
bitflags! {
pub struct Mode: libc::mode_t {
const IRWXU = libc::S_IRWXU;
const IRUSR = libc::S_IRUSR;
const IWUSR = libc::S_IWUSR;
const IXUSR = libc::S_IXUSR;
const IRWXG = libc::S_IRWXG;
const IRGRP = libc::S_IRGRP;
const IWGRP = libc::S_IWGRP;
const IXGRP = libc::S_IXGRP;
const IRWXO = libc::S_IRWXO;
const IROTH = libc::S_IROTH;
const IWOTH = libc::S_IWOTH;
const IXOTH = libc::S_IXOTH;
const ISUID = libc::S_ISUID as libc::mode_t;
const ISGID = libc::S_ISGID as libc::mode_t;
const ISVTX = libc::S_ISVTX as libc::mode_t;
}
}
bitflags! {
pub struct OFlags: libc::c_int {
const ACCMODE = libc::O_ACCMODE;
const APPEND = libc::O_APPEND;
const CREAT = libc::O_CREAT;
const DIRECTORY = libc::O_DIRECTORY;
const DSYNC = {
// Have to use cfg_if: https://github.com/bitflags/bitflags/issues/137
cfg_if! {
if #[cfg(any(target_os = "android",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "wasi",
target_os = "emscripten"))] {
libc::O_DSYNC
} else if #[cfg(target_os = "freebsd")] {
// https://github.com/bytecodealliance/wasmtime/pull/756
libc::O_SYNC
}
}
};
const EXCL = libc::O_EXCL;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
all(target_os = "linux", not(target_env = "musl")),
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
const FSYNC = libc::O_FSYNC;
const NOFOLLOW = libc::O_NOFOLLOW;
const NONBLOCK = libc::O_NONBLOCK;
const RDONLY = libc::O_RDONLY;
const WRONLY = libc::O_WRONLY;
const RDWR = libc::O_RDWR;
#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "netbsd",
target_os = "openbsd",
target_os = "wasi",
target_os = "emscripten"))]
const RSYNC = {
// Have to use cfg_if: https://github.com/bitflags/bitflags/issues/137
cfg_if! {
if #[cfg(any(target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_os = "wasi",
target_os = "emscripten"))] {
libc::O_RSYNC
} else if #[cfg(target_os = "android")] {
// Android defines O_RSYNC as O_SYNC
libc::O_SYNC
}
}
};
const SYNC = libc::O_SYNC;
const TRUNC = libc::O_TRUNC;
#[cfg(any(target_os = "linux",
target_os = "fuchsia",
target_os = "redox"))]
const PATH = libc::O_PATH;
#[cfg(any(target_os = "linux",
target_os = "fuchsia",
target_os = "hermit",
target_os = "solaris",
target_os = "haiku",
target_os = "netbsd",
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "vxworks",
target_os = "macos",
target_os = "ios",
target_os = "redox"))]
const CLOEXEC = libc::O_CLOEXEC;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileType {
CharacterDevice,
Directory,
BlockDevice,
RegularFile,
Symlink,
Fifo,
Socket,
Unknown,
}
impl FileType {
pub fn from_stat_st_mode(st_mode: libc::mode_t) -> Self {
match st_mode & libc::S_IFMT {
libc::S_IFIFO => Self::Fifo,
libc::S_IFCHR => Self::CharacterDevice,
libc::S_IFDIR => Self::Directory,
libc::S_IFBLK => Self::BlockDevice,
libc::S_IFREG => Self::RegularFile,
libc::S_IFLNK => Self::Symlink,
libc::S_IFSOCK => Self::Socket,
_ => Self::Unknown, // Should we actually panic here since this one *should* never happen?
}
}
pub fn from_dirent_d_type(d_type: u8) -> Self {
match d_type {
libc::DT_CHR => Self::CharacterDevice,
libc::DT_DIR => Self::Directory,
libc::DT_BLK => Self::BlockDevice,
libc::DT_REG => Self::RegularFile,
libc::DT_LNK => Self::Symlink,
libc::DT_SOCK => Self::Socket,
libc::DT_FIFO => Self::Fifo,
/* libc::DT_UNKNOWN */ _ => Self::Unknown,
}
}
}
pub unsafe fn openat<P: AsRef<Path>>(
dirfd: RawFd,
path: P,
oflag: OFlags,
mode: Mode,
) -> Result<RawFd> {
let path = cstr(path)?;
from_result(libc::openat(
dirfd,
path.as_ptr(),
oflag.bits(),
libc::c_uint::from(mode.bits()),
))
}
pub unsafe fn readlinkat<P: AsRef<Path>>(dirfd: RawFd, path: P) -> Result<OsString> {
let path = cstr(path)?;
let buffer = &mut [0u8; libc::PATH_MAX as usize + 1];
let nread = from_result(libc::readlinkat(
dirfd,
path.as_ptr(),
buffer.as_mut_ptr() as *mut _,
buffer.len(),
))?;
// We can just unwrap() this, because readlinkat returns an ssize_t which is either -1
// (handled above) or non-negative and will fit in a size_t/usize, which is what we're
// converting it to here.
let nread = nread.try_into().unwrap();
let link = OsStr::from_bytes(&buffer[0..nread]);
Ok(link.into())
}
pub unsafe fn mkdirat<P: AsRef<Path>>(dirfd: RawFd, path: P, mode: Mode) -> Result<()> {
let path = cstr(path)?;
from_success_code(libc::mkdirat(dirfd, path.as_ptr(), mode.bits()))
}
pub unsafe fn linkat<P: AsRef<Path>, Q: AsRef<Path>>(
old_dirfd: RawFd,
old_path: P,
new_dirfd: RawFd,
new_path: Q,
flags: AtFlags,
) -> Result<()> {
let old_path = cstr(old_path)?;
let new_path = cstr(new_path)?;
from_success_code(libc::linkat(
old_dirfd,
old_path.as_ptr(),
new_dirfd,
new_path.as_ptr(),
flags.bits(),
))
}
pub unsafe fn unlinkat<P: AsRef<Path>>(dirfd: RawFd, path: P, flags: AtFlags) -> Result<()> {
let path = cstr(path)?;
from_success_code(libc::unlinkat(dirfd, path.as_ptr(), flags.bits()))
}
pub unsafe fn renameat<P: AsRef<Path>, Q: AsRef<Path>>(
old_dirfd: RawFd,
old_path: P,
new_dirfd: RawFd,
new_path: Q,
) -> Result<()> {
let old_path = cstr(old_path)?;
let new_path = cstr(new_path)?;
from_success_code(libc::renameat(
old_dirfd,
old_path.as_ptr(),
new_dirfd,
new_path.as_ptr(),
))
}
pub unsafe fn symlinkat<P: AsRef<Path>, Q: AsRef<Path>>(
old_path: P,
new_dirfd: RawFd,
new_path: Q,
) -> Result<()> {
let old_path = cstr(old_path)?;
let new_path = cstr(new_path)?;
from_success_code(libc::symlinkat(
old_path.as_ptr(),
new_dirfd,
new_path.as_ptr(),
))
}
pub unsafe fn fstatat<P: AsRef<Path>>(dirfd: RawFd, path: P, flags: AtFlags) -> Result<libc::stat> {
use std::mem::MaybeUninit;
let path = cstr(path)?;
let mut filestat = MaybeUninit::<libc::stat>::uninit();
from_result(libc::fstatat(
dirfd,
path.as_ptr(),
filestat.as_mut_ptr(),
flags.bits(),
))?;
Ok(filestat.assume_init())
}
pub unsafe fn fstat(fd: RawFd) -> Result<libc::stat> {
use std::mem::MaybeUninit;
let mut filestat = MaybeUninit::<libc::stat>::uninit();
from_result(libc::fstat(fd, filestat.as_mut_ptr()))?;
Ok(filestat.assume_init())
}
/// `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)`.
pub unsafe fn fionread(fd: RawFd) -> Result<u32> {
let mut nread: libc::c_int = 0;
from_result(libc::ioctl(fd, libc::FIONREAD, &mut nread as *mut _))?;
// FIONREAD returns a non-negative int if it doesn't fail, or it'll fit in a u32 if it does.
//
// For the future, if we want to be super cautious and avoid assuming int is 32-bit, we could
// widen fionread's return type here, since the one place that calls it wants a u64 anyway.
Ok(nread.try_into().unwrap())
}
/// This function is unsafe because it operates on a raw file descriptor.
/// It's provided, because std::io::Seek requires a mutable borrow.
pub unsafe fn tell(fd: RawFd) -> Result<u64> {
let offset = from_result(libc::lseek(fd, 0, libc::SEEK_CUR))?;
// lseek returns an off_t, which we can assume is a non-negative i64 if it doesn't fail.
// So we can unwrap() this conversion.
Ok(offset.try_into().unwrap())
}

View File

@@ -1,78 +0,0 @@
//! This module consists of helper types and functions for dealing
//! with setting the file times (mainly in `path_filestat_set_times` syscall for now).
//!
//! The vast majority of the code contained within and in platform-specific implementations
//! (`super::linux::filetime` and `super::bsd::filetime`) is based on the [filetime] crate.
//! Kudos @alexcrichton!
//!
//! [filetime]: https://github.com/alexcrichton/filetime
use std::io::Result;
pub use super::sys::filetime::*;
/// Internal trait which specialises `filetime::FileTime`'s
/// `seconds` and `nanoseconds` accessors for different
/// pointer widths (32 and 64bit currently).
pub(crate) trait FileTimeExt {
fn seconds_(&self) -> Result<libc::time_t>;
fn nanoseconds_(&self) -> libc::c_long;
}
impl FileTimeExt for filetime::FileTime {
fn seconds_(&self) -> Result<libc::time_t> {
use std::convert::TryInto;
use std::io::Error;
let sec = match self.seconds().try_into() {
Ok(sec) => sec,
Err(_) => {
tracing::debug!("filetime_to_timespec failed converting seconds to required width");
return Err(Error::from_raw_os_error(libc::EOVERFLOW));
}
};
Ok(sec)
}
fn nanoseconds_(&self) -> libc::c_long {
use std::convert::TryInto;
// According to [filetime] docs, since the nanoseconds value is always less than 1 billion,
// any value should be convertible to `libc::c_long`, hence we can `unwrap` outright.
//
// [filetime]: https://docs.rs/filetime/0.2.8/filetime/struct.FileTime.html#method.nanoseconds
self.nanoseconds().try_into().unwrap()
}
}
/// A wrapper `enum` around `filetime::FileTime` struct, but unlike the original, this
/// type allows the possibility of specifying `FileTime::Now` as a valid enumeration which,
/// in turn, if `utimensat` is available on the host, will use a special const setting
/// `UTIME_NOW`.
#[derive(Debug, Copy, Clone)]
pub enum FileTime {
Now,
Omit,
FileTime(filetime::FileTime),
}
/// Converts `FileTime` to `libc::timespec`. If `FileTime::Now` variant is specified, this
/// resolves to `UTIME_NOW` special const, `FileTime::Omit` variant resolves to `UTIME_OMIT`, and
/// `FileTime::FileTime(ft)` where `ft := filetime::FileTime` uses [filetime] crate's original
/// implementation which can be found here: [filetime::unix::to_timespec].
///
/// [filetime]: https://github.com/alexcrichton/filetime
/// [filetime::unix::to_timespec]: https://github.com/alexcrichton/filetime/blob/master/src/unix/mod.rs#L30
pub(crate) fn to_timespec(ft: &FileTime) -> Result<libc::timespec> {
let ts = match ft {
FileTime::Now => libc::timespec {
tv_sec: 0,
tv_nsec: libc::UTIME_NOW,
},
FileTime::Omit => libc::timespec {
tv_sec: 0,
tv_nsec: libc::UTIME_OMIT,
},
FileTime::FileTime(ft) => libc::timespec {
tv_sec: ft.seconds_()?,
tv_nsec: ft.nanoseconds_(),
},
};
Ok(ts)
}

View File

@@ -1,89 +0,0 @@
//! `yanix` stands for Yet Another Nix crate, and, well, it is simply
//! a yet another crate in the spirit of the [nix] crate. As such,
//! this crate is inspired by the original `nix` crate, however,
//! it takes a different approach, using lower-level interfaces with
//! less abstraction, so that it fits better with its main use case
//! which is our WASI implementation, [wasi-common].
//!
//! [nix]: https://github.com/nix-rust/nix
//! [wasi-common]: https://github.com/bytecodealliance/wasmtime/tree/main/crates/wasi-common
#![cfg(any(unix, target_os = "wasi"))]
#[cfg(not(target_os = "wasi"))] // not implemented for WASI in yanix yet
pub mod clock;
pub mod dir;
pub mod fcntl;
pub mod file;
pub mod filetime;
#[cfg(not(target_os = "wasi"))] // not implemented for WASI in yanix yet
pub mod poll;
#[cfg(not(target_os = "wasi"))] // not supported in WASI yet
pub mod socket;
mod sys;
pub mod fadvise {
pub use super::sys::fadvise::*;
}
use std::ffi::CString;
use std::io::{Error, Result};
use std::path::Path;
fn from_success_code<T: IsZero>(t: T) -> Result<()> {
if t.is_zero() {
Ok(())
} else {
Err(Error::last_os_error())
}
}
fn from_result<T: IsMinusOne>(t: T) -> Result<T> {
if t.is_minus_one() {
Err(Error::last_os_error())
} else {
Ok(t)
}
}
trait IsZero {
fn is_zero(&self) -> bool;
}
macro_rules! impl_is_zero {
($($t:ident)*) => ($(impl IsZero for $t {
fn is_zero(&self) -> bool {
*self == 0
}
})*)
}
impl_is_zero! { i32 i64 isize }
trait IsMinusOne {
fn is_minus_one(&self) -> bool;
}
macro_rules! impl_is_minus_one {
($($t:ident)*) => ($(impl IsMinusOne for $t {
fn is_minus_one(&self) -> bool {
*self == -1
}
})*)
}
impl_is_minus_one! { i32 i64 isize }
/// Convert an `AsRef<Path>` into a `CString`.
fn cstr<P: AsRef<Path>>(path: P) -> Result<CString> {
#[cfg(target_os = "hermit")]
use std::os::hermit::ext::ffi::OsStrExt;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "vxworks")]
use std::os::vxworks::ext::ffi::OsStrExt;
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::OsStrExt;
Ok(CString::new(path.as_ref().as_os_str().as_bytes())?)
}

View File

@@ -1,49 +0,0 @@
use crate::from_result;
use bitflags::bitflags;
use std::{convert::TryInto, io::Result, os::unix::prelude::*};
bitflags! {
pub struct PollFlags: libc::c_short {
const POLLIN = libc::POLLIN;
const POLLPRI = libc::POLLPRI;
const POLLOUT = libc::POLLOUT;
const POLLRDNORM = libc::POLLRDNORM;
const POLLWRNORM = libc::POLLWRNORM;
const POLLRDBAND = libc::POLLRDBAND;
const POLLWRBAND = libc::POLLWRBAND;
const POLLERR = libc::POLLERR;
const POLLHUP = libc::POLLHUP;
const POLLNVAL = libc::POLLNVAL;
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct PollFd(libc::pollfd);
impl PollFd {
pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self {
Self(libc::pollfd {
fd,
events: events.bits(),
revents: PollFlags::empty().bits(),
})
}
pub fn revents(self) -> Option<PollFlags> {
PollFlags::from_bits(self.0.revents)
}
}
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<usize> {
let nready = from_result(unsafe {
libc::poll(
fds.as_mut_ptr() as *mut libc::pollfd,
fds.len() as libc::nfds_t,
timeout,
)
})?;
// When poll doesn't fail, its return value is a non-negative int, which will
// always be convertable to usize, so we can unwrap() here.
Ok(nready.try_into().unwrap())
}

View File

@@ -1,32 +0,0 @@
use crate::from_success_code;
use std::io::Result;
use std::os::unix::prelude::*;
#[derive(Debug, Clone, Copy)]
#[repr(i32)]
pub enum SockType {
Stream = libc::SOCK_STREAM,
Datagram = libc::SOCK_DGRAM,
SeqPacket = libc::SOCK_SEQPACKET,
Raw = libc::SOCK_RAW,
Rdm = libc::SOCK_RDM,
}
pub unsafe fn get_socket_type(fd: RawFd) -> Result<SockType> {
use std::mem::{self, MaybeUninit};
let mut buffer = MaybeUninit::<SockType>::zeroed().assume_init();
let mut out_len = mem::size_of::<SockType>() as libc::socklen_t;
from_success_code(libc::getsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_TYPE,
&mut buffer as *mut SockType as *mut _,
&mut out_len,
))?;
assert_eq!(
out_len as usize,
mem::size_of::<SockType>(),
"invalid SockType value"
);
Ok(buffer)
}

View File

@@ -1,59 +0,0 @@
use crate::dir::{Dir, Entry, EntryExt, SeekLoc};
use std::{
io::{Error, Result},
ops::Deref,
};
#[derive(Copy, Clone, Debug)]
pub(crate) struct EntryImpl {
dirent: libc::dirent,
loc: SeekLoc,
}
impl Deref for EntryImpl {
type Target = libc::dirent;
fn deref(&self) -> &Self::Target {
&self.dirent
}
}
pub(crate) fn iter_impl(dir: &Dir) -> Option<Result<EntryImpl>> {
let errno = Error::last_os_error();
let dirent = unsafe { libc::readdir(dir.as_raw().as_ptr()) };
if dirent.is_null() {
let curr_errno = Error::last_os_error();
if errno.raw_os_error() != curr_errno.raw_os_error() {
// TODO This should be verified on different BSD-flavours.
//
// According to 4.3BSD/POSIX.1-2001 man pages, there was an error
// if the errno value has changed at some point during the sequence
// of readdir calls.
Some(Err(curr_errno))
} else {
// Not an error. We've simply reached the end of the stream.
None
}
} else {
Some(Ok(EntryImpl {
dirent: unsafe { *dirent },
loc: dir.tell(),
}))
}
}
impl EntryExt for Entry {
#[cfg(target_os = "freebsd")]
fn ino(&self) -> u64 {
self.0.d_fileno.into()
}
#[cfg(not(target_os = "freebsd"))]
fn ino(&self) -> u64 {
self.0.d_ino.into()
}
fn seek_loc(&self) -> Result<SeekLoc> {
Ok(self.0.loc)
}
}

View File

@@ -1,91 +0,0 @@
use crate::from_success_code;
use std::{convert::TryInto, io::Result, os::unix::prelude::*};
#[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))]
#[derive(Debug, Copy, Clone)]
#[repr(i32)]
pub enum PosixFadviseAdvice {
Normal,
Sequential,
Random,
NoReuse,
WillNeed,
DontNeed,
}
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
#[derive(Debug, Copy, Clone)]
#[repr(i32)]
pub enum PosixFadviseAdvice {
Normal = libc::POSIX_FADV_NORMAL,
Sequential = libc::POSIX_FADV_SEQUENTIAL,
Random = libc::POSIX_FADV_RANDOM,
NoReuse = libc::POSIX_FADV_NOREUSE,
WillNeed = libc::POSIX_FADV_WILLNEED,
DontNeed = libc::POSIX_FADV_DONTNEED,
}
// There's no posix_fadvise on macOS but we can use fcntl with F_RDADVISE
// command instead to achieve the same
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub unsafe fn posix_fadvise(
fd: RawFd,
offset: libc::off_t,
len: libc::off_t,
_advice: PosixFadviseAdvice,
) -> Result<()> {
// 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 ra_count = match len.try_into() {
Ok(ra_count) => ra_count,
Err(_) => {
// This conversion can fail, because it's converting into int. But in that case, the user
// is providing a dubiously large hint. This is not confirmed (no helpful info in the man
// pages), but offhand, a 2+ GiB advisory read async seems unlikely to help with any kind
// of performance, so we log and exit early with a no-op.
tracing::warn!(
"`len` too big to fit in the host's command. Returning early with no-op!"
);
return Ok(());
}
};
let advisory = libc::radvisory {
ra_offset: offset,
ra_count,
};
from_success_code(libc::fcntl(fd, libc::F_RDADVISE, &advisory))
}
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
pub unsafe fn posix_fadvise(
fd: RawFd,
offset: libc::off_t,
len: libc::off_t,
advice: PosixFadviseAdvice,
) -> Result<()> {
from_success_code(libc::posix_fadvise(fd, offset, len, advice as libc::c_int))
}
// On BSDs without support we leave it as no-op
#[cfg(not(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd"
)))]
pub unsafe fn posix_fadvise(
_fd: RawFd,
_offset: libc::off_t,
_len: libc::off_t,
_advice: PosixFadviseAdvice,
) -> Result<()> {
Ok(())
}

View File

@@ -1,20 +0,0 @@
use std::{
io::{Error, Result},
os::unix::prelude::*,
};
pub unsafe fn isatty(fd: RawFd) -> Result<bool> {
let res = libc::isatty(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.
let errno = Error::last_os_error();
if errno.raw_os_error().unwrap() == libc::ENOTTY {
Ok(false)
} else {
Err(errno)
}
}
}

View File

@@ -1,77 +0,0 @@
//! This module consists of helper types and functions for dealing
//! with setting the file times specific to BSD-style *nixes.
use crate::filetime::FileTime;
use crate::{cstr, from_success_code};
use std::ffi::CStr;
use std::fs::File;
use std::io::Result;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
/// Wrapper for `utimensat` syscall, however, with an added twist such that `utimensat` symbol
/// is firstly resolved (i.e., we check whether it exists on the host), and only used if that is
/// the case. Otherwise, the syscall resorts to a less accurate `utimesat` emulated syscall.
/// The original implementation can be found here: [filetime::unix::macos::set_times]
///
/// [filetime::unix::macos::set_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/macos.rs#L49
pub fn utimensat(
dirfd: &File,
path: &str,
atime: FileTime,
mtime: FileTime,
symlink_nofollow: bool,
) -> Result<()> {
use crate::filetime::to_timespec;
use std::os::unix::prelude::*;
// Attempt to use the `utimensat` syscall, but if it's not supported by the
// current kernel then fall back to an older syscall.
if let Some(func) = fetch_utimensat() {
let flags = if symlink_nofollow {
libc::AT_SYMLINK_NOFOLLOW
} else {
0
};
let path = cstr(path)?;
let times = [to_timespec(&atime)?, to_timespec(&mtime)?];
return from_success_code(unsafe {
func(dirfd.as_raw_fd(), path.as_ptr(), times.as_ptr(), flags)
});
}
super::utimesat::utimesat(dirfd, path, atime, mtime, symlink_nofollow)
}
/// Wraps `fetch` specifically targetting `utimensat` symbol. If the symbol exists
/// on the host, then returns an `Some(unsafe fn)`.
fn fetch_utimensat() -> Option<
unsafe extern "C" fn(
libc::c_int,
*const libc::c_char,
*const libc::timespec,
libc::c_int,
) -> libc::c_int,
> {
static ADDR: AtomicUsize = AtomicUsize::new(0);
unsafe {
fetch(&ADDR, CStr::from_bytes_with_nul_unchecked(b"utimensat\0"))
.map(|sym| std::mem::transmute(sym))
}
}
/// Fetches a symbol by `name` and stores it in `cache`.
fn fetch(cache: &AtomicUsize, name: &CStr) -> Option<usize> {
match cache.load(SeqCst) {
0 => {}
1 => return None,
n => return Some(n),
}
let sym = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) };
let (val, ret) = if sym.is_null() {
(1, None)
} else {
(sym as usize, Some(sym as usize))
};
cache.store(val, SeqCst);
return ret;
}

View File

@@ -1,6 +0,0 @@
pub(crate) mod dir;
pub(crate) mod fadvise;
pub(crate) mod file;
pub(crate) mod filetime;
#[path = "../linux/utimesat.rs"]
pub(crate) mod utimesat;

View File

@@ -1,30 +0,0 @@
//! This module consists of helper types and functions for dealing
//! with setting the file times specific to Emscripten.
use crate::filetime::FileTime;
use crate::{cstr, from_success_code};
use std::fs::File;
use std::io::Result;
/// Wrapper for `utimensat` syscall. In Emscripten, there is no point in dynamically resolving
/// if `utimensat` is available as it always was and will be.
pub fn utimensat(
dirfd: &File,
path: &str,
atime: FileTime,
mtime: FileTime,
symlink_nofollow: bool,
) -> Result<()> {
use crate::filetime::to_timespec;
use std::os::unix::prelude::*;
let flags = if symlink_nofollow {
libc::AT_SYMLINK_NOFOLLOW
} else {
0
};
let path = cstr(path)?;
let times = [to_timespec(&atime)?, to_timespec(&mtime)?];
from_success_code(unsafe {
libc::utimensat(dirfd.as_raw_fd(), path.as_ptr(), times.as_ptr(), flags)
})
}

View File

@@ -1,7 +0,0 @@
#[path = "../linux/dir.rs"]
pub(crate) mod dir;
#[path = "../linux/fadvise.rs"]
pub(crate) mod fadvise;
#[path = "../linux/file.rs"]
pub(crate) mod file;
pub(crate) mod filetime;

View File

@@ -1,47 +0,0 @@
use crate::dir::{Dir, Entry, EntryExt, SeekLoc};
use std::{
io::{Error, Result},
ops::Deref,
};
#[derive(Copy, Clone, Debug)]
pub(crate) struct EntryImpl(libc::dirent64);
impl Deref for EntryImpl {
type Target = libc::dirent64;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl EntryExt for Entry {
fn ino(&self) -> u64 {
self.0.d_ino.into()
}
fn seek_loc(&self) -> Result<SeekLoc> {
unsafe { SeekLoc::from_raw(self.0.d_off) }
}
}
pub(crate) fn iter_impl(dir: &Dir) -> Option<Result<EntryImpl>> {
let errno = Error::last_os_error();
let dirent = unsafe { libc::readdir64(dir.as_raw().as_ptr()) };
if dirent.is_null() {
let curr_errno = Error::last_os_error();
if errno.raw_os_error() != curr_errno.raw_os_error() {
// TODO This should be verified on different BSD-flavours.
//
// According to 4.3BSD/POSIX.1-2001 man pages, there was an error
// if the errno value has changed at some point during the sequence
// of readdir calls.
Some(Err(curr_errno))
} else {
// Not an error. We've simply reached the end of the stream.
None
}
} else {
Some(Ok(EntryImpl(unsafe { *dirent })))
}
}

View File

@@ -1,23 +0,0 @@
use crate::from_success_code;
use std::io::Result;
use std::os::unix::prelude::*;
#[derive(Debug, Copy, Clone)]
#[repr(i32)]
pub enum PosixFadviseAdvice {
Normal = libc::POSIX_FADV_NORMAL,
Sequential = libc::POSIX_FADV_SEQUENTIAL,
Random = libc::POSIX_FADV_RANDOM,
NoReuse = libc::POSIX_FADV_NOREUSE,
WillNeed = libc::POSIX_FADV_WILLNEED,
DontNeed = libc::POSIX_FADV_DONTNEED,
}
pub unsafe fn posix_fadvise(
fd: RawFd,
offset: libc::off_t,
len: libc::off_t,
advice: PosixFadviseAdvice,
) -> Result<()> {
from_success_code(libc::posix_fadvise(fd, offset, len, advice as libc::c_int))
}

View File

@@ -1,26 +0,0 @@
use std::{
io::{Error, Result},
os::unix::prelude::*,
};
pub unsafe fn isatty(fd: RawFd) -> Result<bool> {
let res = libc::isatty(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.
let errno = Error::last_os_error();
let raw_errno = errno.raw_os_error().unwrap();
// 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
if raw_errno == libc::ENOTTY || raw_errno == libc::EINVAL {
Ok(false)
} else {
Err(errno)
}
}
}

View File

@@ -1,60 +0,0 @@
//! This module consists of helper types and functions for dealing
//! with setting the file times specific to Linux.
use crate::filetime::FileTime;
use crate::{cstr, from_success_code};
use std::fs::File;
use std::io::Result;
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
/// Wrapper for `utimensat` syscall, however, with an added twist such that `utimensat` symbol
/// is firstly resolved (i.e., we check whether it exists on the host), and only used if that is
/// the case. Otherwise, the syscall resorts to a less accurate `utimesat` emulated syscall.
/// The original implementation can be found here: [filetime::unix::linux::set_times]
///
/// [filetime::unix::linux::set_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/linux.rs#L64
pub fn utimensat(
dirfd: &File,
path: &str,
atime: FileTime,
mtime: FileTime,
symlink_nofollow: bool,
) -> Result<()> {
use crate::filetime::to_timespec;
use std::os::unix::prelude::*;
let flags = if symlink_nofollow {
libc::AT_SYMLINK_NOFOLLOW
} else {
0
};
// Attempt to use the `utimensat` syscall, but if it's not supported by the
// current kernel then fall back to an older syscall.
static INVALID: AtomicBool = AtomicBool::new(false);
if !INVALID.load(Relaxed) {
let path = cstr(path)?;
let times = [to_timespec(&atime)?, to_timespec(&mtime)?];
let res = from_success_code(unsafe {
libc::syscall(
libc::SYS_utimensat,
dirfd.as_raw_fd(),
path.as_ptr(),
times.as_ptr(),
flags,
)
});
let err = match res {
Ok(()) => return Ok(()),
Err(e) => e,
};
if err.raw_os_error().unwrap() == libc::ENOSYS {
INVALID.store(true, Relaxed);
}
return Err(err);
}
#[cfg(not(target_os = "android"))]
return super::utimesat::utimesat(dirfd, path, atime, mtime, symlink_nofollow);
#[cfg(target_os = "android")]
unreachable!();
}

View File

@@ -1,6 +0,0 @@
pub(crate) mod dir;
pub(crate) mod fadvise;
pub(crate) mod file;
pub(crate) mod filetime;
#[cfg(not(target_os = "android"))]
pub(crate) mod utimesat;

View File

@@ -1,81 +0,0 @@
use crate::filetime::FileTime;
use crate::filetime::FileTimeExt;
use crate::{cstr, from_success_code};
use std::fs;
use std::io::Result;
/// Combines `openat` with `utimes` to emulate `utimensat` on platforms where it is
/// not available. The logic for setting file times is based on [filetime::unix::set_file_handles_times].
///
/// [filetime::unix::set_file_handles_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L24
pub fn utimesat(
dirfd: &fs::File,
path: &str,
atime: FileTime,
mtime: FileTime,
symlink_nofollow: bool,
) -> Result<()> {
use std::os::unix::prelude::*;
// emulate *at syscall by reading the path from a combination of
// (fd, path)
let path = cstr(path)?;
let mut flags = libc::O_RDWR;
if symlink_nofollow {
flags |= libc::O_NOFOLLOW;
}
let fd = unsafe { libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flags) };
let f = unsafe { fs::File::from_raw_fd(fd) };
let (atime, mtime) = get_times(atime, mtime, || f.metadata().map_err(Into::into))?;
let times = [to_timeval(atime)?, to_timeval(mtime)?];
from_success_code(unsafe { libc::futimes(f.as_raw_fd(), times.as_ptr()) })
}
/// Converts `filetime::FileTime` to `libc::timeval`.
fn to_timeval(ft: filetime::FileTime) -> Result<libc::timeval> {
Ok(libc::timeval {
tv_sec: ft.seconds_()?,
tv_usec: (ft.nanoseconds_() / 1000) as libc::suseconds_t,
})
}
/// For a provided pair of access and modified `FileTime`s, converts the input to
/// `filetime::FileTime` used later in `utimensat` function. For variants `FileTime::Now`
/// and `FileTime::Omit`, this function will make two syscalls: either accessing current
/// system time, or accessing the file's metadata.
///
/// The original implementation can be found here: [filetime::unix::get_times].
///
/// [filetime::unix::get_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/utimes.rs#L42
fn get_times(
atime: FileTime,
mtime: FileTime,
current: impl Fn() -> Result<fs::Metadata>,
) -> Result<(filetime::FileTime, filetime::FileTime)> {
use std::time::SystemTime;
let atime = match atime {
FileTime::Now => {
let time = SystemTime::now();
filetime::FileTime::from_system_time(time)
}
FileTime::Omit => {
let meta = current()?;
filetime::FileTime::from_last_access_time(&meta)
}
FileTime::FileTime(ft) => ft,
};
let mtime = match mtime {
FileTime::Now => {
let time = SystemTime::now();
filetime::FileTime::from_system_time(time)
}
FileTime::Omit => {
let meta = current()?;
filetime::FileTime::from_last_modification_time(&meta)
}
FileTime::FileTime(ft) => ft,
};
Ok((atime, mtime))
}

View File

@@ -1,32 +0,0 @@
use crate::dir::SeekLoc;
use cfg_if::cfg_if;
use std::io::Result;
cfg_if! {
if #[cfg(any(target_os = "linux",
target_os = "android"))] {
mod linux;
pub(crate) use linux::*;
} else if #[cfg(target_os = "emscripten")] {
mod emscripten;
pub(crate) use emscripten::*;
} else if #[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"))] {
mod bsd;
pub(crate) use bsd::*;
} else if #[cfg(target_os = "wasi")] {
mod wasi;
pub(crate) use wasi::*;
} else {
compile_error!("yanix doesn't compile for this platform yet");
}
}
pub trait EntryExt {
fn ino(&self) -> u64;
fn seek_loc(&self) -> Result<SeekLoc>;
}

View File

@@ -1,23 +0,0 @@
use crate::from_success_code;
use std::io::Result;
use std::os::wasi::prelude::*;
#[derive(Debug, Copy, Clone)]
#[repr(i32)]
pub enum PosixFadviseAdvice {
Normal = libc::POSIX_FADV_NORMAL,
Sequential = libc::POSIX_FADV_SEQUENTIAL,
Random = libc::POSIX_FADV_RANDOM,
NoReuse = libc::POSIX_FADV_NOREUSE,
WillNeed = libc::POSIX_FADV_WILLNEED,
DontNeed = libc::POSIX_FADV_DONTNEED,
}
pub unsafe fn posix_fadvise(
fd: RawFd,
offset: libc::off_t,
len: libc::off_t,
advice: PosixFadviseAdvice,
) -> Result<()> {
from_success_code(libc::posix_fadvise(fd, offset, len, advice as libc::c_int))
}

View File

@@ -1,21 +0,0 @@
use std::{
io::{Error, Result},
os::wasi::prelude::*,
};
pub unsafe fn isatty(fd: RawFd) -> Result<bool> {
let res = libc::isatty(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.
let errno = Error::last_os_error();
let raw_errno = errno.raw_os_error().unwrap();
if raw_errno == libc::ENOTTY {
Ok(false)
} else {
Err(errno)
}
}
}

View File

@@ -1,33 +0,0 @@
use crate::filetime::FileTime;
use crate::from_success_code;
use std::fs::File;
use std::io;
pub fn utimensat(
dirfd: &File,
path: &str,
atime: FileTime,
mtime: FileTime,
symlink_nofollow: bool,
) -> io::Result<()> {
use crate::filetime::to_timespec;
use std::ffi::CString;
use std::os::wasi::prelude::*;
let p = CString::new(path.as_bytes())?;
let times = [to_timespec(&atime)?, to_timespec(&mtime)?];
let flags = if symlink_nofollow {
libc::AT_SYMLINK_NOFOLLOW
} else {
0
};
from_success_code(unsafe {
libc::utimensat(
dirfd.as_raw_fd() as libc::c_int,
p.as_ptr(),
times.as_ptr(),
flags,
)
})
}

View File

@@ -1,3 +0,0 @@
pub(crate) mod fadvise;
pub(crate) mod file;
pub(crate) mod filetime;