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:
Jakub Konka
2020-04-09 22:18:19 +02:00
committed by GitHub
parent 6fd0451bc3
commit de919382b3
34 changed files with 1807 additions and 1964 deletions

View File

@@ -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;

View 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)
}
}

View 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
}
}

View 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);

View File

@@ -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;

View File

@@ -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))
}

View File

@@ -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))
}))
})))
}

View File

@@ -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;

View 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)
}
}

View 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
}
}

View File

@@ -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");
}
}
}

View File

@@ -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)

View 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))
}
}

View 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(())
}

View File

@@ -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
};