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 impl WasiCtxBuilder {
20     /// Builder for a new `WasiCtx`.
new() -> Self21     pub fn new() -> Self {
22         let null = dev_null();
23         WasiCtxBuilder {
24             fds: HashMap::new(),
25             preopens: HashMap::new(),
26             args: vec![],
27             env: HashMap::new(),
28         }
29         .fd_dup_for_io_desc(0, &null, false /* writeable */)
30         .fd_dup_for_io_desc(1, &null, true  /* writeable */)
31         .fd_dup_for_io_desc(2, &null, true  /* writeable */)
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