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:
@@ -1,112 +1,22 @@
|
||||
use crate::entry::{Descriptor, Entry, EntryRights};
|
||||
use crate::sys;
|
||||
use crate::sys::entry::OsHandle;
|
||||
use crate::entry::{Entry, EntryRights};
|
||||
use crate::handle::Handle;
|
||||
use crate::wasi::{types, Errno, Result};
|
||||
use std::path::{Component, Path};
|
||||
use std::str;
|
||||
use wiggle::{GuestBorrows, GuestPtr};
|
||||
|
||||
pub(crate) use sys::path::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PathGet {
|
||||
dirfd: Descriptor,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl PathGet {
|
||||
pub(crate) fn dirfd(&self) -> &Descriptor {
|
||||
&self.dirfd
|
||||
}
|
||||
|
||||
pub(crate) fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub(crate) fn create_directory(self) -> Result<()> {
|
||||
match &self.dirfd {
|
||||
Descriptor::OsHandle(file) => create_directory(&file, &self.path),
|
||||
Descriptor::VirtualFile(virt) => virt.create_directory(&Path::new(&self.path)),
|
||||
other => {
|
||||
panic!("invalid descriptor to create directory: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn open_with(
|
||||
self,
|
||||
read: bool,
|
||||
write: bool,
|
||||
oflags: types::Oflags,
|
||||
fs_flags: types::Fdflags,
|
||||
) -> Result<Descriptor> {
|
||||
match &self.dirfd {
|
||||
Descriptor::OsHandle(_) => {
|
||||
open(self, read, write, oflags, fs_flags).map_err(Into::into)
|
||||
}
|
||||
Descriptor::VirtualFile(virt) => virt
|
||||
.openat(Path::new(&self.path), read, write, oflags, fs_flags)
|
||||
.map(|file| Descriptor::VirtualFile(file)),
|
||||
other => {
|
||||
panic!("invalid descriptor to path_open: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PathRef<'a, 'b> {
|
||||
dirfd: &'a Descriptor,
|
||||
path: &'b str,
|
||||
}
|
||||
|
||||
impl<'a, 'b> PathRef<'a, 'b> {
|
||||
fn new(dirfd: &'a Descriptor, path: &'b str) -> Self {
|
||||
PathRef { dirfd, path }
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<Descriptor> {
|
||||
match self.dirfd {
|
||||
Descriptor::OsHandle(file) => Ok(Descriptor::OsHandle(OsHandle::from(openat(
|
||||
&file, &self.path,
|
||||
)?))),
|
||||
Descriptor::VirtualFile(virt) => virt
|
||||
.openat(
|
||||
Path::new(&self.path),
|
||||
false,
|
||||
false,
|
||||
types::Oflags::DIRECTORY,
|
||||
types::Fdflags::empty(),
|
||||
)
|
||||
.map(|file| Descriptor::VirtualFile(file)),
|
||||
other => {
|
||||
panic!("invalid descriptor for open: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn readlink(&self) -> Result<String> {
|
||||
match self.dirfd {
|
||||
Descriptor::OsHandle(file) => readlinkat(file, self.path),
|
||||
Descriptor::VirtualFile(virt) => {
|
||||
virt.readlinkat(Path::new(self.path)).map_err(Into::into)
|
||||
}
|
||||
other => {
|
||||
panic!("invalid descriptor for readlink: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) use crate::sys::path::{from_host, open_rights};
|
||||
|
||||
/// Normalizes a path to ensure that the target path is located under the directory provided.
|
||||
///
|
||||
/// This is a workaround for not having Capsicum support in the OS.
|
||||
pub(crate) fn get(
|
||||
fe: &Entry,
|
||||
entry: &Entry,
|
||||
required_rights: &EntryRights,
|
||||
dirflags: types::Lookupflags,
|
||||
path: &GuestPtr<'_, str>,
|
||||
needs_final_component: bool,
|
||||
) -> Result<PathGet> {
|
||||
) -> Result<(Box<dyn Handle>, String)> {
|
||||
const MAX_SYMLINK_EXPANSIONS: usize = 128;
|
||||
|
||||
// Extract path as &str from guest's memory.
|
||||
@@ -123,17 +33,13 @@ pub(crate) fn get(
|
||||
return Err(Errno::Ilseq);
|
||||
}
|
||||
|
||||
if fe.file_type != types::Filetype::Directory {
|
||||
if entry.file_type != types::Filetype::Directory {
|
||||
// if `dirfd` doesn't refer to a directory, return `Notdir`.
|
||||
return Err(Errno::Notdir);
|
||||
}
|
||||
|
||||
let desc = fe.as_descriptor(required_rights)?;
|
||||
let dirfd = match &*desc.borrow() {
|
||||
Descriptor::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into())?,
|
||||
Descriptor::VirtualFile(virt) => virt.try_clone().map(Descriptor::VirtualFile)?,
|
||||
_ => return Err(Errno::Badf),
|
||||
};
|
||||
let handle = entry.as_handle(required_rights)?;
|
||||
let dirfd = handle.try_clone()?;
|
||||
|
||||
// Stack of directory file descriptors. Index 0 always corresponds with the directory provided
|
||||
// to this function. Entering a directory causes a file descriptor to be pushed, while handling
|
||||
@@ -198,9 +104,14 @@ pub(crate) fn get(
|
||||
}
|
||||
|
||||
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
|
||||
match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head)
|
||||
.open()
|
||||
{
|
||||
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||
match fd.openat(
|
||||
&head,
|
||||
false,
|
||||
false,
|
||||
types::Oflags::DIRECTORY,
|
||||
types::Fdflags::empty(),
|
||||
) {
|
||||
Ok(new_dir) => {
|
||||
dir_stack.push(new_dir);
|
||||
}
|
||||
@@ -211,12 +122,8 @@ pub(crate) fn get(
|
||||
// this with ENOTDIR because of the O_DIRECTORY flag.
|
||||
{
|
||||
// attempt symlink expansion
|
||||
let mut link_path = PathRef::new(
|
||||
dir_stack.last().ok_or(Errno::Notcapable)?,
|
||||
&head,
|
||||
)
|
||||
.readlink()?;
|
||||
|
||||
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||
let mut link_path = fd.readlinkat(&head)?;
|
||||
symlink_expansions += 1;
|
||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||
return Err(Errno::Loop);
|
||||
@@ -246,9 +153,8 @@ pub(crate) fn get(
|
||||
{
|
||||
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
|
||||
// symlink expansion
|
||||
match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head)
|
||||
.readlink()
|
||||
{
|
||||
let fd = dir_stack.last().ok_or(Errno::Notcapable)?;
|
||||
match fd.readlinkat(&head) {
|
||||
Ok(mut link_path) => {
|
||||
symlink_expansions += 1;
|
||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||
@@ -282,20 +188,14 @@ pub(crate) fn get(
|
||||
}
|
||||
|
||||
// not a symlink, so we're done;
|
||||
return Ok(PathGet {
|
||||
dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?,
|
||||
path: head,
|
||||
});
|
||||
return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, head));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// no further components to process. means we've hit a case like "." or "a/..", or if the
|
||||
// input path has trailing slashes and `needs_final_component` is not set
|
||||
return Ok(PathGet {
|
||||
dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?,
|
||||
path: String::from("."),
|
||||
});
|
||||
return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, String::from(".")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user