Make Handle a trait required for any WASI-compatible handle (#1443)
* Make Handle a trait required for any WASI-compatible handle
OK, so this PR is a bit of an experiment that came about somewhat itself
when I was looking at refactoring use of `Rc<RefCell<Descriptor>>` inside
`Entry` struct. I've noticed that since we've placed `VirtualFile` on the
same level as `OsHandle` and `Stdin` etc., we've ended up necessiitating
checks for different combinations such as "is a real OS resource being mixed
up with a virtual resource?", and if that was the case, we'd panic since
this was clearly not allowed (e.g., symlinking, or worse renaming).
Therefore, it seemed natural for virtual file to be on the same level
as _any_ OS handle (regardless of whether it's an actual file, socket,
or stdio handle). In other words, we should ideally envision the following
hierarchy:
```
\-- OsHandle \-- OsFile
-- Stdio
\-- Virtual
```
This way, we can deal with the mix up at a level above which cleans up
our logic significantly.
On the other hand, when looking through the `virtfs`, the trait approach
to some type that's a valid `Handle` grew on me, and I think this
is the way to go. And this is what this PR is proposing, a trait
`Handle` which features enough functionality to make both virtual and
OS ops to work. Now, inside `Entry` we can safely store something like
`Rc<dyn Handle>` where `Handle` can downcast to either `VirtualFile` or
`VirtualDir`, or `OsHandle` if its an actual OS resource. Note that
I've left `Handle` as one massive trait, but I reckon we could split
it up into several smaller traits, each dealing with some bit of WASI
functionality. I'm hoping this would perhaps make it easier to figure
out polyfilling between snapshots and the new upcoming ephemeral
snapshot since a lot of boilerplate functionality is now done as part
of the `Handle` trait implementation.
Next, I've redone the original `OsHandle` to be an `OsFile` which
now stores a raw descriptor/handle (`RawFd`/`RawHandle`) inside a
`Cell` so that we can handle interior mutability in an easy (read,
non-panicky) way. In order not to lose the perks of derefercing to
`std::fs::File`, I've added a convenience trait `AsFile` which
will take `OsFile` by reference (or the stdio handles) and create
a non-owned `ManuallyDrop<File>` resource which can be passed around
and acted upon the way we'd normally do on `&File`. This change of
course implies that we now have to worry about properly closing all
OS resources stored as part of `OsFile`, thus this type now implements
`Drop` trait which essentially speaking moves the raw descriptor/handle
into a `File` and drops it.
Finally, I've redone setting time info on relative paths on *nix using
the same approach as advocated in the virtual fs. Namely, we do an
`openat` followed by `filestat_set_times` on the obtained descriptor.
This effectively removes the need for custom `filetime` module in
`yanix`. However, this does probably incur additional cost of at least
one additional syscall, and I haven't checked whether this approach
performs as expected on platforms such as NixOS which as far as I remember
had some weirdness todo with linking `utimensat` symbols, etc. Still,
this change is worth considering given that the implementation of
`path_filestat_set_times` cleans up a lot, albeit with some additional
cost.
* Fix tests on Windows
* Address comments plus minor consistency cleanup
* Address comments
* Fix formatting
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -1742,18 +1742,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.105"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff"
|
checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.105"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8"
|
checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1762,9 +1762,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.50"
|
version = "1.0.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867"
|
checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -1909,9 +1909,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termios"
|
name = "termios"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625"
|
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::entry::{Descriptor, Entry};
|
use crate::entry::{Entry, EntryHandle};
|
||||||
use crate::fdpool::FdPool;
|
use crate::fdpool::FdPool;
|
||||||
use crate::sys::entry::OsHandle;
|
use crate::handle::Handle;
|
||||||
|
use crate::sys::oshandle::{OsHandle, OsHandleExt};
|
||||||
use crate::virtfs::{VirtualDir, VirtualDirEntry};
|
use crate::virtfs::{VirtualDir, VirtualDirEntry};
|
||||||
use crate::wasi::types;
|
use crate::wasi::types;
|
||||||
use crate::wasi::{Errno, Result};
|
use crate::wasi::{Errno, Result};
|
||||||
@@ -42,7 +43,7 @@ pub enum WasiCtxBuilderError {
|
|||||||
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
|
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
|
||||||
|
|
||||||
enum PendingEntry {
|
enum PendingEntry {
|
||||||
Thunk(fn() -> io::Result<Entry>),
|
Thunk(fn() -> io::Result<OsHandle>),
|
||||||
File(File),
|
File(File),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ impl std::fmt::Debug for PendingEntry {
|
|||||||
Self::Thunk(f) => write!(
|
Self::Thunk(f) => write!(
|
||||||
fmt,
|
fmt,
|
||||||
"PendingEntry::Thunk({:p})",
|
"PendingEntry::Thunk({:p})",
|
||||||
f as *const fn() -> io::Result<Entry>
|
f as *const fn() -> io::Result<OsHandle>
|
||||||
),
|
),
|
||||||
Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f),
|
Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f),
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ pub struct WasiCtxBuilder {
|
|||||||
stdin: Option<PendingEntry>,
|
stdin: Option<PendingEntry>,
|
||||||
stdout: Option<PendingEntry>,
|
stdout: Option<PendingEntry>,
|
||||||
stderr: Option<PendingEntry>,
|
stderr: Option<PendingEntry>,
|
||||||
preopens: Option<Vec<(PathBuf, Descriptor)>>,
|
preopens: Option<Vec<(PathBuf, Box<dyn Handle>)>>,
|
||||||
args: Option<Vec<PendingCString>>,
|
args: Option<Vec<PendingCString>>,
|
||||||
env: Option<HashMap<PendingCString, PendingCString>>,
|
env: Option<HashMap<PendingCString, PendingCString>>,
|
||||||
}
|
}
|
||||||
@@ -117,9 +118,9 @@ pub struct WasiCtxBuilder {
|
|||||||
impl WasiCtxBuilder {
|
impl WasiCtxBuilder {
|
||||||
/// Builder for a new `WasiCtx`.
|
/// Builder for a new `WasiCtx`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let stdin = Some(PendingEntry::Thunk(Entry::null));
|
let stdin = Some(PendingEntry::Thunk(OsHandle::from_null));
|
||||||
let stdout = Some(PendingEntry::Thunk(Entry::null));
|
let stdout = Some(PendingEntry::Thunk(OsHandle::from_null));
|
||||||
let stderr = Some(PendingEntry::Thunk(Entry::null));
|
let stderr = Some(PendingEntry::Thunk(OsHandle::from_null));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stdin,
|
stdin,
|
||||||
@@ -166,27 +167,27 @@ impl WasiCtxBuilder {
|
|||||||
|
|
||||||
/// Inherit stdin from the host process.
|
/// Inherit stdin from the host process.
|
||||||
pub fn inherit_stdin(&mut self) -> &mut Self {
|
pub fn inherit_stdin(&mut self) -> &mut Self {
|
||||||
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
|
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inherit stdout from the host process.
|
/// Inherit stdout from the host process.
|
||||||
pub fn inherit_stdout(&mut self) -> &mut Self {
|
pub fn inherit_stdout(&mut self) -> &mut Self {
|
||||||
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
|
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inherit stdout from the host process.
|
/// Inherit stdout from the host process.
|
||||||
pub fn inherit_stderr(&mut self) -> &mut Self {
|
pub fn inherit_stderr(&mut self) -> &mut Self {
|
||||||
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
|
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inherit the stdin, stdout, and stderr streams from the host process.
|
/// Inherit the stdin, stdout, and stderr streams from the host process.
|
||||||
pub fn inherit_stdio(&mut self) -> &mut Self {
|
pub fn inherit_stdio(&mut self) -> &mut Self {
|
||||||
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
|
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
|
||||||
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
|
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
|
||||||
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
|
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +251,7 @@ impl WasiCtxBuilder {
|
|||||||
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
|
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
|
||||||
self.preopens.as_mut().unwrap().push((
|
self.preopens.as_mut().unwrap().push((
|
||||||
guest_path.as_ref().to_owned(),
|
guest_path.as_ref().to_owned(),
|
||||||
Descriptor::OsHandle(OsHandle::from(dir)),
|
Box::new(OsHandle::from(dir)),
|
||||||
));
|
));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -287,7 +288,7 @@ impl WasiCtxBuilder {
|
|||||||
self.preopens
|
self.preopens
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push((guest_path.as_ref().to_owned(), Descriptor::VirtualFile(dir)));
|
.push((guest_path.as_ref().to_owned(), dir));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,41 +335,36 @@ impl WasiCtxBuilder {
|
|||||||
] {
|
] {
|
||||||
log::debug!("WasiCtx inserting entry {:?}", pending);
|
log::debug!("WasiCtx inserting entry {:?}", pending);
|
||||||
let fd = match pending {
|
let fd = match pending {
|
||||||
PendingEntry::Thunk(f) => entries
|
PendingEntry::Thunk(f) => {
|
||||||
.insert(f()?)
|
let handle = EntryHandle::new(f()?);
|
||||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?,
|
let entry = Entry::from(handle)?;
|
||||||
PendingEntry::File(f) => entries
|
entries
|
||||||
.insert(Entry::from(Descriptor::OsHandle(OsHandle::from(f)))?)
|
.insert(entry)
|
||||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?,
|
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
|
||||||
|
}
|
||||||
|
PendingEntry::File(f) => {
|
||||||
|
let handle = EntryHandle::new(OsHandle::from(f));
|
||||||
|
let entry = Entry::from(handle)?;
|
||||||
|
entries
|
||||||
|
.insert(entry)
|
||||||
|
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
log::debug!("WasiCtx inserted at {:?}", fd);
|
log::debug!("WasiCtx inserted at {:?}", fd);
|
||||||
}
|
}
|
||||||
// Then add the preopen entries.
|
// Then add the preopen entries.
|
||||||
for (guest_path, dir) in self.preopens.take().unwrap() {
|
for (guest_path, dir) in self.preopens.take().unwrap() {
|
||||||
match &dir {
|
if !dir.is_directory() {
|
||||||
Descriptor::OsHandle(handle) => {
|
|
||||||
if !handle.metadata()?.is_dir() {
|
|
||||||
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
|
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(virt) => {
|
|
||||||
if !virt.is_directory() {
|
|
||||||
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
|
|
||||||
panic!("implementation error, stdin/stdout/stderr shouldn't be in the list of preopens");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut entry = Entry::from(dir)?;
|
let handle = EntryHandle::from(dir);
|
||||||
|
let mut entry = Entry::from(handle)?;
|
||||||
entry.preopen_path = Some(guest_path);
|
entry.preopen_path = Some(guest_path);
|
||||||
log::debug!("WasiCtx inserting {:?}", entry);
|
|
||||||
let fd = entries
|
let fd = entries
|
||||||
.insert(entry)
|
.insert(entry)
|
||||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
|
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
|
||||||
log::debug!("WasiCtx inserted at {:?}", fd);
|
log::debug!("WasiCtx inserted at {:?}", fd);
|
||||||
log::debug!("WasiCtx entries = {:?}", entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(WasiCtx {
|
Ok(WasiCtx {
|
||||||
@@ -379,7 +375,6 @@ impl WasiCtxBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct EntryTable {
|
struct EntryTable {
|
||||||
fd_pool: FdPool,
|
fd_pool: FdPool,
|
||||||
entries: HashMap<types::Fd, Rc<Entry>>,
|
entries: HashMap<types::Fd, Rc<Entry>>,
|
||||||
@@ -418,7 +413,6 @@ impl EntryTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WasiCtx {
|
pub struct WasiCtx {
|
||||||
entries: RefCell<EntryTable>,
|
entries: RefCell<EntryTable>,
|
||||||
pub(crate) args: Vec<CString>,
|
pub(crate) args: Vec<CString>,
|
||||||
|
|||||||
@@ -1,81 +1,53 @@
|
|||||||
use crate::sys::dev_null;
|
use crate::handle::Handle;
|
||||||
use crate::sys::entry::{descriptor_as_oshandle, determine_type_and_access_rights, OsHandle};
|
|
||||||
use crate::virtfs::VirtualFile;
|
|
||||||
use crate::wasi::types::{Filetype, Rights};
|
use crate::wasi::types::{Filetype, Rights};
|
||||||
use crate::wasi::{Errno, Result};
|
use crate::wasi::{Errno, Result};
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::Cell;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{fmt, fs, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
pub(crate) enum Descriptor {
|
pub(crate) struct EntryHandle(Rc<dyn Handle>);
|
||||||
OsHandle(OsHandle),
|
|
||||||
VirtualFile(Box<dyn VirtualFile>),
|
impl EntryHandle {
|
||||||
Stdin,
|
pub(crate) fn new<T: Handle + 'static>(handle: T) -> Self {
|
||||||
Stdout,
|
Self(Rc::new(handle))
|
||||||
Stderr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<OsHandle> for Descriptor {
|
pub(crate) fn get(&self) -> Self {
|
||||||
fn from(handle: OsHandle) -> Self {
|
Self(Rc::clone(&self.0))
|
||||||
Self::OsHandle(handle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<dyn VirtualFile>> for Descriptor {
|
impl From<Box<dyn Handle>> for EntryHandle {
|
||||||
fn from(virt: Box<dyn VirtualFile>) -> Self {
|
fn from(handle: Box<dyn Handle>) -> Self {
|
||||||
Self::VirtualFile(virt)
|
Self(handle.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Descriptor {
|
impl Deref for EntryHandle {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
type Target = dyn Handle;
|
||||||
match self {
|
|
||||||
Self::OsHandle(handle) => write!(f, "{:?}", handle),
|
|
||||||
Self::VirtualFile(_) => write!(f, "VirtualFile"),
|
|
||||||
Self::Stdin => write!(f, "Stdin"),
|
|
||||||
Self::Stdout => write!(f, "Stdout"),
|
|
||||||
Self::Stderr => write!(f, "Stderr"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Descriptor {
|
|
||||||
/// Return an `OsHandle`, which may be a stream or socket file descriptor.
|
|
||||||
pub(crate) fn as_os_handle<'descriptor>(&'descriptor self) -> OsHandleRef<'descriptor> {
|
|
||||||
descriptor_as_oshandle(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This allows an `OsHandle` to be temporarily borrowed from a
|
|
||||||
/// `Descriptor`. The `Descriptor` continues to own the resource,
|
|
||||||
/// and `OsHandleRef`'s lifetime parameter ensures that it doesn't
|
|
||||||
/// outlive the `Descriptor`.
|
|
||||||
pub(crate) struct OsHandleRef<'descriptor> {
|
|
||||||
handle: ManuallyDrop<OsHandle>,
|
|
||||||
_ref: PhantomData<&'descriptor Descriptor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'descriptor> OsHandleRef<'descriptor> {
|
|
||||||
pub(crate) fn new(handle: ManuallyDrop<OsHandle>) -> Self {
|
|
||||||
OsHandleRef {
|
|
||||||
handle,
|
|
||||||
_ref: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'descriptor> Deref for OsHandleRef<'descriptor> {
|
|
||||||
type Target = fs::File;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.handle
|
&*self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
|
||||||
|
/// certain rights `rights` in order to be accessed correctly.
|
||||||
|
///
|
||||||
|
/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or
|
||||||
|
/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` method
|
||||||
|
/// which require a set of base and inheriting rights to be specified, verifying whether the stored
|
||||||
|
/// `Descriptor` object is valid for the rights specified.
|
||||||
|
pub(crate) struct Entry {
|
||||||
|
pub(crate) file_type: Filetype,
|
||||||
|
handle: EntryHandle,
|
||||||
|
pub(crate) rights: Cell<EntryRights>,
|
||||||
|
pub(crate) preopen_path: Option<PathBuf>,
|
||||||
|
// TODO: directories
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents rights of an `Entry` entity, either already held or
|
/// Represents rights of an `Entry` entity, either already held or
|
||||||
/// required.
|
/// required.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -123,79 +95,17 @@ impl fmt::Display for EntryRights {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
|
|
||||||
/// certain rights `rights` in order to be accessed correctly.
|
|
||||||
///
|
|
||||||
/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or
|
|
||||||
/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` method
|
|
||||||
/// which require a set of base and inheriting rights to be specified, verifying whether the stored
|
|
||||||
/// `Descriptor` object is valid for the rights specified.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Entry {
|
|
||||||
pub(crate) file_type: Filetype,
|
|
||||||
descriptor: Rc<RefCell<Descriptor>>,
|
|
||||||
pub(crate) rights: Cell<EntryRights>,
|
|
||||||
pub(crate) preopen_path: Option<PathBuf>,
|
|
||||||
// TODO: directories
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
pub(crate) fn from(file: Descriptor) -> io::Result<Self> {
|
pub(crate) fn from(handle: EntryHandle) -> io::Result<Self> {
|
||||||
match file {
|
let file_type = handle.get_file_type()?;
|
||||||
Descriptor::OsHandle(handle) => unsafe { determine_type_and_access_rights(&handle) }
|
let rights = handle.get_rights()?;
|
||||||
.map(|(file_type, rights)| Self {
|
|
||||||
file_type,
|
|
||||||
descriptor: Rc::new(RefCell::new(handle.into())),
|
|
||||||
rights: Cell::new(rights),
|
|
||||||
preopen_path: None,
|
|
||||||
}),
|
|
||||||
Descriptor::VirtualFile(virt) => {
|
|
||||||
let file_type = virt.get_file_type();
|
|
||||||
let rights = EntryRights::new(virt.get_rights_base(), virt.get_rights_inheriting());
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
file_type,
|
file_type,
|
||||||
descriptor: Rc::new(RefCell::new(virt.into())),
|
handle,
|
||||||
rights: Cell::new(rights),
|
rights: Cell::new(rights),
|
||||||
preopen_path: None,
|
preopen_path: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
|
|
||||||
panic!("implementation error, stdin/stdout/stderr Entry must not be constructed from Entry::from");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn duplicate_stdin() -> io::Result<Self> {
|
|
||||||
unsafe { determine_type_and_access_rights(&io::stdin()) }.map(|(file_type, rights)| Self {
|
|
||||||
file_type,
|
|
||||||
descriptor: Rc::new(RefCell::new(Descriptor::Stdin)),
|
|
||||||
rights: Cell::new(rights),
|
|
||||||
preopen_path: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn duplicate_stdout() -> io::Result<Self> {
|
|
||||||
unsafe { determine_type_and_access_rights(&io::stdout()) }.map(|(file_type, rights)| Self {
|
|
||||||
file_type,
|
|
||||||
descriptor: Rc::new(RefCell::new(Descriptor::Stdout)),
|
|
||||||
rights: Cell::new(rights),
|
|
||||||
preopen_path: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn duplicate_stderr() -> io::Result<Self> {
|
|
||||||
unsafe { determine_type_and_access_rights(&io::stderr()) }.map(|(file_type, rights)| Self {
|
|
||||||
file_type,
|
|
||||||
descriptor: Rc::new(RefCell::new(Descriptor::Stderr)),
|
|
||||||
rights: Cell::new(rights),
|
|
||||||
preopen_path: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn null() -> io::Result<Self> {
|
|
||||||
Self::from(OsHandle::from(dev_null()?).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert this `Entry` into a host `Descriptor` object provided the specified
|
/// Convert this `Entry` into a host `Descriptor` object provided the specified
|
||||||
/// `rights` rights are set on this `Entry` object.
|
/// `rights` rights are set on this `Entry` object.
|
||||||
@@ -205,9 +115,9 @@ impl Entry {
|
|||||||
/// `EntryRights` structure is a subset of rights attached to this `Entry`. The check is
|
/// `EntryRights` structure is a subset of rights attached to this `Entry`. The check is
|
||||||
/// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable`
|
/// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable`
|
||||||
/// is returned.
|
/// is returned.
|
||||||
pub(crate) fn as_descriptor(&self, rights: &EntryRights) -> Result<Rc<RefCell<Descriptor>>> {
|
pub(crate) fn as_handle(&self, rights: &EntryRights) -> Result<EntryHandle> {
|
||||||
self.validate_rights(rights)?;
|
self.validate_rights(rights)?;
|
||||||
Ok(Rc::clone(&self.descriptor))
|
Ok(self.handle.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if
|
/// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if
|
||||||
|
|||||||
121
crates/wasi-common/src/handle.rs
Normal file
121
crates/wasi-common/src/handle.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use crate::entry::EntryRights;
|
||||||
|
use crate::wasi::{types, Errno, Result};
|
||||||
|
use std::any::Any;
|
||||||
|
use std::io::{self, SeekFrom};
|
||||||
|
|
||||||
|
pub(crate) trait Handle {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>>;
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype>;
|
||||||
|
fn get_rights(&self) -> io::Result<EntryRights> {
|
||||||
|
Ok(EntryRights::empty())
|
||||||
|
}
|
||||||
|
fn is_directory(&self) -> bool {
|
||||||
|
if let Ok(ft) = self.get_file_type() {
|
||||||
|
return ft == types::Filetype::Directory;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
// TODO perhaps should be a separate trait?
|
||||||
|
// FdOps
|
||||||
|
fn advise(
|
||||||
|
&self,
|
||||||
|
_advice: types::Advice,
|
||||||
|
_offset: types::Filesize,
|
||||||
|
_len: types::Filesize,
|
||||||
|
) -> Result<()> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn datasync(&self) -> Result<()> {
|
||||||
|
Err(Errno::Inval)
|
||||||
|
}
|
||||||
|
fn fdstat_get(&self) -> Result<types::Fdflags> {
|
||||||
|
Ok(types::Fdflags::empty())
|
||||||
|
}
|
||||||
|
fn fdstat_set_flags(&self, _fdflags: types::Fdflags) -> Result<()> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn filestat_get(&self) -> Result<types::Filestat> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn filestat_set_times(
|
||||||
|
&self,
|
||||||
|
_atim: types::Timestamp,
|
||||||
|
_mtim: types::Timestamp,
|
||||||
|
_fst_flags: types::Fstflags,
|
||||||
|
) -> Result<()> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result<usize> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result<usize> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn readdir<'a>(
|
||||||
|
&'a self,
|
||||||
|
_cookie: types::Dircookie,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>> + 'a>> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn seek(&self, _offset: SeekFrom) -> Result<u64> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
fn sync(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn write_vectored(&self, _iovs: &[io::IoSlice], _isatty: bool) -> Result<usize> {
|
||||||
|
Err(Errno::Badf)
|
||||||
|
}
|
||||||
|
// TODO perhaps should be a separate trait?
|
||||||
|
// PathOps
|
||||||
|
fn create_directory(&self, _path: &str) -> Result<()> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn openat(
|
||||||
|
&self,
|
||||||
|
_path: &str,
|
||||||
|
_read: bool,
|
||||||
|
_write: bool,
|
||||||
|
_oflags: types::Oflags,
|
||||||
|
_fd_flags: types::Fdflags,
|
||||||
|
) -> Result<Box<dyn Handle>> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn link(
|
||||||
|
&self,
|
||||||
|
_old_path: &str,
|
||||||
|
_new_handle: Box<dyn Handle>,
|
||||||
|
_new_path: &str,
|
||||||
|
_follow: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn readlinkat(&self, _path: &str) -> Result<String> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn remove_directory(&self, _path: &str) -> Result<()> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
fn unlink_file(&self, _path: &str) -> Result<()> {
|
||||||
|
Err(Errno::Acces)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,12 +21,11 @@
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod clock;
|
|
||||||
mod ctx;
|
mod ctx;
|
||||||
mod entry;
|
mod entry;
|
||||||
mod fd;
|
|
||||||
mod fdpool;
|
mod fdpool;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
mod handle;
|
||||||
pub mod old;
|
pub mod old;
|
||||||
mod path;
|
mod path;
|
||||||
mod poll;
|
mod poll;
|
||||||
|
|||||||
@@ -1,112 +1,22 @@
|
|||||||
use crate::entry::{Descriptor, Entry, EntryRights};
|
use crate::entry::{Entry, EntryRights};
|
||||||
use crate::sys;
|
use crate::handle::Handle;
|
||||||
use crate::sys::entry::OsHandle;
|
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use std::path::{Component, Path};
|
use std::path::{Component, Path};
|
||||||
use std::str;
|
use std::str;
|
||||||
use wiggle::{GuestBorrows, GuestPtr};
|
use wiggle::{GuestBorrows, GuestPtr};
|
||||||
|
|
||||||
pub(crate) use sys::path::*;
|
pub(crate) use crate::sys::path::{from_host, open_rights};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct PathGet {
|
|
||||||
dirfd: Descriptor,
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathGet {
|
|
||||||
pub(crate) fn dirfd(&self) -> &Descriptor {
|
|
||||||
&self.dirfd
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn path(&self) -> &str {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn create_directory(self) -> Result<()> {
|
|
||||||
match &self.dirfd {
|
|
||||||
Descriptor::OsHandle(file) => create_directory(&file, &self.path),
|
|
||||||
Descriptor::VirtualFile(virt) => virt.create_directory(&Path::new(&self.path)),
|
|
||||||
other => {
|
|
||||||
panic!("invalid descriptor to create directory: {:?}", other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn open_with(
|
|
||||||
self,
|
|
||||||
read: bool,
|
|
||||||
write: bool,
|
|
||||||
oflags: types::Oflags,
|
|
||||||
fs_flags: types::Fdflags,
|
|
||||||
) -> Result<Descriptor> {
|
|
||||||
match &self.dirfd {
|
|
||||||
Descriptor::OsHandle(_) => {
|
|
||||||
open(self, read, write, oflags, fs_flags).map_err(Into::into)
|
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(virt) => virt
|
|
||||||
.openat(Path::new(&self.path), read, write, oflags, fs_flags)
|
|
||||||
.map(|file| Descriptor::VirtualFile(file)),
|
|
||||||
other => {
|
|
||||||
panic!("invalid descriptor to path_open: {:?}", other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PathRef<'a, 'b> {
|
|
||||||
dirfd: &'a Descriptor,
|
|
||||||
path: &'b str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> PathRef<'a, 'b> {
|
|
||||||
fn new(dirfd: &'a Descriptor, path: &'b str) -> Self {
|
|
||||||
PathRef { dirfd, path }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&self) -> Result<Descriptor> {
|
|
||||||
match self.dirfd {
|
|
||||||
Descriptor::OsHandle(file) => Ok(Descriptor::OsHandle(OsHandle::from(openat(
|
|
||||||
&file, &self.path,
|
|
||||||
)?))),
|
|
||||||
Descriptor::VirtualFile(virt) => virt
|
|
||||||
.openat(
|
|
||||||
Path::new(&self.path),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
types::Oflags::DIRECTORY,
|
|
||||||
types::Fdflags::empty(),
|
|
||||||
)
|
|
||||||
.map(|file| Descriptor::VirtualFile(file)),
|
|
||||||
other => {
|
|
||||||
panic!("invalid descriptor for open: {:?}", other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readlink(&self) -> Result<String> {
|
|
||||||
match self.dirfd {
|
|
||||||
Descriptor::OsHandle(file) => readlinkat(file, self.path),
|
|
||||||
Descriptor::VirtualFile(virt) => {
|
|
||||||
virt.readlinkat(Path::new(self.path)).map_err(Into::into)
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
panic!("invalid descriptor for readlink: {:?}", other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Normalizes a path to ensure that the target path is located under the directory provided.
|
/// 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.
|
/// This is a workaround for not having Capsicum support in the OS.
|
||||||
pub(crate) fn get(
|
pub(crate) fn get(
|
||||||
fe: &Entry,
|
entry: &Entry,
|
||||||
required_rights: &EntryRights,
|
required_rights: &EntryRights,
|
||||||
dirflags: types::Lookupflags,
|
dirflags: types::Lookupflags,
|
||||||
path: &GuestPtr<'_, str>,
|
path: &GuestPtr<'_, str>,
|
||||||
needs_final_component: bool,
|
needs_final_component: bool,
|
||||||
) -> Result<PathGet> {
|
) -> Result<(Box<dyn Handle>, String)> {
|
||||||
const MAX_SYMLINK_EXPANSIONS: usize = 128;
|
const MAX_SYMLINK_EXPANSIONS: usize = 128;
|
||||||
|
|
||||||
// Extract path as &str from guest's memory.
|
// Extract path as &str from guest's memory.
|
||||||
@@ -123,17 +33,13 @@ pub(crate) fn get(
|
|||||||
return Err(Errno::Ilseq);
|
return Err(Errno::Ilseq);
|
||||||
}
|
}
|
||||||
|
|
||||||
if fe.file_type != types::Filetype::Directory {
|
if entry.file_type != types::Filetype::Directory {
|
||||||
// if `dirfd` doesn't refer to a directory, return `Notdir`.
|
// if `dirfd` doesn't refer to a directory, return `Notdir`.
|
||||||
return Err(Errno::Notdir);
|
return Err(Errno::Notdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
let desc = fe.as_descriptor(required_rights)?;
|
let handle = entry.as_handle(required_rights)?;
|
||||||
let dirfd = match &*desc.borrow() {
|
let dirfd = handle.try_clone()?;
|
||||||
Descriptor::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into())?,
|
|
||||||
Descriptor::VirtualFile(virt) => virt.try_clone().map(Descriptor::VirtualFile)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stack of directory file descriptors. Index 0 always corresponds with the directory provided
|
// 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
|
// to this function. Entering a directory causes a file descriptor to be pushed, while handling
|
||||||
@@ -198,9 +104,14 @@ pub(crate) fn get(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
|
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
|
||||||
match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head)
|
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||||
.open()
|
match fd.openat(
|
||||||
{
|
&head,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
types::Oflags::DIRECTORY,
|
||||||
|
types::Fdflags::empty(),
|
||||||
|
) {
|
||||||
Ok(new_dir) => {
|
Ok(new_dir) => {
|
||||||
dir_stack.push(new_dir);
|
dir_stack.push(new_dir);
|
||||||
}
|
}
|
||||||
@@ -211,12 +122,8 @@ pub(crate) fn get(
|
|||||||
// this with ENOTDIR because of the O_DIRECTORY flag.
|
// this with ENOTDIR because of the O_DIRECTORY flag.
|
||||||
{
|
{
|
||||||
// attempt symlink expansion
|
// attempt symlink expansion
|
||||||
let mut link_path = PathRef::new(
|
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||||
dir_stack.last().ok_or(Errno::Notcapable)?,
|
let mut link_path = fd.readlinkat(&head)?;
|
||||||
&head,
|
|
||||||
)
|
|
||||||
.readlink()?;
|
|
||||||
|
|
||||||
symlink_expansions += 1;
|
symlink_expansions += 1;
|
||||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||||
return Err(Errno::Loop);
|
return Err(Errno::Loop);
|
||||||
@@ -246,9 +153,8 @@ pub(crate) fn get(
|
|||||||
{
|
{
|
||||||
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
|
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
|
||||||
// symlink expansion
|
// symlink expansion
|
||||||
match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head)
|
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||||
.readlink()
|
match fd.readlinkat(&head) {
|
||||||
{
|
|
||||||
Ok(mut link_path) => {
|
Ok(mut link_path) => {
|
||||||
symlink_expansions += 1;
|
symlink_expansions += 1;
|
||||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||||
@@ -282,20 +188,14 @@ pub(crate) fn get(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not a symlink, so we're done;
|
// not a symlink, so we're done;
|
||||||
return Ok(PathGet {
|
return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, head));
|
||||||
dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?,
|
|
||||||
path: head,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// no further components to process. means we've hit a case like "." or "a/..", or if the
|
// 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
|
// input path has trailing slashes and `needs_final_component` is not set
|
||||||
return Ok(PathGet {
|
return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, String::from(".")));
|
||||||
dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?,
|
|
||||||
path: String::from("."),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use crate::entry::Descriptor;
|
use crate::entry::EntryHandle;
|
||||||
use crate::sys;
|
|
||||||
use crate::wasi::types;
|
use crate::wasi::types;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
pub(crate) use sys::poll::*;
|
pub(crate) use crate::sys::poll::*;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub(crate) struct ClockEventData {
|
pub(crate) struct ClockEventData {
|
||||||
@@ -12,9 +9,8 @@ pub(crate) struct ClockEventData {
|
|||||||
pub(crate) userdata: types::Userdata,
|
pub(crate) userdata: types::Userdata,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct FdEventData {
|
pub(crate) struct FdEventData {
|
||||||
pub(crate) descriptor: Rc<RefCell<Descriptor>>,
|
pub(crate) handle: EntryHandle,
|
||||||
pub(crate) r#type: types::Eventtype,
|
pub(crate) r#type: types::Eventtype,
|
||||||
pub(crate) userdata: types::Userdata,
|
pub(crate) userdata: types::Userdata,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
use crate::entry::{Descriptor, Entry, EntryRights};
|
use crate::entry::{Entry, EntryHandle, EntryRights};
|
||||||
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
|
use crate::sys::clock;
|
||||||
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
|
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
|
||||||
use crate::wasi::{types, AsBytes, Errno, Result};
|
use crate::wasi::{types, AsBytes, Errno, Result};
|
||||||
use crate::WasiCtx;
|
use crate::WasiCtx;
|
||||||
use crate::{clock, fd, path, poll};
|
use crate::{path, poll};
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::io::{self, SeekFrom};
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
use wiggle::{GuestBorrows, GuestPtr};
|
use wiggle::{GuestBorrows, GuestPtr};
|
||||||
|
|
||||||
impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||||
@@ -94,13 +93,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_ADVISE);
|
let required_rights = EntryRights::from_base(types::Rights::FD_ADVISE);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
entry
|
||||||
match &*desc.borrow() {
|
.as_handle(&required_rights)?
|
||||||
Descriptor::OsHandle(fd) => fd::advise(&fd, advice, offset, len)?,
|
.advise(advice, offset, len)
|
||||||
Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_allocate(
|
fn fd_allocate(
|
||||||
@@ -111,24 +106,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_ALLOCATE);
|
let required_rights = EntryRights::from_base(types::Rights::FD_ALLOCATE);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
entry.as_handle(&required_rights)?.allocate(offset, len)
|
||||||
match &*desc.borrow() {
|
|
||||||
Descriptor::OsHandle(fd) => {
|
|
||||||
let metadata = fd.metadata()?;
|
|
||||||
let current_size = metadata.len();
|
|
||||||
let wanted_size = offset.checked_add(len).ok_or(Errno::TooBig)?;
|
|
||||||
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
|
||||||
if wanted_size > i64::max_value() as u64 {
|
|
||||||
return Err(Errno::TooBig);
|
|
||||||
}
|
|
||||||
if wanted_size > current_size {
|
|
||||||
fd.set_len(wanted_size)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(virt) => virt.allocate(offset, len)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_close(&self, fd: types::Fd) -> Result<()> {
|
fn fd_close(&self, fd: types::Fd) -> Result<()> {
|
||||||
@@ -145,26 +123,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
fn fd_datasync(&self, fd: types::Fd) -> Result<()> {
|
fn fd_datasync(&self, fd: types::Fd) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_DATASYNC);
|
let required_rights = EntryRights::from_base(types::Rights::FD_DATASYNC);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let file = entry.as_descriptor(&required_rights)?;
|
entry.as_handle(&required_rights)?.datasync()
|
||||||
match &*file.borrow() {
|
|
||||||
Descriptor::OsHandle(fd) => fd.sync_data()?,
|
|
||||||
Descriptor::VirtualFile(virt) => virt.datasync()?,
|
|
||||||
other => other.as_os_handle().sync_data()?,
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat> {
|
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat> {
|
||||||
let fe = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let wasi_file = fe.as_descriptor(&EntryRights::empty())?;
|
let file = entry.as_handle(&EntryRights::empty())?;
|
||||||
let fs_flags = match &*wasi_file.borrow() {
|
let fs_flags = file.fdstat_get()?;
|
||||||
Descriptor::OsHandle(wasi_fd) => fd::fdstat_get(&wasi_fd)?,
|
let rights = entry.rights.get();
|
||||||
Descriptor::VirtualFile(virt) => virt.fdstat_get(),
|
|
||||||
other => fd::fdstat_get(&other.as_os_handle())?,
|
|
||||||
};
|
|
||||||
let rights = fe.rights.get();
|
|
||||||
let fdstat = types::Fdstat {
|
let fdstat = types::Fdstat {
|
||||||
fs_filetype: fe.file_type,
|
fs_filetype: entry.file_type,
|
||||||
fs_rights_base: rights.base,
|
fs_rights_base: rights.base,
|
||||||
fs_rights_inheriting: rights.inheriting,
|
fs_rights_inheriting: rights.inheriting,
|
||||||
fs_flags,
|
fs_flags,
|
||||||
@@ -175,23 +143,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> {
|
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS);
|
let required_rights = EntryRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
entry.as_handle(&required_rights)?.fdstat_set_flags(flags)
|
||||||
let maybe_new_desc = match &*desc.borrow() {
|
|
||||||
Descriptor::OsHandle(handle) => {
|
|
||||||
fd::fdstat_set_flags(&handle, flags)?.map(Descriptor::OsHandle)
|
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(handle) => {
|
|
||||||
handle.fdstat_set_flags(flags)?.map(Descriptor::VirtualFile)
|
|
||||||
}
|
|
||||||
stream => {
|
|
||||||
fd::fdstat_set_flags(&stream.as_os_handle(), flags)?.map(Descriptor::OsHandle)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// TODO What happens on None?
|
|
||||||
if let Some(new_desc) = maybe_new_desc {
|
|
||||||
*desc.borrow_mut() = new_desc;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_fdstat_set_rights(
|
fn fd_fdstat_set_rights(
|
||||||
@@ -212,29 +164,18 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat> {
|
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_GET);
|
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_GET);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
let host_filestat = entry.as_handle(&required_rights)?.filestat_get()?;
|
||||||
let host_filestat = match &*desc.borrow() {
|
|
||||||
Descriptor::OsHandle(fd) => fd::filestat_get(&fd)?,
|
|
||||||
Descriptor::VirtualFile(virt) => virt.filestat_get()?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
Ok(host_filestat)
|
Ok(host_filestat)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> {
|
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE);
|
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
|
||||||
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
||||||
if size > i64::max_value() as u64 {
|
if size > i64::max_value() as u64 {
|
||||||
return Err(Errno::TooBig);
|
return Err(Errno::TooBig);
|
||||||
}
|
}
|
||||||
match &*desc.borrow() {
|
entry.as_handle(&required_rights)?.filestat_set_size(size)
|
||||||
Descriptor::OsHandle(fd) => fd.set_len(size)?,
|
|
||||||
Descriptor::VirtualFile(virt) => virt.filestat_set_size(size)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_filestat_set_times(
|
fn fd_filestat_set_times(
|
||||||
@@ -246,9 +187,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES);
|
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
entry
|
||||||
fd::filestat_set_times_impl(&desc.borrow(), atim, mtim, fst_flags)?;
|
.as_handle(&required_rights)?
|
||||||
Ok(())
|
.filestat_set_times(atim, mtim, fst_flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_pread(
|
fn fd_pread(
|
||||||
@@ -274,25 +215,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
let required_rights =
|
let required_rights =
|
||||||
EntryRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK);
|
EntryRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
|
||||||
|
|
||||||
if offset > i64::max_value() as u64 {
|
if offset > i64::max_value() as u64 {
|
||||||
return Err(Errno::Io);
|
return Err(Errno::Io);
|
||||||
}
|
}
|
||||||
|
let host_nread = entry
|
||||||
let host_nread = match &*desc.borrow() {
|
.as_handle(&required_rights)?
|
||||||
Descriptor::OsHandle(fd) => {
|
.preadv(&mut buf, offset)?
|
||||||
let mut fd: &File = fd;
|
.try_into()?;
|
||||||
let cur_pos = fd.seek(SeekFrom::Current(0))?;
|
|
||||||
fd.seek(SeekFrom::Start(offset))?;
|
|
||||||
let nread = fd.read_vectored(&mut buf)?;
|
|
||||||
fd.seek(SeekFrom::Start(cur_pos))?;
|
|
||||||
nread
|
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(virt) => virt.preadv(&mut buf, offset)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
let host_nread = host_nread.try_into()?;
|
|
||||||
Ok(host_nread)
|
Ok(host_nread)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,26 +291,15 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
let required_rights =
|
let required_rights =
|
||||||
EntryRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK);
|
EntryRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
|
||||||
|
|
||||||
if offset > i64::max_value() as u64 {
|
if offset > i64::max_value() as u64 {
|
||||||
return Err(Errno::Io);
|
return Err(Errno::Io);
|
||||||
}
|
}
|
||||||
|
|
||||||
let host_nwritten = match &*desc.borrow() {
|
let host_nwritten = entry
|
||||||
Descriptor::OsHandle(fd) => {
|
.as_handle(&required_rights)?
|
||||||
let mut fd: &File = fd;
|
.pwritev(&buf, offset)?
|
||||||
let cur_pos = fd.seek(SeekFrom::Current(0))?;
|
.try_into()?;
|
||||||
fd.seek(SeekFrom::Start(offset))?;
|
|
||||||
let nwritten = fd.write_vectored(&buf)?;
|
|
||||||
fd.seek(SeekFrom::Start(cur_pos))?;
|
|
||||||
nwritten
|
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(virt) => virt.pwritev(&buf, offset)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
let host_nwritten = host_nwritten.try_into()?;
|
|
||||||
|
|
||||||
Ok(host_nwritten)
|
Ok(host_nwritten)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,13 +320,10 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
|
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_READ);
|
let required_rights = EntryRights::from_base(types::Rights::FD_READ);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let host_nread = match &*entry.as_descriptor(&required_rights)?.borrow() {
|
let host_nread = entry
|
||||||
Descriptor::OsHandle(file) => (file as &File).read_vectored(&mut slices)?,
|
.as_handle(&required_rights)?
|
||||||
Descriptor::VirtualFile(virt) => virt.read_vectored(&mut slices)?,
|
.read_vectored(&mut slices)?
|
||||||
Descriptor::Stdin => io::stdin().read_vectored(&mut slices)?,
|
.try_into()?;
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
let host_nread = host_nread.try_into()?;
|
|
||||||
|
|
||||||
Ok(host_nread)
|
Ok(host_nread)
|
||||||
}
|
}
|
||||||
@@ -422,16 +337,10 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<types::Size> {
|
) -> Result<types::Size> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_READDIR);
|
let required_rights = EntryRights::from_base(types::Rights::FD_READDIR);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
|
||||||
|
|
||||||
fn copy_entities<T: Iterator<Item = Result<(types::Dirent, String)>>>(
|
|
||||||
iter: T,
|
|
||||||
buf: &GuestPtr<u8>,
|
|
||||||
buf_len: types::Size,
|
|
||||||
) -> Result<types::Size> {
|
|
||||||
let mut bufused = 0;
|
let mut bufused = 0;
|
||||||
let mut buf = buf.clone();
|
let mut buf = buf.clone();
|
||||||
for pair in iter {
|
for pair in entry.as_handle(&required_rights)?.readdir(cookie)? {
|
||||||
let (dirent, name) = pair?;
|
let (dirent, name) = pair?;
|
||||||
let dirent_raw = dirent.as_bytes()?;
|
let dirent_raw = dirent.as_bytes()?;
|
||||||
let dirent_len: types::Size = dirent_raw.len().try_into()?;
|
let dirent_len: types::Size = dirent_raw.len().try_into()?;
|
||||||
@@ -448,13 +357,6 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
bufused += offset;
|
bufused += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(bufused)
|
|
||||||
}
|
|
||||||
let bufused = match &*desc.borrow() {
|
|
||||||
Descriptor::OsHandle(file) => copy_entities(fd::readdir(file, cookie)?, buf, buf_len)?,
|
|
||||||
Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, buf, buf_len)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(bufused)
|
Ok(bufused)
|
||||||
}
|
}
|
||||||
@@ -495,43 +397,27 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
};
|
};
|
||||||
let required_rights = EntryRights::from_base(base);
|
let required_rights = EntryRights::from_base(base);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
|
||||||
let pos = match whence {
|
let pos = match whence {
|
||||||
types::Whence::Cur => SeekFrom::Current(offset),
|
types::Whence::Cur => SeekFrom::Current(offset),
|
||||||
types::Whence::End => SeekFrom::End(offset),
|
types::Whence::End => SeekFrom::End(offset),
|
||||||
types::Whence::Set => SeekFrom::Start(offset as u64),
|
types::Whence::Set => SeekFrom::Start(offset as u64),
|
||||||
};
|
};
|
||||||
let host_newoffset = match &*desc.borrow() {
|
let host_newoffset = entry.as_handle(&required_rights)?.seek(pos)?;
|
||||||
Descriptor::OsHandle(fd) => (fd as &File).seek(pos)?,
|
|
||||||
Descriptor::VirtualFile(virt) => virt.seek(pos)?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(host_newoffset)
|
Ok(host_newoffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_sync(&self, fd: types::Fd) -> Result<()> {
|
fn fd_sync(&self, fd: types::Fd) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_SYNC);
|
let required_rights = EntryRights::from_base(types::Rights::FD_SYNC);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
entry.as_handle(&required_rights)?.sync()
|
||||||
match &*desc.borrow() {
|
|
||||||
Descriptor::OsHandle(fd) => fd.sync_all()?,
|
|
||||||
Descriptor::VirtualFile(virt) => virt.sync()?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize> {
|
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_TELL);
|
let required_rights = EntryRights::from_base(types::Rights::FD_TELL);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
let host_offset = entry
|
||||||
let host_offset = match &*desc.borrow() {
|
.as_handle(&required_rights)?
|
||||||
Descriptor::OsHandle(fd) => (fd as &File).seek(SeekFrom::Current(0))?,
|
.seek(SeekFrom::Current(0))?;
|
||||||
Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?,
|
|
||||||
_ => return Err(Errno::Badf),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(host_offset)
|
Ok(host_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,63 +435,28 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
};
|
};
|
||||||
slices.push(io::IoSlice::new(slice));
|
slices.push(io::IoSlice::new(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform unbuffered writes
|
|
||||||
let required_rights = EntryRights::from_base(types::Rights::FD_WRITE);
|
let required_rights = EntryRights::from_base(types::Rights::FD_WRITE);
|
||||||
let entry = self.get_entry(fd)?;
|
let entry = self.get_entry(fd)?;
|
||||||
let isatty = entry.isatty();
|
let isatty = entry.isatty();
|
||||||
let desc = entry.as_descriptor(&required_rights)?;
|
let host_nwritten = entry
|
||||||
let host_nwritten = match &*desc.borrow() {
|
.as_handle(&required_rights)?
|
||||||
Descriptor::OsHandle(file) => {
|
.write_vectored(&slices, isatty)?
|
||||||
if isatty {
|
.try_into()?;
|
||||||
SandboxedTTYWriter::new(&mut (file as &File)).write_vectored(&slices)?
|
Ok(host_nwritten)
|
||||||
} else {
|
|
||||||
(file as &File).write_vectored(&slices)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Descriptor::VirtualFile(virt) => {
|
|
||||||
if isatty {
|
|
||||||
unimplemented!("writes to virtual tty");
|
|
||||||
} else {
|
|
||||||
virt.write_vectored(&slices)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Descriptor::Stdin => return Err(Errno::Badf),
|
|
||||||
Descriptor::Stdout => {
|
|
||||||
// lock for the duration of the scope
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
let nwritten = if isatty {
|
|
||||||
SandboxedTTYWriter::new(&mut stdout).write_vectored(&slices)?
|
|
||||||
} else {
|
|
||||||
stdout.write_vectored(&slices)?
|
|
||||||
};
|
|
||||||
stdout.flush()?;
|
|
||||||
nwritten
|
|
||||||
}
|
|
||||||
// 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.
|
|
||||||
Descriptor::Stderr => {
|
|
||||||
SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&slices)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(host_nwritten.try_into()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||||
let required_rights =
|
let required_rights =
|
||||||
EntryRights::from_base(types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY);
|
EntryRights::from_base(types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved = path::get(
|
let (dirfd, path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
path,
|
path,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
resolved.create_directory()
|
dirfd.create_directory(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_filestat_get(
|
fn path_filestat_get(
|
||||||
@@ -616,20 +467,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<types::Filestat> {
|
) -> Result<types::Filestat> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_GET);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_GET);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved = path::get(&entry, &required_rights, flags, path, false)?;
|
let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?;
|
||||||
let host_filestat = match resolved.dirfd() {
|
let host_filestat = dirfd
|
||||||
Descriptor::VirtualFile(virt) => virt
|
|
||||||
.openat(
|
.openat(
|
||||||
std::path::Path::new(resolved.path()),
|
&path,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
types::Oflags::empty(),
|
types::Oflags::empty(),
|
||||||
types::Fdflags::empty(),
|
types::Fdflags::empty(),
|
||||||
)?
|
)?
|
||||||
.filestat_get()?,
|
.filestat_get()?;
|
||||||
_ => path::filestat_get(resolved, flags)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(host_filestat)
|
Ok(host_filestat)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,13 +491,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved = path::get(&entry, &required_rights, flags, path, false)?;
|
let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?;
|
||||||
match resolved.dirfd() {
|
dirfd
|
||||||
Descriptor::VirtualFile(_virt) => {
|
.openat(
|
||||||
unimplemented!("virtual filestat_set_times");
|
&path,
|
||||||
}
|
false,
|
||||||
_ => path::filestat_set_times(resolved, flags, atim, mtim, fst_flags),
|
false,
|
||||||
}
|
types::Oflags::empty(),
|
||||||
|
types::Fdflags::empty(),
|
||||||
|
)?
|
||||||
|
.filestat_set_times(atim, mtim, fst_flags)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_link(
|
fn path_link(
|
||||||
@@ -663,7 +514,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_SOURCE);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_SOURCE);
|
||||||
let old_entry = self.get_entry(old_fd)?;
|
let old_entry = self.get_entry(old_fd)?;
|
||||||
let resolved_old = path::get(
|
let (old_dirfd, old_path) = path::get(
|
||||||
&old_entry,
|
&old_entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
@@ -672,16 +523,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
)?;
|
)?;
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_TARGET);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_TARGET);
|
||||||
let new_entry = self.get_entry(new_fd)?;
|
let new_entry = self.get_entry(new_fd)?;
|
||||||
let resolved_new = path::get(
|
let (new_dirfd, new_path) = path::get(
|
||||||
&new_entry,
|
&new_entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
new_path,
|
new_path,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
path::link(
|
old_dirfd.link(
|
||||||
resolved_old,
|
&old_path,
|
||||||
resolved_new,
|
new_dirfd,
|
||||||
|
&new_path,
|
||||||
old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW),
|
old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -701,20 +553,15 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
oflags,
|
oflags,
|
||||||
fdflags,
|
fdflags,
|
||||||
);
|
);
|
||||||
|
|
||||||
trace!(" | needed_rights={}", needed_rights);
|
trace!(" | needed_rights={}", needed_rights);
|
||||||
|
|
||||||
let resolved = {
|
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
path::get(
|
let (dirfd, path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&needed_rights,
|
&needed_rights,
|
||||||
dirflags,
|
dirflags,
|
||||||
path,
|
path,
|
||||||
oflags & types::Oflags::CREAT != types::Oflags::empty(),
|
oflags & types::Oflags::CREAT != types::Oflags::empty(),
|
||||||
)?
|
)?;
|
||||||
};
|
|
||||||
|
|
||||||
// which open mode do we need?
|
// which open mode do we need?
|
||||||
let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR)
|
let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR)
|
||||||
!= types::Rights::empty();
|
!= types::Rights::empty();
|
||||||
@@ -724,15 +571,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
| types::Rights::FD_ALLOCATE
|
| types::Rights::FD_ALLOCATE
|
||||||
| types::Rights::FD_FILESTAT_SET_SIZE)
|
| types::Rights::FD_FILESTAT_SET_SIZE)
|
||||||
!= types::Rights::empty();
|
!= types::Rights::empty();
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
" | calling path_open impl: read={}, write={}",
|
" | calling path_open impl: read={}, write={}",
|
||||||
read,
|
read,
|
||||||
write
|
write
|
||||||
);
|
);
|
||||||
|
let fd = dirfd.openat(&path, read, write, oflags, fdflags)?;
|
||||||
let fd = resolved.open_with(read, write, oflags, fdflags)?;
|
let fe = Entry::from(EntryHandle::from(fd))?;
|
||||||
let fe = Entry::from(fd)?;
|
|
||||||
// We need to manually deny the rights which are not explicitly requested
|
// We need to manually deny the rights which are not explicitly requested
|
||||||
// because Entry::from will assign maximal consistent rights.
|
// because Entry::from will assign maximal consistent rights.
|
||||||
let mut rights = fe.rights.get();
|
let mut rights = fe.rights.get();
|
||||||
@@ -752,47 +597,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<types::Size> {
|
) -> Result<types::Size> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_READLINK);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_READLINK);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved = path::get(
|
let (dirfd, path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
path,
|
path,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let slice = unsafe {
|
let slice = unsafe {
|
||||||
let mut bc = GuestBorrows::new();
|
let mut bc = GuestBorrows::new();
|
||||||
let buf = buf.as_array(buf_len);
|
let buf = buf.as_array(buf_len);
|
||||||
let raw = buf.as_raw(&mut bc)?;
|
let raw = buf.as_raw(&mut bc)?;
|
||||||
&mut *raw
|
&mut *raw
|
||||||
};
|
};
|
||||||
let host_bufused = match resolved.dirfd() {
|
let host_bufused = dirfd.readlink(&path, slice)?.try_into()?;
|
||||||
Descriptor::VirtualFile(_virt) => {
|
|
||||||
unimplemented!("virtual readlink");
|
|
||||||
}
|
|
||||||
_ => path::readlink(resolved, slice)?,
|
|
||||||
};
|
|
||||||
let host_bufused = host_bufused.try_into()?;
|
|
||||||
Ok(host_bufused)
|
Ok(host_bufused)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved = path::get(
|
let (dirfd, path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
path,
|
path,
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
dirfd.remove_directory(&path)
|
||||||
debug!("path_remove_directory resolved={:?}", resolved);
|
|
||||||
|
|
||||||
match resolved.dirfd() {
|
|
||||||
Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()),
|
|
||||||
_ => path::remove_directory(resolved),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_rename(
|
fn path_rename(
|
||||||
@@ -804,7 +636,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_SOURCE);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_SOURCE);
|
||||||
let entry = self.get_entry(old_fd)?;
|
let entry = self.get_entry(old_fd)?;
|
||||||
let resolved_old = path::get(
|
let (old_dirfd, old_path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
@@ -813,26 +645,14 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
)?;
|
)?;
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_TARGET);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_TARGET);
|
||||||
let entry = self.get_entry(new_fd)?;
|
let entry = self.get_entry(new_fd)?;
|
||||||
let resolved_new = path::get(
|
let (new_dirfd, new_path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
new_path,
|
new_path,
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
old_dirfd.rename(&old_path, new_dirfd, &new_path)
|
||||||
debug!("path_rename resolved_old={:?}", resolved_old);
|
|
||||||
debug!("path_rename resolved_new={:?}", resolved_new);
|
|
||||||
|
|
||||||
if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) =
|
|
||||||
(resolved_old.dirfd(), resolved_new.dirfd())
|
|
||||||
{
|
|
||||||
path::rename(resolved_old, resolved_new)
|
|
||||||
} else {
|
|
||||||
// Virtual files do not support rename, at the moment, and streams don't have paths to
|
|
||||||
// rename, so any combination of Descriptor that gets here is an error in the making.
|
|
||||||
panic!("path_rename with one or more non-OS files");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_symlink(
|
fn path_symlink(
|
||||||
@@ -843,44 +663,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_SYMLINK);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_SYMLINK);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved_new = path::get(
|
let (new_fd, new_path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
new_path,
|
new_path,
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let old_path = unsafe {
|
let old_path = unsafe {
|
||||||
let mut bc = GuestBorrows::new();
|
let mut bc = GuestBorrows::new();
|
||||||
let raw = old_path.as_raw(&mut bc)?;
|
let raw = old_path.as_raw(&mut bc)?;
|
||||||
&*raw
|
&*raw
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!(" | old_path='{}'", old_path);
|
trace!(" | old_path='{}'", old_path);
|
||||||
|
new_fd.symlink(&old_path, &new_path)
|
||||||
match resolved_new.dirfd() {
|
|
||||||
Descriptor::VirtualFile(_virt) => {
|
|
||||||
unimplemented!("virtual path_symlink");
|
|
||||||
}
|
|
||||||
_non_virtual => path::symlink(old_path, resolved_new),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||||
let required_rights = EntryRights::from_base(types::Rights::PATH_UNLINK_FILE);
|
let required_rights = EntryRights::from_base(types::Rights::PATH_UNLINK_FILE);
|
||||||
let entry = self.get_entry(dirfd)?;
|
let entry = self.get_entry(dirfd)?;
|
||||||
let resolved = path::get(
|
let (dirfd, path) = path::get(
|
||||||
&entry,
|
&entry,
|
||||||
&required_rights,
|
&required_rights,
|
||||||
types::Lookupflags::empty(),
|
types::Lookupflags::empty(),
|
||||||
path,
|
path,
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
match resolved.dirfd() {
|
dirfd.unlink_file(&path)?;
|
||||||
Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()),
|
Ok(())
|
||||||
_ => path::unlink_file(resolved),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_oneoff(
|
fn poll_oneoff(
|
||||||
@@ -949,7 +759,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fd_events.push(poll::FdEventData {
|
fd_events.push(poll::FdEventData {
|
||||||
descriptor: entry.as_descriptor(&required_rights)?,
|
handle: entry.as_handle(&required_rights)?,
|
||||||
r#type: types::Eventtype::FdRead,
|
r#type: types::Eventtype::FdRead,
|
||||||
userdata: subscription.userdata,
|
userdata: subscription.userdata,
|
||||||
});
|
});
|
||||||
@@ -975,7 +785,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fd_events.push(poll::FdEventData {
|
fd_events.push(poll::FdEventData {
|
||||||
descriptor: entry.as_descriptor(&required_rights)?,
|
handle: entry.as_handle(&required_rights)?,
|
||||||
r#type: types::Eventtype::FdWrite,
|
r#type: types::Eventtype::FdWrite,
|
||||||
userdata: subscription.userdata,
|
userdata: subscription.userdata,
|
||||||
});
|
});
|
||||||
@@ -984,7 +794,6 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
}
|
}
|
||||||
debug!("poll_oneoff events = {:?}", events);
|
debug!("poll_oneoff events = {:?}", events);
|
||||||
debug!("poll_oneoff timeout = {:?}", timeout);
|
debug!("poll_oneoff timeout = {:?}", timeout);
|
||||||
debug!("poll_oneoff fd_events = {:?}", fd_events);
|
|
||||||
// The underlying implementation should successfully and immediately return
|
// The underlying implementation should successfully and immediately return
|
||||||
// if no events have been passed. Such situation may occur if all provided
|
// if no events have been passed. Such situation may occur if all provided
|
||||||
// events have been filtered out as errors in the code above.
|
// events have been filtered out as errors in the code above.
|
||||||
@@ -997,6 +806,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
let event_ptr = event_ptr?;
|
let event_ptr = event_ptr?;
|
||||||
event_ptr.write(event)?;
|
event_ptr.write(event)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!(" | *nevents={:?}", nevents);
|
||||||
|
|
||||||
Ok(nevents)
|
Ok(nevents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::sys;
|
|
||||||
use crate::wasi::types::{Subclockflags, SubscriptionClock};
|
use crate::wasi::types::{Subclockflags, SubscriptionClock};
|
||||||
use crate::wasi::{Errno, Result};
|
use crate::wasi::{Errno, Result};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
pub(crate) use sys::clock::*;
|
pub(crate) use super::sys_impl::clock::*;
|
||||||
|
|
||||||
pub(crate) fn to_relative_ns_delay(clock: &SubscriptionClock) -> Result<u128> {
|
pub(crate) fn to_relative_ns_delay(clock: &SubscriptionClock) -> Result<u128> {
|
||||||
if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME {
|
if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME {
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
use crate::entry::Descriptor;
|
|
||||||
use crate::sys;
|
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use filetime::{set_file_handle_times, FileTime};
|
use filetime::{set_file_handle_times, FileTime};
|
||||||
|
use std::fs::File;
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
pub(crate) use sys::fd::*;
|
pub(crate) use super::sys_impl::fd::*;
|
||||||
|
|
||||||
pub(crate) fn filestat_set_times_impl(
|
pub(crate) fn filestat_set_times(
|
||||||
file: &Descriptor,
|
file: &File,
|
||||||
st_atim: types::Timestamp,
|
st_atim: types::Timestamp,
|
||||||
st_mtim: types::Timestamp,
|
st_mtim: types::Timestamp,
|
||||||
fst_flags: types::Fstflags,
|
fst_flags: types::Fstflags,
|
||||||
@@ -39,13 +38,7 @@ pub(crate) fn filestat_set_times_impl(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
match file {
|
|
||||||
Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into),
|
set_file_handle_times(file, atim, mtim)?;
|
||||||
Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim),
|
Ok(())
|
||||||
_ => {
|
|
||||||
unreachable!(
|
|
||||||
"implementation error: fd should have been checked to not be a stream already"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
|
pub(crate) mod clock;
|
||||||
|
pub(crate) mod fd;
|
||||||
|
pub(crate) mod oshandle;
|
||||||
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(unix)] {
|
if #[cfg(unix)] {
|
||||||
mod unix;
|
mod unix;
|
||||||
pub(crate) use unix::*;
|
use unix as sys_impl;
|
||||||
pub use unix::preopen_dir;
|
pub use unix::preopen_dir;
|
||||||
} else if #[cfg(windows)] {
|
} else if #[cfg(windows)] {
|
||||||
mod windows;
|
mod windows;
|
||||||
pub(crate) use windows::*;
|
use windows as sys_impl;
|
||||||
pub use windows::preopen_dir;
|
pub use windows::preopen_dir;
|
||||||
} else {
|
} else {
|
||||||
compile_error!("wasi-common doesn't compile for this platform yet");
|
compile_error!("wasi-common doesn't compile for this platform yet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) use sys_impl::path;
|
||||||
|
pub(crate) use sys_impl::poll;
|
||||||
|
|||||||
277
crates/wasi-common/src/sys/oshandle.rs
Normal file
277
crates/wasi-common/src/sys/oshandle.rs
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
use super::{fd, path};
|
||||||
|
use crate::entry::EntryRights;
|
||||||
|
use crate::handle::Handle;
|
||||||
|
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
|
||||||
|
use crate::wasi::{types, Errno, Result};
|
||||||
|
use log::{debug, error};
|
||||||
|
use std::any::Any;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
|
pub(crate) use super::sys_impl::oshandle::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum OsHandle {
|
||||||
|
OsFile(OsFile),
|
||||||
|
Stdin,
|
||||||
|
Stdout,
|
||||||
|
Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OsHandle {
|
||||||
|
pub(crate) fn as_os_file(&self) -> Result<&OsFile> {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(fd) => Ok(fd),
|
||||||
|
_ => Err(Errno::Badf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stdin() -> Self {
|
||||||
|
Self::Stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stdout() -> Self {
|
||||||
|
Self::Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stderr() -> Self {
|
||||||
|
Self::Stderr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait AsFile {
|
||||||
|
fn as_file(&self) -> ManuallyDrop<File>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait OsHandleExt: Sized {
|
||||||
|
/// Returns the file type.
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype>;
|
||||||
|
/// Returns the set of all possible rights that are both relevant for the file
|
||||||
|
/// type and consistent with the open mode.
|
||||||
|
fn get_rights(&self, filetype: types::Filetype) -> io::Result<EntryRights>;
|
||||||
|
fn from_null() -> io::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OsFile> for OsHandle {
|
||||||
|
fn from(file: OsFile) -> Self {
|
||||||
|
Self::OsFile(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handle for OsHandle {
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
||||||
|
let new_handle = match self {
|
||||||
|
Self::OsFile(file) => Self::OsFile(file.try_clone()?),
|
||||||
|
Self::Stdin => Self::Stdin,
|
||||||
|
Self::Stdout => Self::Stdout,
|
||||||
|
Self::Stderr => Self::Stderr,
|
||||||
|
};
|
||||||
|
Ok(Box::new(new_handle))
|
||||||
|
}
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||||
|
<Self as OsHandleExt>::get_file_type(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rights(&self) -> io::Result<EntryRights> {
|
||||||
|
<Self as OsHandleExt>::get_rights(self, <Self as Handle>::get_file_type(self)?)
|
||||||
|
}
|
||||||
|
// FdOps
|
||||||
|
fn advise(
|
||||||
|
&self,
|
||||||
|
advice: types::Advice,
|
||||||
|
offset: types::Filesize,
|
||||||
|
len: types::Filesize,
|
||||||
|
) -> Result<()> {
|
||||||
|
fd::advise(self.as_os_file()?, advice, offset, len)
|
||||||
|
}
|
||||||
|
fn allocate(&self, offset: types::Filesize, len: types::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(Errno::TooBig)?;
|
||||||
|
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
||||||
|
if wanted_size > i64::max_value() as u64 {
|
||||||
|
return Err(Errno::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<types::Fdflags> {
|
||||||
|
fd::fdstat_get(&self.as_file())
|
||||||
|
}
|
||||||
|
fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> {
|
||||||
|
if let Some(new_file) = fd::fdstat_set_flags(&self.as_file(), fdflags)? {
|
||||||
|
// If we don't deal with OsFile, then something went wrong, and we
|
||||||
|
// should fail. On the other hand, is that even possible?
|
||||||
|
self.as_os_file()?.update_from(new_file);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn filestat_get(&self) -> Result<types::Filestat> {
|
||||||
|
fd::filestat_get(&self.as_file())
|
||||||
|
}
|
||||||
|
fn filestat_set_size(&self, size: types::Filesize) -> Result<()> {
|
||||||
|
self.as_os_file()?.as_file().set_len(size)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn filestat_set_times(
|
||||||
|
&self,
|
||||||
|
atim: types::Timestamp,
|
||||||
|
mtim: types::Timestamp,
|
||||||
|
fst_flags: types::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_os_file()?.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_os_file()?.as_file();
|
||||||
|
let cur_pos = fd.seek(SeekFrom::Current(0))?;
|
||||||
|
fd.seek(SeekFrom::Start(offset))?;
|
||||||
|
let nwritten = self.write_vectored(&buf, false)?;
|
||||||
|
fd.seek(SeekFrom::Start(cur_pos))?;
|
||||||
|
Ok(nwritten)
|
||||||
|
}
|
||||||
|
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
|
||||||
|
let nread = match self {
|
||||||
|
Self::OsFile(file) => file.as_file().read_vectored(iovs)?,
|
||||||
|
Self::Stdin => io::stdin().read_vectored(iovs)?,
|
||||||
|
_ => return Err(Errno::Badf),
|
||||||
|
};
|
||||||
|
Ok(nread)
|
||||||
|
}
|
||||||
|
fn readdir<'a>(
|
||||||
|
&'a self,
|
||||||
|
cookie: types::Dircookie,
|
||||||
|
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>> + 'a>> {
|
||||||
|
fd::readdir(self.as_os_file()?, cookie)
|
||||||
|
}
|
||||||
|
fn seek(&self, offset: SeekFrom) -> Result<u64> {
|
||||||
|
let pos = self.as_os_file()?.as_file().seek(offset)?;
|
||||||
|
Ok(pos)
|
||||||
|
}
|
||||||
|
fn sync(&self) -> Result<()> {
|
||||||
|
self.as_os_file()?.as_file().sync_all()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn write_vectored(&self, iovs: &[io::IoSlice], isatty: bool) -> Result<usize> {
|
||||||
|
let nwritten = match self {
|
||||||
|
Self::OsFile(file) => {
|
||||||
|
let mut file: &File = &file.as_file();
|
||||||
|
if isatty {
|
||||||
|
SandboxedTTYWriter::new(&mut file).write_vectored(&iovs)?
|
||||||
|
} else {
|
||||||
|
file.write_vectored(&iovs)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Stdin => return Err(Errno::Badf),
|
||||||
|
Self::Stdout => {
|
||||||
|
// lock for the duration of the scope
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
let nwritten = if isatty {
|
||||||
|
SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)?
|
||||||
|
} else {
|
||||||
|
stdout.write_vectored(&iovs)?
|
||||||
|
};
|
||||||
|
stdout.flush()?;
|
||||||
|
nwritten
|
||||||
|
}
|
||||||
|
// 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.
|
||||||
|
Self::Stderr => SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?,
|
||||||
|
};
|
||||||
|
Ok(nwritten)
|
||||||
|
}
|
||||||
|
// PathOps
|
||||||
|
fn create_directory(&self, path: &str) -> Result<()> {
|
||||||
|
path::create_directory(self.as_os_file()?, path)
|
||||||
|
}
|
||||||
|
fn openat(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
read: bool,
|
||||||
|
write: bool,
|
||||||
|
oflags: types::Oflags,
|
||||||
|
fd_flags: types::Fdflags,
|
||||||
|
) -> Result<Box<dyn Handle>> {
|
||||||
|
let handle = path::open(self.as_os_file()?, path, read, write, oflags, fd_flags)?;
|
||||||
|
Ok(Box::new(handle))
|
||||||
|
}
|
||||||
|
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 OS resource with Virtual");
|
||||||
|
return Err(Errno::Badf);
|
||||||
|
}
|
||||||
|
Some(handle) => handle,
|
||||||
|
};
|
||||||
|
path::link(
|
||||||
|
self.as_os_file()?,
|
||||||
|
old_path,
|
||||||
|
new_handle.as_os_file()?,
|
||||||
|
new_path,
|
||||||
|
follow,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn symlink(&self, old_path: &str, new_path: &str) -> Result<()> {
|
||||||
|
path::symlink(old_path, self.as_os_file()?, new_path)
|
||||||
|
}
|
||||||
|
fn readlink(&self, path: &str, buf: &mut [u8]) -> Result<usize> {
|
||||||
|
path::readlink(self.as_os_file()?, path, buf)
|
||||||
|
}
|
||||||
|
fn readlinkat(&self, path: &str) -> Result<String> {
|
||||||
|
path::readlinkat(self.as_os_file()?, 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 link OS resource with Virtual");
|
||||||
|
return Err(Errno::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.as_os_file()?,
|
||||||
|
old_path,
|
||||||
|
new_handle.as_os_file()?,
|
||||||
|
new_path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn remove_directory(&self, path: &str) -> Result<()> {
|
||||||
|
debug!("remove_directory (dirfd, path)=({:?}, {:?})", self, path);
|
||||||
|
path::remove_directory(self.as_os_file()?, path)
|
||||||
|
}
|
||||||
|
fn unlink_file(&self, path: &str) -> Result<()> {
|
||||||
|
path::unlink_file(self.as_os_file()?, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
pub(crate) mod oshandle;
|
pub(crate) mod osfile;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
|
|
||||||
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC;
|
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC;
|
||||||
|
|||||||
109
crates/wasi-common/src/sys/unix/bsd/osfile.rs
Normal file
109
crates/wasi-common/src/sys/unix/bsd/osfile.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use crate::sys::oshandle::AsFile;
|
||||||
|
use crate::wasi::Result;
|
||||||
|
use std::cell::{Cell, RefCell, RefMut};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||||
|
use yanix::dir::Dir;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct OsFile {
|
||||||
|
fd: Cell<RawFd>,
|
||||||
|
// In case that this `OsHandle` actually refers to a directory,
|
||||||
|
// 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.
|
||||||
|
dir: RefCell<Option<Dir>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OsFile {
|
||||||
|
/// 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) {
|
||||||
|
let new_fd = other.into_raw_fd();
|
||||||
|
let old_fd = self.fd.get();
|
||||||
|
self.fd.set(new_fd);
|
||||||
|
// We need to remember to close the old_fd.
|
||||||
|
unsafe {
|
||||||
|
File::from_raw_fd(old_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Clones `self` uninitializing the `Dir` stream pointer
|
||||||
|
/// (if any).
|
||||||
|
pub(crate) fn try_clone(&self) -> io::Result<Self> {
|
||||||
|
let fd = self.as_file().try_clone()?;
|
||||||
|
Ok(Self {
|
||||||
|
fd: Cell::new(fd.into_raw_fd()),
|
||||||
|
dir: RefCell::new(None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Returns the `Dir` stream pointer associated with
|
||||||
|
/// this instance.
|
||||||
|
///
|
||||||
|
/// Initializes the `Dir` stream pointer if `None`.
|
||||||
|
pub(crate) fn dir_stream(&self) -> Result<RefMut<Dir>> {
|
||||||
|
if self.dir.borrow().is_none() {
|
||||||
|
// We need to duplicate the fd, 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 file = self.try_clone()?;
|
||||||
|
let d = Dir::from(file)?;
|
||||||
|
*self.dir.borrow_mut() = Some(d);
|
||||||
|
}
|
||||||
|
Ok(RefMut::map(self.dir.borrow_mut(), |dir| {
|
||||||
|
dir.as_mut().unwrap()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for OsFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
File::from_raw_fd(self.as_raw_fd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for OsFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.fd.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRawFd for OsFile {
|
||||||
|
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||||
|
Self {
|
||||||
|
fd: Cell::new(fd),
|
||||||
|
dir: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoRawFd for OsFile {
|
||||||
|
fn into_raw_fd(self) -> RawFd {
|
||||||
|
// We need to prevent dropping of the OsFile
|
||||||
|
let wrapped = ManuallyDrop::new(self);
|
||||||
|
wrapped.fd.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFile for OsFile {
|
||||||
|
fn as_file(&self) -> ManuallyDrop<File> {
|
||||||
|
let file = unsafe { File::from_raw_fd(self.fd.get()) };
|
||||||
|
ManuallyDrop::new(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
use crate::wasi::Result;
|
|
||||||
use std::cell::{RefCell, RefMut};
|
|
||||||
use std::fs;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
|
||||||
use yanix::dir::Dir;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct OsHandle {
|
|
||||||
file: fs::File,
|
|
||||||
// In case that this `OsHandle` actually refers to a directory,
|
|
||||||
// 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.
|
|
||||||
dir: RefCell<Option<Dir>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OsHandle {
|
|
||||||
pub(crate) fn dir_stream(&self) -> Result<RefMut<Dir>> {
|
|
||||||
if self.dir.borrow().is_none() {
|
|
||||||
// We need to duplicate the fd, 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 fd = self.file.try_clone()?;
|
|
||||||
let d = Dir::from(fd)?;
|
|
||||||
*self.dir.borrow_mut() = Some(d);
|
|
||||||
}
|
|
||||||
Ok(RefMut::map(self.dir.borrow_mut(), |dir| {
|
|
||||||
dir.as_mut().unwrap()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<fs::File> for OsHandle {
|
|
||||||
fn from(file: fs::File) -> Self {
|
|
||||||
Self {
|
|
||||||
file,
|
|
||||||
dir: RefCell::new(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for OsHandle {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for OsHandle {
|
|
||||||
type Target = fs::File;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,10 @@
|
|||||||
use crate::path::PathGet;
|
use super::osfile::OsFile;
|
||||||
use crate::wasi::{Errno, Result};
|
use crate::wasi::{Errno, Result};
|
||||||
use std::os::unix::prelude::AsRawFd;
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
|
pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||||
use yanix::file::{unlinkat, AtFlag};
|
use yanix::file::{unlinkat, AtFlag};
|
||||||
match unsafe {
|
match unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty()) } {
|
||||||
unlinkat(
|
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
resolved.path(),
|
|
||||||
AtFlag::empty(),
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let raw_errno = err.raw_os_error().unwrap();
|
let raw_errno = err.raw_os_error().unwrap();
|
||||||
// Non-Linux implementations may return EPERM when attempting to remove a
|
// Non-Linux implementations may return EPERM when attempting to remove a
|
||||||
@@ -23,13 +17,7 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
|
|||||||
use yanix::file::{fstatat, FileType};
|
use yanix::file::{fstatat, FileType};
|
||||||
|
|
||||||
if raw_errno == libc::EPERM {
|
if raw_errno == libc::EPERM {
|
||||||
match unsafe {
|
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } {
|
||||||
fstatat(
|
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
resolved.path(),
|
|
||||||
AtFlag::SYMLINK_NOFOLLOW,
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
Ok(stat) => {
|
Ok(stat) => {
|
||||||
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory {
|
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory {
|
||||||
return Err(Errno::Isdir);
|
return Err(Errno::Isdir);
|
||||||
@@ -47,13 +35,17 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Result<()> {
|
||||||
use yanix::file::{fstatat, symlinkat, AtFlag};
|
use yanix::file::{fstatat, symlinkat, AtFlag};
|
||||||
|
|
||||||
log::debug!("path_symlink old_path = {:?}", old_path);
|
log::debug!("path_symlink old_path = {:?}", old_path);
|
||||||
log::debug!("path_symlink resolved = {:?}", resolved);
|
log::debug!(
|
||||||
|
"path_symlink (new_dirfd, new_path) = ({:?}, {:?})",
|
||||||
|
new_dirfd,
|
||||||
|
new_path
|
||||||
|
);
|
||||||
|
|
||||||
match unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) } {
|
match unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path) } {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.raw_os_error().unwrap() == libc::ENOTDIR {
|
if err.raw_os_error().unwrap() == libc::ENOTDIR {
|
||||||
// On BSD, symlinkat returns ENOTDIR when it should in fact
|
// On BSD, symlinkat returns ENOTDIR when it should in fact
|
||||||
@@ -61,14 +53,9 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
|||||||
// the trailing slash in the target path. Thus, we strip
|
// the trailing slash in the target path. Thus, we strip
|
||||||
// the trailing slash and check if the path exists, and
|
// the trailing slash and check if the path exists, and
|
||||||
// adjust the error code appropriately.
|
// adjust the error code appropriately.
|
||||||
let new_path = resolved.path().trim_end_matches('/');
|
let new_path = new_path.trim_end_matches('/');
|
||||||
match unsafe {
|
match unsafe { fstatat(new_dirfd.as_raw_fd(), new_path, AtFlag::SYMLINK_NOFOLLOW) }
|
||||||
fstatat(
|
{
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
new_path,
|
|
||||||
AtFlag::SYMLINK_NOFOLLOW,
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
Ok(_) => return Err(Errno::Exist),
|
Ok(_) => return Err(Errno::Exist),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::debug!("path_symlink fstatat error: {:?}", err);
|
log::debug!("path_symlink fstatat error: {:?}", err);
|
||||||
@@ -81,14 +68,19 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
pub(crate) fn rename(
|
||||||
|
old_dirfd: &OsFile,
|
||||||
|
old_path: &str,
|
||||||
|
new_dirfd: &OsFile,
|
||||||
|
new_path: &str,
|
||||||
|
) -> Result<()> {
|
||||||
use yanix::file::{fstatat, renameat, AtFlag};
|
use yanix::file::{fstatat, renameat, AtFlag};
|
||||||
match unsafe {
|
match unsafe {
|
||||||
renameat(
|
renameat(
|
||||||
resolved_old.dirfd().as_raw_fd(),
|
old_dirfd.as_raw_fd(),
|
||||||
resolved_old.path(),
|
old_path,
|
||||||
resolved_new.dirfd().as_raw_fd(),
|
new_dirfd.as_raw_fd(),
|
||||||
resolved_new.path(),
|
new_path,
|
||||||
)
|
)
|
||||||
} {
|
} {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -103,16 +95,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
|||||||
// Verify on other BSD-based OSes.
|
// Verify on other BSD-based OSes.
|
||||||
if err.raw_os_error().unwrap() == libc::ENOENT {
|
if err.raw_os_error().unwrap() == libc::ENOENT {
|
||||||
// check if the source path exists
|
// check if the source path exists
|
||||||
match unsafe {
|
match unsafe { fstatat(old_dirfd.as_raw_fd(), old_path, AtFlag::SYMLINK_NOFOLLOW) }
|
||||||
fstatat(
|
{
|
||||||
resolved_old.dirfd().as_raw_fd(),
|
|
||||||
resolved_old.path(),
|
|
||||||
AtFlag::SYMLINK_NOFOLLOW,
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// check if destination contains a trailing slash
|
// check if destination contains a trailing slash
|
||||||
if resolved_new.path().contains('/') {
|
if new_path.contains('/') {
|
||||||
return Err(Errno::Notdir);
|
return Err(Errno::Notdir);
|
||||||
} else {
|
} else {
|
||||||
return Err(Errno::Noent);
|
return Err(Errno::Noent);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#[path = "../linux/oshandle.rs"]
|
#[path = "../linux/osfile.rs"]
|
||||||
pub(crate) mod oshandle;
|
pub(crate) mod osfile;
|
||||||
#[path = "../linux/path.rs"]
|
#[path = "../linux/path.rs"]
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
|
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
use crate::entry::{Descriptor, EntryRights, OsHandleRef};
|
|
||||||
use crate::wasi::{types, RightsExt};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd};
|
|
||||||
|
|
||||||
pub(crate) use super::sys_impl::oshandle::*;
|
|
||||||
|
|
||||||
impl AsRawFd for Descriptor {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
match self {
|
|
||||||
Self::OsHandle(file) => file.as_raw_fd(),
|
|
||||||
Self::VirtualFile(_) => panic!("virtual files do not have a raw fd"),
|
|
||||||
Self::Stdin => io::stdin().as_raw_fd(),
|
|
||||||
Self::Stdout => io::stdout().as_raw_fd(),
|
|
||||||
Self::Stderr => io::stderr().as_raw_fd(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn descriptor_as_oshandle<'lifetime>(
|
|
||||||
desc: &'lifetime Descriptor,
|
|
||||||
) -> OsHandleRef<'lifetime> {
|
|
||||||
OsHandleRef::new(ManuallyDrop::new(OsHandle::from(unsafe {
|
|
||||||
File::from_raw_fd(desc.as_raw_fd())
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the set of all possible rights that are both relevant for the file
|
|
||||||
/// type and consistent with the open mode.
|
|
||||||
///
|
|
||||||
/// This function is unsafe because it operates on a raw file descriptor.
|
|
||||||
pub(crate) unsafe fn determine_type_and_access_rights<Fd: AsRawFd>(
|
|
||||||
fd: &Fd,
|
|
||||||
) -> io::Result<(types::Filetype, EntryRights)> {
|
|
||||||
let (file_type, mut rights) = determine_type_rights(fd)?;
|
|
||||||
|
|
||||||
use yanix::{fcntl, file::OFlag};
|
|
||||||
let flags = fcntl::get_status_flags(fd.as_raw_fd())?;
|
|
||||||
let accmode = flags & OFlag::ACCMODE;
|
|
||||||
if accmode == OFlag::RDONLY {
|
|
||||||
rights.base &= !types::Rights::FD_WRITE;
|
|
||||||
} else if accmode == OFlag::WRONLY {
|
|
||||||
rights.base &= !types::Rights::FD_READ;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((file_type, rights))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the set of all possible rights that are relevant for file type.
|
|
||||||
///
|
|
||||||
/// This function is unsafe because it operates on a raw file descriptor.
|
|
||||||
pub(crate) unsafe fn determine_type_rights<Fd: AsRawFd>(
|
|
||||||
fd: &Fd,
|
|
||||||
) -> io::Result<(types::Filetype, EntryRights)> {
|
|
||||||
let (file_type, rights) = {
|
|
||||||
// we just make a `File` here for convenience; we don't want it to close when it drops
|
|
||||||
let file = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd.as_raw_fd()));
|
|
||||||
let ft = file.metadata()?.file_type();
|
|
||||||
let (filetype, base, inheriting) = if ft.is_block_device() {
|
|
||||||
log::debug!("Host fd {:?} is a block device", fd.as_raw_fd());
|
|
||||||
(
|
|
||||||
types::Filetype::BlockDevice,
|
|
||||||
types::Rights::block_device_base(),
|
|
||||||
types::Rights::block_device_inheriting(),
|
|
||||||
)
|
|
||||||
} else if ft.is_char_device() {
|
|
||||||
log::debug!("Host fd {:?} is a char device", fd.as_raw_fd());
|
|
||||||
use yanix::file::isatty;
|
|
||||||
if isatty(fd.as_raw_fd())? {
|
|
||||||
(
|
|
||||||
types::Filetype::CharacterDevice,
|
|
||||||
types::Rights::tty_base(),
|
|
||||||
types::Rights::tty_base(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
types::Filetype::CharacterDevice,
|
|
||||||
types::Rights::character_device_base(),
|
|
||||||
types::Rights::character_device_inheriting(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if ft.is_dir() {
|
|
||||||
log::debug!("Host fd {:?} is a directory", fd.as_raw_fd());
|
|
||||||
(
|
|
||||||
types::Filetype::Directory,
|
|
||||||
types::Rights::directory_base(),
|
|
||||||
types::Rights::directory_inheriting(),
|
|
||||||
)
|
|
||||||
} else if ft.is_file() {
|
|
||||||
log::debug!("Host fd {:?} is a file", fd.as_raw_fd());
|
|
||||||
(
|
|
||||||
types::Filetype::RegularFile,
|
|
||||||
types::Rights::regular_file_base(),
|
|
||||||
types::Rights::regular_file_inheriting(),
|
|
||||||
)
|
|
||||||
} else if ft.is_socket() {
|
|
||||||
log::debug!("Host fd {:?} is a socket", fd.as_raw_fd());
|
|
||||||
use yanix::socket::{get_socket_type, SockType};
|
|
||||||
match get_socket_type(fd.as_raw_fd())? {
|
|
||||||
SockType::Datagram => (
|
|
||||||
types::Filetype::SocketDgram,
|
|
||||||
types::Rights::socket_base(),
|
|
||||||
types::Rights::socket_inheriting(),
|
|
||||||
),
|
|
||||||
SockType::Stream => (
|
|
||||||
types::Filetype::SocketStream,
|
|
||||||
types::Rights::socket_base(),
|
|
||||||
types::Rights::socket_inheriting(),
|
|
||||||
),
|
|
||||||
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
|
||||||
}
|
|
||||||
} else if ft.is_fifo() {
|
|
||||||
log::debug!("Host fd {:?} is a fifo", fd.as_raw_fd());
|
|
||||||
(
|
|
||||||
types::Filetype::Unknown,
|
|
||||||
types::Rights::regular_file_base(),
|
|
||||||
types::Rights::regular_file_inheriting(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log::debug!("Host fd {:?} is unknown", fd.as_raw_fd());
|
|
||||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
|
||||||
};
|
|
||||||
(filetype, EntryRights::new(base, inheriting))
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((file_type, rights))
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::sys::entry::OsHandle;
|
use super::oshandle::OsFile;
|
||||||
use crate::wasi::{self, types, Result};
|
use crate::wasi::{self, types, Result};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -9,7 +9,7 @@ pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
|
|||||||
Ok(fdflags.into())
|
Ok(fdflags.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Option<OsHandle>> {
|
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Option<OsFile>> {
|
||||||
unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), fdflags.into())? };
|
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
|
// We return None here to signal that the operation succeeded on the original
|
||||||
// file descriptor and mutating the original WASI Descriptor is thus unnecessary.
|
// file descriptor and mutating the original WASI Descriptor is thus unnecessary.
|
||||||
@@ -18,7 +18,7 @@ pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn advise(
|
pub(crate) fn advise(
|
||||||
file: &File,
|
file: &OsFile,
|
||||||
advice: types::Advice,
|
advice: types::Advice,
|
||||||
offset: types::Filesize,
|
offset: types::Filesize,
|
||||||
len: types::Filesize,
|
len: types::Filesize,
|
||||||
@@ -38,21 +38,21 @@ pub(crate) fn advise(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn filestat_get(file: &std::fs::File) -> Result<types::Filestat> {
|
pub(crate) fn filestat_get(file: &File) -> Result<types::Filestat> {
|
||||||
use yanix::file::fstat;
|
use yanix::file::fstat;
|
||||||
let stat = unsafe { fstat(file.as_raw_fd())? };
|
let stat = unsafe { fstat(file.as_raw_fd())? };
|
||||||
Ok(stat.try_into()?)
|
Ok(stat.try_into()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn readdir<'a>(
|
pub(crate) fn readdir<'a>(
|
||||||
os_handle: &'a OsHandle,
|
file: &'a OsFile,
|
||||||
cookie: types::Dircookie,
|
cookie: types::Dircookie,
|
||||||
) -> Result<impl Iterator<Item = Result<(types::Dirent, String)>> + 'a> {
|
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>> + 'a>> {
|
||||||
use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc};
|
use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc};
|
||||||
|
|
||||||
// Get an instance of `Dir`; this is host-specific due to intricasies
|
// Get an instance of `Dir`; this is host-specific due to intricasies
|
||||||
// of managing a dir stream between Linux and BSD *nixes
|
// of managing a dir stream between Linux and BSD *nixes
|
||||||
let mut dir = os_handle.dir_stream()?;
|
let mut dir = file.dir_stream()?;
|
||||||
|
|
||||||
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
|
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
|
||||||
// new items may not be returned to the caller.
|
// new items may not be returned to the caller.
|
||||||
@@ -65,7 +65,7 @@ pub(crate) fn readdir<'a>(
|
|||||||
dir.seek(loc);
|
dir.seek(loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DirIter::new(dir).map(|entry| {
|
Ok(Box::new(DirIter::new(dir).map(|entry| {
|
||||||
let entry: Entry = entry?;
|
let entry: Entry = entry?;
|
||||||
let name = entry.file_name().to_str()?.to_owned();
|
let name = entry.file_name().to_str()?.to_owned();
|
||||||
let dirent = types::Dirent {
|
let dirent = types::Dirent {
|
||||||
@@ -75,5 +75,5 @@ pub(crate) fn readdir<'a>(
|
|||||||
d_type: entry.file_type().into(),
|
d_type: entry.file_type().into(),
|
||||||
};
|
};
|
||||||
Ok((dirent, name))
|
Ok((dirent, name))
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pub(crate) mod oshandle;
|
pub(crate) mod osfile;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
|
|
||||||
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC;
|
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC;
|
||||||
|
|||||||
82
crates/wasi-common/src/sys/unix/linux/osfile.rs
Normal file
82
crates/wasi-common/src/sys/unix/linux/osfile.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use crate::sys::oshandle::AsFile;
|
||||||
|
use crate::wasi::Result;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||||
|
use yanix::dir::Dir;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct OsFile(Cell<RawFd>);
|
||||||
|
|
||||||
|
impl OsFile {
|
||||||
|
/// Consumes `other` taking the ownership of the underlying
|
||||||
|
/// `RawFd` file descriptor.
|
||||||
|
pub(crate) fn update_from(&self, other: Self) {
|
||||||
|
let new_fd = other.into_raw_fd();
|
||||||
|
let old_fd = self.0.get();
|
||||||
|
self.0.set(new_fd);
|
||||||
|
// We need to remember to close the old_fd.
|
||||||
|
unsafe {
|
||||||
|
File::from_raw_fd(old_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Clones `self`.
|
||||||
|
pub(crate) fn try_clone(&self) -> io::Result<Self> {
|
||||||
|
let fd = self.as_file().try_clone()?;
|
||||||
|
Ok(Self(Cell::new(fd.into_raw_fd())))
|
||||||
|
}
|
||||||
|
/// Returns the `Dir` stream pointer associated with
|
||||||
|
/// this instance.
|
||||||
|
pub(crate) fn dir_stream(&self) -> Result<Box<Dir>> {
|
||||||
|
// We need to duplicate the fd, because `opendir(3)`:
|
||||||
|
// After a successful call to fdopendir(), fd is used internally by the implementation,
|
||||||
|
// and should not otherwise be used by the application.
|
||||||
|
// `opendir(3p)` also says that it's undefined behavior to
|
||||||
|
// modify the state of the fd in a different way than by accessing DIR*.
|
||||||
|
//
|
||||||
|
// Still, rewinddir will be needed because the two file descriptors
|
||||||
|
// share progress. But we can safely execute closedir now.
|
||||||
|
let file = self.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)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for OsFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
File::from_raw_fd(self.as_raw_fd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for OsFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRawFd for OsFile {
|
||||||
|
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||||
|
Self(Cell::new(fd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoRawFd for OsFile {
|
||||||
|
fn into_raw_fd(self) -> RawFd {
|
||||||
|
// We need to prevent dropping of the OsFile
|
||||||
|
let wrapped = ManuallyDrop::new(self);
|
||||||
|
wrapped.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFile for OsFile {
|
||||||
|
fn as_file(&self) -> ManuallyDrop<File> {
|
||||||
|
let file = unsafe { File::from_raw_fd(self.0.get()) };
|
||||||
|
ManuallyDrop::new(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
use crate::wasi::Result;
|
|
||||||
use std::fs;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
|
||||||
use yanix::dir::Dir;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct OsHandle(fs::File);
|
|
||||||
|
|
||||||
impl OsHandle {
|
|
||||||
pub(crate) fn dir_stream(&self) -> Result<Box<Dir>> {
|
|
||||||
// We need to duplicate the fd, because `opendir(3)`:
|
|
||||||
// After a successful call to fdopendir(), fd is used internally by the implementation,
|
|
||||||
// and should not otherwise be used by the application.
|
|
||||||
// `opendir(3p)` also says that it's undefined behavior to
|
|
||||||
// modify the state of the fd in a different way than by accessing DIR*.
|
|
||||||
//
|
|
||||||
// Still, rewinddir will be needed because the two file descriptors
|
|
||||||
// share progress. But we can safely execute closedir now.
|
|
||||||
let fd = self.0.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(fd)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<fs::File> for OsHandle {
|
|
||||||
fn from(file: fs::File) -> Self {
|
|
||||||
Self(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for OsHandle {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.0.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for OsHandle {
|
|
||||||
type Target = fs::File;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,41 @@
|
|||||||
use crate::entry::Descriptor;
|
use super::osfile::OsFile;
|
||||||
use crate::path::PathGet;
|
|
||||||
use crate::wasi::Result;
|
use crate::wasi::Result;
|
||||||
use std::os::unix::prelude::AsRawFd;
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
|
pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||||
use yanix::file::{unlinkat, AtFlag};
|
use yanix::file::{unlinkat, AtFlag};
|
||||||
unsafe {
|
unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty())? };
|
||||||
unlinkat(
|
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
resolved.path(),
|
|
||||||
AtFlag::empty(),
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Result<()> {
|
||||||
use yanix::file::symlinkat;
|
use yanix::file::symlinkat;
|
||||||
|
|
||||||
log::debug!("path_symlink old_path = {:?}", old_path);
|
log::debug!("path_symlink old_path = {:?}", old_path);
|
||||||
log::debug!("path_symlink resolved = {:?}", resolved);
|
log::debug!(
|
||||||
|
"path_symlink (new_dirfd, new_path) = ({:?}, {:?})",
|
||||||
|
new_dirfd,
|
||||||
|
new_path
|
||||||
|
);
|
||||||
|
|
||||||
unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path())? };
|
unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path)? };
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
pub(crate) fn rename(
|
||||||
|
old_dirfd: &OsFile,
|
||||||
|
old_path: &str,
|
||||||
|
new_dirfd: &OsFile,
|
||||||
|
new_path: &str,
|
||||||
|
) -> Result<()> {
|
||||||
use yanix::file::renameat;
|
use yanix::file::renameat;
|
||||||
match (resolved_old.dirfd(), resolved_new.dirfd()) {
|
|
||||||
(Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
renameat(
|
renameat(
|
||||||
resolved_old_file.as_raw_fd(),
|
old_dirfd.as_raw_fd(),
|
||||||
resolved_old.path(),
|
old_path,
|
||||||
resolved_new_file.as_raw_fd(),
|
new_dirfd.as_raw_fd(),
|
||||||
resolved_new.path(),
|
new_path,
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
unimplemented!("path_link with one or more virtual files");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pub(crate) mod clock;
|
pub(crate) mod clock;
|
||||||
pub(crate) mod entry;
|
|
||||||
pub(crate) mod fd;
|
pub(crate) mod fd;
|
||||||
|
pub(crate) mod oshandle;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
pub(crate) mod poll;
|
pub(crate) mod poll;
|
||||||
|
|
||||||
@@ -24,16 +24,13 @@ cfg_if::cfg_if! {
|
|||||||
|
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use sys_impl::O_RSYNC;
|
|
||||||
use yanix::clock::ClockId;
|
use yanix::clock::ClockId;
|
||||||
use yanix::file::{AtFlag, OFlag};
|
use yanix::file::{AtFlag, OFlag};
|
||||||
|
|
||||||
pub(crate) fn dev_null() -> io::Result<File> {
|
pub(crate) use sys_impl::*;
|
||||||
OpenOptions::new().read(true).write(true).open("/dev/null")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
|
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
|
||||||
File::open(path)
|
File::open(path)
|
||||||
|
|||||||
123
crates/wasi-common/src/sys/unix/oshandle.rs
Normal file
123
crates/wasi-common/src/sys/unix/oshandle.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use crate::entry::EntryRights;
|
||||||
|
use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt};
|
||||||
|
use crate::wasi::{types, RightsExt};
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, IntoRawFd, RawFd};
|
||||||
|
|
||||||
|
pub(crate) use super::sys_impl::osfile::*;
|
||||||
|
|
||||||
|
impl AsRawFd for OsHandle {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(file) => file.as_raw_fd(),
|
||||||
|
Self::Stdin => io::stdin().as_raw_fd(),
|
||||||
|
Self::Stdout => io::stdout().as_raw_fd(),
|
||||||
|
Self::Stderr => io::stderr().as_raw_fd(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFile for OsHandle {
|
||||||
|
fn as_file(&self) -> ManuallyDrop<File> {
|
||||||
|
let file = unsafe { File::from_raw_fd(self.as_raw_fd()) };
|
||||||
|
ManuallyDrop::new(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<File> for OsHandle {
|
||||||
|
fn from(file: File) -> Self {
|
||||||
|
Self::from(unsafe { OsFile::from_raw_fd(file.into_raw_fd()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OsHandleExt for OsHandle {
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||||
|
let file = self.as_file();
|
||||||
|
let ft = file.metadata()?.file_type();
|
||||||
|
let file_type = if ft.is_block_device() {
|
||||||
|
log::debug!("Host fd {:?} is a block device", self.as_raw_fd());
|
||||||
|
types::Filetype::BlockDevice
|
||||||
|
} else if ft.is_char_device() {
|
||||||
|
log::debug!("Host fd {:?} is a char device", self.as_raw_fd());
|
||||||
|
types::Filetype::CharacterDevice
|
||||||
|
} else if ft.is_dir() {
|
||||||
|
log::debug!("Host fd {:?} is a directory", self.as_raw_fd());
|
||||||
|
types::Filetype::Directory
|
||||||
|
} else if ft.is_file() {
|
||||||
|
log::debug!("Host fd {:?} is a file", self.as_raw_fd());
|
||||||
|
types::Filetype::RegularFile
|
||||||
|
} else if ft.is_socket() {
|
||||||
|
log::debug!("Host fd {:?} is a socket", self.as_raw_fd());
|
||||||
|
use yanix::socket::{get_socket_type, SockType};
|
||||||
|
match unsafe { get_socket_type(self.as_raw_fd())? } {
|
||||||
|
SockType::Datagram => types::Filetype::SocketDgram,
|
||||||
|
SockType::Stream => types::Filetype::SocketStream,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||||
|
}
|
||||||
|
} else if ft.is_fifo() {
|
||||||
|
log::debug!("Host fd {:?} is a fifo", self.as_raw_fd());
|
||||||
|
types::Filetype::Unknown
|
||||||
|
} else {
|
||||||
|
log::debug!("Host fd {:?} is unknown", self.as_raw_fd());
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(file_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rights(&self, file_type: types::Filetype) -> io::Result<EntryRights> {
|
||||||
|
use yanix::{fcntl, file::OFlag};
|
||||||
|
let (base, inheriting) = match file_type {
|
||||||
|
types::Filetype::BlockDevice => (
|
||||||
|
types::Rights::block_device_base(),
|
||||||
|
types::Rights::block_device_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::CharacterDevice => {
|
||||||
|
use yanix::file::isatty;
|
||||||
|
if unsafe { isatty(self.as_raw_fd())? } {
|
||||||
|
(types::Rights::tty_base(), types::Rights::tty_base())
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
types::Rights::character_device_base(),
|
||||||
|
types::Rights::character_device_inheriting(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
types::Filetype::Directory => (
|
||||||
|
types::Rights::directory_base(),
|
||||||
|
types::Rights::directory_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::RegularFile => (
|
||||||
|
types::Rights::regular_file_base(),
|
||||||
|
types::Rights::regular_file_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::SocketDgram | types::Filetype::SocketStream => (
|
||||||
|
types::Rights::socket_base(),
|
||||||
|
types::Rights::socket_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::SymbolicLink | types::Filetype::Unknown => (
|
||||||
|
types::Rights::regular_file_base(),
|
||||||
|
types::Rights::regular_file_inheriting(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let mut rights = EntryRights::new(base, inheriting);
|
||||||
|
let flags = unsafe { fcntl::get_status_flags(self.as_raw_fd())? };
|
||||||
|
let accmode = flags & OFlag::ACCMODE;
|
||||||
|
if accmode == OFlag::RDONLY {
|
||||||
|
rights.base &= !types::Rights::FD_WRITE;
|
||||||
|
} else if accmode == OFlag::WRONLY {
|
||||||
|
rights.base &= !types::Rights::FD_READ;
|
||||||
|
}
|
||||||
|
Ok(rights)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_null() -> io::Result<Self> {
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open("/dev/null")?;
|
||||||
|
Ok(Self::from(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
use crate::entry::{Descriptor, EntryRights};
|
use super::oshandle::OsFile;
|
||||||
use crate::path::PathGet;
|
use crate::entry::EntryRights;
|
||||||
use crate::sys::entry::OsHandle;
|
use crate::sys::oshandle::OsHandle;
|
||||||
use crate::sys::unix::sys_impl;
|
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt};
|
use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt};
|
||||||
use std::str;
|
use std::str;
|
||||||
use yanix::file::OFlag;
|
use yanix::file::OFlag;
|
||||||
|
|
||||||
pub(crate) use sys_impl::path::*;
|
pub(crate) use super::sys_impl::path::*;
|
||||||
|
|
||||||
/// Creates owned WASI path from OS string.
|
/// Creates owned WASI path from OS string.
|
||||||
///
|
///
|
||||||
@@ -44,32 +41,14 @@ pub(crate) fn open_rights(
|
|||||||
if fdflags.contains(OFlag::DSYNC) {
|
if fdflags.contains(OFlag::DSYNC) {
|
||||||
needed_inheriting |= types::Rights::FD_DATASYNC;
|
needed_inheriting |= types::Rights::FD_DATASYNC;
|
||||||
}
|
}
|
||||||
if fdflags.intersects(sys_impl::O_RSYNC | OFlag::SYNC) {
|
if fdflags.intersects(super::O_RSYNC | OFlag::SYNC) {
|
||||||
needed_inheriting |= types::Rights::FD_SYNC;
|
needed_inheriting |= types::Rights::FD_SYNC;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntryRights::new(needed_base, needed_inheriting)
|
EntryRights::new(needed_base, needed_inheriting)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
|
pub(crate) fn readlinkat(dirfd: &OsFile, path: &str) -> Result<String> {
|
||||||
use std::os::unix::prelude::{AsRawFd, FromRawFd};
|
|
||||||
use yanix::file::{openat, Mode};
|
|
||||||
|
|
||||||
log::debug!("path_get openat path = {:?}", path);
|
|
||||||
|
|
||||||
let raw_fd = unsafe {
|
|
||||||
openat(
|
|
||||||
dirfd.as_raw_fd(),
|
|
||||||
path,
|
|
||||||
OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW,
|
|
||||||
Mode::empty(),
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
let file = unsafe { File::from_raw_fd(raw_fd) };
|
|
||||||
Ok(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result<String> {
|
|
||||||
use std::os::unix::prelude::AsRawFd;
|
use std::os::unix::prelude::AsRawFd;
|
||||||
use yanix::file::readlinkat;
|
use yanix::file::readlinkat;
|
||||||
|
|
||||||
@@ -80,15 +59,17 @@ pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result<String> {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_directory(base: &File, path: &str) -> Result<()> {
|
pub(crate) fn create_directory(base: &OsFile, path: &str) -> Result<()> {
|
||||||
use yanix::file::{mkdirat, Mode};
|
use yanix::file::{mkdirat, Mode};
|
||||||
unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? };
|
unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? };
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn link(
|
pub(crate) fn link(
|
||||||
resolved_old: PathGet,
|
old_dirfd: &OsFile,
|
||||||
resolved_new: PathGet,
|
old_path: &str,
|
||||||
|
new_dirfd: &OsFile,
|
||||||
|
new_path: &str,
|
||||||
follow_symlinks: bool,
|
follow_symlinks: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use yanix::file::{linkat, AtFlag};
|
use yanix::file::{linkat, AtFlag};
|
||||||
@@ -99,10 +80,10 @@ pub(crate) fn link(
|
|||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
linkat(
|
linkat(
|
||||||
resolved_old.dirfd().as_raw_fd(),
|
old_dirfd.as_raw_fd(),
|
||||||
resolved_old.path(),
|
old_path,
|
||||||
resolved_new.dirfd().as_raw_fd(),
|
new_dirfd.as_raw_fd(),
|
||||||
resolved_new.path(),
|
new_path,
|
||||||
flags,
|
flags,
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
@@ -110,12 +91,13 @@ pub(crate) fn link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn open(
|
pub(crate) fn open(
|
||||||
resolved: PathGet,
|
dirfd: &OsFile,
|
||||||
|
path: &str,
|
||||||
read: bool,
|
read: bool,
|
||||||
write: bool,
|
write: bool,
|
||||||
oflags: types::Oflags,
|
oflags: types::Oflags,
|
||||||
fs_flags: types::Fdflags,
|
fs_flags: types::Fdflags,
|
||||||
) -> Result<Descriptor> {
|
) -> Result<OsHandle> {
|
||||||
use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag};
|
use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag};
|
||||||
|
|
||||||
let mut nix_all_oflags = if read && write {
|
let mut nix_all_oflags = if read && write {
|
||||||
@@ -139,13 +121,14 @@ pub(crate) fn open(
|
|||||||
// umask is, but don't set the executable flag, because it isn't yet
|
// umask is, but don't set the executable flag, because it isn't yet
|
||||||
// meaningful for WASI programs to create executable files.
|
// meaningful for WASI programs to create executable files.
|
||||||
|
|
||||||
log::debug!("path_open resolved = {:?}", resolved);
|
log::debug!("path_open dirfd = {:?}", dirfd);
|
||||||
|
log::debug!("path_open path = {:?}", path);
|
||||||
log::debug!("path_open oflags = {:?}", nix_all_oflags);
|
log::debug!("path_open oflags = {:?}", nix_all_oflags);
|
||||||
|
|
||||||
let fd_no = unsafe {
|
let fd_no = unsafe {
|
||||||
openat(
|
openat(
|
||||||
resolved.dirfd().as_raw_fd(),
|
dirfd.as_raw_fd(),
|
||||||
resolved.path(),
|
path,
|
||||||
nix_all_oflags,
|
nix_all_oflags,
|
||||||
Mode::from_bits_truncate(0o666),
|
Mode::from_bits_truncate(0o666),
|
||||||
)
|
)
|
||||||
@@ -156,13 +139,7 @@ pub(crate) fn open(
|
|||||||
match e.raw_os_error().unwrap() {
|
match e.raw_os_error().unwrap() {
|
||||||
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
|
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
|
||||||
libc::ENXIO => {
|
libc::ENXIO => {
|
||||||
match unsafe {
|
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } {
|
||||||
fstatat(
|
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
resolved.path(),
|
|
||||||
AtFlag::SYMLINK_NOFOLLOW,
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
Ok(stat) => {
|
Ok(stat) => {
|
||||||
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket {
|
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket {
|
||||||
return Err(Errno::Notsup);
|
return Err(Errno::Notsup);
|
||||||
@@ -178,13 +155,7 @@ pub(crate) fn open(
|
|||||||
libc::ENOTDIR
|
libc::ENOTDIR
|
||||||
if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() =>
|
if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() =>
|
||||||
{
|
{
|
||||||
match unsafe {
|
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } {
|
||||||
fstatat(
|
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
resolved.path(),
|
|
||||||
AtFlag::SYMLINK_NOFOLLOW,
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
Ok(stat) => {
|
Ok(stat) => {
|
||||||
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink {
|
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink {
|
||||||
return Err(Errno::Loop);
|
return Err(Errno::Loop);
|
||||||
@@ -210,13 +181,13 @@ pub(crate) fn open(
|
|||||||
log::debug!("path_open (host) new_fd = {:?}", new_fd);
|
log::debug!("path_open (host) new_fd = {:?}", new_fd);
|
||||||
|
|
||||||
// Determine the type of the new file descriptor and which rights contradict with this type
|
// Determine the type of the new file descriptor and which rights contradict with this type
|
||||||
Ok(OsHandle::from(unsafe { File::from_raw_fd(new_fd) }).into())
|
Ok(OsHandle::from(unsafe { OsFile::from_raw_fd(new_fd) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result<usize> {
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use yanix::file::readlinkat;
|
use yanix::file::readlinkat;
|
||||||
let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path())? };
|
let read_link = unsafe { readlinkat(dirfd.as_raw_fd(), path)? };
|
||||||
let read_link = from_host(read_link)?;
|
let read_link = from_host(read_link)?;
|
||||||
let copy_len = min(read_link.len(), buf.len());
|
let copy_len = min(read_link.len(), buf.len());
|
||||||
if copy_len > 0 {
|
if copy_len > 0 {
|
||||||
@@ -225,73 +196,8 @@ pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
|||||||
Ok(copy_len)
|
Ok(copy_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn filestat_get(
|
pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||||
resolved: PathGet,
|
|
||||||
dirflags: types::Lookupflags,
|
|
||||||
) -> Result<types::Filestat> {
|
|
||||||
use yanix::file::fstatat;
|
|
||||||
let atflags = dirflags.into();
|
|
||||||
let filestat = unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags)? };
|
|
||||||
let filestat = filestat.try_into()?;
|
|
||||||
Ok(filestat)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn filestat_set_times(
|
|
||||||
resolved: PathGet,
|
|
||||||
dirflags: types::Lookupflags,
|
|
||||||
st_atim: types::Timestamp,
|
|
||||||
st_mtim: types::Timestamp,
|
|
||||||
fst_flags: types::Fstflags,
|
|
||||||
) -> Result<()> {
|
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
|
||||||
use yanix::filetime::*;
|
|
||||||
|
|
||||||
let set_atim = fst_flags.contains(&types::Fstflags::ATIM);
|
|
||||||
let set_atim_now = fst_flags.contains(&types::Fstflags::ATIM_NOW);
|
|
||||||
let set_mtim = fst_flags.contains(&types::Fstflags::MTIM);
|
|
||||||
let set_mtim_now = fst_flags.contains(&types::Fstflags::MTIM_NOW);
|
|
||||||
|
|
||||||
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
|
|
||||||
return Err(Errno::Inval);
|
|
||||||
}
|
|
||||||
|
|
||||||
let symlink_nofollow = types::Lookupflags::SYMLINK_FOLLOW != dirflags;
|
|
||||||
let atim = if set_atim {
|
|
||||||
let time = UNIX_EPOCH + Duration::from_nanos(st_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(st_mtim);
|
|
||||||
FileTime::FileTime(filetime::FileTime::from_system_time(time))
|
|
||||||
} else if set_mtim_now {
|
|
||||||
FileTime::Now
|
|
||||||
} else {
|
|
||||||
FileTime::Omit
|
|
||||||
};
|
|
||||||
|
|
||||||
utimensat(
|
|
||||||
&resolved.dirfd().as_os_handle(),
|
|
||||||
resolved.path(),
|
|
||||||
atim,
|
|
||||||
mtim,
|
|
||||||
symlink_nofollow,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> {
|
|
||||||
use yanix::file::{unlinkat, AtFlag};
|
use yanix::file::{unlinkat, AtFlag};
|
||||||
|
unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::REMOVEDIR)? };
|
||||||
unsafe {
|
|
||||||
unlinkat(
|
|
||||||
resolved.dirfd().as_raw_fd(),
|
|
||||||
resolved.path(),
|
|
||||||
AtFlag::REMOVEDIR,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
use super::super::oshandle::OsHandle;
|
||||||
use crate::poll::{ClockEventData, FdEventData};
|
use crate::poll::{ClockEventData, FdEventData};
|
||||||
|
use crate::sys::oshandle::AsFile;
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use std::io;
|
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(
|
pub(crate) fn oneoff(
|
||||||
timeout: Option<ClockEventData>,
|
timeout: Option<ClockEventData>,
|
||||||
fd_events: Vec<FdEventData>,
|
fd_events: Vec<FdEventData>,
|
||||||
events: &mut Vec<types::Event>,
|
events: &mut Vec<types::Event>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
|
||||||
use yanix::poll::{poll, PollFd, PollFlags};
|
|
||||||
|
|
||||||
if fd_events.is_empty() && timeout.is_none() {
|
if fd_events.is_empty() && timeout.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -26,7 +28,12 @@ pub(crate) fn oneoff(
|
|||||||
// events we filtered before. If we get something else here, the code has a serious bug.
|
// events we filtered before. If we get something else here, the code has a serious bug.
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
unsafe { PollFd::new(event.descriptor.borrow().as_raw_fd(), flags) }
|
let handle = event
|
||||||
|
.handle
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<OsHandle>()
|
||||||
|
.expect("can poll FdEvent for OS resources only");
|
||||||
|
unsafe { PollFd::new(handle.as_raw_fd(), flags) }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -73,26 +80,22 @@ fn handle_fd_event(
|
|||||||
ready_events: impl Iterator<Item = (FdEventData, yanix::poll::PollFd)>,
|
ready_events: impl Iterator<Item = (FdEventData, yanix::poll::PollFd)>,
|
||||||
events: &mut Vec<types::Event>,
|
events: &mut Vec<types::Event>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use crate::entry::Descriptor;
|
fn query_nbytes(handle: &OsHandle) -> Result<u64> {
|
||||||
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
|
||||||
use yanix::{file::fionread, poll::PollFlags};
|
|
||||||
|
|
||||||
fn query_nbytes(fd: &Descriptor) -> Result<u64> {
|
|
||||||
// fionread may overflow for large files, so use another way for regular files.
|
// fionread may overflow for large files, so use another way for regular files.
|
||||||
if let Descriptor::OsHandle(os_handle) = fd {
|
if let OsHandle::OsFile(file) = handle {
|
||||||
let meta = os_handle.metadata()?;
|
let meta = file.as_file().metadata()?;
|
||||||
if meta.file_type().is_file() {
|
if meta.file_type().is_file() {
|
||||||
use yanix::file::tell;
|
use yanix::file::tell;
|
||||||
let len = meta.len();
|
let len = meta.len();
|
||||||
let host_offset = unsafe { tell(os_handle.as_raw_fd())? };
|
let host_offset = unsafe { tell(file.as_raw_fd())? };
|
||||||
return Ok(len - host_offset);
|
return Ok(len - host_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsafe { Ok(fionread(fd.as_raw_fd())?.into()) }
|
unsafe { Ok(fionread(handle.as_raw_fd())?.into()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
for (fd_event, poll_fd) in ready_events {
|
for (fd_event, poll_fd) in ready_events {
|
||||||
log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event);
|
// log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event);
|
||||||
log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd);
|
log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd);
|
||||||
|
|
||||||
let revents = match poll_fd.revents() {
|
let revents = match poll_fd.revents() {
|
||||||
@@ -103,7 +106,12 @@ fn handle_fd_event(
|
|||||||
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
|
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
|
||||||
|
|
||||||
let nbytes = if fd_event.r#type == types::Eventtype::FdRead {
|
let nbytes = if fd_event.r#type == types::Eventtype::FdRead {
|
||||||
query_nbytes(&fd_event.descriptor.borrow())?
|
let handle = fd_event
|
||||||
|
.handle
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<OsHandle>()
|
||||||
|
.expect("can poll FdEvent for OS resources only");
|
||||||
|
query_nbytes(handle)?
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
use crate::entry::{Descriptor, EntryRights, OsHandleRef};
|
|
||||||
use crate::wasi::{types, RightsExt};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct OsHandle(File);
|
|
||||||
|
|
||||||
impl From<File> for OsHandle {
|
|
||||||
fn from(file: File) -> Self {
|
|
||||||
Self(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawHandle for OsHandle {
|
|
||||||
fn as_raw_handle(&self) -> RawHandle {
|
|
||||||
self.0.as_raw_handle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for OsHandle {
|
|
||||||
type Target = File;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawHandle for Descriptor {
|
|
||||||
fn as_raw_handle(&self) -> RawHandle {
|
|
||||||
match self {
|
|
||||||
Self::OsHandle(file) => file.as_raw_handle(),
|
|
||||||
Self::VirtualFile(_file) => {
|
|
||||||
unimplemented!("virtual as_raw_handle");
|
|
||||||
}
|
|
||||||
Self::Stdin => io::stdin().as_raw_handle(),
|
|
||||||
Self::Stdout => io::stdout().as_raw_handle(),
|
|
||||||
Self::Stderr => io::stderr().as_raw_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn descriptor_as_oshandle<'lifetime>(
|
|
||||||
desc: &'lifetime Descriptor,
|
|
||||||
) -> OsHandleRef<'lifetime> {
|
|
||||||
OsHandleRef::new(ManuallyDrop::new(OsHandle::from(unsafe {
|
|
||||||
File::from_raw_handle(desc.as_raw_handle())
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the set of all possible rights that are both relevant for the file
|
|
||||||
/// type and consistent with the open mode.
|
|
||||||
///
|
|
||||||
/// This function is unsafe because it operates on a raw file descriptor.
|
|
||||||
pub(crate) unsafe fn determine_type_and_access_rights<Handle: AsRawHandle>(
|
|
||||||
handle: &Handle,
|
|
||||||
) -> io::Result<(types::Filetype, EntryRights)> {
|
|
||||||
use winx::file::{query_access_information, AccessMode};
|
|
||||||
|
|
||||||
let (file_type, mut rights) = determine_type_rights(handle)?;
|
|
||||||
|
|
||||||
match file_type {
|
|
||||||
types::Filetype::Directory | types::Filetype::RegularFile => {
|
|
||||||
let mode = query_access_information(handle.as_raw_handle())?;
|
|
||||||
if mode.contains(AccessMode::FILE_GENERIC_READ) {
|
|
||||||
rights.base |= types::Rights::FD_READ;
|
|
||||||
}
|
|
||||||
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
|
|
||||||
rights.base |= types::Rights::FD_WRITE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// TODO: is there a way around this? On windows, it seems
|
|
||||||
// we cannot check access rights for anything but dirs and regular files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((file_type, rights))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the set of all possible rights that are relevant for file type.
|
|
||||||
///
|
|
||||||
/// This function is unsafe because it operates on a raw file descriptor.
|
|
||||||
pub(crate) unsafe fn determine_type_rights<Handle: AsRawHandle>(
|
|
||||||
handle: &Handle,
|
|
||||||
) -> io::Result<(types::Filetype, EntryRights)> {
|
|
||||||
let (file_type, rights) = {
|
|
||||||
let file_type = winx::file::get_file_type(handle.as_raw_handle())?;
|
|
||||||
let (file_type, base, inheriting) = if file_type.is_char() {
|
|
||||||
// character file: LPT device or console
|
|
||||||
// TODO: rule out LPT device
|
|
||||||
(
|
|
||||||
types::Filetype::CharacterDevice,
|
|
||||||
types::Rights::tty_base(),
|
|
||||||
types::Rights::tty_base(),
|
|
||||||
)
|
|
||||||
} else if file_type.is_disk() {
|
|
||||||
// disk file: file, dir or disk device
|
|
||||||
let file = std::mem::ManuallyDrop::new(File::from_raw_handle(handle.as_raw_handle()));
|
|
||||||
let meta = file.metadata()?;
|
|
||||||
if meta.is_dir() {
|
|
||||||
(
|
|
||||||
types::Filetype::Directory,
|
|
||||||
types::Rights::directory_base(),
|
|
||||||
types::Rights::directory_inheriting(),
|
|
||||||
)
|
|
||||||
} else if meta.is_file() {
|
|
||||||
(
|
|
||||||
types::Filetype::RegularFile,
|
|
||||||
types::Rights::regular_file_base(),
|
|
||||||
types::Rights::regular_file_inheriting(),
|
|
||||||
)
|
|
||||||
} 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?
|
|
||||||
(
|
|
||||||
types::Filetype::SocketStream,
|
|
||||||
types::Rights::socket_base(),
|
|
||||||
types::Rights::socket_inheriting(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
|
||||||
};
|
|
||||||
(file_type, EntryRights::new(base, inheriting))
|
|
||||||
};
|
|
||||||
Ok((file_type, rights))
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::file_serial_no;
|
use super::file_serial_no;
|
||||||
|
use super::oshandle::OsFile;
|
||||||
use crate::path;
|
use crate::path;
|
||||||
use crate::sys::entry::OsHandle;
|
use crate::sys::oshandle::AsFile;
|
||||||
use crate::wasi::{types, Result};
|
use crate::wasi::{types, Result};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
@@ -10,9 +11,9 @@ use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use winx::file::{AccessMode, FileModeInformation, Flags};
|
use winx::file::{AccessMode, FileModeInformation, Flags};
|
||||||
|
|
||||||
pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
|
pub(crate) fn fdstat_get(file: &File) -> Result<types::Fdflags> {
|
||||||
let mut fdflags = types::Fdflags::empty();
|
let mut fdflags = types::Fdflags::empty();
|
||||||
let handle = fd.as_raw_handle();
|
let handle = file.as_raw_handle();
|
||||||
let access_mode = winx::file::query_access_information(handle)?;
|
let access_mode = winx::file::query_access_information(handle)?;
|
||||||
let mode = winx::file::query_mode_information(handle)?;
|
let mode = winx::file::query_mode_information(handle)?;
|
||||||
|
|
||||||
@@ -34,27 +35,30 @@ pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
|
|||||||
Ok(fdflags)
|
Ok(fdflags)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Option<OsHandle>> {
|
// TODO Investigate further for Stdio handles. `ReOpenFile` requires the file
|
||||||
let handle = fd.as_raw_handle();
|
// 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: types::Fdflags) -> Result<Option<OsFile>> {
|
||||||
|
let handle = file.as_raw_handle();
|
||||||
let access_mode = winx::file::query_access_information(handle)?;
|
let access_mode = winx::file::query_access_information(handle)?;
|
||||||
|
|
||||||
let new_access_mode = file_access_mode_from_fdflags(
|
let new_access_mode = file_access_mode_from_fdflags(
|
||||||
fdflags,
|
fdflags,
|
||||||
access_mode.contains(AccessMode::FILE_READ_DATA),
|
access_mode.contains(AccessMode::FILE_READ_DATA),
|
||||||
access_mode.contains(AccessMode::FILE_WRITE_DATA)
|
access_mode.contains(AccessMode::FILE_WRITE_DATA)
|
||||||
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
|
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
|
||||||
);
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
Ok(Some(OsHandle::from(File::from_raw_handle(
|
Ok(Some(OsFile::from_raw_handle(winx::file::reopen_file(
|
||||||
winx::file::reopen_file(handle, new_access_mode, fdflags.into())?,
|
handle,
|
||||||
))))
|
new_access_mode,
|
||||||
|
fdflags.into(),
|
||||||
|
)?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn advise(
|
pub(crate) fn advise(
|
||||||
_file: &File,
|
_file: &OsFile,
|
||||||
_advice: types::Advice,
|
_advice: types::Advice,
|
||||||
_offset: types::Filesize,
|
_offset: types::Filesize,
|
||||||
_len: types::Filesize,
|
_len: types::Filesize,
|
||||||
@@ -116,13 +120,13 @@ fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: boo
|
|||||||
// .. gets cookie = 2
|
// .. gets cookie = 2
|
||||||
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
|
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
|
||||||
pub(crate) fn readdir(
|
pub(crate) fn readdir(
|
||||||
fd: &File,
|
file: &OsFile,
|
||||||
cookie: types::Dircookie,
|
cookie: types::Dircookie,
|
||||||
) -> Result<impl Iterator<Item = Result<(types::Dirent, String)>>> {
|
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
|
||||||
use winx::file::get_file_path;
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
let cookie = cookie.try_into()?;
|
let cookie = cookie.try_into()?;
|
||||||
let path = get_file_path(fd)?;
|
let path = get_file_path(&file.as_file())?;
|
||||||
// std::fs::ReadDir doesn't return . and .., so we need to emulate it
|
// std::fs::ReadDir doesn't return . and .., so we need to emulate it
|
||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
// The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too
|
// The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too
|
||||||
@@ -155,7 +159,7 @@ pub(crate) fn readdir(
|
|||||||
// small host_buf, but this is difficult to implement efficiently.
|
// small host_buf, but this is difficult to implement efficiently.
|
||||||
//
|
//
|
||||||
// See https://github.com/WebAssembly/WASI/issues/61 for more details.
|
// See https://github.com/WebAssembly/WASI/issues/61 for more details.
|
||||||
Ok(iter.skip(cookie))
|
Ok(Box::new(iter.skip(cookie)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dirent_from_path<P: AsRef<Path>>(
|
fn dirent_from_path<P: AsRef<Path>>(
|
||||||
@@ -182,7 +186,7 @@ fn dirent_from_path<P: AsRef<Path>>(
|
|||||||
Ok((dirent, name))
|
Ok((dirent, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn filestat_get(file: &std::fs::File) -> Result<types::Filestat> {
|
pub(crate) fn filestat_get(file: &File) -> Result<types::Filestat> {
|
||||||
let filestat = file.try_into()?;
|
let filestat = file.try_into()?;
|
||||||
Ok(filestat)
|
Ok(filestat)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
pub(crate) mod clock;
|
pub(crate) mod clock;
|
||||||
pub(crate) mod entry;
|
|
||||||
pub(crate) mod fd;
|
pub(crate) mod fd;
|
||||||
|
pub(crate) mod oshandle;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
pub(crate) mod poll;
|
pub(crate) mod poll;
|
||||||
|
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use std::{io, string};
|
use std::{io, string};
|
||||||
use winapi::shared::winerror;
|
use winapi::shared::winerror;
|
||||||
use winx::file::{CreationDisposition, Flags};
|
use winx::file::{CreationDisposition, Flags};
|
||||||
|
|
||||||
pub(crate) fn dev_null() -> io::Result<File> {
|
|
||||||
OpenOptions::new().read(true).write(true).open("NUL")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
|
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::os::windows::fs::OpenOptionsExt;
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
|
|||||||
169
crates/wasi-common/src/sys/windows/oshandle.rs
Normal file
169
crates/wasi-common/src/sys/windows/oshandle.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
use crate::entry::EntryRights;
|
||||||
|
use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt};
|
||||||
|
use crate::wasi::{types, RightsExt};
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct OsFile(Cell<RawHandle>);
|
||||||
|
|
||||||
|
impl OsFile {
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Clones `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())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for OsFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
File::from_raw_handle(self.as_raw_handle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawHandle for OsFile {
|
||||||
|
fn as_raw_handle(&self) -> RawHandle {
|
||||||
|
self.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRawHandle for OsFile {
|
||||||
|
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
|
||||||
|
Self(Cell::new(handle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoRawHandle for OsFile {
|
||||||
|
fn into_raw_handle(self) -> RawHandle {
|
||||||
|
// We need to prevent dropping of the OsFile
|
||||||
|
let wrapped = ManuallyDrop::new(self);
|
||||||
|
wrapped.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFile for OsFile {
|
||||||
|
fn as_file(&self) -> ManuallyDrop<File> {
|
||||||
|
let file = unsafe { File::from_raw_handle(self.0.get()) };
|
||||||
|
ManuallyDrop::new(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawHandle for OsHandle {
|
||||||
|
fn as_raw_handle(&self) -> RawHandle {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(file) => file.as_raw_handle(),
|
||||||
|
Self::Stdin => io::stdin().as_raw_handle(),
|
||||||
|
Self::Stdout => io::stdout().as_raw_handle(),
|
||||||
|
Self::Stderr => io::stderr().as_raw_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFile for OsHandle {
|
||||||
|
fn as_file(&self) -> ManuallyDrop<File> {
|
||||||
|
let file = unsafe { File::from_raw_handle(self.as_raw_handle()) };
|
||||||
|
ManuallyDrop::new(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<File> for OsHandle {
|
||||||
|
fn from(file: File) -> Self {
|
||||||
|
Self::from(unsafe { OsFile::from_raw_handle(file.into_raw_handle()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OsHandleExt for OsHandle {
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||||
|
let file_type = unsafe { winx::file::get_file_type(self.as_raw_handle())? };
|
||||||
|
let file_type = if file_type.is_char() {
|
||||||
|
// character file: LPT device or console
|
||||||
|
// TODO: rule out LPT device
|
||||||
|
types::Filetype::CharacterDevice
|
||||||
|
} else if file_type.is_disk() {
|
||||||
|
// disk file: file, dir or disk device
|
||||||
|
let file = self.as_file();
|
||||||
|
let meta = file.metadata()?;
|
||||||
|
if meta.is_dir() {
|
||||||
|
types::Filetype::Directory
|
||||||
|
} else if meta.is_file() {
|
||||||
|
types::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?
|
||||||
|
types::Filetype::SocketStream
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||||
|
};
|
||||||
|
Ok(file_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rights(&self, file_type: types::Filetype) -> io::Result<EntryRights> {
|
||||||
|
use winx::file::{query_access_information, AccessMode};
|
||||||
|
let (base, inheriting) = match file_type {
|
||||||
|
types::Filetype::BlockDevice => (
|
||||||
|
types::Rights::block_device_base(),
|
||||||
|
types::Rights::block_device_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::CharacterDevice => {
|
||||||
|
(types::Rights::tty_base(), types::Rights::tty_base())
|
||||||
|
}
|
||||||
|
types::Filetype::Directory => (
|
||||||
|
types::Rights::directory_base(),
|
||||||
|
types::Rights::directory_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::RegularFile => (
|
||||||
|
types::Rights::regular_file_base(),
|
||||||
|
types::Rights::regular_file_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::SocketDgram | types::Filetype::SocketStream => (
|
||||||
|
types::Rights::socket_base(),
|
||||||
|
types::Rights::socket_inheriting(),
|
||||||
|
),
|
||||||
|
types::Filetype::SymbolicLink | types::Filetype::Unknown => (
|
||||||
|
types::Rights::regular_file_base(),
|
||||||
|
types::Rights::regular_file_inheriting(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let mut rights = EntryRights::new(base, inheriting);
|
||||||
|
match file_type {
|
||||||
|
types::Filetype::Directory | types::Filetype::RegularFile => {
|
||||||
|
let mode = query_access_information(self.as_raw_handle())?;
|
||||||
|
if mode.contains(AccessMode::FILE_GENERIC_READ) {
|
||||||
|
rights.base |= types::Rights::FD_READ;
|
||||||
|
}
|
||||||
|
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
|
||||||
|
rights.base |= types::Rights::FD_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO: is there a way around this? On windows, it seems
|
||||||
|
// we cannot check access rights for anything but dirs and regular files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(rights)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_null() -> io::Result<Self> {
|
||||||
|
let file = OpenOptions::new().read(true).write(true).open("NUL")?;
|
||||||
|
Ok(Self::from(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,83 @@
|
|||||||
use crate::entry::{Descriptor, EntryRights};
|
use super::oshandle::OsFile;
|
||||||
use crate::fd;
|
use crate::entry::EntryRights;
|
||||||
use crate::path::PathGet;
|
use crate::sys::oshandle::{AsFile, OsHandle};
|
||||||
use crate::sys::entry::OsHandle;
|
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use log::debug;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs::{File, Metadata, OpenOptions};
|
use std::fs::{self, Metadata, OpenOptions};
|
||||||
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||||
use std::os::windows::fs::OpenOptionsExt;
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use winapi::shared::winerror;
|
use winapi::shared::winerror;
|
||||||
use winx::file::AccessMode;
|
use winx::file::AccessMode;
|
||||||
|
|
||||||
|
fn strip_trailing_slashes_and_concatenate(dirfd: &OsFile, 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: &OsFile, 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(Errno::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));
|
||||||
|
|
||||||
|
log::debug!("out_path={:?}", out_path);
|
||||||
|
|
||||||
|
Ok(out_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_access_mode_from_fdflags(fdflags: types::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(&types::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.
|
/// Creates owned WASI path from OS string.
|
||||||
///
|
///
|
||||||
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
|
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
|
||||||
@@ -23,24 +88,6 @@ pub(crate) fn from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
|
|||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait PathGetExt {
|
|
||||||
fn concatenate(&self) -> Result<PathBuf>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathGetExt for PathGet {
|
|
||||||
fn concatenate(&self) -> Result<PathBuf> {
|
|
||||||
match self.dirfd() {
|
|
||||||
Descriptor::OsHandle(file) => concatenate(file, Path::new(self.path())),
|
|
||||||
Descriptor::VirtualFile(_virt) => {
|
|
||||||
panic!("concatenate on a virtual base");
|
|
||||||
}
|
|
||||||
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
|
|
||||||
unreachable!("streams do not have paths and should not be accessible via PathGet");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn open_rights(
|
pub(crate) fn open_rights(
|
||||||
input_rights: &EntryRights,
|
input_rights: &EntryRights,
|
||||||
oflags: types::Oflags,
|
oflags: types::Oflags,
|
||||||
@@ -69,30 +116,7 @@ pub(crate) fn open_rights(
|
|||||||
EntryRights::new(needed_base, needed_inheriting)
|
EntryRights::new(needed_base, needed_inheriting)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
|
pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result<String> {
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::os::windows::fs::OpenOptionsExt;
|
|
||||||
use winx::file::Flags;
|
|
||||||
|
|
||||||
let path = concatenate(dirfd, Path::new(path))?;
|
|
||||||
let err = match OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
|
|
||||||
.open(&path)
|
|
||||||
{
|
|
||||||
Ok(file) => return Ok(file),
|
|
||||||
Err(e) => e,
|
|
||||||
};
|
|
||||||
if let Some(code) = err.raw_os_error() {
|
|
||||||
log::debug!("openat error={:?}", code);
|
|
||||||
if code as u32 == winerror::ERROR_INVALID_NAME {
|
|
||||||
return Err(Errno::Notdir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
|
|
||||||
use winx::file::get_file_path;
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
let path = concatenate(dirfd, Path::new(s_path))?;
|
let path = concatenate(dirfd, Path::new(s_path))?;
|
||||||
@@ -102,7 +126,7 @@ pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
|
|||||||
// we need to strip the prefix from the absolute path
|
// we need to strip the prefix from the absolute path
|
||||||
// as otherwise we will error out since WASI is not capable
|
// as otherwise we will error out since WASI is not capable
|
||||||
// of dealing with absolute paths
|
// of dealing with absolute paths
|
||||||
let dir_path = get_file_path(dirfd)?;
|
let dir_path = get_file_path(&dirfd.as_file())?;
|
||||||
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
||||||
let target_path = target_path
|
let target_path = target_path
|
||||||
.strip_prefix(dir_path)
|
.strip_prefix(dir_path)
|
||||||
@@ -127,54 +151,25 @@ pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
|
|||||||
Err(err.into())
|
Err(err.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
|
pub(crate) fn create_directory(file: &OsFile, path: &str) -> Result<()> {
|
||||||
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: &File, path: P) -> Result<PathBuf> {
|
|
||||||
use winx::file::get_file_path;
|
|
||||||
|
|
||||||
// WASI is not able to deal with absolute paths
|
|
||||||
// so error out if absolute
|
|
||||||
if path.as_ref().is_absolute() {
|
|
||||||
return Err(Errno::Notcapable);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dir_path = get_file_path(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));
|
|
||||||
|
|
||||||
log::debug!("out_path={:?}", out_path);
|
|
||||||
|
|
||||||
Ok(out_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn create_directory(file: &File, path: &str) -> Result<()> {
|
|
||||||
let path = concatenate(file, path)?;
|
let path = concatenate(file, path)?;
|
||||||
std::fs::create_dir(&path)?;
|
std::fs::create_dir(&path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn link(
|
pub(crate) fn link(
|
||||||
resolved_old: PathGet,
|
old_dirfd: &OsFile,
|
||||||
resolved_new: PathGet,
|
old_path: &str,
|
||||||
|
new_dirfd: &OsFile,
|
||||||
|
new_path: &str,
|
||||||
follow_symlinks: bool,
|
follow_symlinks: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
let mut old_path = resolved_old.concatenate()?;
|
let mut old_path = concatenate(old_dirfd, old_path)?;
|
||||||
let new_path = resolved_new.concatenate()?;
|
let new_path = concatenate(new_dirfd, new_path)?;
|
||||||
if follow_symlinks {
|
if follow_symlinks {
|
||||||
// in particular, this will return an error if the target path doesn't exist
|
// in particular, this will return an error if the target path doesn't exist
|
||||||
debug!("Following symlinks for path: {:?}", old_path);
|
log::debug!("Following symlinks for path: {:?}", old_path);
|
||||||
old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() {
|
old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() {
|
||||||
// fs::canonicalize under Windows will return:
|
// fs::canonicalize under Windows will return:
|
||||||
// * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink
|
// * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink
|
||||||
@@ -183,12 +178,13 @@ pub(crate) fn link(
|
|||||||
_ => e.into(),
|
_ => e.into(),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
fs::hard_link(&old_path, &new_path).or_else(|err| {
|
let err = match fs::hard_link(&old_path, &new_path) {
|
||||||
match err.raw_os_error() {
|
Ok(()) => return Ok(()),
|
||||||
Some(code) => {
|
Err(e) => e,
|
||||||
debug!("path_link at fs::hard_link error code={:?}", code);
|
};
|
||||||
match code as u32 {
|
if let Some(code) = err.raw_os_error() {
|
||||||
winerror::ERROR_ACCESS_DENIED => {
|
log::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
|
// 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
|
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
|
||||||
// to `EACCES`. We detect and correct this case here.
|
// to `EACCES`. We detect and correct this case here.
|
||||||
@@ -196,26 +192,18 @@ pub(crate) fn link(
|
|||||||
return Err(Errno::Perm);
|
return Err(Errno::Perm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(err.into())
|
Err(err.into())
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
log::debug!("Inconvertible OS error: {}", err);
|
|
||||||
Err(Errno::Io)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn open(
|
pub(crate) fn open(
|
||||||
resolved: PathGet,
|
dirfd: &OsFile,
|
||||||
|
path: &str,
|
||||||
read: bool,
|
read: bool,
|
||||||
write: bool,
|
write: bool,
|
||||||
oflags: types::Oflags,
|
oflags: types::Oflags,
|
||||||
fdflags: types::Fdflags,
|
fdflags: types::Fdflags,
|
||||||
) -> Result<Descriptor> {
|
) -> Result<OsHandle> {
|
||||||
use winx::file::{AccessMode, CreationDisposition, Flags};
|
use winx::file::{AccessMode, CreationDisposition, Flags};
|
||||||
|
|
||||||
let is_trunc = oflags.contains(&types::Oflags::TRUNC);
|
let is_trunc = oflags.contains(&types::Oflags::TRUNC);
|
||||||
@@ -245,9 +233,7 @@ pub(crate) fn open(
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
let path = concatenate(dirfd, path)?;
|
||||||
let path = resolved.concatenate()?;
|
|
||||||
|
|
||||||
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
|
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
|
||||||
Ok(file_type) => {
|
Ok(file_type) => {
|
||||||
// check if we are trying to open a symlink
|
// check if we are trying to open a symlink
|
||||||
@@ -262,13 +248,19 @@ pub(crate) fn open(
|
|||||||
Err(err) => match err.raw_os_error() {
|
Err(err) => match err.raw_os_error() {
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
log::debug!("path_open at symlink_metadata error code={:?}", code);
|
log::debug!("path_open at symlink_metadata error code={:?}", code);
|
||||||
|
match code as u32 {
|
||||||
if code as u32 != winerror::ERROR_FILE_NOT_FOUND {
|
winerror::ERROR_FILE_NOT_FOUND => {
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
// file not found, let it proceed to actually
|
// file not found, let it proceed to actually
|
||||||
// trying to open it
|
// 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(Errno::Notdir);
|
||||||
|
}
|
||||||
|
_ => return Err(err.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
log::debug!("Inconvertible OS error: {}", err);
|
log::debug!("Inconvertible OS error: {}", err);
|
||||||
return Err(Errno::Io);
|
return Err(Errno::Io);
|
||||||
@@ -284,49 +276,25 @@ pub(crate) fn open(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let flags: Flags = fdflags.into();
|
let flags: Flags = fdflags.into();
|
||||||
opts.access_mode(access_mode.bits())
|
let file = opts
|
||||||
|
.access_mode(access_mode.bits())
|
||||||
.custom_flags(flags.bits())
|
.custom_flags(flags.bits())
|
||||||
.open(&path)
|
.open(&path)?;
|
||||||
.map(|f| OsHandle::from(f).into())
|
let handle = OsHandle::from(file);
|
||||||
.map_err(Into::into)
|
Ok(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode {
|
pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result<usize> {
|
||||||
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(&types::Fdflags::APPEND) {
|
|
||||||
access_mode.insert(AccessMode::FILE_APPEND_DATA);
|
|
||||||
access_mode.remove(AccessMode::FILE_WRITE_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
access_mode
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
|
||||||
use winx::file::get_file_path;
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
let path = resolved.concatenate()?;
|
let path = concatenate(dirfd, path)?;
|
||||||
let target_path = path.read_link()?;
|
let target_path = path.read_link()?;
|
||||||
|
|
||||||
// since on Windows we are effectively emulating 'at' syscalls
|
// since on Windows we are effectively emulating 'at' syscalls
|
||||||
// we need to strip the prefix from the absolute path
|
// we need to strip the prefix from the absolute path
|
||||||
// as otherwise we will error out since WASI is not capable
|
// as otherwise we will error out since WASI is not capable
|
||||||
// of dealing with absolute paths
|
// of dealing with absolute paths
|
||||||
let dir_path = get_file_path(&resolved.dirfd().as_os_handle())?;
|
let dir_path = get_file_path(&dirfd.as_file())?;
|
||||||
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
||||||
let target_path = target_path
|
let target_path = target_path
|
||||||
.strip_prefix(dir_path)
|
.strip_prefix(dir_path)
|
||||||
@@ -353,20 +321,16 @@ pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result<Option<PathBuf>> {
|
pub(crate) fn rename(
|
||||||
if resolved.path().ends_with('/') {
|
old_dirfd: &OsFile,
|
||||||
let suffix = resolved.path().trim_end_matches('/');
|
old_path_: &str,
|
||||||
concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some)
|
new_dirfd: &OsFile,
|
||||||
} else {
|
new_path_: &str,
|
||||||
Ok(None)
|
) -> Result<()> {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
let old_path = resolved_old.concatenate()?;
|
let old_path = concatenate(old_dirfd, old_path_)?;
|
||||||
let new_path = resolved_new.concatenate()?;
|
let new_path = concatenate(new_dirfd, new_path_)?;
|
||||||
|
|
||||||
// First sanity check: check we're not trying to rename dir to file or vice versa.
|
// 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].
|
// NB on Windows, the former is actually permitted [std::fs::rename].
|
||||||
@@ -377,7 +341,7 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
|||||||
}
|
}
|
||||||
// Second sanity check: check we're not trying to rename a file into a path
|
// Second sanity check: check we're not trying to rename a file into a path
|
||||||
// ending in a trailing slash.
|
// ending in a trailing slash.
|
||||||
if old_path.is_file() && resolved_new.path().ends_with('/') {
|
if old_path.is_file() && new_path_.ends_with('/') {
|
||||||
return Err(Errno::Notdir);
|
return Err(Errno::Notdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +370,9 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
|||||||
winerror::ERROR_INVALID_NAME => {
|
winerror::ERROR_INVALID_NAME => {
|
||||||
// If source contains trailing slashes, check if we are dealing with
|
// If source contains trailing slashes, check if we are dealing with
|
||||||
// a file instead of a dir, and if so, throw ENOTDIR.
|
// a file instead of a dir, and if so, throw ENOTDIR.
|
||||||
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? {
|
if let Some(path) =
|
||||||
|
strip_trailing_slashes_and_concatenate(old_dirfd, old_path_)?
|
||||||
|
{
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
return Err(Errno::Notdir);
|
return Err(Errno::Notdir);
|
||||||
}
|
}
|
||||||
@@ -424,38 +390,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn filestat_get(
|
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path_: &str) -> Result<()> {
|
||||||
resolved: PathGet,
|
|
||||||
_dirflags: types::Lookupflags,
|
|
||||||
) -> Result<types::Filestat> {
|
|
||||||
let path = resolved.concatenate()?;
|
|
||||||
let file = File::open(path)?;
|
|
||||||
let filestat = (&file).try_into()?;
|
|
||||||
Ok(filestat)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn filestat_set_times(
|
|
||||||
resolved: PathGet,
|
|
||||||
_dirflags: types::Lookupflags,
|
|
||||||
st_atim: types::Timestamp,
|
|
||||||
st_mtim: types::Timestamp,
|
|
||||||
fst_flags: types::Fstflags,
|
|
||||||
) -> Result<()> {
|
|
||||||
use winx::file::AccessMode;
|
|
||||||
let path = resolved.concatenate()?;
|
|
||||||
let file = OpenOptions::new()
|
|
||||||
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
|
|
||||||
.open(path)?;
|
|
||||||
let modifiable_fd = Descriptor::OsHandle(OsHandle::from(file));
|
|
||||||
fd::filestat_set_times_impl(&modifiable_fd, st_atim, st_mtim, fst_flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
|
||||||
use std::fs;
|
|
||||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||||
|
|
||||||
let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?;
|
let old_path = concatenate(new_dirfd, Path::new(old_path))?;
|
||||||
let new_path = resolved.concatenate()?;
|
let new_path = concatenate(new_dirfd, new_path_)?;
|
||||||
|
|
||||||
// Windows distinguishes between file and directory symlinks.
|
// Windows distinguishes between file and directory symlinks.
|
||||||
// If the source doesn't exist or is an exotic file type, we fall back
|
// If the source doesn't exist or is an exotic file type, we fall back
|
||||||
@@ -488,7 +427,9 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
|||||||
//
|
//
|
||||||
// Since POSIX will return EEXIST in such case, we simulate this behavior
|
// Since POSIX will return EEXIST in such case, we simulate this behavior
|
||||||
winerror::ERROR_INVALID_NAME => {
|
winerror::ERROR_INVALID_NAME => {
|
||||||
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? {
|
if let Some(path) =
|
||||||
|
strip_trailing_slashes_and_concatenate(new_dirfd, new_path_)?
|
||||||
|
{
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Err(Errno::Exist);
|
return Err(Errno::Exist);
|
||||||
}
|
}
|
||||||
@@ -506,10 +447,10 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
|
pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
let path = resolved.concatenate()?;
|
let path = concatenate(dirfd, path)?;
|
||||||
let file_type = path
|
let file_type = path
|
||||||
.symlink_metadata()
|
.symlink_metadata()
|
||||||
.map(|metadata| metadata.file_type())?;
|
.map(|metadata| metadata.file_type())?;
|
||||||
@@ -548,7 +489,7 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> {
|
pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||||
let path = resolved.concatenate()?;
|
let path = concatenate(dirfd, path)?;
|
||||||
std::fs::remove_dir(&path).map_err(Into::into)
|
std::fs::remove_dir(&path).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::entry::Descriptor;
|
use super::super::oshandle::OsHandle;
|
||||||
use crate::poll::{ClockEventData, FdEventData};
|
use crate::poll::{ClockEventData, FdEventData};
|
||||||
|
use crate::sys::oshandle::AsFile;
|
||||||
use crate::wasi::{types, Errno, Result};
|
use crate::wasi::{types, Errno, Result};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::{debug, error, trace, warn};
|
use log::{debug, error, trace, warn};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::os::windows::io::AsRawHandle;
|
use std::os::windows::io::AsRawHandle;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
|
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@@ -141,10 +141,18 @@ fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec<types::E
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_rw_event(event: FdEventData, out_events: &mut Vec<types::Event>) {
|
fn handle_rw_event(event: FdEventData, out_events: &mut Vec<types::Event>) {
|
||||||
let size = match &*event.descriptor.borrow() {
|
let handle = event
|
||||||
Descriptor::OsHandle(os_handle) => {
|
.handle
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<OsHandle>()
|
||||||
|
.expect("can poll FdEvent for OS resources only");
|
||||||
|
let size = match handle {
|
||||||
|
OsHandle::OsFile(file) => {
|
||||||
if event.r#type == types::Eventtype::FdRead {
|
if event.r#type == types::Eventtype::FdRead {
|
||||||
os_handle.metadata().map(|m| m.len()).map_err(Into::into)
|
file.as_file()
|
||||||
|
.metadata()
|
||||||
|
.map(|m| m.len())
|
||||||
|
.map_err(Into::into)
|
||||||
} else {
|
} else {
|
||||||
// The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and
|
// 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
|
// the implementation on Unix just returns 0 here, so it's probably fine
|
||||||
@@ -154,12 +162,9 @@ fn handle_rw_event(event: FdEventData, out_events: &mut Vec<types::Event>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We return the only universally correct lower bound, see the comment later in the function.
|
// We return the only universally correct lower bound, see the comment later in the function.
|
||||||
Descriptor::Stdin => Ok(1),
|
OsHandle::Stdin => Ok(1),
|
||||||
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
|
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
|
||||||
Descriptor::Stdout | Descriptor::Stderr => Ok(0),
|
OsHandle::Stdout | OsHandle::Stderr => Ok(0),
|
||||||
Descriptor::VirtualFile(_) => {
|
|
||||||
panic!("virtual files do not get rw events");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_event = make_rw_event(&event, size);
|
let new_event = make_rw_event(&event, size);
|
||||||
@@ -201,21 +206,21 @@ pub(crate) fn oneoff(
|
|||||||
let mut pipe_events = vec![];
|
let mut pipe_events = vec![];
|
||||||
|
|
||||||
for event in fd_events {
|
for event in fd_events {
|
||||||
let descriptor = Rc::clone(&event.descriptor);
|
let handle = event
|
||||||
match &*descriptor.borrow() {
|
.handle
|
||||||
Descriptor::Stdin if event.r#type == types::Eventtype::FdRead => {
|
.as_any()
|
||||||
stdin_events.push(event)
|
.downcast_ref::<OsHandle>()
|
||||||
}
|
.expect("can poll FdEvent for OS resources only");
|
||||||
|
match handle {
|
||||||
|
OsHandle::Stdin if event.r#type == types::Eventtype::FdRead => stdin_events.push(event),
|
||||||
// stdout/stderr are always considered ready to write because there seems to
|
// stdout/stderr are always considered ready to write because there seems to
|
||||||
// be no way of checking if a write to stdout would block.
|
// 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
|
// If stdin is polled for anything else then reading, then it is also
|
||||||
// considered immediately ready, following the behavior on Linux.
|
// considered immediately ready, following the behavior on Linux.
|
||||||
Descriptor::Stdin | Descriptor::Stderr | Descriptor::Stdout => {
|
OsHandle::Stdin | OsHandle::Stderr | OsHandle::Stdout => immediate_events.push(event),
|
||||||
immediate_events.push(event)
|
OsHandle::OsFile(file) => {
|
||||||
}
|
let ftype = unsafe { winx::file::get_file_type(file.as_raw_handle()) }?;
|
||||||
Descriptor::OsHandle(os_handle) => {
|
|
||||||
let ftype = unsafe { winx::file::get_file_type(os_handle.as_raw_handle()) }?;
|
|
||||||
if ftype.is_unknown() || ftype.is_char() {
|
if ftype.is_unknown() || ftype.is_char() {
|
||||||
debug!("poll_oneoff: unsupported file type: {:?}", ftype);
|
debug!("poll_oneoff: unsupported file type: {:?}", ftype);
|
||||||
handle_error_event(event, Errno::Notsup, events);
|
handle_error_event(event, Errno::Notsup, events);
|
||||||
@@ -227,9 +232,6 @@ pub(crate) fn oneoff(
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Descriptor::VirtualFile(_) => {
|
|
||||||
panic!("virtual files do not get rw events");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use crate::entry::EntryRights;
|
||||||
|
use crate::handle::Handle;
|
||||||
use crate::wasi::{self, types, Errno, Result, RightsExt};
|
use crate::wasi::{self, types, Errno, Result, RightsExt};
|
||||||
use filetime::FileTime;
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
use std::any::Any;
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -22,132 +24,10 @@ impl VirtualDirEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Files and directories may be moved, and for implementation reasons retain a reference to their
|
/// Files and directories may be moved, and for implementation reasons retain a reference to their
|
||||||
/// parent VirtualFile, so files that can be moved must provide an interface to update their parent
|
/// parent Handle, so files that can be moved must provide an interface to update their parent
|
||||||
/// reference.
|
/// reference.
|
||||||
pub(crate) trait MovableFile {
|
pub(crate) trait MovableFile {
|
||||||
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>);
|
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>);
|
||||||
}
|
|
||||||
|
|
||||||
/// `VirtualFile` encompasses the whole interface of a `File`, `Directory`, or `Stream`-like
|
|
||||||
/// object, suitable for forwarding from `wasi-common` public interfaces. `File` and
|
|
||||||
/// `Directory`-style objects can be moved, so implemetors of this trait must also implement
|
|
||||||
/// `MovableFile`.
|
|
||||||
///
|
|
||||||
/// Default implementations of functions here fail in ways that are intended to mimic a file-like
|
|
||||||
/// object with no permissions, no content, and that cannot be used in any way.
|
|
||||||
// TODO This trait should potentially be made unsafe since we need to assert that we don't
|
|
||||||
// reenter wasm or try to reborrow/read/etc. from wasm memory.
|
|
||||||
pub(crate) trait VirtualFile: MovableFile {
|
|
||||||
fn fdstat_get(&self) -> types::Fdflags {
|
|
||||||
types::Fdflags::empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>>;
|
|
||||||
|
|
||||||
fn readlinkat(&self, _path: &Path) -> Result<String> {
|
|
||||||
Err(Errno::Acces)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn openat(
|
|
||||||
&self,
|
|
||||||
_path: &Path,
|
|
||||||
_read: bool,
|
|
||||||
_write: bool,
|
|
||||||
_oflags: types::Oflags,
|
|
||||||
_fd_flags: types::Fdflags,
|
|
||||||
) -> Result<Box<dyn VirtualFile>> {
|
|
||||||
Err(Errno::Acces)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_directory(&self, _path: &str) -> Result<()> {
|
|
||||||
Err(Errno::Acces)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unlink_file(&self, _path: &str) -> Result<()> {
|
|
||||||
Err(Errno::Acces)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn datasync(&self) -> Result<()> {
|
|
||||||
Err(Errno::Inval)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync(&self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_directory(&self, _path: &Path) -> Result<()> {
|
|
||||||
Err(Errno::Acces)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readdir(
|
|
||||||
&self,
|
|
||||||
_cookie: types::Dircookie,
|
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result<usize> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result<usize> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result<usize> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seek(&self, _offset: SeekFrom) -> Result<u64> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advise(
|
|
||||||
&self,
|
|
||||||
_advice: types::Advice,
|
|
||||||
_offset: types::Filesize,
|
|
||||||
_len: types::Filesize,
|
|
||||||
) -> Result<()> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filestat_get(&self) -> Result<types::Filestat> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filestat_set_times(&self, _atim: Option<FileTime>, _mtim: Option<FileTime>) -> Result<()> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fdstat_set_flags(&self, _fdflags: types::Fdflags) -> Result<Option<Box<dyn VirtualFile>>> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_file_type(&self) -> types::Filetype;
|
|
||||||
|
|
||||||
fn is_directory(&self) -> bool {
|
|
||||||
self.get_file_type() == types::Filetype::Directory
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rights_base(&self) -> types::Rights {
|
|
||||||
types::Rights::empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rights_inheriting(&self) -> types::Rights {
|
|
||||||
types::Rights::empty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileContents {
|
pub trait FileContents {
|
||||||
@@ -255,7 +135,7 @@ impl VecFileContents {
|
|||||||
/// of data and permissions on a filesystem.
|
/// of data and permissions on a filesystem.
|
||||||
pub struct InMemoryFile {
|
pub struct InMemoryFile {
|
||||||
cursor: Cell<types::Filesize>,
|
cursor: Cell<types::Filesize>,
|
||||||
parent: Rc<RefCell<Option<Box<dyn VirtualFile>>>>,
|
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
|
||||||
fd_flags: Cell<types::Fdflags>,
|
fd_flags: Cell<types::Fdflags>,
|
||||||
data: Rc<RefCell<Box<dyn FileContents>>>,
|
data: Rc<RefCell<Box<dyn FileContents>>>,
|
||||||
}
|
}
|
||||||
@@ -281,17 +161,16 @@ impl InMemoryFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MovableFile for InMemoryFile {
|
impl MovableFile for InMemoryFile {
|
||||||
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>) {
|
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>) {
|
||||||
*self.parent.borrow_mut() = new_parent;
|
*self.parent.borrow_mut() = new_parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualFile for InMemoryFile {
|
impl Handle for InMemoryFile {
|
||||||
fn fdstat_get(&self) -> types::Fdflags {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self.fd_flags.get()
|
self
|
||||||
}
|
}
|
||||||
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
||||||
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>> {
|
|
||||||
Ok(Box::new(Self {
|
Ok(Box::new(Self {
|
||||||
cursor: Cell::new(0),
|
cursor: Cell::new(0),
|
||||||
fd_flags: self.fd_flags.clone(),
|
fd_flags: self.fd_flags.clone(),
|
||||||
@@ -299,68 +178,113 @@ impl VirtualFile for InMemoryFile {
|
|||||||
data: Rc::clone(&self.data),
|
data: Rc::clone(&self.data),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||||
fn readlinkat(&self, _path: &Path) -> Result<String> {
|
Ok(types::Filetype::RegularFile)
|
||||||
// no symlink support, so always say it's invalid.
|
|
||||||
Err(Errno::Notdir)
|
|
||||||
}
|
}
|
||||||
|
fn get_rights(&self) -> io::Result<EntryRights> {
|
||||||
fn openat(
|
Ok(EntryRights::new(
|
||||||
|
types::Rights::regular_file_base(),
|
||||||
|
types::Rights::regular_file_inheriting(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
// FdOps
|
||||||
|
fn advise(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
_advice: types::Advice,
|
||||||
read: bool,
|
_offset: types::Filesize,
|
||||||
write: bool,
|
_len: types::Filesize,
|
||||||
oflags: types::Oflags,
|
) -> Result<()> {
|
||||||
fd_flags: types::Fdflags,
|
// we'll just ignore advice for now, unless it's totally invalid
|
||||||
) -> Result<Box<dyn VirtualFile>> {
|
Ok(())
|
||||||
log::trace!(
|
}
|
||||||
"InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
|
fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> {
|
||||||
path,
|
let new_limit = offset.checked_add(len).ok_or(Errno::Fbig)?;
|
||||||
read,
|
let mut data = self.data.borrow_mut();
|
||||||
write,
|
|
||||||
oflags,
|
|
||||||
fd_flags
|
|
||||||
);
|
|
||||||
|
|
||||||
if oflags.contains(&types::Oflags::DIRECTORY) {
|
if new_limit > data.max_size() {
|
||||||
log::trace!(
|
return Err(Errno::Fbig);
|
||||||
"InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.",
|
|
||||||
path
|
|
||||||
);
|
|
||||||
log::trace!(" return Notdir");
|
|
||||||
return Err(Errno::Notdir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == Path::new(".") {
|
if new_limit > data.size() {
|
||||||
return self.try_clone().map_err(Into::into);
|
data.resize(new_limit)?;
|
||||||
} else if path == Path::new("..") {
|
|
||||||
match &*self.parent.borrow() {
|
|
||||||
Some(file) => file.try_clone().map_err(Into::into),
|
|
||||||
None => self.try_clone().map_err(Into::into),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Errno::Acces)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_directory(&self, _path: &str) -> Result<()> {
|
Ok(())
|
||||||
Err(Errno::Notdir)
|
|
||||||
}
|
}
|
||||||
|
fn fdstat_get(&self) -> Result<types::Fdflags> {
|
||||||
fn unlink_file(&self, _path: &str) -> Result<()> {
|
Ok(self.fd_flags.get())
|
||||||
Err(Errno::Notdir)
|
|
||||||
}
|
}
|
||||||
|
fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> {
|
||||||
fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<Option<Box<dyn VirtualFile>>> {
|
|
||||||
self.fd_flags.set(fdflags);
|
self.fd_flags.set(fdflags);
|
||||||
// We return None here to signal that the operation succeeded on the original
|
Ok(())
|
||||||
// file descriptor and mutating the original WASI Descriptor is thus unnecessary.
|
}
|
||||||
// This is needed as on Windows this operation required reopening a file. So we're
|
fn filestat_get(&self) -> Result<types::Filestat> {
|
||||||
// adhering to the common signature required across platforms.
|
let stat = types::Filestat {
|
||||||
Ok(None)
|
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: types::Filesize) -> Result<()> {
|
||||||
|
let mut data = self.data.borrow_mut();
|
||||||
|
if st_size > data.max_size() {
|
||||||
|
return Err(Errno::Fbig);
|
||||||
|
}
|
||||||
|
data.resize(st_size)
|
||||||
|
}
|
||||||
|
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: types::Filesize) -> Result<usize> {
|
||||||
|
self.data.borrow_mut().preadv(buf, offset)
|
||||||
|
}
|
||||||
|
fn pwritev(&self, buf: &[io::IoSlice], offset: types::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());
|
||||||
|
self.data.borrow_mut().preadv(iovs, self.cursor.get())
|
||||||
|
}
|
||||||
|
fn seek(&self, offset: SeekFrom) -> Result<types::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(Errno::Inval)?
|
||||||
|
} else {
|
||||||
|
self.cursor
|
||||||
|
.get()
|
||||||
|
.checked_add(offset as u64)
|
||||||
|
.ok_or(Errno::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(|_| Errno::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(|_| Errno::Inval)?;
|
||||||
|
self.cursor.set(std::cmp::min(content_len, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.cursor.get())
|
||||||
|
}
|
||||||
|
fn write_vectored(&self, iovs: &[io::IoSlice], isatty: bool) -> Result<usize> {
|
||||||
|
if isatty {
|
||||||
|
unimplemented!("writes to virtual tty");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
|
|
||||||
trace!("write_vectored(iovs={:?})", iovs);
|
trace!("write_vectored(iovs={:?})", iovs);
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
|
|
||||||
@@ -406,110 +330,73 @@ impl VirtualFile for InMemoryFile {
|
|||||||
|
|
||||||
Ok(written)
|
Ok(written)
|
||||||
}
|
}
|
||||||
|
// PathOps
|
||||||
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
|
fn create_directory(&self, _path: &str) -> Result<()> {
|
||||||
trace!("read_vectored(iovs={:?})", iovs);
|
Err(Errno::Notdir)
|
||||||
trace!(" | *read_start={:?}", self.cursor.get());
|
|
||||||
self.data.borrow_mut().preadv(iovs, self.cursor.get())
|
|
||||||
}
|
}
|
||||||
|
fn openat(
|
||||||
fn preadv(&self, buf: &mut [io::IoSliceMut], offset: types::Filesize) -> Result<usize> {
|
|
||||||
self.data.borrow_mut().preadv(buf, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pwritev(&self, buf: &[io::IoSlice], offset: types::Filesize) -> Result<usize> {
|
|
||||||
self.data.borrow_mut().pwritev(buf, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seek(&self, offset: SeekFrom) -> Result<types::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(Errno::Inval)?
|
|
||||||
} else {
|
|
||||||
self.cursor
|
|
||||||
.get()
|
|
||||||
.checked_add(offset as u64)
|
|
||||||
.ok_or(Errno::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(|_| Errno::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(|_| Errno::Inval)?;
|
|
||||||
self.cursor.set(std::cmp::min(content_len, offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.cursor.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advise(
|
|
||||||
&self,
|
&self,
|
||||||
_advice: types::Advice,
|
path: &str,
|
||||||
_offset: types::Filesize,
|
read: bool,
|
||||||
_len: types::Filesize,
|
write: bool,
|
||||||
|
oflags: types::Oflags,
|
||||||
|
fd_flags: types::Fdflags,
|
||||||
|
) -> Result<Box<dyn Handle>> {
|
||||||
|
log::trace!(
|
||||||
|
"InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
|
||||||
|
path,
|
||||||
|
read,
|
||||||
|
write,
|
||||||
|
oflags,
|
||||||
|
fd_flags
|
||||||
|
);
|
||||||
|
|
||||||
|
if oflags.contains(&types::Oflags::DIRECTORY) {
|
||||||
|
log::trace!(
|
||||||
|
"InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.",
|
||||||
|
path
|
||||||
|
);
|
||||||
|
log::trace!(" return Notdir");
|
||||||
|
return Err(Errno::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(Errno::Acces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn link(
|
||||||
|
&self,
|
||||||
|
_old_path: &str,
|
||||||
|
_new_handle: Box<dyn Handle>,
|
||||||
|
_new_path: &str,
|
||||||
|
_follow: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// we'll just ignore advice for now, unless it's totally invalid
|
Err(Errno::Notdir)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
|
||||||
fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> {
|
Err(Errno::Notdir)
|
||||||
let new_limit = offset.checked_add(len).ok_or(Errno::Fbig)?;
|
|
||||||
let mut data = self.data.borrow_mut();
|
|
||||||
|
|
||||||
if new_limit > data.max_size() {
|
|
||||||
return Err(Errno::Fbig);
|
|
||||||
}
|
}
|
||||||
|
fn readlinkat(&self, _path: &str) -> Result<String> {
|
||||||
if new_limit > data.size() {
|
Err(Errno::Notdir)
|
||||||
data.resize(new_limit)?;
|
|
||||||
}
|
}
|
||||||
|
fn rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
|
||||||
Ok(())
|
Err(Errno::Notdir)
|
||||||
}
|
}
|
||||||
|
fn remove_directory(&self, _path: &str) -> Result<()> {
|
||||||
fn filestat_set_size(&self, st_size: types::Filesize) -> Result<()> {
|
Err(Errno::Notdir)
|
||||||
let mut data = self.data.borrow_mut();
|
|
||||||
if st_size > data.max_size() {
|
|
||||||
return Err(Errno::Fbig);
|
|
||||||
}
|
}
|
||||||
data.resize(st_size)
|
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
|
||||||
|
Err(Errno::Notdir)
|
||||||
}
|
}
|
||||||
|
fn unlink_file(&self, _path: &str) -> Result<()> {
|
||||||
fn filestat_get(&self) -> Result<types::Filestat> {
|
Err(Errno::Notdir)
|
||||||
let stat = types::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 get_file_type(&self) -> types::Filetype {
|
|
||||||
types::Filetype::RegularFile
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rights_base(&self) -> types::Rights {
|
|
||||||
types::Rights::regular_file_base()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rights_inheriting(&self) -> types::Rights {
|
|
||||||
types::Rights::regular_file_inheriting()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,8 +405,8 @@ pub struct VirtualDir {
|
|||||||
writable: bool,
|
writable: bool,
|
||||||
// All copies of this `VirtualDir` must share `parent`, and changes in one copy's `parent`
|
// 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`.
|
// must be reflected in all handles, so they share `Rc` of an underlying `parent`.
|
||||||
parent: Rc<RefCell<Option<Box<dyn VirtualFile>>>>,
|
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
|
||||||
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn VirtualFile>>>>,
|
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDir {
|
impl VirtualDir {
|
||||||
@@ -563,7 +450,7 @@ impl VirtualDir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MovableFile for VirtualDir {
|
impl MovableFile for VirtualDir {
|
||||||
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>) {
|
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>) {
|
||||||
*self.parent.borrow_mut() = new_parent;
|
*self.parent.borrow_mut() = new_parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,200 +462,47 @@ const PARENT_DIR_COOKIE: u32 = 1;
|
|||||||
// that would wrap and be mapped to the same dir cookies as `self` or `parent`.
|
// that would wrap and be mapped to the same dir cookies as `self` or `parent`.
|
||||||
const RESERVED_ENTRY_COUNT: u32 = 2;
|
const RESERVED_ENTRY_COUNT: u32 = 2;
|
||||||
|
|
||||||
impl VirtualFile for VirtualDir {
|
impl Handle for VirtualDir {
|
||||||
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>> {
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
||||||
Ok(Box::new(Self {
|
Ok(Box::new(Self {
|
||||||
writable: self.writable,
|
writable: self.writable,
|
||||||
parent: Rc::clone(&self.parent),
|
parent: Rc::clone(&self.parent),
|
||||||
entries: Rc::clone(&self.entries),
|
entries: Rc::clone(&self.entries),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||||
fn readlinkat(&self, _path: &Path) -> Result<String> {
|
Ok(types::Filetype::Directory)
|
||||||
// Files are not symbolic links or directories, faithfully report Notdir.
|
|
||||||
Err(Errno::Notdir)
|
|
||||||
}
|
}
|
||||||
|
fn get_rights(&self) -> io::Result<EntryRights> {
|
||||||
fn openat(
|
Ok(EntryRights::new(
|
||||||
&self,
|
types::Rights::directory_base(),
|
||||||
path: &Path,
|
types::Rights::directory_inheriting(),
|
||||||
read: bool,
|
))
|
||||||
write: bool,
|
|
||||||
oflags: types::Oflags,
|
|
||||||
fd_flags: types::Fdflags,
|
|
||||||
) -> Result<Box<dyn VirtualFile>> {
|
|
||||||
log::trace!(
|
|
||||||
"VirtualDir::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
|
|
||||||
path,
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
oflags,
|
|
||||||
fd_flags
|
|
||||||
);
|
|
||||||
|
|
||||||
if path == Path::new(".") {
|
|
||||||
return self.try_clone().map_err(Into::into);
|
|
||||||
} else if path == Path::new("..") {
|
|
||||||
match &*self.parent.borrow() {
|
|
||||||
Some(file) => {
|
|
||||||
return file.try_clone().map_err(Into::into);
|
|
||||||
}
|
}
|
||||||
None => {
|
// FdOps
|
||||||
return self.try_clone().map_err(Into::into);
|
fn filestat_get(&self) -> Result<types::Filestat> {
|
||||||
|
let stat = types::Filestat {
|
||||||
|
dev: 0,
|
||||||
|
ino: 0,
|
||||||
|
nlink: 0,
|
||||||
|
size: 0,
|
||||||
|
atim: 0,
|
||||||
|
ctim: 0,
|
||||||
|
mtim: 0,
|
||||||
|
filetype: self.get_file_type()?,
|
||||||
|
};
|
||||||
|
Ok(stat)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.file_name().ok_or(Errno::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 = types::Oflags::CREAT | types::Oflags::EXCL;
|
|
||||||
if (oflags & creat_excl_mask) == creat_excl_mask {
|
|
||||||
log::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name);
|
|
||||||
log::trace!(" return Exist");
|
|
||||||
return Err(Errno::Exist);
|
|
||||||
}
|
|
||||||
|
|
||||||
if oflags.contains(&types::Oflags::DIRECTORY)
|
|
||||||
&& e.get().get_file_type() != types::Filetype::Directory
|
|
||||||
{
|
|
||||||
log::trace!(
|
|
||||||
"VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.",
|
|
||||||
file_name
|
|
||||||
);
|
|
||||||
log::trace!(" return Notdir");
|
|
||||||
return Err(Errno::Notdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.get().try_clone().map_err(Into::into)
|
|
||||||
}
|
|
||||||
Entry::Vacant(v) => {
|
|
||||||
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(Errno::Nospc);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!(
|
|
||||||
"VirtualDir::openat creating an InMemoryFile named {}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
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(Errno::Acces)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() != types::Filetype::Directory {
|
|
||||||
return Err(Errno::Notdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okay, but is the directory empty?
|
|
||||||
let iter = e.get().readdir(wasi::DIRCOOKIE_START)?;
|
|
||||||
if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() {
|
|
||||||
return Err(Errno::Notempty);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alright, it's an empty directory. We can remove it.
|
|
||||||
let removed = e.remove_entry();
|
|
||||||
|
|
||||||
// And sever the file's parent ref to avoid Rc cycles.
|
|
||||||
removed.1.set_parent(None);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Entry::Vacant(_) => {
|
|
||||||
log::trace!(
|
|
||||||
"VirtualDir::remove_directory failed to remove {}, no such entry",
|
|
||||||
trimmed_path
|
|
||||||
);
|
|
||||||
Err(Errno::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(Errno::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() == types::Filetype::Directory {
|
|
||||||
return Err(Errno::Isdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
let removed = e.remove_entry();
|
|
||||||
|
|
||||||
// Sever the file's parent ref to avoid Rc cycles.
|
|
||||||
removed.1.set_parent(None);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Entry::Vacant(_) => {
|
|
||||||
log::trace!(
|
|
||||||
"VirtualDir::unlink_file failed to remove {}, no such entry",
|
|
||||||
trimmed_path
|
|
||||||
);
|
|
||||||
Err(Errno::Noent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_directory(&self, path: &Path) -> Result<()> {
|
|
||||||
let mut entries = self.entries.borrow_mut();
|
|
||||||
match entries.entry(path.to_owned()) {
|
|
||||||
Entry::Occupied(_) => Err(Errno::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(Errno::Acces)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result<usize> {
|
|
||||||
Err(Errno::Badf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readdir(
|
fn readdir(
|
||||||
&self,
|
&self,
|
||||||
cookie: types::Dircookie,
|
cookie: types::Dircookie,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
|
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
|
||||||
struct VirtualDirIter {
|
struct VirtualDirIter {
|
||||||
start: u32,
|
start: u32,
|
||||||
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn VirtualFile>>>>,
|
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
|
||||||
}
|
}
|
||||||
impl Iterator for VirtualDirIter {
|
impl Iterator for VirtualDirIter {
|
||||||
type Item = Result<(types::Dirent, String)>;
|
type Item = Result<(types::Dirent, String)>;
|
||||||
@@ -821,7 +555,7 @@ impl VirtualFile for VirtualDir {
|
|||||||
let dirent = || -> Result<types::Dirent> {
|
let dirent = || -> Result<types::Dirent> {
|
||||||
let dirent = types::Dirent {
|
let dirent = types::Dirent {
|
||||||
d_namlen: name.len().try_into()?,
|
d_namlen: name.len().try_into()?,
|
||||||
d_type: file.get_file_type(),
|
d_type: file.get_file_type()?,
|
||||||
d_ino: 0,
|
d_ino: 0,
|
||||||
d_next: self.start as u64,
|
d_next: self.start as u64,
|
||||||
};
|
};
|
||||||
@@ -843,30 +577,185 @@ impl VirtualFile for VirtualDir {
|
|||||||
entries: Rc::clone(&self.entries),
|
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(Errno::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(Errno::Acces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn openat(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
read: bool,
|
||||||
|
write: bool,
|
||||||
|
oflags: types::Oflags,
|
||||||
|
fd_flags: types::Fdflags,
|
||||||
|
) -> Result<Box<dyn Handle>> {
|
||||||
|
log::trace!(
|
||||||
|
"VirtualDir::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
|
||||||
|
path,
|
||||||
|
read,
|
||||||
|
write,
|
||||||
|
oflags,
|
||||||
|
fd_flags
|
||||||
|
);
|
||||||
|
|
||||||
fn filestat_get(&self) -> Result<types::Filestat> {
|
if path == "." {
|
||||||
let stat = types::Filestat {
|
return self.try_clone().map_err(Into::into);
|
||||||
dev: 0,
|
} else if path == ".." {
|
||||||
ino: 0,
|
match &*self.parent.borrow() {
|
||||||
nlink: 0,
|
Some(file) => {
|
||||||
size: 0,
|
return file.try_clone().map_err(Into::into);
|
||||||
atim: 0,
|
}
|
||||||
ctim: 0,
|
None => {
|
||||||
mtim: 0,
|
return self.try_clone().map_err(Into::into);
|
||||||
filetype: self.get_file_type(),
|
}
|
||||||
};
|
}
|
||||||
Ok(stat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_file_type(&self) -> types::Filetype {
|
// openat may have been passed a path with a trailing slash, but files are mapped to paths
|
||||||
types::Filetype::Directory
|
// with trailing slashes normalized out.
|
||||||
|
let file_name = Path::new(path).file_name().ok_or(Errno::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 = types::Oflags::CREAT | types::Oflags::EXCL;
|
||||||
|
if (oflags & creat_excl_mask) == creat_excl_mask {
|
||||||
|
log::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name);
|
||||||
|
log::trace!(" return Exist");
|
||||||
|
return Err(Errno::Exist);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rights_base(&self) -> types::Rights {
|
if oflags.contains(&types::Oflags::DIRECTORY)
|
||||||
types::Rights::directory_base()
|
&& e.get().get_file_type()? != types::Filetype::Directory
|
||||||
|
{
|
||||||
|
log::trace!(
|
||||||
|
"VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.",
|
||||||
|
file_name
|
||||||
|
);
|
||||||
|
log::trace!(" return Notdir");
|
||||||
|
return Err(Errno::Notdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rights_inheriting(&self) -> types::Rights {
|
e.get().try_clone().map_err(Into::into)
|
||||||
types::Rights::directory_inheriting()
|
}
|
||||||
|
Entry::Vacant(v) => {
|
||||||
|
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(Errno::Nospc);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::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(Errno::Acces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn readlinkat(&self, _path: &str) -> Result<String> {
|
||||||
|
// Files are not symbolic links or directories, faithfully report Notdir.
|
||||||
|
Err(Errno::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()? != types::Filetype::Directory {
|
||||||
|
return Err(Errno::Notdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, but is the directory empty?
|
||||||
|
let iter = e.get().readdir(wasi::DIRCOOKIE_START)?;
|
||||||
|
if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() {
|
||||||
|
return Err(Errno::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(_) => {
|
||||||
|
log::trace!(
|
||||||
|
"VirtualDir::remove_directory failed to remove {}, no such entry",
|
||||||
|
trimmed_path
|
||||||
|
);
|
||||||
|
Err(Errno::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(Errno::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()? == types::Filetype::Directory {
|
||||||
|
return Err(Errno::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(_) => {
|
||||||
|
log::trace!(
|
||||||
|
"VirtualDir::unlink_file failed to remove {}, no such entry",
|
||||||
|
trimmed_path
|
||||||
|
);
|
||||||
|
Err(Errno::Noent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user