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]]
|
||||
name = "serde"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff"
|
||||
checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8"
|
||||
checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1762,9 +1762,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.50"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867"
|
||||
checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -1909,9 +1909,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termios"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625"
|
||||
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::entry::{Descriptor, Entry};
|
||||
use crate::entry::{Entry, EntryHandle};
|
||||
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::wasi::types;
|
||||
use crate::wasi::{Errno, Result};
|
||||
@@ -42,7 +43,7 @@ pub enum WasiCtxBuilderError {
|
||||
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
|
||||
|
||||
enum PendingEntry {
|
||||
Thunk(fn() -> io::Result<Entry>),
|
||||
Thunk(fn() -> io::Result<OsHandle>),
|
||||
File(File),
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ impl std::fmt::Debug for PendingEntry {
|
||||
Self::Thunk(f) => write!(
|
||||
fmt,
|
||||
"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),
|
||||
}
|
||||
@@ -109,7 +110,7 @@ pub struct WasiCtxBuilder {
|
||||
stdin: Option<PendingEntry>,
|
||||
stdout: Option<PendingEntry>,
|
||||
stderr: Option<PendingEntry>,
|
||||
preopens: Option<Vec<(PathBuf, Descriptor)>>,
|
||||
preopens: Option<Vec<(PathBuf, Box<dyn Handle>)>>,
|
||||
args: Option<Vec<PendingCString>>,
|
||||
env: Option<HashMap<PendingCString, PendingCString>>,
|
||||
}
|
||||
@@ -117,9 +118,9 @@ pub struct WasiCtxBuilder {
|
||||
impl WasiCtxBuilder {
|
||||
/// Builder for a new `WasiCtx`.
|
||||
pub fn new() -> Self {
|
||||
let stdin = Some(PendingEntry::Thunk(Entry::null));
|
||||
let stdout = Some(PendingEntry::Thunk(Entry::null));
|
||||
let stderr = Some(PendingEntry::Thunk(Entry::null));
|
||||
let stdin = Some(PendingEntry::Thunk(OsHandle::from_null));
|
||||
let stdout = Some(PendingEntry::Thunk(OsHandle::from_null));
|
||||
let stderr = Some(PendingEntry::Thunk(OsHandle::from_null));
|
||||
|
||||
Self {
|
||||
stdin,
|
||||
@@ -166,27 +167,27 @@ impl WasiCtxBuilder {
|
||||
|
||||
/// Inherit stdin from the host process.
|
||||
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
|
||||
}
|
||||
|
||||
/// Inherit stdout from the host process.
|
||||
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
|
||||
}
|
||||
|
||||
/// Inherit stdout from the host process.
|
||||
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
|
||||
}
|
||||
|
||||
/// Inherit the stdin, stdout, and stderr streams from the host process.
|
||||
pub fn inherit_stdio(&mut self) -> &mut Self {
|
||||
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
|
||||
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
|
||||
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
|
||||
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
|
||||
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
|
||||
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -250,7 +251,7 @@ impl WasiCtxBuilder {
|
||||
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
|
||||
self.preopens.as_mut().unwrap().push((
|
||||
guest_path.as_ref().to_owned(),
|
||||
Descriptor::OsHandle(OsHandle::from(dir)),
|
||||
Box::new(OsHandle::from(dir)),
|
||||
));
|
||||
self
|
||||
}
|
||||
@@ -287,7 +288,7 @@ impl WasiCtxBuilder {
|
||||
self.preopens
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push((guest_path.as_ref().to_owned(), Descriptor::VirtualFile(dir)));
|
||||
.push((guest_path.as_ref().to_owned(), dir));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -334,41 +335,36 @@ impl WasiCtxBuilder {
|
||||
] {
|
||||
log::debug!("WasiCtx inserting entry {:?}", pending);
|
||||
let fd = match pending {
|
||||
PendingEntry::Thunk(f) => entries
|
||||
.insert(f()?)
|
||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?,
|
||||
PendingEntry::File(f) => entries
|
||||
.insert(Entry::from(Descriptor::OsHandle(OsHandle::from(f)))?)
|
||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?,
|
||||
PendingEntry::Thunk(f) => {
|
||||
let handle = EntryHandle::new(f()?);
|
||||
let entry = Entry::from(handle)?;
|
||||
entries
|
||||
.insert(entry)
|
||||
.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);
|
||||
}
|
||||
// Then add the preopen entries.
|
||||
for (guest_path, dir) in self.preopens.take().unwrap() {
|
||||
match &dir {
|
||||
Descriptor::OsHandle(handle) => {
|
||||
if !handle.metadata()?.is_dir() {
|
||||
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");
|
||||
}
|
||||
if !dir.is_directory() {
|
||||
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
|
||||
}
|
||||
|
||||
let mut entry = Entry::from(dir)?;
|
||||
let handle = EntryHandle::from(dir);
|
||||
let mut entry = Entry::from(handle)?;
|
||||
entry.preopen_path = Some(guest_path);
|
||||
log::debug!("WasiCtx inserting {:?}", entry);
|
||||
let fd = entries
|
||||
.insert(entry)
|
||||
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
|
||||
log::debug!("WasiCtx inserted at {:?}", fd);
|
||||
log::debug!("WasiCtx entries = {:?}", entries);
|
||||
}
|
||||
|
||||
Ok(WasiCtx {
|
||||
@@ -379,7 +375,6 @@ impl WasiCtxBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EntryTable {
|
||||
fd_pool: FdPool,
|
||||
entries: HashMap<types::Fd, Rc<Entry>>,
|
||||
@@ -418,7 +413,6 @@ impl EntryTable {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WasiCtx {
|
||||
entries: RefCell<EntryTable>,
|
||||
pub(crate) args: Vec<CString>,
|
||||
|
||||
@@ -1,81 +1,53 @@
|
||||
use crate::sys::dev_null;
|
||||
use crate::sys::entry::{descriptor_as_oshandle, determine_type_and_access_rights, OsHandle};
|
||||
use crate::virtfs::VirtualFile;
|
||||
use crate::handle::Handle;
|
||||
use crate::wasi::types::{Filetype, Rights};
|
||||
use crate::wasi::{Errno, Result};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::cell::Cell;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, fs, io};
|
||||
use std::{fmt, io};
|
||||
|
||||
pub(crate) enum Descriptor {
|
||||
OsHandle(OsHandle),
|
||||
VirtualFile(Box<dyn VirtualFile>),
|
||||
Stdin,
|
||||
Stdout,
|
||||
Stderr,
|
||||
}
|
||||
pub(crate) struct EntryHandle(Rc<dyn Handle>);
|
||||
|
||||
impl From<OsHandle> for Descriptor {
|
||||
fn from(handle: OsHandle) -> Self {
|
||||
Self::OsHandle(handle)
|
||||
impl EntryHandle {
|
||||
pub(crate) fn new<T: Handle + 'static>(handle: T) -> Self {
|
||||
Self(Rc::new(handle))
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self) -> Self {
|
||||
Self(Rc::clone(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn VirtualFile>> for Descriptor {
|
||||
fn from(virt: Box<dyn VirtualFile>) -> Self {
|
||||
Self::VirtualFile(virt)
|
||||
impl From<Box<dyn Handle>> for EntryHandle {
|
||||
fn from(handle: Box<dyn Handle>) -> Self {
|
||||
Self(handle.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Descriptor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
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;
|
||||
impl Deref for EntryHandle {
|
||||
type Target = dyn Handle;
|
||||
|
||||
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
|
||||
/// required.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -123,80 +95,18 @@ 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 {
|
||||
pub(crate) fn from(file: Descriptor) -> io::Result<Self> {
|
||||
match file {
|
||||
Descriptor::OsHandle(handle) => unsafe { determine_type_and_access_rights(&handle) }
|
||||
.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 {
|
||||
file_type,
|
||||
descriptor: Rc::new(RefCell::new(virt.into())),
|
||||
rights: Cell::new(rights),
|
||||
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 {
|
||||
pub(crate) fn from(handle: EntryHandle) -> io::Result<Self> {
|
||||
let file_type = handle.get_file_type()?;
|
||||
let rights = handle.get_rights()?;
|
||||
Ok(Self {
|
||||
file_type,
|
||||
descriptor: Rc::new(RefCell::new(Descriptor::Stdin)),
|
||||
handle,
|
||||
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
|
||||
/// `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
|
||||
/// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable`
|
||||
/// 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)?;
|
||||
Ok(Rc::clone(&self.descriptor))
|
||||
Ok(self.handle.get())
|
||||
}
|
||||
|
||||
/// 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 entry;
|
||||
mod fd;
|
||||
mod fdpool;
|
||||
pub mod fs;
|
||||
mod handle;
|
||||
pub mod old;
|
||||
mod path;
|
||||
mod poll;
|
||||
|
||||
@@ -1,112 +1,22 @@
|
||||
use crate::entry::{Descriptor, Entry, EntryRights};
|
||||
use crate::sys;
|
||||
use crate::sys::entry::OsHandle;
|
||||
use crate::entry::{Entry, EntryRights};
|
||||
use crate::handle::Handle;
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use std::path::{Component, Path};
|
||||
use std::str;
|
||||
use wiggle::{GuestBorrows, GuestPtr};
|
||||
|
||||
pub(crate) use sys::path::*;
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) use crate::sys::path::{from_host, open_rights};
|
||||
|
||||
/// Normalizes a path to ensure that the target path is located under the directory provided.
|
||||
///
|
||||
/// This is a workaround for not having Capsicum support in the OS.
|
||||
pub(crate) fn get(
|
||||
fe: &Entry,
|
||||
entry: &Entry,
|
||||
required_rights: &EntryRights,
|
||||
dirflags: types::Lookupflags,
|
||||
path: &GuestPtr<'_, str>,
|
||||
needs_final_component: bool,
|
||||
) -> Result<PathGet> {
|
||||
) -> Result<(Box<dyn Handle>, String)> {
|
||||
const MAX_SYMLINK_EXPANSIONS: usize = 128;
|
||||
|
||||
// Extract path as &str from guest's memory.
|
||||
@@ -123,17 +33,13 @@ pub(crate) fn get(
|
||||
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`.
|
||||
return Err(Errno::Notdir);
|
||||
}
|
||||
|
||||
let desc = fe.as_descriptor(required_rights)?;
|
||||
let dirfd = match &*desc.borrow() {
|
||||
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),
|
||||
};
|
||||
let handle = entry.as_handle(required_rights)?;
|
||||
let dirfd = handle.try_clone()?;
|
||||
|
||||
// Stack of directory file descriptors. Index 0 always corresponds with the directory provided
|
||||
// to this function. Entering a directory causes a file descriptor to be pushed, while handling
|
||||
@@ -198,9 +104,14 @@ pub(crate) fn get(
|
||||
}
|
||||
|
||||
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
|
||||
match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head)
|
||||
.open()
|
||||
{
|
||||
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||
match fd.openat(
|
||||
&head,
|
||||
false,
|
||||
false,
|
||||
types::Oflags::DIRECTORY,
|
||||
types::Fdflags::empty(),
|
||||
) {
|
||||
Ok(new_dir) => {
|
||||
dir_stack.push(new_dir);
|
||||
}
|
||||
@@ -211,12 +122,8 @@ pub(crate) fn get(
|
||||
// this with ENOTDIR because of the O_DIRECTORY flag.
|
||||
{
|
||||
// attempt symlink expansion
|
||||
let mut link_path = PathRef::new(
|
||||
dir_stack.last().ok_or(Errno::Notcapable)?,
|
||||
&head,
|
||||
)
|
||||
.readlink()?;
|
||||
|
||||
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||
let mut link_path = fd.readlinkat(&head)?;
|
||||
symlink_expansions += 1;
|
||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||
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
|
||||
// symlink expansion
|
||||
match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head)
|
||||
.readlink()
|
||||
{
|
||||
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||
match fd.readlinkat(&head) {
|
||||
Ok(mut link_path) => {
|
||||
symlink_expansions += 1;
|
||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||
@@ -282,20 +188,14 @@ pub(crate) fn get(
|
||||
}
|
||||
|
||||
// not a symlink, so we're done;
|
||||
return Ok(PathGet {
|
||||
dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?,
|
||||
path: head,
|
||||
});
|
||||
return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, head));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// no further components to process. means we've hit a case like "." or "a/..", or if the
|
||||
// input path has trailing slashes and `needs_final_component` is not set
|
||||
return Ok(PathGet {
|
||||
dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?,
|
||||
path: String::from("."),
|
||||
});
|
||||
return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, String::from(".")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use crate::entry::Descriptor;
|
||||
use crate::sys;
|
||||
use crate::entry::EntryHandle;
|
||||
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)]
|
||||
pub(crate) struct ClockEventData {
|
||||
@@ -12,9 +9,8 @@ pub(crate) struct ClockEventData {
|
||||
pub(crate) userdata: types::Userdata,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FdEventData {
|
||||
pub(crate) descriptor: Rc<RefCell<Descriptor>>,
|
||||
pub(crate) handle: EntryHandle,
|
||||
pub(crate) r#type: types::Eventtype,
|
||||
pub(crate) userdata: types::Userdata,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::entry::{Descriptor, Entry, EntryRights};
|
||||
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
|
||||
use crate::entry::{Entry, EntryHandle, EntryRights};
|
||||
use crate::sys::clock;
|
||||
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
|
||||
use crate::wasi::{types, AsBytes, Errno, Result};
|
||||
use crate::WasiCtx;
|
||||
use crate::{clock, fd, path, poll};
|
||||
use crate::{path, poll};
|
||||
use log::{debug, error, trace};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{self, SeekFrom};
|
||||
use wiggle::{GuestBorrows, GuestPtr};
|
||||
|
||||
impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
@@ -94,13 +93,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_ADVISE);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => fd::advise(&fd, advice, offset, len)?,
|
||||
Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len)?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
Ok(())
|
||||
entry
|
||||
.as_handle(&required_rights)?
|
||||
.advise(advice, offset, len)
|
||||
}
|
||||
|
||||
fn fd_allocate(
|
||||
@@ -111,24 +106,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_ALLOCATE);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
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(())
|
||||
entry.as_handle(&required_rights)?.allocate(offset, len)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_DATASYNC);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let file = entry.as_descriptor(&required_rights)?;
|
||||
match &*file.borrow() {
|
||||
Descriptor::OsHandle(fd) => fd.sync_data()?,
|
||||
Descriptor::VirtualFile(virt) => virt.datasync()?,
|
||||
other => other.as_os_handle().sync_data()?,
|
||||
};
|
||||
Ok(())
|
||||
entry.as_handle(&required_rights)?.datasync()
|
||||
}
|
||||
|
||||
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat> {
|
||||
let fe = self.get_entry(fd)?;
|
||||
let wasi_file = fe.as_descriptor(&EntryRights::empty())?;
|
||||
let fs_flags = match &*wasi_file.borrow() {
|
||||
Descriptor::OsHandle(wasi_fd) => fd::fdstat_get(&wasi_fd)?,
|
||||
Descriptor::VirtualFile(virt) => virt.fdstat_get(),
|
||||
other => fd::fdstat_get(&other.as_os_handle())?,
|
||||
};
|
||||
let rights = fe.rights.get();
|
||||
let entry = self.get_entry(fd)?;
|
||||
let file = entry.as_handle(&EntryRights::empty())?;
|
||||
let fs_flags = file.fdstat_get()?;
|
||||
let rights = entry.rights.get();
|
||||
let fdstat = types::Fdstat {
|
||||
fs_filetype: fe.file_type,
|
||||
fs_filetype: entry.file_type,
|
||||
fs_rights_base: rights.base,
|
||||
fs_rights_inheriting: rights.inheriting,
|
||||
fs_flags,
|
||||
@@ -175,23 +143,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
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 entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
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(())
|
||||
entry.as_handle(&required_rights)?.fdstat_set_flags(flags)
|
||||
}
|
||||
|
||||
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> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_GET);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
let host_filestat = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => fd::filestat_get(&fd)?,
|
||||
Descriptor::VirtualFile(virt) => virt.filestat_get()?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
let host_filestat = entry.as_handle(&required_rights)?.filestat_get()?;
|
||||
Ok(host_filestat)
|
||||
}
|
||||
|
||||
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE);
|
||||
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
|
||||
if size > i64::max_value() as u64 {
|
||||
return Err(Errno::TooBig);
|
||||
}
|
||||
match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => fd.set_len(size)?,
|
||||
Descriptor::VirtualFile(virt) => virt.filestat_set_size(size)?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
Ok(())
|
||||
entry.as_handle(&required_rights)?.filestat_set_size(size)
|
||||
}
|
||||
|
||||
fn fd_filestat_set_times(
|
||||
@@ -246,9 +187,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
fd::filestat_set_times_impl(&desc.borrow(), atim, mtim, fst_flags)?;
|
||||
Ok(())
|
||||
entry
|
||||
.as_handle(&required_rights)?
|
||||
.filestat_set_times(atim, mtim, fst_flags)
|
||||
}
|
||||
|
||||
fn fd_pread(
|
||||
@@ -274,25 +215,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
let required_rights =
|
||||
EntryRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
|
||||
if offset > i64::max_value() as u64 {
|
||||
return Err(Errno::Io);
|
||||
}
|
||||
|
||||
let host_nread = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => {
|
||||
let mut fd: &File = fd;
|
||||
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()?;
|
||||
let host_nread = entry
|
||||
.as_handle(&required_rights)?
|
||||
.preadv(&mut buf, offset)?
|
||||
.try_into()?;
|
||||
Ok(host_nread)
|
||||
}
|
||||
|
||||
@@ -362,26 +291,15 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
let required_rights =
|
||||
EntryRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
|
||||
if offset > i64::max_value() as u64 {
|
||||
return Err(Errno::Io);
|
||||
}
|
||||
|
||||
let host_nwritten = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => {
|
||||
let mut fd: &File = fd;
|
||||
let cur_pos = fd.seek(SeekFrom::Current(0))?;
|
||||
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()?;
|
||||
|
||||
let host_nwritten = entry
|
||||
.as_handle(&required_rights)?
|
||||
.pwritev(&buf, offset)?
|
||||
.try_into()?;
|
||||
Ok(host_nwritten)
|
||||
}
|
||||
|
||||
@@ -402,13 +320,10 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_READ);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let host_nread = match &*entry.as_descriptor(&required_rights)?.borrow() {
|
||||
Descriptor::OsHandle(file) => (file as &File).read_vectored(&mut slices)?,
|
||||
Descriptor::VirtualFile(virt) => virt.read_vectored(&mut slices)?,
|
||||
Descriptor::Stdin => io::stdin().read_vectored(&mut slices)?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
let host_nread = host_nread.try_into()?;
|
||||
let host_nread = entry
|
||||
.as_handle(&required_rights)?
|
||||
.read_vectored(&mut slices)?
|
||||
.try_into()?;
|
||||
|
||||
Ok(host_nread)
|
||||
}
|
||||
@@ -422,39 +337,26 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<types::Size> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_READDIR);
|
||||
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 buf = buf.clone();
|
||||
for pair in iter {
|
||||
let (dirent, name) = pair?;
|
||||
let dirent_raw = dirent.as_bytes()?;
|
||||
let dirent_len: types::Size = dirent_raw.len().try_into()?;
|
||||
let name_raw = name.as_bytes();
|
||||
let name_len = name_raw.len().try_into()?;
|
||||
let offset = dirent_len.checked_add(name_len).ok_or(Errno::Overflow)?;
|
||||
if (buf_len - bufused) < offset {
|
||||
break;
|
||||
} else {
|
||||
buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?;
|
||||
buf = buf.add(dirent_len)?;
|
||||
buf.as_array(name_len).copy_from_slice(name_raw)?;
|
||||
buf = buf.add(name_len)?;
|
||||
bufused += offset;
|
||||
}
|
||||
let mut bufused = 0;
|
||||
let mut buf = buf.clone();
|
||||
for pair in entry.as_handle(&required_rights)?.readdir(cookie)? {
|
||||
let (dirent, name) = pair?;
|
||||
let dirent_raw = dirent.as_bytes()?;
|
||||
let dirent_len: types::Size = dirent_raw.len().try_into()?;
|
||||
let name_raw = name.as_bytes();
|
||||
let name_len = name_raw.len().try_into()?;
|
||||
let offset = dirent_len.checked_add(name_len).ok_or(Errno::Overflow)?;
|
||||
if (buf_len - bufused) < offset {
|
||||
break;
|
||||
} else {
|
||||
buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?;
|
||||
buf = buf.add(dirent_len)?;
|
||||
buf.as_array(name_len).copy_from_slice(name_raw)?;
|
||||
buf = buf.add(name_len)?;
|
||||
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)
|
||||
}
|
||||
@@ -495,43 +397,27 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
};
|
||||
let required_rights = EntryRights::from_base(base);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
let pos = match whence {
|
||||
types::Whence::Cur => SeekFrom::Current(offset),
|
||||
types::Whence::End => SeekFrom::End(offset),
|
||||
types::Whence::Set => SeekFrom::Start(offset as u64),
|
||||
};
|
||||
let host_newoffset = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => (fd as &File).seek(pos)?,
|
||||
Descriptor::VirtualFile(virt) => virt.seek(pos)?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
|
||||
let host_newoffset = entry.as_handle(&required_rights)?.seek(pos)?;
|
||||
Ok(host_newoffset)
|
||||
}
|
||||
|
||||
fn fd_sync(&self, fd: types::Fd) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_SYNC);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => fd.sync_all()?,
|
||||
Descriptor::VirtualFile(virt) => virt.sync()?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
Ok(())
|
||||
entry.as_handle(&required_rights)?.sync()
|
||||
}
|
||||
|
||||
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_TELL);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
let host_offset = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(fd) => (fd as &File).seek(SeekFrom::Current(0))?,
|
||||
Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
|
||||
let host_offset = entry
|
||||
.as_handle(&required_rights)?
|
||||
.seek(SeekFrom::Current(0))?;
|
||||
Ok(host_offset)
|
||||
}
|
||||
|
||||
@@ -549,63 +435,28 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
};
|
||||
slices.push(io::IoSlice::new(slice));
|
||||
}
|
||||
|
||||
// perform unbuffered writes
|
||||
let required_rights = EntryRights::from_base(types::Rights::FD_WRITE);
|
||||
let entry = self.get_entry(fd)?;
|
||||
let isatty = entry.isatty();
|
||||
let desc = entry.as_descriptor(&required_rights)?;
|
||||
let host_nwritten = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(file) => {
|
||||
if isatty {
|
||||
SandboxedTTYWriter::new(&mut (file as &File)).write_vectored(&slices)?
|
||||
} 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()?)
|
||||
let host_nwritten = entry
|
||||
.as_handle(&required_rights)?
|
||||
.write_vectored(&slices, isatty)?
|
||||
.try_into()?;
|
||||
Ok(host_nwritten)
|
||||
}
|
||||
|
||||
fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||
let required_rights =
|
||||
EntryRights::from_base(types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved = path::get(
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
false,
|
||||
)?;
|
||||
resolved.create_directory()
|
||||
dirfd.create_directory(&path)
|
||||
}
|
||||
|
||||
fn path_filestat_get(
|
||||
@@ -616,20 +467,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<types::Filestat> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_GET);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved = path::get(&entry, &required_rights, flags, path, false)?;
|
||||
let host_filestat = match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(virt) => virt
|
||||
.openat(
|
||||
std::path::Path::new(resolved.path()),
|
||||
false,
|
||||
false,
|
||||
types::Oflags::empty(),
|
||||
types::Fdflags::empty(),
|
||||
)?
|
||||
.filestat_get()?,
|
||||
_ => path::filestat_get(resolved, flags)?,
|
||||
};
|
||||
|
||||
let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?;
|
||||
let host_filestat = dirfd
|
||||
.openat(
|
||||
&path,
|
||||
false,
|
||||
false,
|
||||
types::Oflags::empty(),
|
||||
types::Fdflags::empty(),
|
||||
)?
|
||||
.filestat_get()?;
|
||||
Ok(host_filestat)
|
||||
}
|
||||
|
||||
@@ -644,13 +491,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved = path::get(&entry, &required_rights, flags, path, false)?;
|
||||
match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(_virt) => {
|
||||
unimplemented!("virtual filestat_set_times");
|
||||
}
|
||||
_ => path::filestat_set_times(resolved, flags, atim, mtim, fst_flags),
|
||||
}
|
||||
let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?;
|
||||
dirfd
|
||||
.openat(
|
||||
&path,
|
||||
false,
|
||||
false,
|
||||
types::Oflags::empty(),
|
||||
types::Fdflags::empty(),
|
||||
)?
|
||||
.filestat_set_times(atim, mtim, fst_flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path_link(
|
||||
@@ -663,7 +514,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_SOURCE);
|
||||
let old_entry = self.get_entry(old_fd)?;
|
||||
let resolved_old = path::get(
|
||||
let (old_dirfd, old_path) = path::get(
|
||||
&old_entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
@@ -672,16 +523,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
)?;
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_TARGET);
|
||||
let new_entry = self.get_entry(new_fd)?;
|
||||
let resolved_new = path::get(
|
||||
let (new_dirfd, new_path) = path::get(
|
||||
&new_entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path,
|
||||
false,
|
||||
)?;
|
||||
path::link(
|
||||
resolved_old,
|
||||
resolved_new,
|
||||
old_dirfd.link(
|
||||
&old_path,
|
||||
new_dirfd,
|
||||
&new_path,
|
||||
old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW),
|
||||
)
|
||||
}
|
||||
@@ -701,20 +553,15 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
oflags,
|
||||
fdflags,
|
||||
);
|
||||
|
||||
trace!(" | needed_rights={}", needed_rights);
|
||||
|
||||
let resolved = {
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
path::get(
|
||||
&entry,
|
||||
&needed_rights,
|
||||
dirflags,
|
||||
path,
|
||||
oflags & types::Oflags::CREAT != types::Oflags::empty(),
|
||||
)?
|
||||
};
|
||||
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&needed_rights,
|
||||
dirflags,
|
||||
path,
|
||||
oflags & types::Oflags::CREAT != types::Oflags::empty(),
|
||||
)?;
|
||||
// which open mode do we need?
|
||||
let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR)
|
||||
!= types::Rights::empty();
|
||||
@@ -724,15 +571,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
| types::Rights::FD_ALLOCATE
|
||||
| types::Rights::FD_FILESTAT_SET_SIZE)
|
||||
!= types::Rights::empty();
|
||||
|
||||
trace!(
|
||||
" | calling path_open impl: read={}, write={}",
|
||||
read,
|
||||
write
|
||||
);
|
||||
|
||||
let fd = resolved.open_with(read, write, oflags, fdflags)?;
|
||||
let fe = Entry::from(fd)?;
|
||||
let fd = dirfd.openat(&path, read, write, oflags, fdflags)?;
|
||||
let fe = Entry::from(EntryHandle::from(fd))?;
|
||||
// We need to manually deny the rights which are not explicitly requested
|
||||
// because Entry::from will assign maximal consistent rights.
|
||||
let mut rights = fe.rights.get();
|
||||
@@ -752,47 +597,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<types::Size> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_READLINK);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved = path::get(
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let slice = unsafe {
|
||||
let mut bc = GuestBorrows::new();
|
||||
let buf = buf.as_array(buf_len);
|
||||
let raw = buf.as_raw(&mut bc)?;
|
||||
&mut *raw
|
||||
};
|
||||
let host_bufused = match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(_virt) => {
|
||||
unimplemented!("virtual readlink");
|
||||
}
|
||||
_ => path::readlink(resolved, slice)?,
|
||||
};
|
||||
let host_bufused = host_bufused.try_into()?;
|
||||
let host_bufused = dirfd.readlink(&path, slice)?.try_into()?;
|
||||
Ok(host_bufused)
|
||||
}
|
||||
|
||||
fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved = path::get(
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
true,
|
||||
)?;
|
||||
|
||||
debug!("path_remove_directory resolved={:?}", resolved);
|
||||
|
||||
match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()),
|
||||
_ => path::remove_directory(resolved),
|
||||
}
|
||||
dirfd.remove_directory(&path)
|
||||
}
|
||||
|
||||
fn path_rename(
|
||||
@@ -804,7 +636,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_SOURCE);
|
||||
let entry = self.get_entry(old_fd)?;
|
||||
let resolved_old = path::get(
|
||||
let (old_dirfd, old_path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
@@ -813,26 +645,14 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
)?;
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_TARGET);
|
||||
let entry = self.get_entry(new_fd)?;
|
||||
let resolved_new = path::get(
|
||||
let (new_dirfd, new_path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path,
|
||||
true,
|
||||
)?;
|
||||
|
||||
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");
|
||||
}
|
||||
old_dirfd.rename(&old_path, new_dirfd, &new_path)
|
||||
}
|
||||
|
||||
fn path_symlink(
|
||||
@@ -843,44 +663,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_SYMLINK);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved_new = path::get(
|
||||
let (new_fd, new_path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
new_path,
|
||||
true,
|
||||
)?;
|
||||
|
||||
let old_path = unsafe {
|
||||
let mut bc = GuestBorrows::new();
|
||||
let raw = old_path.as_raw(&mut bc)?;
|
||||
&*raw
|
||||
};
|
||||
|
||||
trace!(" | old_path='{}'", old_path);
|
||||
|
||||
match resolved_new.dirfd() {
|
||||
Descriptor::VirtualFile(_virt) => {
|
||||
unimplemented!("virtual path_symlink");
|
||||
}
|
||||
_non_virtual => path::symlink(old_path, resolved_new),
|
||||
}
|
||||
new_fd.symlink(&old_path, &new_path)
|
||||
}
|
||||
|
||||
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> {
|
||||
let required_rights = EntryRights::from_base(types::Rights::PATH_UNLINK_FILE);
|
||||
let entry = self.get_entry(dirfd)?;
|
||||
let resolved = path::get(
|
||||
let (dirfd, path) = path::get(
|
||||
&entry,
|
||||
&required_rights,
|
||||
types::Lookupflags::empty(),
|
||||
path,
|
||||
false,
|
||||
)?;
|
||||
match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()),
|
||||
_ => path::unlink_file(resolved),
|
||||
}
|
||||
dirfd.unlink_file(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_oneoff(
|
||||
@@ -949,7 +759,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
}
|
||||
};
|
||||
fd_events.push(poll::FdEventData {
|
||||
descriptor: entry.as_descriptor(&required_rights)?,
|
||||
handle: entry.as_handle(&required_rights)?,
|
||||
r#type: types::Eventtype::FdRead,
|
||||
userdata: subscription.userdata,
|
||||
});
|
||||
@@ -975,7 +785,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
}
|
||||
};
|
||||
fd_events.push(poll::FdEventData {
|
||||
descriptor: entry.as_descriptor(&required_rights)?,
|
||||
handle: entry.as_handle(&required_rights)?,
|
||||
r#type: types::Eventtype::FdWrite,
|
||||
userdata: subscription.userdata,
|
||||
});
|
||||
@@ -984,7 +794,6 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
}
|
||||
debug!("poll_oneoff events = {:?}", events);
|
||||
debug!("poll_oneoff timeout = {:?}", timeout);
|
||||
debug!("poll_oneoff fd_events = {:?}", fd_events);
|
||||
// The underlying implementation should successfully and immediately return
|
||||
// if no events have been passed. Such situation may occur if all provided
|
||||
// events have been filtered out as errors in the code above.
|
||||
@@ -997,6 +806,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
let event_ptr = event_ptr?;
|
||||
event_ptr.write(event)?;
|
||||
}
|
||||
|
||||
trace!(" | *nevents={:?}", nevents);
|
||||
|
||||
Ok(nevents)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::sys;
|
||||
use crate::wasi::types::{Subclockflags, SubscriptionClock};
|
||||
use crate::wasi::{Errno, Result};
|
||||
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> {
|
||||
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 filetime::{set_file_handle_times, FileTime};
|
||||
use std::fs::File;
|
||||
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(
|
||||
file: &Descriptor,
|
||||
pub(crate) fn filestat_set_times(
|
||||
file: &File,
|
||||
st_atim: types::Timestamp,
|
||||
st_mtim: types::Timestamp,
|
||||
fst_flags: types::Fstflags,
|
||||
@@ -39,13 +38,7 @@ pub(crate) fn filestat_set_times_impl(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into),
|
||||
Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim),
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set_file_handle_times(file, atim, mtim)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
pub(crate) mod clock;
|
||||
pub(crate) mod fd;
|
||||
pub(crate) mod oshandle;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
mod unix;
|
||||
pub(crate) use unix::*;
|
||||
use unix as sys_impl;
|
||||
pub use unix::preopen_dir;
|
||||
} else if #[cfg(windows)] {
|
||||
mod windows;
|
||||
pub(crate) use windows::*;
|
||||
use windows as sys_impl;
|
||||
pub use windows::preopen_dir;
|
||||
} else {
|
||||
compile_error!("wasi-common doesn't compile for this platform yet");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use sys_impl::path;
|
||||
pub(crate) use sys_impl::poll;
|
||||
|
||||
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) 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 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};
|
||||
match unsafe {
|
||||
unlinkat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
AtFlag::empty(),
|
||||
)
|
||||
} {
|
||||
match unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty()) } {
|
||||
Err(err) => {
|
||||
let raw_errno = err.raw_os_error().unwrap();
|
||||
// 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};
|
||||
|
||||
if raw_errno == libc::EPERM {
|
||||
match unsafe {
|
||||
fstatat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
AtFlag::SYMLINK_NOFOLLOW,
|
||||
)
|
||||
} {
|
||||
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } {
|
||||
Ok(stat) => {
|
||||
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory {
|
||||
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};
|
||||
|
||||
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) => {
|
||||
if err.raw_os_error().unwrap() == libc::ENOTDIR {
|
||||
// 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 and check if the path exists, and
|
||||
// adjust the error code appropriately.
|
||||
let new_path = resolved.path().trim_end_matches('/');
|
||||
match unsafe {
|
||||
fstatat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
new_path,
|
||||
AtFlag::SYMLINK_NOFOLLOW,
|
||||
)
|
||||
} {
|
||||
let new_path = new_path.trim_end_matches('/');
|
||||
match unsafe { fstatat(new_dirfd.as_raw_fd(), new_path, AtFlag::SYMLINK_NOFOLLOW) }
|
||||
{
|
||||
Ok(_) => return Err(Errno::Exist),
|
||||
Err(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};
|
||||
match unsafe {
|
||||
renameat(
|
||||
resolved_old.dirfd().as_raw_fd(),
|
||||
resolved_old.path(),
|
||||
resolved_new.dirfd().as_raw_fd(),
|
||||
resolved_new.path(),
|
||||
old_dirfd.as_raw_fd(),
|
||||
old_path,
|
||||
new_dirfd.as_raw_fd(),
|
||||
new_path,
|
||||
)
|
||||
} {
|
||||
Err(err) => {
|
||||
@@ -103,16 +95,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
||||
// Verify on other BSD-based OSes.
|
||||
if err.raw_os_error().unwrap() == libc::ENOENT {
|
||||
// check if the source path exists
|
||||
match unsafe {
|
||||
fstatat(
|
||||
resolved_old.dirfd().as_raw_fd(),
|
||||
resolved_old.path(),
|
||||
AtFlag::SYMLINK_NOFOLLOW,
|
||||
)
|
||||
} {
|
||||
match unsafe { fstatat(old_dirfd.as_raw_fd(), old_path, AtFlag::SYMLINK_NOFOLLOW) }
|
||||
{
|
||||
Ok(_) => {
|
||||
// check if destination contains a trailing slash
|
||||
if resolved_new.path().contains('/') {
|
||||
if new_path.contains('/') {
|
||||
return Err(Errno::Notdir);
|
||||
} else {
|
||||
return Err(Errno::Noent);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#[path = "../linux/oshandle.rs"]
|
||||
pub(crate) mod oshandle;
|
||||
#[path = "../linux/osfile.rs"]
|
||||
pub(crate) mod osfile;
|
||||
#[path = "../linux/path.rs"]
|
||||
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 std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
@@ -9,7 +9,7 @@ pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
|
||||
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())? };
|
||||
// We return None here to signal that the operation succeeded on the original
|
||||
// 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(
|
||||
file: &File,
|
||||
file: &OsFile,
|
||||
advice: types::Advice,
|
||||
offset: types::Filesize,
|
||||
len: types::Filesize,
|
||||
@@ -38,21 +38,21 @@ pub(crate) fn advise(
|
||||
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;
|
||||
let stat = unsafe { fstat(file.as_raw_fd())? };
|
||||
Ok(stat.try_into()?)
|
||||
}
|
||||
|
||||
pub(crate) fn readdir<'a>(
|
||||
os_handle: &'a OsHandle,
|
||||
file: &'a OsFile,
|
||||
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};
|
||||
|
||||
// Get an instance of `Dir`; this is host-specific due to intricasies
|
||||
// of managing a dir stream between Linux and BSD *nixes
|
||||
let mut dir = os_handle.dir_stream()?;
|
||||
let mut dir = file.dir_stream()?;
|
||||
|
||||
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
|
||||
// new items may not be returned to the caller.
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn readdir<'a>(
|
||||
dir.seek(loc);
|
||||
}
|
||||
|
||||
Ok(DirIter::new(dir).map(|entry| {
|
||||
Ok(Box::new(DirIter::new(dir).map(|entry| {
|
||||
let entry: Entry = entry?;
|
||||
let name = entry.file_name().to_str()?.to_owned();
|
||||
let dirent = types::Dirent {
|
||||
@@ -75,5 +75,5 @@ pub(crate) fn readdir<'a>(
|
||||
d_type: entry.file_type().into(),
|
||||
};
|
||||
Ok((dirent, name))
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub(crate) mod oshandle;
|
||||
pub(crate) mod osfile;
|
||||
pub(crate) mod path;
|
||||
|
||||
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 crate::path::PathGet;
|
||||
use super::osfile::OsFile;
|
||||
use crate::wasi::Result;
|
||||
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};
|
||||
unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty())? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Result<()> {
|
||||
use yanix::file::symlinkat;
|
||||
|
||||
log::debug!("path_symlink old_path = {:?}", old_path);
|
||||
log::debug!(
|
||||
"path_symlink (new_dirfd, new_path) = ({:?}, {:?})",
|
||||
new_dirfd,
|
||||
new_path
|
||||
);
|
||||
|
||||
unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path)? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn rename(
|
||||
old_dirfd: &OsFile,
|
||||
old_path: &str,
|
||||
new_dirfd: &OsFile,
|
||||
new_path: &str,
|
||||
) -> Result<()> {
|
||||
use yanix::file::renameat;
|
||||
unsafe {
|
||||
unlinkat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
AtFlag::empty(),
|
||||
renameat(
|
||||
old_dirfd.as_raw_fd(),
|
||||
old_path,
|
||||
new_dirfd.as_raw_fd(),
|
||||
new_path,
|
||||
)?
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
||||
use yanix::file::symlinkat;
|
||||
|
||||
log::debug!("path_symlink old_path = {:?}", old_path);
|
||||
log::debug!("path_symlink resolved = {:?}", resolved);
|
||||
|
||||
unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path())? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
||||
use yanix::file::renameat;
|
||||
match (resolved_old.dirfd(), resolved_new.dirfd()) {
|
||||
(Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => {
|
||||
unsafe {
|
||||
renameat(
|
||||
resolved_old_file.as_raw_fd(),
|
||||
resolved_old.path(),
|
||||
resolved_new_file.as_raw_fd(),
|
||||
resolved_new.path(),
|
||||
)?
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("path_link with one or more virtual files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub(crate) mod clock;
|
||||
pub(crate) mod entry;
|
||||
pub(crate) mod fd;
|
||||
pub(crate) mod oshandle;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod poll;
|
||||
|
||||
@@ -24,16 +24,13 @@ cfg_if::cfg_if! {
|
||||
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use sys_impl::O_RSYNC;
|
||||
use yanix::clock::ClockId;
|
||||
use yanix::file::{AtFlag, OFlag};
|
||||
|
||||
pub(crate) fn dev_null() -> io::Result<File> {
|
||||
OpenOptions::new().read(true).write(true).open("/dev/null")
|
||||
}
|
||||
pub(crate) use sys_impl::*;
|
||||
|
||||
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
|
||||
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 crate::path::PathGet;
|
||||
use crate::sys::entry::OsHandle;
|
||||
use crate::sys::unix::sys_impl;
|
||||
use super::oshandle::OsFile;
|
||||
use crate::entry::EntryRights;
|
||||
use crate::sys::oshandle::OsHandle;
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt};
|
||||
use std::str;
|
||||
use yanix::file::OFlag;
|
||||
|
||||
pub(crate) use sys_impl::path::*;
|
||||
pub(crate) use super::sys_impl::path::*;
|
||||
|
||||
/// Creates owned WASI path from OS string.
|
||||
///
|
||||
@@ -44,32 +41,14 @@ pub(crate) fn open_rights(
|
||||
if fdflags.contains(OFlag::DSYNC) {
|
||||
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;
|
||||
}
|
||||
|
||||
EntryRights::new(needed_base, needed_inheriting)
|
||||
}
|
||||
|
||||
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
|
||||
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> {
|
||||
pub(crate) fn readlinkat(dirfd: &OsFile, path: &str) -> Result<String> {
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use yanix::file::readlinkat;
|
||||
|
||||
@@ -80,15 +59,17 @@ pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result<String> {
|
||||
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};
|
||||
unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn link(
|
||||
resolved_old: PathGet,
|
||||
resolved_new: PathGet,
|
||||
old_dirfd: &OsFile,
|
||||
old_path: &str,
|
||||
new_dirfd: &OsFile,
|
||||
new_path: &str,
|
||||
follow_symlinks: bool,
|
||||
) -> Result<()> {
|
||||
use yanix::file::{linkat, AtFlag};
|
||||
@@ -99,10 +80,10 @@ pub(crate) fn link(
|
||||
};
|
||||
unsafe {
|
||||
linkat(
|
||||
resolved_old.dirfd().as_raw_fd(),
|
||||
resolved_old.path(),
|
||||
resolved_new.dirfd().as_raw_fd(),
|
||||
resolved_new.path(),
|
||||
old_dirfd.as_raw_fd(),
|
||||
old_path,
|
||||
new_dirfd.as_raw_fd(),
|
||||
new_path,
|
||||
flags,
|
||||
)?
|
||||
};
|
||||
@@ -110,12 +91,13 @@ pub(crate) fn link(
|
||||
}
|
||||
|
||||
pub(crate) fn open(
|
||||
resolved: PathGet,
|
||||
dirfd: &OsFile,
|
||||
path: &str,
|
||||
read: bool,
|
||||
write: bool,
|
||||
oflags: types::Oflags,
|
||||
fs_flags: types::Fdflags,
|
||||
) -> Result<Descriptor> {
|
||||
) -> Result<OsHandle> {
|
||||
use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag};
|
||||
|
||||
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
|
||||
// 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);
|
||||
|
||||
let fd_no = unsafe {
|
||||
openat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
dirfd.as_raw_fd(),
|
||||
path,
|
||||
nix_all_oflags,
|
||||
Mode::from_bits_truncate(0o666),
|
||||
)
|
||||
@@ -156,13 +139,7 @@ pub(crate) fn open(
|
||||
match e.raw_os_error().unwrap() {
|
||||
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
|
||||
libc::ENXIO => {
|
||||
match unsafe {
|
||||
fstatat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
AtFlag::SYMLINK_NOFOLLOW,
|
||||
)
|
||||
} {
|
||||
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } {
|
||||
Ok(stat) => {
|
||||
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket {
|
||||
return Err(Errno::Notsup);
|
||||
@@ -178,13 +155,7 @@ pub(crate) fn open(
|
||||
libc::ENOTDIR
|
||||
if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() =>
|
||||
{
|
||||
match unsafe {
|
||||
fstatat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
AtFlag::SYMLINK_NOFOLLOW,
|
||||
)
|
||||
} {
|
||||
match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } {
|
||||
Ok(stat) => {
|
||||
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink {
|
||||
return Err(Errno::Loop);
|
||||
@@ -210,13 +181,13 @@ pub(crate) fn open(
|
||||
log::debug!("path_open (host) new_fd = {:?}", new_fd);
|
||||
|
||||
// Determine the type of the new file descriptor and which rights contradict with this type
|
||||
Ok(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 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 copy_len = min(read_link.len(), buf.len());
|
||||
if copy_len > 0 {
|
||||
@@ -225,73 +196,8 @@ pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
||||
Ok(copy_len)
|
||||
}
|
||||
|
||||
pub(crate) fn filestat_get(
|
||||
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<()> {
|
||||
pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||
use yanix::file::{unlinkat, AtFlag};
|
||||
|
||||
unsafe {
|
||||
unlinkat(
|
||||
resolved.dirfd().as_raw_fd(),
|
||||
resolved.path(),
|
||||
AtFlag::REMOVEDIR,
|
||||
)?
|
||||
};
|
||||
unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::REMOVEDIR)? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use super::super::oshandle::OsHandle;
|
||||
use crate::poll::{ClockEventData, FdEventData};
|
||||
use crate::sys::oshandle::AsFile;
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use std::io;
|
||||
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
||||
use yanix::file::fionread;
|
||||
use yanix::poll::{poll, PollFd, PollFlags};
|
||||
|
||||
pub(crate) fn oneoff(
|
||||
timeout: Option<ClockEventData>,
|
||||
fd_events: Vec<FdEventData>,
|
||||
events: &mut Vec<types::Event>,
|
||||
) -> Result<()> {
|
||||
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
||||
use yanix::poll::{poll, PollFd, PollFlags};
|
||||
|
||||
if fd_events.is_empty() && timeout.is_none() {
|
||||
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.
|
||||
_ => 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();
|
||||
|
||||
@@ -73,26 +80,22 @@ fn handle_fd_event(
|
||||
ready_events: impl Iterator<Item = (FdEventData, yanix::poll::PollFd)>,
|
||||
events: &mut Vec<types::Event>,
|
||||
) -> Result<()> {
|
||||
use crate::entry::Descriptor;
|
||||
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
||||
use yanix::{file::fionread, poll::PollFlags};
|
||||
|
||||
fn query_nbytes(fd: &Descriptor) -> Result<u64> {
|
||||
fn query_nbytes(handle: &OsHandle) -> Result<u64> {
|
||||
// fionread may overflow for large files, so use another way for regular files.
|
||||
if let Descriptor::OsHandle(os_handle) = fd {
|
||||
let meta = os_handle.metadata()?;
|
||||
if let OsHandle::OsFile(file) = handle {
|
||||
let meta = file.as_file().metadata()?;
|
||||
if meta.file_type().is_file() {
|
||||
use yanix::file::tell;
|
||||
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);
|
||||
}
|
||||
}
|
||||
unsafe { Ok(fionread(fd.as_raw_fd())?.into()) }
|
||||
unsafe { Ok(fionread(handle.as_raw_fd())?.into()) }
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let revents = match poll_fd.revents() {
|
||||
@@ -103,7 +106,12 @@ fn handle_fd_event(
|
||||
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
|
||||
|
||||
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 {
|
||||
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::oshandle::OsFile;
|
||||
use crate::path;
|
||||
use crate::sys::entry::OsHandle;
|
||||
use crate::sys::oshandle::AsFile;
|
||||
use crate::wasi::{types, Result};
|
||||
use log::trace;
|
||||
use std::convert::TryInto;
|
||||
@@ -10,9 +11,9 @@ use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
|
||||
use std::path::Path;
|
||||
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 handle = fd.as_raw_handle();
|
||||
let handle = file.as_raw_handle();
|
||||
let access_mode = winx::file::query_access_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)
|
||||
}
|
||||
|
||||
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Option<OsHandle>> {
|
||||
let handle = fd.as_raw_handle();
|
||||
|
||||
// TODO Investigate further for Stdio handles. `ReOpenFile` requires the file
|
||||
// handle came from `CreateFile`, but the Rust's libstd will use `GetStdHandle`
|
||||
// rather than `CreateFile`. Relevant discussion can be found in:
|
||||
// https://github.com/rust-lang/rust/issues/40490
|
||||
pub(crate) fn fdstat_set_flags(file: &File, fdflags: types::Fdflags) -> Result<Option<OsFile>> {
|
||||
let handle = file.as_raw_handle();
|
||||
let access_mode = winx::file::query_access_information(handle)?;
|
||||
|
||||
let new_access_mode = file_access_mode_from_fdflags(
|
||||
fdflags,
|
||||
access_mode.contains(AccessMode::FILE_READ_DATA),
|
||||
access_mode.contains(AccessMode::FILE_WRITE_DATA)
|
||||
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
|
||||
);
|
||||
|
||||
unsafe {
|
||||
Ok(Some(OsHandle::from(File::from_raw_handle(
|
||||
winx::file::reopen_file(handle, new_access_mode, fdflags.into())?,
|
||||
))))
|
||||
Ok(Some(OsFile::from_raw_handle(winx::file::reopen_file(
|
||||
handle,
|
||||
new_access_mode,
|
||||
fdflags.into(),
|
||||
)?)))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn advise(
|
||||
_file: &File,
|
||||
_file: &OsFile,
|
||||
_advice: types::Advice,
|
||||
_offset: 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
|
||||
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
|
||||
pub(crate) fn readdir(
|
||||
fd: &File,
|
||||
file: &OsFile,
|
||||
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;
|
||||
|
||||
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
|
||||
let path = Path::new(&path);
|
||||
// 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.
|
||||
//
|
||||
// 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>>(
|
||||
@@ -182,7 +186,7 @@ fn dirent_from_path<P: AsRef<Path>>(
|
||||
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()?;
|
||||
Ok(filestat)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
pub(crate) mod clock;
|
||||
pub(crate) mod entry;
|
||||
pub(crate) mod fd;
|
||||
pub(crate) mod oshandle;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod poll;
|
||||
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{io, string};
|
||||
use winapi::shared::winerror;
|
||||
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> {
|
||||
use std::fs::OpenOptions;
|
||||
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 crate::fd;
|
||||
use crate::path::PathGet;
|
||||
use crate::sys::entry::OsHandle;
|
||||
use super::oshandle::OsFile;
|
||||
use crate::entry::EntryRights;
|
||||
use crate::sys::oshandle::{AsFile, OsHandle};
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use log::debug;
|
||||
use std::convert::TryInto;
|
||||
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::fs::OpenOptionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use winapi::shared::winerror;
|
||||
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.
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
|
||||
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(
|
||||
input_rights: &EntryRights,
|
||||
oflags: types::Oflags,
|
||||
@@ -69,30 +116,7 @@ pub(crate) fn open_rights(
|
||||
EntryRights::new(needed_base, needed_inheriting)
|
||||
}
|
||||
|
||||
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
use winx::file::Flags;
|
||||
|
||||
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> {
|
||||
pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result<String> {
|
||||
use winx::file::get_file_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
|
||||
// as otherwise we will error out since WASI is not capable
|
||||
// of dealing with absolute paths
|
||||
let dir_path = get_file_path(dirfd)?;
|
||||
let dir_path = get_file_path(&dirfd.as_file())?;
|
||||
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
||||
let target_path = target_path
|
||||
.strip_prefix(dir_path)
|
||||
@@ -127,54 +151,25 @@ pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
|
||||
Err(err.into())
|
||||
}
|
||||
|
||||
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: &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<()> {
|
||||
pub(crate) fn create_directory(file: &OsFile, path: &str) -> Result<()> {
|
||||
let path = concatenate(file, path)?;
|
||||
std::fs::create_dir(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn link(
|
||||
resolved_old: PathGet,
|
||||
resolved_new: PathGet,
|
||||
old_dirfd: &OsFile,
|
||||
old_path: &str,
|
||||
new_dirfd: &OsFile,
|
||||
new_path: &str,
|
||||
follow_symlinks: bool,
|
||||
) -> Result<()> {
|
||||
use std::fs;
|
||||
let mut old_path = resolved_old.concatenate()?;
|
||||
let new_path = resolved_new.concatenate()?;
|
||||
let mut old_path = concatenate(old_dirfd, old_path)?;
|
||||
let new_path = concatenate(new_dirfd, new_path)?;
|
||||
if follow_symlinks {
|
||||
// in particular, this will return an error if the target path doesn't exist
|
||||
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() {
|
||||
// fs::canonicalize under Windows will return:
|
||||
// * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink
|
||||
@@ -183,39 +178,32 @@ pub(crate) fn link(
|
||||
_ => e.into(),
|
||||
})?;
|
||||
}
|
||||
fs::hard_link(&old_path, &new_path).or_else(|err| {
|
||||
match err.raw_os_error() {
|
||||
Some(code) => {
|
||||
debug!("path_link at fs::hard_link error code={:?}", code);
|
||||
match code as u32 {
|
||||
winerror::ERROR_ACCESS_DENIED => {
|
||||
// If an attempt is made to create a hard link to a directory, POSIX-compliant
|
||||
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
|
||||
// to `EACCES`. We detect and correct this case here.
|
||||
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
|
||||
return Err(Errno::Perm);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Err(err.into())
|
||||
}
|
||||
None => {
|
||||
log::debug!("Inconvertible OS error: {}", err);
|
||||
Err(Errno::Io)
|
||||
let err = match fs::hard_link(&old_path, &new_path) {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) => e,
|
||||
};
|
||||
if let Some(code) = err.raw_os_error() {
|
||||
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
|
||||
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
|
||||
// to `EACCES`. We detect and correct this case here.
|
||||
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
|
||||
return Err(Errno::Perm);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(err.into())
|
||||
}
|
||||
|
||||
pub(crate) fn open(
|
||||
resolved: PathGet,
|
||||
dirfd: &OsFile,
|
||||
path: &str,
|
||||
read: bool,
|
||||
write: bool,
|
||||
oflags: types::Oflags,
|
||||
fdflags: types::Fdflags,
|
||||
) -> Result<Descriptor> {
|
||||
) -> Result<OsHandle> {
|
||||
use winx::file::{AccessMode, CreationDisposition, Flags};
|
||||
|
||||
let is_trunc = oflags.contains(&types::Oflags::TRUNC);
|
||||
@@ -245,9 +233,7 @@ pub(crate) fn open(
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let path = resolved.concatenate()?;
|
||||
|
||||
let path = concatenate(dirfd, path)?;
|
||||
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
|
||||
Ok(file_type) => {
|
||||
// check if we are trying to open a symlink
|
||||
@@ -262,12 +248,18 @@ pub(crate) fn open(
|
||||
Err(err) => match err.raw_os_error() {
|
||||
Some(code) => {
|
||||
log::debug!("path_open at symlink_metadata error code={:?}", code);
|
||||
|
||||
if code as u32 != winerror::ERROR_FILE_NOT_FOUND {
|
||||
return Err(err.into());
|
||||
}
|
||||
// file not found, let it proceed to actually
|
||||
// trying to open it
|
||||
match code as u32 {
|
||||
winerror::ERROR_FILE_NOT_FOUND => {
|
||||
// file not found, let it proceed to actually
|
||||
// trying to open it
|
||||
}
|
||||
winerror::ERROR_INVALID_NAME => {
|
||||
// TODO rethink this. For now, migrate how we handled
|
||||
// it in `path::openat` on Windows.
|
||||
return Err(Errno::Notdir);
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
};
|
||||
}
|
||||
None => {
|
||||
log::debug!("Inconvertible OS error: {}", err);
|
||||
@@ -284,49 +276,25 @@ pub(crate) fn open(
|
||||
}
|
||||
|
||||
let flags: Flags = fdflags.into();
|
||||
opts.access_mode(access_mode.bits())
|
||||
let file = opts
|
||||
.access_mode(access_mode.bits())
|
||||
.custom_flags(flags.bits())
|
||||
.open(&path)
|
||||
.map(|f| OsHandle::from(f).into())
|
||||
.map_err(Into::into)
|
||||
.open(&path)?;
|
||||
let handle = OsHandle::from(file);
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode {
|
||||
let mut access_mode = AccessMode::READ_CONTROL;
|
||||
|
||||
// Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode
|
||||
// The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead
|
||||
// These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below)
|
||||
if read {
|
||||
access_mode.insert(AccessMode::FILE_GENERIC_READ);
|
||||
}
|
||||
|
||||
if write {
|
||||
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
|
||||
}
|
||||
|
||||
// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
|
||||
// This makes the handle "append only".
|
||||
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
|
||||
if fdflags.contains(&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> {
|
||||
pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result<usize> {
|
||||
use winx::file::get_file_path;
|
||||
|
||||
let path = resolved.concatenate()?;
|
||||
let path = concatenate(dirfd, path)?;
|
||||
let target_path = path.read_link()?;
|
||||
|
||||
// since on Windows we are effectively emulating 'at' syscalls
|
||||
// we need to strip the prefix from the absolute path
|
||||
// as otherwise we will error out since WASI is not capable
|
||||
// of dealing with absolute paths
|
||||
let dir_path = get_file_path(&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 target_path = target_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>> {
|
||||
if resolved.path().ends_with('/') {
|
||||
let suffix = resolved.path().trim_end_matches('/');
|
||||
concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
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 std::fs;
|
||||
|
||||
let old_path = resolved_old.concatenate()?;
|
||||
let new_path = resolved_new.concatenate()?;
|
||||
let old_path = concatenate(old_dirfd, old_path_)?;
|
||||
let new_path = concatenate(new_dirfd, new_path_)?;
|
||||
|
||||
// First sanity check: check we're not trying to rename dir to file or vice versa.
|
||||
// NB on Windows, the former is actually permitted [std::fs::rename].
|
||||
@@ -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
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -406,7 +370,9 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
||||
winerror::ERROR_INVALID_NAME => {
|
||||
// If source contains trailing slashes, check if we are dealing with
|
||||
// a file instead of a dir, and if so, throw ENOTDIR.
|
||||
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? {
|
||||
if let Some(path) =
|
||||
strip_trailing_slashes_and_concatenate(old_dirfd, old_path_)?
|
||||
{
|
||||
if path.is_file() {
|
||||
return Err(Errno::Notdir);
|
||||
}
|
||||
@@ -424,38 +390,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn filestat_get(
|
||||
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;
|
||||
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path_: &str) -> Result<()> {
|
||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||
|
||||
let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?;
|
||||
let new_path = resolved.concatenate()?;
|
||||
let old_path = concatenate(new_dirfd, Path::new(old_path))?;
|
||||
let new_path = concatenate(new_dirfd, new_path_)?;
|
||||
|
||||
// Windows distinguishes between file and directory symlinks.
|
||||
// If the source doesn't exist or is an exotic file type, we fall back
|
||||
@@ -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
|
||||
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() {
|
||||
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;
|
||||
|
||||
let path = resolved.concatenate()?;
|
||||
let path = concatenate(dirfd, path)?;
|
||||
let file_type = path
|
||||
.symlink_metadata()
|
||||
.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<()> {
|
||||
let path = resolved.concatenate()?;
|
||||
pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> {
|
||||
let path = concatenate(dirfd, path)?;
|
||||
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::sys::oshandle::AsFile;
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{debug, error, trace, warn};
|
||||
use std::convert::TryInto;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
|
||||
use std::sync::Mutex;
|
||||
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>) {
|
||||
let size = match &*event.descriptor.borrow() {
|
||||
Descriptor::OsHandle(os_handle) => {
|
||||
let handle = event
|
||||
.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 {
|
||||
os_handle.metadata().map(|m| m.len()).map_err(Into::into)
|
||||
file.as_file()
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.map_err(Into::into)
|
||||
} else {
|
||||
// The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and
|
||||
// the implementation on Unix just returns 0 here, so it's probably fine
|
||||
@@ -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.
|
||||
Descriptor::Stdin => Ok(1),
|
||||
OsHandle::Stdin => Ok(1),
|
||||
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
|
||||
Descriptor::Stdout | Descriptor::Stderr => Ok(0),
|
||||
Descriptor::VirtualFile(_) => {
|
||||
panic!("virtual files do not get rw events");
|
||||
}
|
||||
OsHandle::Stdout | OsHandle::Stderr => Ok(0),
|
||||
};
|
||||
|
||||
let new_event = make_rw_event(&event, size);
|
||||
@@ -201,21 +206,21 @@ pub(crate) fn oneoff(
|
||||
let mut pipe_events = vec![];
|
||||
|
||||
for event in fd_events {
|
||||
let descriptor = Rc::clone(&event.descriptor);
|
||||
match &*descriptor.borrow() {
|
||||
Descriptor::Stdin if event.r#type == types::Eventtype::FdRead => {
|
||||
stdin_events.push(event)
|
||||
}
|
||||
let handle = event
|
||||
.handle
|
||||
.as_any()
|
||||
.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
|
||||
// be no way of checking if a write to stdout would block.
|
||||
//
|
||||
// If stdin is polled for anything else then reading, then it is also
|
||||
// considered immediately ready, following the behavior on Linux.
|
||||
Descriptor::Stdin | Descriptor::Stderr | Descriptor::Stdout => {
|
||||
immediate_events.push(event)
|
||||
}
|
||||
Descriptor::OsHandle(os_handle) => {
|
||||
let ftype = unsafe { winx::file::get_file_type(os_handle.as_raw_handle()) }?;
|
||||
OsHandle::Stdin | OsHandle::Stderr | OsHandle::Stdout => immediate_events.push(event),
|
||||
OsHandle::OsFile(file) => {
|
||||
let ftype = unsafe { winx::file::get_file_type(file.as_raw_handle()) }?;
|
||||
if ftype.is_unknown() || ftype.is_char() {
|
||||
debug!("poll_oneoff: unsupported file type: {:?}", ftype);
|
||||
handle_error_event(event, Errno::Notsup, events);
|
||||
@@ -227,9 +232,6 @@ pub(crate) fn oneoff(
|
||||
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 filetime::FileTime;
|
||||
use log::trace;
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::hash_map::Entry;
|
||||
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
|
||||
/// 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.
|
||||
pub(crate) trait MovableFile {
|
||||
fn set_parent(&self, new_parent: Option<Box<dyn VirtualFile>>);
|
||||
}
|
||||
|
||||
/// `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()
|
||||
}
|
||||
fn set_parent(&self, new_parent: Option<Box<dyn Handle>>);
|
||||
}
|
||||
|
||||
pub trait FileContents {
|
||||
@@ -255,7 +135,7 @@ impl VecFileContents {
|
||||
/// of data and permissions on a filesystem.
|
||||
pub struct InMemoryFile {
|
||||
cursor: Cell<types::Filesize>,
|
||||
parent: Rc<RefCell<Option<Box<dyn VirtualFile>>>>,
|
||||
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
|
||||
fd_flags: Cell<types::Fdflags>,
|
||||
data: Rc<RefCell<Box<dyn FileContents>>>,
|
||||
}
|
||||
@@ -281,17 +161,16 @@ impl 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;
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualFile for InMemoryFile {
|
||||
fn fdstat_get(&self) -> types::Fdflags {
|
||||
self.fd_flags.get()
|
||||
impl Handle for InMemoryFile {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>> {
|
||||
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
||||
Ok(Box::new(Self {
|
||||
cursor: Cell::new(0),
|
||||
fd_flags: self.fd_flags.clone(),
|
||||
@@ -299,68 +178,113 @@ impl VirtualFile for InMemoryFile {
|
||||
data: Rc::clone(&self.data),
|
||||
}))
|
||||
}
|
||||
|
||||
fn readlinkat(&self, _path: &Path) -> Result<String> {
|
||||
// no symlink support, so always say it's invalid.
|
||||
Err(Errno::Notdir)
|
||||
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||
Ok(types::Filetype::RegularFile)
|
||||
}
|
||||
|
||||
fn openat(
|
||||
fn get_rights(&self) -> io::Result<EntryRights> {
|
||||
Ok(EntryRights::new(
|
||||
types::Rights::regular_file_base(),
|
||||
types::Rights::regular_file_inheriting(),
|
||||
))
|
||||
}
|
||||
// FdOps
|
||||
fn advise(
|
||||
&self,
|
||||
path: &Path,
|
||||
read: bool,
|
||||
write: bool,
|
||||
oflags: types::Oflags,
|
||||
fd_flags: types::Fdflags,
|
||||
) -> Result<Box<dyn VirtualFile>> {
|
||||
log::trace!(
|
||||
"InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}",
|
||||
path,
|
||||
read,
|
||||
write,
|
||||
oflags,
|
||||
fd_flags
|
||||
);
|
||||
_advice: types::Advice,
|
||||
_offset: types::Filesize,
|
||||
_len: types::Filesize,
|
||||
) -> Result<()> {
|
||||
// we'll just ignore advice for now, unless it's totally invalid
|
||||
Ok(())
|
||||
}
|
||||
fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> {
|
||||
let new_limit = offset.checked_add(len).ok_or(Errno::Fbig)?;
|
||||
let mut data = self.data.borrow_mut();
|
||||
|
||||
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 new_limit > data.max_size() {
|
||||
return Err(Errno::Fbig);
|
||||
}
|
||||
|
||||
if path == Path::new(".") {
|
||||
return self.try_clone().map_err(Into::into);
|
||||
} 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)
|
||||
if new_limit > data.size() {
|
||||
data.resize(new_limit)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_directory(&self, _path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlink_file(&self, _path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
fn fdstat_get(&self) -> Result<types::Fdflags> {
|
||||
Ok(self.fd_flags.get())
|
||||
}
|
||||
|
||||
fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<Option<Box<dyn VirtualFile>>> {
|
||||
fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> {
|
||||
self.fd_flags.set(fdflags);
|
||||
// We return None here to signal that the operation succeeded on the original
|
||||
// file descriptor and mutating the original WASI Descriptor is thus unnecessary.
|
||||
// This is needed as on Windows this operation required reopening a file. So we're
|
||||
// adhering to the common signature required across platforms.
|
||||
Ok(None)
|
||||
Ok(())
|
||||
}
|
||||
fn filestat_get(&self) -> Result<types::Filestat> {
|
||||
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 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);
|
||||
let mut data = self.data.borrow_mut();
|
||||
|
||||
@@ -406,110 +330,73 @@ impl VirtualFile for InMemoryFile {
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
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())
|
||||
// PathOps
|
||||
fn create_directory(&self, _path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
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(
|
||||
fn openat(
|
||||
&self,
|
||||
_advice: types::Advice,
|
||||
_offset: types::Filesize,
|
||||
_len: types::Filesize,
|
||||
path: &str,
|
||||
read: bool,
|
||||
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<()> {
|
||||
// we'll just ignore advice for now, unless it's totally invalid
|
||||
Ok(())
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> {
|
||||
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);
|
||||
}
|
||||
|
||||
if new_limit > data.size() {
|
||||
data.resize(new_limit)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result<usize> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
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 readlinkat(&self, _path: &str) -> Result<String> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
fn filestat_get(&self) -> Result<types::Filestat> {
|
||||
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 rename(&self, _old_path: &str, _new_handle: Box<dyn Handle>, _new_path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
fn get_file_type(&self) -> types::Filetype {
|
||||
types::Filetype::RegularFile
|
||||
fn remove_directory(&self, _path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
fn get_rights_base(&self) -> types::Rights {
|
||||
types::Rights::regular_file_base()
|
||||
fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
|
||||
fn get_rights_inheriting(&self) -> types::Rights {
|
||||
types::Rights::regular_file_inheriting()
|
||||
fn unlink_file(&self, _path: &str) -> Result<()> {
|
||||
Err(Errno::Notdir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,8 +405,8 @@ pub struct VirtualDir {
|
||||
writable: bool,
|
||||
// All copies of this `VirtualDir` must share `parent`, and changes in one copy's `parent`
|
||||
// must be reflected in all handles, so they share `Rc` of an underlying `parent`.
|
||||
parent: Rc<RefCell<Option<Box<dyn VirtualFile>>>>,
|
||||
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn VirtualFile>>>>,
|
||||
parent: Rc<RefCell<Option<Box<dyn Handle>>>>,
|
||||
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
|
||||
}
|
||||
|
||||
impl VirtualDir {
|
||||
@@ -563,7 +450,7 @@ impl 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;
|
||||
}
|
||||
}
|
||||
@@ -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`.
|
||||
const RESERVED_ENTRY_COUNT: u32 = 2;
|
||||
|
||||
impl VirtualFile for VirtualDir {
|
||||
fn try_clone(&self) -> io::Result<Box<dyn VirtualFile>> {
|
||||
impl Handle for VirtualDir {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
|
||||
Ok(Box::new(Self {
|
||||
writable: self.writable,
|
||||
parent: Rc::clone(&self.parent),
|
||||
entries: Rc::clone(&self.entries),
|
||||
}))
|
||||
}
|
||||
|
||||
fn readlinkat(&self, _path: &Path) -> Result<String> {
|
||||
// Files are not symbolic links or directories, faithfully report Notdir.
|
||||
Err(Errno::Notdir)
|
||||
fn get_file_type(&self) -> io::Result<types::Filetype> {
|
||||
Ok(types::Filetype::Directory)
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&self,
|
||||
path: &Path,
|
||||
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 => {
|
||||
return self.try_clone().map_err(Into::into);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// openat may have been passed a path with a trailing slash, but files are mapped to paths
|
||||
// with trailing slashes normalized out.
|
||||
let file_name = path.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 get_rights(&self) -> io::Result<EntryRights> {
|
||||
Ok(EntryRights::new(
|
||||
types::Rights::directory_base(),
|
||||
types::Rights::directory_inheriting(),
|
||||
))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
// FdOps
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
cookie: types::Dircookie,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
|
||||
struct VirtualDirIter {
|
||||
start: u32,
|
||||
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn VirtualFile>>>>,
|
||||
entries: Rc<RefCell<HashMap<PathBuf, Box<dyn Handle>>>>,
|
||||
}
|
||||
impl Iterator for VirtualDirIter {
|
||||
type Item = Result<(types::Dirent, String)>;
|
||||
@@ -821,7 +555,7 @@ impl VirtualFile for VirtualDir {
|
||||
let dirent = || -> Result<types::Dirent> {
|
||||
let dirent = types::Dirent {
|
||||
d_namlen: name.len().try_into()?,
|
||||
d_type: file.get_file_type(),
|
||||
d_type: file.get_file_type()?,
|
||||
d_ino: 0,
|
||||
d_next: self.start as u64,
|
||||
};
|
||||
@@ -843,30 +577,185 @@ impl VirtualFile for VirtualDir {
|
||||
entries: Rc::clone(&self.entries),
|
||||
}))
|
||||
}
|
||||
|
||||
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)
|
||||
// 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 get_file_type(&self) -> types::Filetype {
|
||||
types::Filetype::Directory
|
||||
if path == "." {
|
||||
return self.try_clone().map_err(Into::into);
|
||||
} else if path == ".." {
|
||||
match &*self.parent.borrow() {
|
||||
Some(file) => {
|
||||
return file.try_clone().map_err(Into::into);
|
||||
}
|
||||
None => {
|
||||
return self.try_clone().map_err(Into::into);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// openat may have been passed a path with a trailing slash, but files are mapped to paths
|
||||
// with trailing slashes normalized out.
|
||||
let file_name = Path::new(path).file_name().ok_or(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);
|
||||
|
||||
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 get_rights_base(&self) -> types::Rights {
|
||||
types::Rights::directory_base()
|
||||
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);
|
||||
}
|
||||
|
||||
fn get_rights_inheriting(&self) -> types::Rights {
|
||||
types::Rights::directory_inheriting()
|
||||
// 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