1 use crate::fdentry::FdEntry;
2 use crate::host;
3 use failure::{bail, format_err, Error};
4 use nix::unistd::dup;
5 use std::collections::HashMap;
6 use std::ffi::{CStr, CString};
7 use std::fs::File;
8 use std::io::{stderr, stdin, stdout};
9 use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
10 use std::path::{Path, PathBuf};
11
12 pub struct WasiCtxBuilder {
13 fds: HashMap<host::__wasi_fd_t, FdEntry>,
14 preopens: HashMap<PathBuf, File>,
15 args: Vec<CString>,
16 env: HashMap<CString, CString>,
17 }
18
19 lazy_static! {
20 static ref DEV_NULL_FILE: File = dev_null();
21 }
22
23 impl WasiCtxBuilder {
24 /// Builder for a new `WasiCtx`.
new() -> Self25 pub fn new() -> Self {
26 WasiCtxBuilder {
27 fds: HashMap::new(),
28 preopens: HashMap::new(),
29 args: vec![],
30 env: HashMap::new(),
31 }
32 }
33
args(mut self, args: &[&str]) -> Self34 pub fn args(mut self, args: &[&str]) -> Self {
35 self.args = args
36 .into_iter()
37 .map(|arg| CString::new(*arg).expect("argument can be converted to a CString"))
38 .collect();
39 self
40 }
41
arg(mut self, arg: &str) -> Self42 pub fn arg(mut self, arg: &str) -> Self {
43 self.args
44 .push(CString::new(arg).expect("argument can be converted to a CString"));
45 self
46 }
47
c_args<S: AsRef<CStr>>(mut self, args: &[S]) -> Self48 pub fn c_args<S: AsRef<CStr>>(mut self, args: &[S]) -> Self {
49 self.args = args
50 .into_iter()
51 .map(|arg| arg.as_ref().to_owned())
52 .collect();
53 self
54 }
55
c_arg<S: AsRef<CStr>>(mut self, arg: S) -> Self56 pub fn c_arg<S: AsRef<CStr>>(mut self, arg: S) -> Self {
57 self.args.push(arg.as_ref().to_owned());
58 self
59 }
60
inherit_stdio(self) -> Self61 pub fn inherit_stdio(self) -> Self {
62 self.fd_dup(0, &stdin())
63 .fd_dup(1, &stdout())
64 .fd_dup(2, &stderr())
65 }
66
inherit_stdio_no_syscall(self) -> Self67 pub fn inherit_stdio_no_syscall(self) -> Self {
68 self.fd_dup_for_io_desc(0, &stdin(), false /* writeable */)
69 .fd_dup_for_io_desc(1, &stdout(), true /* writeable */)
70 .fd_dup_for_io_desc(2, &stderr(), true /* writeable */)
71 }
72
inherit_env(mut self) -> Self73 pub fn inherit_env(mut self) -> Self {
74 self.env = std::env::vars()
75 .map(|(k, v)| {
76 // TODO: handle errors, and possibly assert that the key is valid per POSIX
77 (
78 CString::new(k).expect("environment key can be converted to a CString"),
79 CString::new(v).expect("environment value can be converted to a CString"),
80 )
81 })
82 .collect();
83 self
84 }
85
env(mut self, k: &str, v: &str) -> Self86 pub fn env(mut self, k: &str, v: &str) -> Self {
87 self.env.insert(
88 // TODO: handle errors, and possibly assert that the key is valid per POSIX
89 CString::new(k).expect("environment key can be converted to a CString"),
90 CString::new(v).expect("environment value can be converted to a CString"),
91 );
92 self
93 }
94
c_env<S, T>(mut self, k: S, v: T) -> Self where S: AsRef<CStr>, T: AsRef<CStr>,95 pub fn c_env<S, T>(mut self, k: S, v: T) -> Self
96 where
97 S: AsRef<CStr>,
98 T: AsRef<CStr>,
99 {
100 self.env
101 .insert(k.as_ref().to_owned(), v.as_ref().to_owned());
102 self
103 }
104
105 /// Add an existing file-like object as a file descriptor in the context.
106 ///
107 /// When the `WasiCtx` is dropped, all of its associated file descriptors are `close`d. If you
108 /// do not want this to close the existing object, use `WasiCtxBuilder::fd_dup()`.
fd<F: IntoRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: F) -> Self109 pub fn fd<F: IntoRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: F) -> Self {
110 // safe because we're getting a valid RawFd from the F directly
111 unsafe { self.raw_fd(wasm_fd, fd.into_raw_fd()) }
112 }
113
114 /// Add an existing file-like object as a duplicate file descriptor in the context.
115 ///
116 /// The underlying file descriptor of this object will be duplicated before being added to the
117 /// context, so it will not be closed when the `WasiCtx` is dropped.
118 ///
119 /// TODO: handle `dup` errors
fd_dup<F: AsRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: &F) -> Self120 pub fn fd_dup<F: AsRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: &F) -> Self {
121 // safe because we're getting a valid RawFd from the F directly
122 unsafe { self.raw_fd(wasm_fd, dup(fd.as_raw_fd()).unwrap()) }
123 }
124
fd_dup_for_io_desc<F: AsRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: &F, writable : bool) -> Self125 pub fn fd_dup_for_io_desc<F: AsRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: &F, writable : bool) -> Self {
126 // safe because we're getting a valid RawFd from the F directly
127 unsafe { self.raw_fd_for_io_desc(wasm_fd, dup(fd.as_raw_fd()).unwrap(), writable) }
128 }
129
130 /// Add an existing file descriptor to the context.
131 ///
132 /// When the `WasiCtx` is dropped, this file descriptor will be `close`d. If you do not want to
133 /// close the existing descriptor, use `WasiCtxBuilder::raw_fd_dup()`.
raw_fd(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self134 pub unsafe fn raw_fd(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self {
135 self.fds.insert(wasm_fd, FdEntry::from_raw_fd(fd));
136 self
137 }
138
raw_fd_for_io_desc(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd, writable : bool) -> Self139 pub unsafe fn raw_fd_for_io_desc(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd, writable : bool) -> Self {
140 self.fds.insert(wasm_fd, FdEntry::from_raw_fd_for_io_desc(fd, writable));
141 self
142 }
143
144 /// Add a duplicate of an existing file descriptor to the context.
145 ///
146 /// The file descriptor will be duplicated before being added to the context, so it will not be
147 /// closed when the `WasiCtx` is dropped.
148 ///
149 /// TODO: handle `dup` errors
raw_fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self150 pub unsafe fn raw_fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self {
151 self.raw_fd(wasm_fd, dup(fd).unwrap())
152 }
153
preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self154 pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self {
155 self.preopens.insert(guest_path.as_ref().to_owned(), dir);
156 self
157 }
158
build(mut self) -> Result<WasiCtx, Error>159 pub fn build(mut self) -> Result<WasiCtx, Error> {
160 // startup code starts looking at fd 3 for preopens
161 let mut preopen_fd = 3;
162 for (guest_path, dir) in self.preopens {
163 if !dir.metadata()?.is_dir() {
164 bail!("preopened file is not a directory");
165 }
166 while self.fds.contains_key(&preopen_fd) {
167 preopen_fd = preopen_fd
168 .checked_add(1)
169 .ok_or(format_err!("not enough file handles"))?;
170 }
171 let mut fe = FdEntry::from_file(dir);
172 fe.preopen_path = Some(guest_path);
173 self.fds.insert(preopen_fd, fe);
174 preopen_fd += 1;
175 }
176
177 let env = self
178 .env
179 .into_iter()
180 .map(|(k, v)| {
181 let mut pair = k.into_bytes();
182 pair.extend_from_slice(b"=");
183 pair.extend_from_slice(v.to_bytes_with_nul());
184 // constructing a new CString from existing CStrings is safe
185 unsafe { CString::from_vec_unchecked(pair) }
186 })
187 .collect();
188
189 Ok(WasiCtx {
190 fds: self.fds,
191 args: self.args,
192 env,
193 })
194 }
195 }
196
197 #[derive(Debug)]
198 pub struct WasiCtx {
199 pub fds: HashMap<host::__wasi_fd_t, FdEntry>,
200 pub args: Vec<CString>,
201 pub env: Vec<CString>,
202 }
203
204 impl WasiCtx {
205 /// Make a new `WasiCtx` with some default settings.
206 ///
207 /// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process.
208 ///
209 /// - Environment variables are inherited from the host process.
210 ///
211 /// To override these behaviors, use `WasiCtxBuilder`.
new(args: &[&str]) -> WasiCtx212 pub fn new(args: &[&str]) -> WasiCtx {
213 WasiCtxBuilder::new()
214 .args(args)
215 .inherit_stdio()
216 .inherit_env()
217 .build()
218 .expect("default options don't fail")
219 }
220
get_fd_entry( &self, fd: host::__wasi_fd_t, rights_base: host::__wasi_rights_t, rights_inheriting: host::__wasi_rights_t, ) -> Result<&FdEntry, host::__wasi_errno_t>221 pub fn get_fd_entry(
222 &self,
223 fd: host::__wasi_fd_t,
224 rights_base: host::__wasi_rights_t,
225 rights_inheriting: host::__wasi_rights_t,
226 ) -> Result<&FdEntry, host::__wasi_errno_t> {
227 if let Some(fe) = self.fds.get(&fd) {
228 // validate rights
229 if !fe.rights_base & rights_base != 0 || !fe.rights_inheriting & rights_inheriting != 0
230 {
231 Err(host::__WASI_ENOTCAPABLE as host::__wasi_errno_t)
232 } else {
233 Ok(fe)
234 }
235 } else {
236 Err(host::__WASI_EBADF as host::__wasi_errno_t)
237 }
238 }
239
insert_fd_entry( &mut self, fe: FdEntry, ) -> Result<host::__wasi_fd_t, host::__wasi_errno_t>240 pub fn insert_fd_entry(
241 &mut self,
242 fe: FdEntry,
243 ) -> Result<host::__wasi_fd_t, host::__wasi_errno_t> {
244 // never insert where stdio handles usually are
245 let mut fd = 3;
246 while self.fds.contains_key(&fd) {
247 if let Some(next_fd) = fd.checked_add(1) {
248 fd = next_fd;
249 } else {
250 return Err(host::__WASI_EMFILE as host::__wasi_errno_t);
251 }
252 }
253 self.fds.insert(fd, fe);
254 Ok(fd)
255 }
256 }
257
dev_null() -> File258 fn dev_null() -> File {
259 File::open("/dev/null").expect("failed to open /dev/null")
260 }
261