Skip to content

Commit a4fd811

Browse files
author
Jakub Konka
committed
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.
1 parent 967827f commit a4fd811

39 files changed

+1532
-1008
lines changed

crates/wasi-common/src/ctx.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use crate::entry::{Entry, EntryHandle};
22
use crate::fdpool::FdPool;
33
use crate::handle::Handle;
4-
use crate::sys::oshandle::{OsHandle, OsHandleExt};
4+
use crate::sys::osfile::{OsFile, OsFileExt};
5+
use crate::sys::stdio::{Stdio, StdioExt};
56
use crate::virtfs::{VirtualDir, VirtualDirEntry};
67
use crate::wasi::types;
78
use crate::wasi::{Errno, Result};
89
use std::borrow::Borrow;
910
use std::cell::RefCell;
1011
use std::collections::HashMap;
12+
use std::convert::TryFrom;
1113
use std::ffi::{self, CString, OsString};
1214
use std::fs::File;
1315
use std::path::{Path, PathBuf};
@@ -43,7 +45,7 @@ pub enum WasiCtxBuilderError {
4345
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
4446

4547
enum PendingEntry {
46-
Thunk(fn() -> io::Result<OsHandle>),
48+
Thunk(fn() -> io::Result<Box<dyn Handle>>),
4749
File(File),
4850
}
4951

@@ -53,7 +55,7 @@ impl std::fmt::Debug for PendingEntry {
5355
Self::Thunk(f) => write!(
5456
fmt,
5557
"PendingEntry::Thunk({:p})",
56-
f as *const fn() -> io::Result<OsHandle>
58+
f as *const fn() -> io::Result<Box<dyn Handle>>
5759
),
5860
Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f),
5961
}
@@ -118,9 +120,9 @@ pub struct WasiCtxBuilder {
118120
impl WasiCtxBuilder {
119121
/// Builder for a new `WasiCtx`.
120122
pub fn new() -> Self {
121-
let stdin = Some(PendingEntry::Thunk(OsHandle::from_null));
122-
let stdout = Some(PendingEntry::Thunk(OsHandle::from_null));
123-
let stderr = Some(PendingEntry::Thunk(OsHandle::from_null));
123+
let stdin = Some(PendingEntry::Thunk(OsFile::from_null));
124+
let stdout = Some(PendingEntry::Thunk(OsFile::from_null));
125+
let stderr = Some(PendingEntry::Thunk(OsFile::from_null));
124126

125127
Self {
126128
stdin,
@@ -167,27 +169,27 @@ impl WasiCtxBuilder {
167169

168170
/// Inherit stdin from the host process.
169171
pub fn inherit_stdin(&mut self) -> &mut Self {
170-
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
172+
self.stdin = Some(PendingEntry::Thunk(Stdio::stdin));
171173
self
172174
}
173175

174176
/// Inherit stdout from the host process.
175177
pub fn inherit_stdout(&mut self) -> &mut Self {
176-
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
178+
self.stdout = Some(PendingEntry::Thunk(Stdio::stdout));
177179
self
178180
}
179181

180182
/// Inherit stdout from the host process.
181183
pub fn inherit_stderr(&mut self) -> &mut Self {
182-
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
184+
self.stderr = Some(PendingEntry::Thunk(Stdio::stderr));
183185
self
184186
}
185187

186188
/// Inherit the stdin, stdout, and stderr streams from the host process.
187189
pub fn inherit_stdio(&mut self) -> &mut Self {
188-
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
189-
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
190-
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
190+
self.stdin = Some(PendingEntry::Thunk(Stdio::stdin));
191+
self.stdout = Some(PendingEntry::Thunk(Stdio::stdout));
192+
self.stderr = Some(PendingEntry::Thunk(Stdio::stderr));
191193
self
192194
}
193195

@@ -251,7 +253,7 @@ impl WasiCtxBuilder {
251253
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
252254
self.preopens.as_mut().unwrap().push((
253255
guest_path.as_ref().to_owned(),
254-
Box::new(OsHandle::from(dir)),
256+
<Box<dyn Handle>>::try_from(dir).expect("valid handle"),
255257
));
256258
self
257259
}
@@ -336,15 +338,16 @@ impl WasiCtxBuilder {
336338
log::debug!("WasiCtx inserting entry {:?}", pending);
337339
let fd = match pending {
338340
PendingEntry::Thunk(f) => {
339-
let handle = EntryHandle::new(f()?);
340-
let entry = Entry::from(handle)?;
341+
let handle = EntryHandle::from(f()?);
342+
let entry = Entry::new(handle);
341343
entries
342344
.insert(entry)
343345
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
344346
}
345347
PendingEntry::File(f) => {
346-
let handle = EntryHandle::new(OsHandle::from(f));
347-
let entry = Entry::from(handle)?;
348+
let handle = <Box<dyn Handle>>::try_from(f)?;
349+
let handle = EntryHandle::from(handle);
350+
let entry = Entry::new(handle);
348351
entries
349352
.insert(entry)
350353
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
@@ -359,7 +362,7 @@ impl WasiCtxBuilder {
359362
}
360363

361364
let handle = EntryHandle::from(dir);
362-
let mut entry = Entry::from(handle)?;
365+
let mut entry = Entry::new(handle);
363366
entry.preopen_path = Some(guest_path);
364367
let fd = entries
365368
.insert(entry)

crates/wasi-common/src/entry.rs

Lines changed: 31 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
1-
use crate::handle::Handle;
1+
use crate::handle::{Handle, HandleRights};
22
use crate::wasi::types::{Filetype, Rights};
33
use crate::wasi::{Errno, Result};
4-
use std::cell::Cell;
54
use std::ops::Deref;
65
use std::path::PathBuf;
76
use std::rc::Rc;
8-
use std::{fmt, io};
97

108
pub(crate) struct EntryHandle(Rc<dyn Handle>);
119

1210
impl EntryHandle {
13-
pub(crate) fn new<T: Handle + 'static>(handle: T) -> Self {
14-
Self(Rc::new(handle))
15-
}
16-
1711
pub(crate) fn get(&self) -> Self {
1812
Self(Rc::clone(&self.0))
1913
}
@@ -33,118 +27,79 @@ impl Deref for EntryHandle {
3327
}
3428
}
3529

36-
/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
37-
/// certain rights `rights` in order to be accessed correctly.
30+
/// An abstraction struct serving as a wrapper for a `Handle` object.
3831
///
39-
/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or
40-
/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` method
32+
/// Here, the `handle` field stores an instance of `Handle` type (such as a file descriptor, or
33+
/// stdin handle), and accessing it can only be done via the provided `Entry::as_handle` method
4134
/// which require a set of base and inheriting rights to be specified, verifying whether the stored
42-
/// `Descriptor` object is valid for the rights specified.
35+
/// `Handle` object is valid for the rights specified.
4336
pub(crate) struct Entry {
44-
pub(crate) file_type: Filetype,
4537
handle: EntryHandle,
46-
pub(crate) rights: Cell<EntryRights>,
4738
pub(crate) preopen_path: Option<PathBuf>,
4839
// TODO: directories
4940
}
5041

51-
/// Represents rights of an `Entry` entity, either already held or
52-
/// required.
53-
#[derive(Debug, Copy, Clone)]
54-
pub(crate) struct EntryRights {
55-
pub(crate) base: Rights,
56-
pub(crate) inheriting: Rights,
57-
}
58-
59-
impl EntryRights {
60-
pub(crate) fn new(base: Rights, inheriting: Rights) -> Self {
61-
Self { base, inheriting }
62-
}
63-
64-
/// Create new `EntryRights` instance from `base` rights only, keeping
65-
/// `inheriting` set to none.
66-
pub(crate) fn from_base(base: Rights) -> Self {
67-
Self {
68-
base,
69-
inheriting: Rights::empty(),
70-
}
71-
}
72-
73-
/// Create new `EntryRights` instance with both `base` and `inheriting`
74-
/// rights set to none.
75-
pub(crate) fn empty() -> Self {
42+
impl Entry {
43+
pub(crate) fn new(handle: EntryHandle) -> Self {
44+
let preopen_path = None;
7645
Self {
77-
base: Rights::empty(),
78-
inheriting: Rights::empty(),
46+
handle,
47+
preopen_path,
7948
}
8049
}
8150

82-
/// Check if `other` is a subset of those rights.
83-
pub(crate) fn contains(&self, other: &Self) -> bool {
84-
self.base.contains(&other.base) && self.inheriting.contains(&other.inheriting)
51+
pub(crate) fn get_file_type(&self) -> Filetype {
52+
self.handle.get_file_type()
8553
}
86-
}
8754

88-
impl fmt::Display for EntryRights {
89-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90-
write!(
91-
f,
92-
"EntryRights {{ base: {}, inheriting: {} }}",
93-
self.base, self.inheriting
94-
)
55+
pub(crate) fn get_rights(&self) -> HandleRights {
56+
self.handle.get_rights()
9557
}
96-
}
9758

98-
impl Entry {
99-
pub(crate) fn from(handle: EntryHandle) -> io::Result<Self> {
100-
let file_type = handle.get_file_type()?;
101-
let rights = handle.get_rights()?;
102-
Ok(Self {
103-
file_type,
104-
handle,
105-
rights: Cell::new(rights),
106-
preopen_path: None,
107-
})
59+
pub(crate) fn set_rights(&self, rights: HandleRights) {
60+
self.handle.set_rights(rights)
10861
}
10962

110-
/// Convert this `Entry` into a host `Descriptor` object provided the specified
63+
/// Convert this `Entry` into a `Handle` object provided the specified
11164
/// `rights` rights are set on this `Entry` object.
11265
///
113-
/// The `Entry` can only be converted into a valid `Descriptor` object if
66+
/// The `Entry` can only be converted into a valid `Handle` object if
11467
/// the specified set of base rights, and inheriting rights encapsulated within `rights`
115-
/// `EntryRights` structure is a subset of rights attached to this `Entry`. The check is
68+
/// `HandleRights` structure is a subset of rights attached to this `Entry`. The check is
11669
/// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable`
11770
/// is returned.
118-
pub(crate) fn as_handle(&self, rights: &EntryRights) -> Result<EntryHandle> {
71+
pub(crate) fn as_handle(&self, rights: &HandleRights) -> Result<EntryHandle> {
11972
self.validate_rights(rights)?;
12073
Ok(self.handle.get())
12174
}
12275

123-
/// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if
76+
/// Check if this `Entry` object satisfies the specified `HandleRights`; i.e., if
12477
/// rights attached to this `Entry` object are a superset.
12578
///
12679
/// Upon unsuccessful check, `Errno::Notcapable` is returned.
127-
pub(crate) fn validate_rights(&self, rights: &EntryRights) -> Result<()> {
128-
if self.rights.get().contains(rights) {
80+
pub(crate) fn validate_rights(&self, rights: &HandleRights) -> Result<()> {
81+
let this_rights = self.handle.get_rights();
82+
if this_rights.contains(rights) {
12983
Ok(())
13084
} else {
13185
log::trace!(
13286
" | validate_rights failed: required rights = {}; actual rights = {}",
13387
rights,
134-
self.rights.get(),
88+
this_rights,
13589
);
13690
Err(Errno::Notcapable)
13791
}
13892
}
13993

94+
// TODO we should probably have a separate struct representing
95+
// char device
14096
/// Test whether this descriptor is considered a tty within WASI.
14197
/// Note that since WASI itself lacks an `isatty` syscall and relies
14298
/// on a conservative approximation, we use the same approximation here.
14399
pub(crate) fn isatty(&self) -> bool {
144-
self.file_type == Filetype::CharacterDevice
145-
&& self
146-
.rights
147-
.get()
148-
.contains(&EntryRights::from_base(Rights::FD_SEEK | Rights::FD_TELL))
100+
let file_type = self.handle.get_file_type();
101+
let rights = self.handle.get_rights();
102+
let required_rights = HandleRights::from_base(Rights::FD_SEEK | Rights::FD_TELL);
103+
file_type == Filetype::CharacterDevice && rights.contains(&required_rights)
149104
}
150105
}

crates/wasi-common/src/handle.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,65 @@
1-
use crate::entry::EntryRights;
2-
use crate::wasi::{types, Errno, Result};
1+
use crate::wasi::types::{self, Rights};
2+
use crate::wasi::{Errno, Result};
33
use std::any::Any;
4+
use std::fmt;
45
use std::io::{self, SeekFrom};
56

7+
/// Represents rights of a `Handle`, either already held or required.
8+
#[derive(Debug, Copy, Clone)]
9+
pub(crate) struct HandleRights {
10+
pub(crate) base: Rights,
11+
pub(crate) inheriting: Rights,
12+
}
13+
14+
impl HandleRights {
15+
pub(crate) fn new(base: Rights, inheriting: Rights) -> Self {
16+
Self { base, inheriting }
17+
}
18+
19+
/// Create new `HandleRights` instance from `base` rights only, keeping
20+
/// `inheriting` set to none.
21+
pub(crate) fn from_base(base: Rights) -> Self {
22+
Self {
23+
base,
24+
inheriting: Rights::empty(),
25+
}
26+
}
27+
28+
/// Create new `HandleRights` instance with both `base` and `inheriting`
29+
/// rights set to none.
30+
pub(crate) fn empty() -> Self {
31+
Self {
32+
base: Rights::empty(),
33+
inheriting: Rights::empty(),
34+
}
35+
}
36+
37+
/// Check if `other` is a subset of those rights.
38+
pub(crate) fn contains(&self, other: &Self) -> bool {
39+
self.base.contains(&other.base) && self.inheriting.contains(&other.inheriting)
40+
}
41+
}
42+
43+
impl fmt::Display for HandleRights {
44+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45+
write!(
46+
f,
47+
"HandleRights {{ base: {}, inheriting: {} }}",
48+
self.base, self.inheriting
49+
)
50+
}
51+
}
52+
653
pub(crate) trait Handle {
754
fn as_any(&self) -> &dyn Any;
855
fn try_clone(&self) -> io::Result<Box<dyn Handle>>;
9-
fn get_file_type(&self) -> io::Result<types::Filetype>;
10-
fn get_rights(&self) -> io::Result<EntryRights> {
11-
Ok(EntryRights::empty())
56+
fn get_file_type(&self) -> types::Filetype;
57+
fn get_rights(&self) -> HandleRights {
58+
HandleRights::empty()
1259
}
60+
fn set_rights(&self, rights: HandleRights);
1361
fn is_directory(&self) -> bool {
14-
if let Ok(ft) = self.get_file_type() {
15-
return ft == types::Filetype::Directory;
16-
}
17-
false
62+
self.get_file_type() == types::Filetype::Directory
1863
}
1964
// TODO perhaps should be a separate trait?
2065
// FdOps

crates/wasi-common/src/path.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::entry::{Entry, EntryRights};
2-
use crate::handle::Handle;
1+
use crate::entry::Entry;
2+
use crate::handle::{Handle, HandleRights};
33
use crate::wasi::{types, Errno, Result};
44
use std::path::{Component, Path};
55
use std::str;
@@ -12,7 +12,7 @@ pub(crate) use crate::sys::path::{from_host, open_rights};
1212
/// This is a workaround for not having Capsicum support in the OS.
1313
pub(crate) fn get(
1414
entry: &Entry,
15-
required_rights: &EntryRights,
15+
required_rights: &HandleRights,
1616
dirflags: types::Lookupflags,
1717
path: &GuestPtr<'_, str>,
1818
needs_final_component: bool,
@@ -33,7 +33,7 @@ pub(crate) fn get(
3333
return Err(Errno::Ilseq);
3434
}
3535

36-
if entry.file_type != types::Filetype::Directory {
36+
if entry.get_file_type() != types::Filetype::Directory {
3737
// if `dirfd` doesn't refer to a directory, return `Notdir`.
3838
return Err(Errno::Notdir);
3939
}

0 commit comments

Comments
 (0)