diff options
Diffstat (limited to 'third_party/rust/lucet-wasi-wasmsbx/src/ctx.rs')
-rw-r--r-- | third_party/rust/lucet-wasi-wasmsbx/src/ctx.rs | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/third_party/rust/lucet-wasi-wasmsbx/src/ctx.rs b/third_party/rust/lucet-wasi-wasmsbx/src/ctx.rs new file mode 100644 index 0000000000..49e96145b5 --- /dev/null +++ b/third_party/rust/lucet-wasi-wasmsbx/src/ctx.rs @@ -0,0 +1,260 @@ +use crate::fdentry::FdEntry; +use crate::host; +use failure::{bail, format_err, Error}; +use nix::unistd::dup; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::fs::File; +use std::io::{stderr, stdin, stdout}; +use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use std::path::{Path, PathBuf}; + +pub struct WasiCtxBuilder { + fds: HashMap<host::__wasi_fd_t, FdEntry>, + preopens: HashMap<PathBuf, File>, + args: Vec<CString>, + env: HashMap<CString, CString>, +} + +lazy_static! { + static ref DEV_NULL_FILE: File = dev_null(); +} + +impl WasiCtxBuilder { + /// Builder for a new `WasiCtx`. + pub fn new() -> Self { + WasiCtxBuilder { + fds: HashMap::new(), + preopens: HashMap::new(), + args: vec![], + env: HashMap::new(), + } + } + + pub fn args(mut self, args: &[&str]) -> Self { + self.args = args + .into_iter() + .map(|arg| CString::new(*arg).expect("argument can be converted to a CString")) + .collect(); + self + } + + pub fn arg(mut self, arg: &str) -> Self { + self.args + .push(CString::new(arg).expect("argument can be converted to a CString")); + self + } + + pub fn c_args<S: AsRef<CStr>>(mut self, args: &[S]) -> Self { + self.args = args + .into_iter() + .map(|arg| arg.as_ref().to_owned()) + .collect(); + self + } + + pub fn c_arg<S: AsRef<CStr>>(mut self, arg: S) -> Self { + self.args.push(arg.as_ref().to_owned()); + self + } + + pub fn inherit_stdio(self) -> Self { + self.fd_dup(0, &stdin()) + .fd_dup(1, &stdout()) + .fd_dup(2, &stderr()) + } + + pub fn inherit_stdio_no_syscall(self) -> Self { + self.fd_dup_for_io_desc(0, &stdin(), false /* writeable */) + .fd_dup_for_io_desc(1, &stdout(), true /* writeable */) + .fd_dup_for_io_desc(2, &stderr(), true /* writeable */) + } + + pub fn inherit_env(mut self) -> Self { + self.env = std::env::vars() + .map(|(k, v)| { + // TODO: handle errors, and possibly assert that the key is valid per POSIX + ( + CString::new(k).expect("environment key can be converted to a CString"), + CString::new(v).expect("environment value can be converted to a CString"), + ) + }) + .collect(); + self + } + + pub fn env(mut self, k: &str, v: &str) -> Self { + self.env.insert( + // TODO: handle errors, and possibly assert that the key is valid per POSIX + CString::new(k).expect("environment key can be converted to a CString"), + CString::new(v).expect("environment value can be converted to a CString"), + ); + self + } + + pub fn c_env<S, T>(mut self, k: S, v: T) -> Self + where + S: AsRef<CStr>, + T: AsRef<CStr>, + { + self.env + .insert(k.as_ref().to_owned(), v.as_ref().to_owned()); + self + } + + /// Add an existing file-like object as a file descriptor in the context. + /// + /// When the `WasiCtx` is dropped, all of its associated file descriptors are `close`d. If you + /// do not want this to close the existing object, use `WasiCtxBuilder::fd_dup()`. + pub fn fd<F: IntoRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: F) -> Self { + // safe because we're getting a valid RawFd from the F directly + unsafe { self.raw_fd(wasm_fd, fd.into_raw_fd()) } + } + + /// Add an existing file-like object as a duplicate file descriptor in the context. + /// + /// The underlying file descriptor of this object will be duplicated before being added to the + /// context, so it will not be closed when the `WasiCtx` is dropped. + /// + /// TODO: handle `dup` errors + pub fn fd_dup<F: AsRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: &F) -> Self { + // safe because we're getting a valid RawFd from the F directly + unsafe { self.raw_fd(wasm_fd, dup(fd.as_raw_fd()).unwrap()) } + } + + pub fn fd_dup_for_io_desc<F: AsRawFd>(self, wasm_fd: host::__wasi_fd_t, fd: &F, writable : bool) -> Self { + // safe because we're getting a valid RawFd from the F directly + unsafe { self.raw_fd_for_io_desc(wasm_fd, dup(fd.as_raw_fd()).unwrap(), writable) } + } + + /// Add an existing file descriptor to the context. + /// + /// When the `WasiCtx` is dropped, this file descriptor will be `close`d. If you do not want to + /// close the existing descriptor, use `WasiCtxBuilder::raw_fd_dup()`. + pub unsafe fn raw_fd(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self { + self.fds.insert(wasm_fd, FdEntry::from_raw_fd(fd)); + self + } + + pub unsafe fn raw_fd_for_io_desc(mut self, wasm_fd: host::__wasi_fd_t, fd: RawFd, writable : bool) -> Self { + self.fds.insert(wasm_fd, FdEntry::from_raw_fd_for_io_desc(fd, writable)); + self + } + + /// Add a duplicate of an existing file descriptor to the context. + /// + /// The file descriptor will be duplicated before being added to the context, so it will not be + /// closed when the `WasiCtx` is dropped. + /// + /// TODO: handle `dup` errors + pub unsafe fn raw_fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: RawFd) -> Self { + self.raw_fd(wasm_fd, dup(fd).unwrap()) + } + + pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self { + self.preopens.insert(guest_path.as_ref().to_owned(), dir); + self + } + + pub fn build(mut self) -> Result<WasiCtx, Error> { + // startup code starts looking at fd 3 for preopens + let mut preopen_fd = 3; + for (guest_path, dir) in self.preopens { + if !dir.metadata()?.is_dir() { + bail!("preopened file is not a directory"); + } + while self.fds.contains_key(&preopen_fd) { + preopen_fd = preopen_fd + .checked_add(1) + .ok_or(format_err!("not enough file handles"))?; + } + let mut fe = FdEntry::from_file(dir); + fe.preopen_path = Some(guest_path); + self.fds.insert(preopen_fd, fe); + preopen_fd += 1; + } + + let env = self + .env + .into_iter() + .map(|(k, v)| { + let mut pair = k.into_bytes(); + pair.extend_from_slice(b"="); + pair.extend_from_slice(v.to_bytes_with_nul()); + // constructing a new CString from existing CStrings is safe + unsafe { CString::from_vec_unchecked(pair) } + }) + .collect(); + + Ok(WasiCtx { + fds: self.fds, + args: self.args, + env, + }) + } +} + +#[derive(Debug)] +pub struct WasiCtx { + pub fds: HashMap<host::__wasi_fd_t, FdEntry>, + pub args: Vec<CString>, + pub env: Vec<CString>, +} + +impl WasiCtx { + /// Make a new `WasiCtx` with some default settings. + /// + /// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process. + /// + /// - Environment variables are inherited from the host process. + /// + /// To override these behaviors, use `WasiCtxBuilder`. + pub fn new(args: &[&str]) -> WasiCtx { + WasiCtxBuilder::new() + .args(args) + .inherit_stdio() + .inherit_env() + .build() + .expect("default options don't fail") + } + + pub fn 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> { + if let Some(fe) = self.fds.get(&fd) { + // validate rights + if !fe.rights_base & rights_base != 0 || !fe.rights_inheriting & rights_inheriting != 0 + { + Err(host::__WASI_ENOTCAPABLE as host::__wasi_errno_t) + } else { + Ok(fe) + } + } else { + Err(host::__WASI_EBADF as host::__wasi_errno_t) + } + } + + pub fn insert_fd_entry( + &mut self, + fe: FdEntry, + ) -> Result<host::__wasi_fd_t, host::__wasi_errno_t> { + // never insert where stdio handles usually are + let mut fd = 3; + while self.fds.contains_key(&fd) { + if let Some(next_fd) = fd.checked_add(1) { + fd = next_fd; + } else { + return Err(host::__WASI_EMFILE as host::__wasi_errno_t); + } + } + self.fds.insert(fd, fe); + Ok(fd) + } +} + +fn dev_null() -> File { + File::open("/dev/null").expect("failed to open /dev/null") +} |