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:
17
crates/wasi-common/src/sys/clock.rs
Normal file
17
crates/wasi-common/src/sys/clock.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::wasi::types::{Subclockflags, SubscriptionClock};
|
||||
use crate::wasi::{Errno, Result};
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub(crate) use super::sys_impl::clock::*;
|
||||
|
||||
pub(crate) fn to_relative_ns_delay(clock: &SubscriptionClock) -> Result<u128> {
|
||||
if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME {
|
||||
return Ok(u128::from(clock.timeout));
|
||||
}
|
||||
let now: u128 = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map_err(|_| Errno::Notcapable)?
|
||||
.as_nanos();
|
||||
let deadline = u128::from(clock.timeout);
|
||||
Ok(deadline.saturating_sub(now))
|
||||
}
|
||||
44
crates/wasi-common/src/sys/fd.rs
Normal file
44
crates/wasi-common/src/sys/fd.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
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 super::sys_impl::fd::*;
|
||||
|
||||
pub(crate) fn filestat_set_times(
|
||||
file: &File,
|
||||
st_atim: types::Timestamp,
|
||||
st_mtim: types::Timestamp,
|
||||
fst_flags: types::Fstflags,
|
||||
) -> Result<()> {
|
||||
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 atim = if set_atim {
|
||||
let time = UNIX_EPOCH + Duration::from_nanos(st_atim);
|
||||
Some(FileTime::from_system_time(time))
|
||||
} else if set_atim_now {
|
||||
let time = SystemTime::now();
|
||||
Some(FileTime::from_system_time(time))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mtim = if set_mtim {
|
||||
let time = UNIX_EPOCH + Duration::from_nanos(st_mtim);
|
||||
Some(FileTime::from_system_time(time))
|
||||
} else if set_mtim_now {
|
||||
let time = SystemTime::now();
|
||||
Some(FileTime::from_system_time(time))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
set_file_handle_times(file, atim, mtim)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,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");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user