Introduce strongly-typed system primitives (#1561)

* Introduce strongly-typed system primitives

This commit does a lot of reshuffling and even some more. It introduces
strongly-typed system primitives which are: `OsFile`, `OsDir`, `Stdio`,
and `OsOther`. Those primitives are separate structs now, each implementing
a subset of `Handle` methods, rather than all being an enumeration of some
supertype such as `OsHandle`. To summarise the structs:

* `OsFile` represents a regular file, and implements fd-ops
  of `Handle` trait
* `OsDir` represents a directory, and primarily implements path-ops, plus
  `readdir` and some common fd-ops such as `fdstat`, etc.
* `Stdio` represents a stdio handle, and implements a subset of fd-ops
  such as `fdstat` _and_ `read_` and `write_vectored` calls
* `OsOther` currently represents anything else and implements a set similar
  to that implemented by `Stdio`

This commit is effectively an experiment and an excercise into better
understanding what's going on for each OS resource/type under-the-hood.
It's meant to give us some intuition in order to move on with the idea
of having strongly-typed handles in WASI both in the syscall impl as well
as at the libc level.

Some more minor changes include making `OsHandle` represent an OS-specific
wrapper for a raw OS handle (Unix fd or Windows handle). Also, since `OsDir`
is tricky across OSes, we also have a supertype of `OsHandle` called
`OsDirHandle` which may store a `DIR*` stream pointer (mainly BSD). Last but not
least, the `Filetype` and `Rights` are now computed when the resource is created,
rather than every time we call `Handle::get_file_type` and `Handle::get_rights`.
Finally, in order to facilitate the latter, I've converted `EntryRights` into
`HandleRights` and pushed them into each `Handle` implementor.

* Do not adjust rights on Stdio

* Clean up testing for TTY and escaping writes

* Implement AsFile for dyn Handle

This cleans up a lot of repeating boilerplate code todo with
dynamic dispatch.

* Delegate definition of OsDir to OS-specific modules

Delegates defining `OsDir` struct to OS-specific modules (BSD, Linux,
Emscripten, Windows). This way, `OsDir` can safely re-use `OsHandle`
for raw OS handle storage, and can store some aux data such as an
initialized stream ptr in case of BSD. As a result, we can safely
get rid of `OsDirHandle` which IMHO was causing unnecessary noise and
overcomplicating the design. On the other hand, delegating definition
of `OsDir` to OS-specific modules isn't super clean in and of itself
either. Perhaps there's a better way of handling this?

* Check if filetype of OS handle matches WASI filetype when creating

It seems prudent to check if the passed in `File` instance is of
type matching that of the requested WASI filetype. In other words,
we'd like to avoid situations where `OsFile` is created from a
pipe.

* Make AsFile fallible

Return `EBADF` in `AsFile` in case a `Handle` cannot be made into
a `std::fs::File`.

* Remove unnecessary as_file conversion

* Remove unnecessary check for TTY for Stdio handle type

* Fix incorrect stdio ctors on Unix

* Split Stdio into three separate types: Stdin, Stdout, Stderr

* Rename PendingEntry::File to PendingEntry::OsHandle to avoid confusion

* Rename OsHandle to RawOsHandle

Also, since `RawOsHandle` on *nix doesn't need interior mutability
wrt the inner raw file descriptor, we can safely swap the `RawFd`
for `File` instance.

* Add docs explaining what OsOther is

* Allow for stdio to be non-character-device (e.g., piped)

* Return error on bad preopen rather than panic
This commit is contained in:
Jakub Konka
2020-05-08 01:00:14 +02:00
committed by GitHub
parent 528d3c1355
commit cbf7cbfa39
39 changed files with 1643 additions and 1073 deletions

View File

@@ -0,0 +1,133 @@
use super::sys_impl::oshandle::RawOsHandle;
use super::{fd, AsFile};
use crate::handle::{Handle, HandleRights};
use crate::wasi::{types, Errno, Result};
use std::any::Any;
use std::cell::Cell;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::Deref;
#[derive(Debug)]
pub(crate) struct OsFile {
rights: Cell<HandleRights>,
handle: RawOsHandle,
}
impl OsFile {
pub(super) fn new(rights: HandleRights, handle: RawOsHandle) -> Self {
let rights = Cell::new(rights);
Self { rights, handle }
}
}
impl Deref for OsFile {
type Target = RawOsHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Handle for OsFile {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
let handle = self.handle.try_clone()?;
let rights = self.rights.clone();
Ok(Box::new(Self { rights, handle }))
}
fn get_file_type(&self) -> types::Filetype {
types::Filetype::RegularFile
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn advise(
&self,
advice: types::Advice,
offset: types::Filesize,
len: types::Filesize,
) -> Result<()> {
fd::advise(self, 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_handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
self.handle.update_from(new_handle);
}
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_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_file()?;
let cur_pos = fd.seek(SeekFrom::Current(0))?;
fd.seek(SeekFrom::Start(offset))?;
let nread = self.read_vectored(buf)?;
fd.seek(SeekFrom::Start(cur_pos))?;
Ok(nread)
}
fn pwritev(&self, buf: &[io::IoSlice], offset: u64) -> Result<usize> {
let mut fd: &File = &*self.as_file()?;
let cur_pos = fd.seek(SeekFrom::Current(0))?;
fd.seek(SeekFrom::Start(offset))?;
let nwritten = self.write_vectored(&buf)?;
fd.seek(SeekFrom::Start(cur_pos))?;
Ok(nwritten)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = self.as_file()?.read_vectored(iovs)?;
Ok(nread)
}
fn seek(&self, offset: SeekFrom) -> Result<u64> {
let pos = self.as_file()?.seek(offset)?;
Ok(pos)
}
fn sync(&self) -> Result<()> {
self.as_file()?.sync_all()?;
Ok(())
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let nwritten = self.as_file()?.write_vectored(&iovs)?;
Ok(nwritten)
}
}