delete wasi-common, yanix, winx
This commit is contained in:
@@ -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"]
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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::*;
|
|
||||||
@@ -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?
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
@@ -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};
|
|
||||||
@@ -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(".")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod wasi_snapshot_preview1;
|
|
||||||
pub mod wasi_unstable;
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub(crate) mod osdir;
|
|
||||||
pub(crate) mod path;
|
|
||||||
|
|
||||||
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::SYNC;
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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))
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub(crate) mod osdir;
|
|
||||||
pub(crate) mod path;
|
|
||||||
|
|
||||||
pub(crate) const O_RSYNC: yanix::file::OFlags = yanix::file::OFlags::RSYNC;
|
|
||||||
@@ -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)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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 }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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 }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" }
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" }
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||
@@ -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() })
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()))
|
|
||||||
}
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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())?)
|
|
||||||
}
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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 })))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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!();
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub(crate) mod fadvise;
|
|
||||||
pub(crate) mod file;
|
|
||||||
pub(crate) mod filetime;
|
|
||||||
Reference in New Issue
Block a user