Skip to content
This repository was archived by the owner on Nov 9, 2019. It is now read-only.

Commit 2fe3530

Browse files
acfoltzerkubkon
authored andcommitted
Improvements to WasiCtxBuilder, and a couple bug fixes (#175)
* fix Linux `isatty` implementation * defer `WasiCtxBuilder` errors to `build()`; don't change API yet This changes the fields on the builder to types that let the various `.arg()`, `.env()`, etc methods infallible, so we don't have to worry about handling any errors till we actually build. This reduces line noise when using a builder in a downstream application. Deferring the processing of the builder fields also has the advantage of eliminating the opening and closing of `/dev/null` for the default stdio file descriptors unless they're actually used by the resulting `WasiCtx`. Unicode errors when inheriting arguments and environment variables no longer cause a panic, but instead go through `OsString`. We return `ENOTCAPABLE` at the end if there are NULs, or if UTF-8 conversion fails on Windows. This also changes the bounds on some of the methods from `AsRef<str>` to `AsRef<[u8]>`. This shouldn't break any existing code, but allows more flexibility when providing arguments. Depending on the outcome of https://github.com/WebAssembly/WASI/issues/8 we may eventually want to require these bytes be UTF-8, so we might want to revisit this later. Finally, this fixes a tiny bug that could arise if we had exactly the maximum number of file descriptors when populating the preopens. * make `WasiCtxBuilder` method types less restrictive This is a separate commit, since it changes the interface that downstream clients have to use, and therefore requires a different commit of `wasmtime` for testing. That `wasmtime` commit is currently on my private fork, so this will need to be amended before merging. Now that failures are deferred until `WasiCtxBuilder::build()`, we don't need to have `Result` types on the other methods any longer. Additionally, using `IntoIterator` rather than `Iterator` as the trait bound for these methods is slightly more general, and saves the client some typing. * enforce that arguments and environment variables are valid UTF-8 * remove now-unnecessary platform-specific OsString handling * `ENOTCAPABLE` -> `EILSEQ` for failed arg/env string conversions * fix up comment style * Apply @acfoltzer's fix to isatty on Linux to BSD
1 parent 12972c7 commit 2fe3530

File tree

5 files changed

+201
-93
lines changed

5 files changed

+201
-93
lines changed

Cargo.toml

+6-6
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ cpu-time = "1.0"
4141

4242

4343
[dev-dependencies]
44-
wasmtime-runtime = { git = "https://github.com/cranestation/wasmtime", rev = "b42e550" }
45-
wasmtime-environ = { git = "https://github.com/cranestation/wasmtime", rev = "b42e550" }
46-
wasmtime-jit = { git = "https://github.com/cranestation/wasmtime", rev = "b42e550" }
47-
wasmtime-wasi = { git = "https://github.com/cranestation/wasmtime", rev = "b42e550" }
48-
wasmtime-api = { git = "https://github.com/cranestation/wasmtime", rev = "b42e550" }
49-
cranelift-codegen = "0.46.1"
44+
wasmtime-runtime = { git = "https://github.com/acfoltzer/wasmtime", rev = "d99e2fb" }
45+
wasmtime-environ = { git = "https://github.com/acfoltzer/wasmtime", rev = "d99e2fb" }
46+
wasmtime-jit = { git = "https://github.com/acfoltzer/wasmtime", rev = "d99e2fb" }
47+
wasmtime-wasi = { git = "https://github.com/acfoltzer/wasmtime", rev = "d99e2fb" }
48+
wasmtime-api = { git = "https://github.com/acfoltzer/wasmtime", rev = "d99e2fb" }
49+
cranelift-codegen = "0.47.0"
5050
target-lexicon = "0.8.1"
5151
pretty_env_logger = "0.3.0"
5252
tempfile = "3.1.0"

src/ctx.rs

+184-85
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,185 @@
11
use crate::fdentry::FdEntry;
2-
use crate::sys::dev_null;
32
use crate::{wasi, Error, Result};
43
use std::borrow::Borrow;
54
use std::collections::HashMap;
65
use std::env;
7-
use std::ffi::CString;
6+
use std::ffi::{CString, OsString};
87
use std::fs::File;
98
use std::path::{Path, PathBuf};
109

10+
enum PendingFdEntry {
11+
Thunk(fn() -> Result<FdEntry>),
12+
File(File),
13+
}
14+
15+
impl std::fmt::Debug for PendingFdEntry {
16+
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
match self {
18+
PendingFdEntry::Thunk(f) => write!(
19+
fmt,
20+
"PendingFdEntry::Thunk({:p})",
21+
f as *const fn() -> Result<FdEntry>
22+
),
23+
PendingFdEntry::File(f) => write!(fmt, "PendingFdEntry::File({:?})", f),
24+
}
25+
}
26+
}
27+
28+
#[derive(Debug, Eq, Hash, PartialEq)]
29+
enum PendingCString {
30+
Bytes(Vec<u8>),
31+
OsString(OsString),
32+
}
33+
34+
impl From<Vec<u8>> for PendingCString {
35+
fn from(bytes: Vec<u8>) -> Self {
36+
Self::Bytes(bytes)
37+
}
38+
}
39+
40+
impl From<OsString> for PendingCString {
41+
fn from(s: OsString) -> Self {
42+
Self::OsString(s)
43+
}
44+
}
45+
46+
impl PendingCString {
47+
fn into_string(self) -> Result<String> {
48+
match self {
49+
PendingCString::Bytes(v) => String::from_utf8(v).map_err(|_| Error::EILSEQ),
50+
PendingCString::OsString(s) => s.into_string().map_err(|_| Error::EILSEQ),
51+
}
52+
}
53+
54+
/// Create a `CString` containing valid UTF-8, or fail with `Error::EILSEQ`.
55+
fn into_utf8_cstring(self) -> Result<CString> {
56+
self.into_string()
57+
.and_then(|s| CString::new(s).map_err(|_| Error::EILSEQ))
58+
}
59+
}
60+
1161
/// A builder allowing customizable construction of `WasiCtx` instances.
1262
pub struct WasiCtxBuilder {
13-
fds: HashMap<wasi::__wasi_fd_t, FdEntry>,
63+
fds: HashMap<wasi::__wasi_fd_t, PendingFdEntry>,
1464
preopens: Vec<(PathBuf, File)>,
15-
args: Vec<CString>,
16-
env: HashMap<CString, CString>,
65+
args: Vec<PendingCString>,
66+
env: HashMap<PendingCString, PendingCString>,
1767
}
1868

1969
impl WasiCtxBuilder {
2070
/// Builder for a new `WasiCtx`.
21-
pub fn new() -> Result<Self> {
71+
pub fn new() -> Self {
2272
let mut builder = Self {
2373
fds: HashMap::new(),
2474
preopens: Vec::new(),
2575
args: vec![],
2676
env: HashMap::new(),
2777
};
2878

29-
builder.fds.insert(0, FdEntry::from(dev_null()?)?);
30-
builder.fds.insert(1, FdEntry::from(dev_null()?)?);
31-
builder.fds.insert(2, FdEntry::from(dev_null()?)?);
79+
builder.fds.insert(0, PendingFdEntry::Thunk(FdEntry::null));
80+
builder.fds.insert(1, PendingFdEntry::Thunk(FdEntry::null));
81+
builder.fds.insert(2, PendingFdEntry::Thunk(FdEntry::null));
3282

33-
Ok(builder)
83+
builder
3484
}
3585

3686
/// Add arguments to the command-line arguments list.
37-
pub fn args<S: AsRef<str>>(mut self, args: impl Iterator<Item = S>) -> Result<Self> {
38-
let args: Result<Vec<CString>> = args
39-
.map(|arg| CString::new(arg.as_ref()).map_err(|_| Error::ENOTCAPABLE))
87+
///
88+
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
89+
/// with `Error::EILSEQ`.
90+
pub fn args<S: AsRef<[u8]>>(mut self, args: impl IntoIterator<Item = S>) -> Self {
91+
self.args = args
92+
.into_iter()
93+
.map(|arg| arg.as_ref().to_vec().into())
4094
.collect();
41-
self.args = args?;
42-
Ok(self)
95+
self
4396
}
4497

4598
/// Add an argument to the command-line arguments list.
46-
pub fn arg(mut self, arg: &str) -> Result<Self> {
47-
self.args
48-
.push(CString::new(arg).map_err(|_| Error::ENOTCAPABLE)?);
49-
Ok(self)
99+
///
100+
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
101+
/// with `Error::EILSEQ`.
102+
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self {
103+
self.args.push(arg.as_ref().to_vec().into());
104+
self
50105
}
51106

52107
/// Inherit the command-line arguments from the host process.
53-
pub fn inherit_args(self) -> Result<Self> {
54-
self.args(env::args())
108+
///
109+
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
110+
/// fail with `Error::EILSEQ`.
111+
pub fn inherit_args(mut self) -> Self {
112+
self.args = env::args_os().map(PendingCString::OsString).collect();
113+
self
55114
}
56115

57116
/// Inherit the stdin, stdout, and stderr streams from the host process.
58-
pub fn inherit_stdio(mut self) -> Result<Self> {
59-
self.fds.insert(0, FdEntry::duplicate_stdin()?);
60-
self.fds.insert(1, FdEntry::duplicate_stdout()?);
61-
self.fds.insert(2, FdEntry::duplicate_stderr()?);
62-
Ok(self)
117+
pub fn inherit_stdio(mut self) -> Self {
118+
self.fds
119+
.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
120+
self.fds
121+
.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
122+
self.fds
123+
.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
124+
self
63125
}
64126

65127
/// Inherit the environment variables from the host process.
66-
pub fn inherit_env(self) -> Result<Self> {
67-
self.envs(std::env::vars())
128+
///
129+
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for
130+
/// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail with
131+
/// `Error::EILSEQ`.
132+
pub fn inherit_env(mut self) -> Self {
133+
self.env = std::env::vars_os()
134+
.map(|(k, v)| (k.into(), v.into()))
135+
.collect();
136+
self
68137
}
69138

70139
/// Add an entry to the environment.
71-
pub fn env<S: AsRef<str>>(mut self, k: S, v: S) -> Result<Self> {
72-
self.env.insert(
73-
CString::new(k.as_ref()).map_err(|_| Error::ENOTCAPABLE)?,
74-
CString::new(v.as_ref()).map_err(|_| Error::ENOTCAPABLE)?,
75-
);
76-
Ok(self)
140+
///
141+
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
142+
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
143+
pub fn env<S: AsRef<[u8]>>(mut self, k: S, v: S) -> Self {
144+
self.env
145+
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
146+
self
77147
}
78148

79149
/// Add entries to the environment.
80-
pub fn envs<S: AsRef<str>, T: Borrow<(S, S)>>(
150+
///
151+
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
152+
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
153+
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
81154
mut self,
82-
envs: impl Iterator<Item = T>,
83-
) -> Result<Self> {
84-
let env: Result<HashMap<CString, CString>> = envs
155+
envs: impl IntoIterator<Item = T>,
156+
) -> Self {
157+
self.env = envs
158+
.into_iter()
85159
.map(|t| {
86160
let (k, v) = t.borrow();
87-
let k = CString::new(k.as_ref()).map_err(|_| Error::ENOTCAPABLE);
88-
let v = CString::new(v.as_ref()).map_err(|_| Error::ENOTCAPABLE);
89-
match (k, v) {
90-
(Ok(k), Ok(v)) => Ok((k, v)),
91-
_ => Err(Error::ENOTCAPABLE),
92-
}
161+
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
93162
})
94163
.collect();
95-
self.env = env?;
96-
Ok(self)
164+
self
97165
}
98166

99167
/// Provide a File to use as stdin
100-
pub fn stdin(mut self, file: File) -> Result<Self> {
101-
self.fds.insert(0, FdEntry::from(file)?);
102-
Ok(self)
168+
pub fn stdin(mut self, file: File) -> Self {
169+
self.fds.insert(0, PendingFdEntry::File(file));
170+
self
103171
}
104172

105173
/// Provide a File to use as stdout
106-
pub fn stdout(mut self, file: File) -> Result<Self> {
107-
self.fds.insert(1, FdEntry::from(file)?);
108-
Ok(self)
174+
pub fn stdout(mut self, file: File) -> Self {
175+
self.fds.insert(1, PendingFdEntry::File(file));
176+
self
109177
}
110178

111179
/// Provide a File to use as stderr
112-
pub fn stderr(mut self, file: File) -> Result<Self> {
113-
self.fds.insert(2, FdEntry::from(file)?);
114-
Ok(self)
180+
pub fn stderr(mut self, file: File) -> Self {
181+
self.fds.insert(2, PendingFdEntry::File(file));
182+
self
115183
}
116184

117185
/// Add a preopened directory.
@@ -121,42 +189,73 @@ impl WasiCtxBuilder {
121189
}
122190

123191
/// Build a `WasiCtx`, consuming this `WasiCtxBuilder`.
124-
pub fn build(mut self) -> Result<WasiCtx> {
125-
// startup code starts looking at fd 3 for preopens
126-
let mut preopen_fd = 3;
192+
///
193+
/// If any of the arguments or environment variables in this builder cannot be converted into
194+
/// `CString`s, either due to NUL bytes or Unicode conversions, this returns `Error::EILSEQ`.
195+
pub fn build(self) -> Result<WasiCtx> {
196+
// Process arguments and environment variables into `CString`s, failing quickly if they
197+
// contain any NUL bytes, or if conversion from `OsString` fails.
198+
let args = self
199+
.args
200+
.into_iter()
201+
.map(|arg| arg.into_utf8_cstring())
202+
.collect::<Result<Vec<CString>>>()?;
203+
204+
let env = self
205+
.env
206+
.into_iter()
207+
.map(|(k, v)| {
208+
k.into_string().and_then(|mut pair| {
209+
v.into_string().and_then(|v| {
210+
pair.push('=');
211+
pair.push_str(v.as_str());
212+
// We have valid UTF-8, but the keys and values have not yet been checked
213+
// for NULs, so we do a final check here.
214+
CString::new(pair).map_err(|_| Error::EILSEQ)
215+
})
216+
})
217+
})
218+
.collect::<Result<Vec<CString>>>()?;
219+
220+
let mut fds: HashMap<wasi::__wasi_fd_t, FdEntry> = HashMap::new();
221+
// Populate the non-preopen fds.
222+
for (fd, pending) in self.fds {
223+
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
224+
match pending {
225+
PendingFdEntry::Thunk(f) => {
226+
fds.insert(fd, f()?);
227+
}
228+
PendingFdEntry::File(f) => {
229+
fds.insert(fd, FdEntry::from(f)?);
230+
}
231+
}
232+
}
233+
// Then add the preopen fds. Startup code in the guest starts looking at fd 3 for preopens,
234+
// so we start from there. This variable is initially 2, though, because the loop
235+
// immediately does the increment and check for overflow.
236+
let mut preopen_fd: wasi::__wasi_fd_t = 2;
127237
for (guest_path, dir) in self.preopens {
238+
// We do the increment at the beginning of the loop body, so that we don't overflow
239+
// unnecessarily if we have exactly the maximum number of file descriptors.
240+
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
241+
128242
if !dir.metadata()?.is_dir() {
129243
return Err(Error::EBADF);
130244
}
131245

132-
while self.fds.contains_key(&preopen_fd) {
246+
// We don't currently allow setting file descriptors other than 0-2, but this will avoid
247+
// collisions if we restore that functionality in the future.
248+
while fds.contains_key(&preopen_fd) {
133249
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
134250
}
135251
let mut fe = FdEntry::from(dir)?;
136252
fe.preopen_path = Some(guest_path);
137253
log::debug!("WasiCtx inserting ({:?}, {:?})", preopen_fd, fe);
138-
self.fds.insert(preopen_fd, fe);
139-
log::debug!("WasiCtx fds = {:?}", self.fds);
140-
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
254+
fds.insert(preopen_fd, fe);
255+
log::debug!("WasiCtx fds = {:?}", fds);
141256
}
142257

143-
let env = self
144-
.env
145-
.into_iter()
146-
.map(|(k, v)| {
147-
let mut pair = k.into_bytes();
148-
pair.push(b'=');
149-
pair.extend_from_slice(v.to_bytes_with_nul());
150-
// constructing a new CString from existing CStrings is safe
151-
unsafe { CString::from_vec_unchecked(pair) }
152-
})
153-
.collect();
154-
155-
Ok(WasiCtx {
156-
fds: self.fds,
157-
args: self.args,
158-
env,
159-
})
258+
Ok(WasiCtx { args, env, fds })
160259
}
161260
}
162261

@@ -175,12 +274,12 @@ impl WasiCtx {
175274
/// - Environment variables are inherited from the host process.
176275
///
177276
/// To override these behaviors, use `WasiCtxBuilder`.
178-
pub fn new<S: AsRef<str>>(args: impl Iterator<Item = S>) -> Result<Self> {
277+
pub fn new<S: AsRef<[u8]>>(args: impl IntoIterator<Item = S>) -> Result<Self> {
179278
WasiCtxBuilder::new()
180-
.and_then(|ctx| ctx.args(args))
181-
.and_then(|ctx| ctx.inherit_stdio())
182-
.and_then(|ctx| ctx.inherit_env())
183-
.and_then(|ctx| ctx.build())
279+
.args(args)
280+
.inherit_stdio()
281+
.inherit_env()
282+
.build()
184283
}
185284

186285
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
@@ -206,7 +305,7 @@ impl WasiCtx {
206305
/// The `FdEntry` will automatically get another free raw WASI `fd` assigned. Note that
207306
/// the two subsequent free raw WASI `fd`s do not have to be stored contiguously.
208307
pub(crate) fn insert_fd_entry(&mut self, fe: FdEntry) -> Result<wasi::__wasi_fd_t> {
209-
// never insert where stdio handles usually are
308+
// Never insert where stdio handles are expected to be.
210309
let mut fd = 3;
211310
while self.fds.contains_key(&fd) {
212311
if let Some(next_fd) = fd.checked_add(1) {

0 commit comments

Comments
 (0)