diff --git a/Cargo.lock b/Cargo.lock index b3eef8e..844c043 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,7 @@ name = "enarx-wasmldr" version = "0.2.0" dependencies = [ "anyhow", + "cfg-if", "env_logger", "log", "structopt", diff --git a/Cargo.toml b/Cargo.toml index 289fa4e..4aca2b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ log = "0.4" wasmparser = "0.80" structopt = "0.3.22" anyhow = "1.0" +cfg-if = "1.0" [build-dependencies] wat = "1.0" diff --git a/README.md b/README.md index 6baaa75..055f996 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,9 @@ $ RUST_LOG=enarx_wasmldr=info RUST_BACKTRACE=1 cargo run return_1.wasm ] ``` -On Unix platforms, the command can also read the workload from the -file descriptor (3): +On Unix platforms, the command can also read the workload from an open file descriptor: ```console -$ RUST_LOG=enarx_wasmldr=info RUST_BACKTRACE=1 cargo run 3< return_1.wasm +$ RUST_LOG=enarx_wasmldr=info RUST_BACKTRACE=1 cargo run -- --module-on-fd=3 3< return_1.wasm ``` diff --git a/src/cli.rs b/src/cli.rs index 417d6f7..b93cdf0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,14 +2,22 @@ #![allow(missing_docs, unused_variables)] // This is a work-in-progress, so... +use anyhow::{bail, Result}; use structopt::{clap::AppSettings, StructOpt}; -use anyhow::{bail, Result}; use std::path::PathBuf; +use std::str::FromStr; + +#[cfg(unix)] +use std::os::unix::io::RawFd; -// The main StructOpt for running `wasmldr` directly +// The main StructOpt for CLI options #[derive(StructOpt, Debug)] -#[structopt(setting=AppSettings::TrailingVarArg)] +#[structopt( + setting = AppSettings::DeriveDisplayOrder, + setting = AppSettings::UnifiedHelpMessage, +)] +/// Enarx Keep Configurator and WebAssembly Loader pub struct RunOptions { /// Pass an environment variable to the program #[structopt( @@ -25,15 +33,24 @@ pub struct RunOptions { #[structopt(long, value_name = "FUNCTION")] invoke: Option, + #[cfg(unix)] + /// Load WebAssembly module from the given FD (must be >=3) + #[structopt(long, value_name = "FD", parse(try_from_str = parse_module_fd))] + pub module_on_fd: Option, + // TODO: --inherit-env // TODO: --stdin, --stdout, --stderr /// Path of the WebAssembly module to run - #[structopt(index = 1, required = true, value_name = "MODULE", parse(from_os_str))] - pub module: PathBuf, + #[structopt( + index = 1, + required_unless = "module-on-fd", + value_name = "MODULE", + parse(from_os_str) + )] + pub module: Option, - // NOTE: this has to come last for TrailingVarArg /// Arguments to pass to the WebAssembly module - #[structopt(value_name = "ARGS")] + #[structopt(value_name = "ARGS", last = true)] pub args: Vec, } @@ -44,3 +61,11 @@ fn parse_env_var(s: &str) -> Result<(String, String)> { } Ok((parts[0].to_owned(), parts[1].to_owned())) } + +fn parse_module_fd(s: &str) -> Result { + let fd = RawFd::from_str(s)?; + if fd <= 2 { + bail!("FD must be >= 3"); + } + Ok(fd) +} diff --git a/src/main.rs b/src/main.rs index 0c54208..124b4b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,10 +19,9 @@ //! ] //! ``` //! -//! On Unix platforms, the command can also read the workload from the -//! file descriptor (3): +//! On Unix platforms, the command can also read the workload from an open file descriptor: //! ```console -//! $ RUST_LOG=enarx_wasmldr=info RUST_BACKTRACE=1 cargo run 3< return_1.wasm +//! $ RUST_LOG=enarx_wasmldr=info RUST_BACKTRACE=1 cargo run -- --module-on-fd=3 3< return_1.wasm //! ``` //! #![deny(missing_docs)] @@ -31,14 +30,21 @@ mod cli; mod workload; +use anyhow::{Context, Result}; +use cfg_if::cfg_if; use log::{debug, info}; use structopt::StructOpt; use std::fs::File; use std::io::Read; -fn main() { - // Initialize the logger, taking settings from the default env vars +#[cfg(unix)] +use std::os::unix::io::FromRawFd; + +fn main() -> Result<()> { + // Initialize the logger, taking filtering and style settings from the + // default env vars (RUST_LOG and RUST_LOG_STYLE). + // The log target is the default target (stderr), so no files get opened. env_logger::Builder::from_default_env().init(); info!("version {} starting up", env!("CARGO_PKG_VERSION")); @@ -47,9 +53,30 @@ fn main() { let opts = cli::RunOptions::from_args(); info!("opts: {:#?}", opts); - info!("reading {:?}", opts.module); - // TODO: don't just panic here... - let mut reader = File::open(&opts.module).expect("Unable to open file"); + cfg_if! { + if #[cfg(unix)] { + let mut reader = match opts.module_on_fd { + Some(fd) => { + info!("reading module from fd {:?}", fd); + // SAFETY: unsafe if something is using the given fd. + // parse_module_fd() enforces fd >= 3, and nothing above + // opens/duplicates new file descriptors, so we're OK. + unsafe { File::from_raw_fd(fd) } + }, + None => { + let path = opts.module.expect("required_unless failure"); + info!("reading module from {:?}", path); + File::open(&path) + .with_context(|| format!("failed opening {:?}", path))? + }, + }; + } else { + let path = opts.module.expect("missing required arg"); + info!("reading module from {:?}", path); + let mut reader = File::open(&path) + .with_context(|| format!("failed opening {:?}", path))?; + } + } let mut bytes = Vec::new(); reader @@ -65,4 +92,6 @@ fn main() { info!("got result: {:#?}", result); // TODO: exit with the resulting code, if the result is a return code // FUTURE: produce attestation report here + + Ok(()) }