-
Notifications
You must be signed in to change notification settings - Fork 824
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
850: Add builder API for WasiState r=MarkMcCaskey a=MarkMcCaskey Nicer to use and it checks for errors! # Review - [x] Add a short description of the the change to the CHANGELOG.md file Co-authored-by: Mark McCaskey <[email protected]> Co-authored-by: Mark McCaskey <[email protected]>
- Loading branch information
Showing
5 changed files
with
308 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
//! Builder code for [`WasiState`] | ||
use crate::state::{WasiFs, WasiState}; | ||
use std::path::{Path, PathBuf}; | ||
|
||
/// Creates an empty [`WasiStateBuilder`]. | ||
pub fn create_wasi_state() -> WasiStateBuilder { | ||
WasiStateBuilder::default() | ||
} | ||
|
||
/// Type for building an instance of [`WasiState`] | ||
/// | ||
/// Usage: | ||
/// | ||
/// ``` | ||
/// # use wasmer_wasi::state::create_wasi_state; | ||
/// create_wasi_state() | ||
/// .env(b"HOME", "/home/home".to_string()) | ||
/// .arg("--help") | ||
/// .envs({ let mut hm = std::collections::HashMap::new(); | ||
/// hm.insert("COLOR_OUTPUT", "TRUE"); | ||
/// hm.insert("PATH", "/usr/bin"); | ||
/// hm | ||
/// }) | ||
/// .args(&["--verbose", "list"]) | ||
/// .preopen_dir("src") | ||
/// .map_dir("dot", ".") | ||
/// .build() | ||
/// .unwrap(); | ||
/// ``` | ||
#[derive(Debug, Default, Clone, PartialEq)] | ||
pub struct WasiStateBuilder { | ||
args: Vec<Vec<u8>>, | ||
envs: Vec<Vec<u8>>, | ||
preopened_files: Vec<PathBuf>, | ||
mapped_dirs: Vec<(String, PathBuf)>, | ||
} | ||
|
||
/// Error type returned when bad data is given to [`WasiStateBuilder`]. | ||
#[derive(Debug, PartialEq, Eq)] | ||
pub enum WasiStateCreationError { | ||
EnvironmentVariableFormatError(String), | ||
ArgumentContainsNulByte(String), | ||
PreopenedDirectoryNotFound(PathBuf), | ||
MappedDirAliasFormattingError(String), | ||
WasiFsCreationError(String), | ||
} | ||
|
||
fn validate_mapped_dir_alias(alias: &str) -> Result<(), WasiStateCreationError> { | ||
for byte in alias.bytes() { | ||
match byte { | ||
b'/' => { | ||
return Err(WasiStateCreationError::MappedDirAliasFormattingError( | ||
format!("Alias \"{}\" contains the character '/'", alias), | ||
)); | ||
} | ||
b'\0' => { | ||
return Err(WasiStateCreationError::MappedDirAliasFormattingError( | ||
format!("Alias \"{}\" contains a nul byte", alias), | ||
)); | ||
} | ||
_ => (), | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
impl WasiStateBuilder { | ||
/// Add an environment variable pair. | ||
/// Environment variable keys and values must not contain the byte `=` (0x3d) | ||
/// or nul (0x0). | ||
pub fn env<Key, Value>(&mut self, key: Key, value: Value) -> &mut Self | ||
where | ||
Key: AsRef<[u8]>, | ||
Value: AsRef<[u8]>, | ||
{ | ||
let key_b = key.as_ref(); | ||
let val_b = value.as_ref(); | ||
|
||
let length = key_b.len() + val_b.len() + 1; | ||
let mut byte_vec = Vec::with_capacity(length); | ||
|
||
byte_vec.extend_from_slice(&key_b); | ||
byte_vec.push(b'='); | ||
byte_vec.extend_from_slice(&val_b); | ||
|
||
self.envs.push(byte_vec); | ||
|
||
self | ||
} | ||
|
||
/// Add an argument. | ||
/// Arguments must not contain the nul (0x0) byte | ||
pub fn arg<Arg>(&mut self, arg: Arg) -> &mut Self | ||
where | ||
Arg: AsRef<[u8]>, | ||
{ | ||
let arg_b = arg.as_ref(); | ||
let mut byte_vec = Vec::with_capacity(arg_b.len()); | ||
byte_vec.extend_from_slice(&arg_b); | ||
self.args.push(byte_vec); | ||
|
||
self | ||
} | ||
|
||
/// Add multiple environment variable pairs. | ||
/// Keys and values must not contain the `=` (0x3d) or nul (0x0) byte. | ||
pub fn envs<I, Key, Value>(&mut self, env_pairs: I) -> &mut Self | ||
where | ||
I: IntoIterator<Item = (Key, Value)>, | ||
Key: AsRef<[u8]>, | ||
Value: AsRef<[u8]>, | ||
{ | ||
for (key, value) in env_pairs { | ||
let key_b = key.as_ref(); | ||
let val_b = value.as_ref(); | ||
|
||
let length = key_b.len() + val_b.len() + 1; | ||
let mut byte_vec = Vec::with_capacity(length); | ||
|
||
byte_vec.extend_from_slice(&key_b); | ||
byte_vec.push(b'='); | ||
byte_vec.extend_from_slice(&val_b); | ||
|
||
self.envs.push(byte_vec); | ||
} | ||
|
||
self | ||
} | ||
|
||
/// Add multiple arguments. | ||
/// Arguments must not contain the nul (0x0) byte | ||
pub fn args<I, Arg>(&mut self, args: I) -> &mut Self | ||
where | ||
I: IntoIterator<Item = Arg>, | ||
Arg: AsRef<[u8]>, | ||
{ | ||
for arg in args { | ||
let arg_b = arg.as_ref(); | ||
let mut byte_vec = Vec::with_capacity(arg_b.len()); | ||
byte_vec.extend_from_slice(&arg_b); | ||
self.args.push(byte_vec); | ||
} | ||
|
||
self | ||
} | ||
|
||
/// Preopen a directory | ||
/// This opens the given directory at the virtual root, `/`, and allows | ||
/// the WASI module to read and write to the given directory. | ||
// TODO: design a simple API for passing in permissions here (i.e. read-only) | ||
pub fn preopen_dir<FilePath>(&mut self, po_dir: FilePath) -> &mut Self | ||
where | ||
FilePath: AsRef<Path>, | ||
{ | ||
let path = po_dir.as_ref(); | ||
self.preopened_files.push(path.to_path_buf()); | ||
|
||
self | ||
} | ||
|
||
/// Preopen a directory | ||
/// This opens the given directory at the virtual root, `/`, and allows | ||
/// the WASI module to read and write to the given directory. | ||
pub fn preopen_dirs<I, FilePath>(&mut self, po_dirs: I) -> &mut Self | ||
where | ||
I: IntoIterator<Item = FilePath>, | ||
FilePath: AsRef<Path>, | ||
{ | ||
for po_dir in po_dirs { | ||
let path = po_dir.as_ref(); | ||
self.preopened_files.push(path.to_path_buf()); | ||
} | ||
|
||
self | ||
} | ||
|
||
/// Preopen a directory with a different name exposed to the WASI. | ||
pub fn map_dir<FilePath>(&mut self, alias: &str, po_dir: FilePath) -> &mut Self | ||
where | ||
FilePath: AsRef<Path>, | ||
{ | ||
let path = po_dir.as_ref(); | ||
self.mapped_dirs | ||
.push((alias.to_string(), path.to_path_buf())); | ||
|
||
self | ||
} | ||
|
||
/// Consumes the [`WasiStateBuilder`] and produces a [`WasiState`] | ||
/// | ||
/// Returns the error from `WasiFs::new` if there's an error | ||
pub fn build(&mut self) -> Result<WasiState, WasiStateCreationError> { | ||
for arg in self.args.iter() { | ||
for b in arg.iter() { | ||
if *b == 0 { | ||
return Err(WasiStateCreationError::ArgumentContainsNulByte( | ||
std::str::from_utf8(arg) | ||
.unwrap_or("Inner error: arg is invalid_utf8!") | ||
.to_string(), | ||
)); | ||
} | ||
} | ||
} | ||
for env in self.envs.iter() { | ||
let mut eq_seen = false; | ||
for b in env.iter() { | ||
match *b { | ||
b'=' => { | ||
if eq_seen { | ||
return Err(WasiStateCreationError::EnvironmentVariableFormatError( | ||
format!( | ||
"found '=' in env var string \"{}\" (key=value)", | ||
std::str::from_utf8(env) | ||
.unwrap_or("Inner error: env var is invalid_utf8!") | ||
), | ||
)); | ||
} | ||
eq_seen = true; | ||
} | ||
0 => { | ||
return Err(WasiStateCreationError::EnvironmentVariableFormatError( | ||
format!( | ||
"found nul byte in env var string \"{}\" (key=value)", | ||
std::str::from_utf8(env) | ||
.unwrap_or("Inner error: env var is invalid_utf8!") | ||
), | ||
)); | ||
} | ||
_ => (), | ||
} | ||
} | ||
} | ||
|
||
for po_f in self.preopened_files.iter() { | ||
if !po_f.exists() { | ||
return Err(WasiStateCreationError::PreopenedDirectoryNotFound( | ||
po_f.clone(), | ||
)); | ||
} | ||
} | ||
|
||
for (alias, po_f) in self.mapped_dirs.iter() { | ||
if !po_f.exists() { | ||
return Err(WasiStateCreationError::PreopenedDirectoryNotFound( | ||
po_f.clone(), | ||
)); | ||
} | ||
validate_mapped_dir_alias(&alias)?; | ||
} | ||
Ok(WasiState { | ||
fs: WasiFs::new(&self.preopened_files, &self.mapped_dirs) | ||
.map_err(WasiStateCreationError::WasiFsCreationError)?, | ||
args: self.args.clone(), | ||
envs: self.envs.clone(), | ||
}) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn env_var_errors() { | ||
let output = create_wasi_state().env("HOM=E", "/home/home").build(); | ||
match output { | ||
Err(WasiStateCreationError::EnvironmentVariableFormatError(_)) => assert!(true), | ||
_ => assert!(false), | ||
} | ||
|
||
let output = create_wasi_state().env("HOME\0", "/home/home").build(); | ||
match output { | ||
Err(WasiStateCreationError::EnvironmentVariableFormatError(_)) => assert!(true), | ||
_ => assert!(false), | ||
} | ||
} | ||
|
||
#[test] | ||
fn nul_character_in_args() { | ||
let output = create_wasi_state().arg("--h\0elp").build(); | ||
match output { | ||
Err(WasiStateCreationError::ArgumentContainsNulByte(_)) => assert!(true), | ||
_ => assert!(false), | ||
} | ||
let output = create_wasi_state().args(&["--help", "--wat\0"]).build(); | ||
match output { | ||
Err(WasiStateCreationError::ArgumentContainsNulByte(_)) => assert!(true), | ||
_ => assert!(false), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters