diff --git a/docs/WASI-intro.md b/docs/WASI-intro.md index 04410bd834..29c98703a7 100644 --- a/docs/WASI-intro.md +++ b/docs/WASI-intro.md @@ -12,8 +12,7 @@ WebAssembly's characteristic sandboxing to include I/O. See the [WASI Overview](WASI-overview.md) for more detailed background information, and the [WASI Tutorial](WASI-tutorial.md) for a walkthrough -showing how various pieces fit together, written in C. For Rust version, -see [rust-wasi-tutorial](https://github.com/kubkon/rust-wasi-tutorial). +showing how various pieces fit together. Note that everything here is a prototype, and while a lot of stuff works, there are numerous missing features and some rough edges. For example, diff --git a/docs/WASI-tutorial.md b/docs/WASI-tutorial.md index df4753c152..67af1b3ab2 100644 --- a/docs/WASI-tutorial.md +++ b/docs/WASI-tutorial.md @@ -1,5 +1,16 @@ # WASI tutorial +We'll split the tutorial into two parts: in the first part we'll walk through +compiling C and Rust programs to WASI, and in the second part how to execute +the compiled WebAssembly module using `wasmtime` runtime. +- [WASI tutorial](#wasi-tutorial) + - [Compiling to WASI](#compiling-to-wasi) + - [From C](#from-c) + - [From Rust](#from-rust) + - [Executing in `wasmtime` runtime](#executing-in-wasmtime-runtime) + +## Compiling to WASI +### From C Let's start with a simple C program which performs a file copy, which will show to compile and run programs, as well as perform simple sandbox configuration. The C code here uses standard POSIX APIs, and doesn't have @@ -62,34 +73,101 @@ which is configured to target WASI and use the WASI sysroot by default, so we ca compile our program like so: ``` -$ clang demo.c +$ clang demo.c -o demo.wasm ``` A few things to note here. First, this is just regular clang, configured to use -a WebAssembly target and sysroot. The name `a.out` is the traditional default -output name that C compilers use, and can be overridden with the "-o" flag in the -usual way. And, the output of clang here is a standard WebAssembly module: +a WebAssembly target and sysroot. Second, the output name specified with the "-o" +flag can be anything you want, and *does not* need to contain the `.wasm` extension. +In fact, the output of clang here is a standard WebAssembly module: ``` -$ file a.out -a.out: WebAssembly (wasm) binary module version 0x1 (MVP) +$ file demo.wasm +demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) ``` -It's a single file containing a self-contained wasm module, that doesn't require + +### From Rust +The same effect can be achieved with Rust. Firstly, go ahead and create a new +binary crate: + +``` +$ cargo new --bin demo +``` + +You can also clone the Rust code with the crate preset for you from +[here](https://github.com/kubkon/rust-wasi-tutorial). + +Now, let's port the C program defined in [From C](#from-c) section to Rust: + +```rust +use std::env; +use std::fs; +use std::io::{Read, Write}; + +fn process(input_fname: &str, output_fname: &str) -> Result<(), String> { + let mut input_file = + fs::File::open(input_fname).map_err(|err| format!("error opening input: {}", err))?; + let mut contents = Vec::new(); + input_file + .read_to_end(&mut contents) + .map_err(|err| format!("read error: {}", err))?; + + let mut output_file = fs::File::create(output_fname) + .map_err(|err| format!("error opening output '{}': {}", output_fname, err))?; + output_file + .write_all(&contents) + .map_err(|err| format!("write error: {}", err)) +} + +fn main() { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + if args.len() < 3 { + eprintln!("{} ", program); + return; + } + + if let Err(err) = process(&args[1], &args[2]) { + eprintln!("{}", err) + } +} +``` + +Let's put this source in the main file of our crate `src/main.rs`. + +In order to build it, we first need to install a WASI-enabled Rust toolchain: + +``` +$ rustup target add wasm32-unknown-wasi --toolchain nightly +$ cargo +nightly build --target wasm32-unknown-wasi +``` + +We should now have the WebAssembly module created in `target/wasm32-unknown-wasi/debug`: + +``` +$ file target/wasm32-unknown-wasi/debug/demo.wasm +demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) +``` + +## Executing in `wasmtime` runtime +The resultant WebAssembly module `demo.wasm` compiled either from C or Rust is simply +a single file containing a self-contained wasm module, that doesn't require any supporting JS code. -We can execute it with wasmtime directly, like so: +We can execute it with `wasmtime` directly, like so: ``` -$ wasmtime a.out -usage: a.out +$ wasmtime demo.wasm +usage: demo.wasm ``` Ok, this program needs some command-line arguments. So let's give it some: ``` $ echo hello world > test.txt -$ wasmtime a.out test.txt /tmp/somewhere.txt +$ wasmtime demo.wasm test.txt /tmp/somewhere.txt error opening input test.txt: Capabilities insufficient ``` @@ -100,17 +178,17 @@ capability to do so. So let's give it capabilities to access files in the requisite directories: ``` -$ wasmtime --dir=. --dir=/tmp a.out test.txt /tmp/somewhere.txt +$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt $ cat /tmp/somewhere.txt hello world ``` Now our program runs as expected! -What's going on under the covers? The `--dir=` option instructs Wasmtime +What's going on under the covers? The `--dir=` option instructs `wasmtime` to *preopen* a directory, and make it available to the program as a capability which can be used to open files inside that directory. Now when the program -calls the C `open` function, passing it either an absolute or relative path, +calls the C/Rust `open` function, passing it either an absolute or relative path, the WASI libc transparently translates that path into a path that's relative to one of the given preopened directories, if possible (using a technique based on [libpreopen](https://github.com/musec/libpreopen)). This way, we can have a @@ -124,7 +202,7 @@ WebAssembly program, and we don't expose the actual current working directory to the WebAssembly program. So providing a full path doesn't work: ``` -$ wasmtime --dir=$PWD --dir=/tmp a.out test.txt /tmp/somewhere.txt +$ wasmtime --dir=$PWD --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt $ cat /tmp/somewhere.txt error opening input test.txt: Capabilities insufficient ``` @@ -135,22 +213,22 @@ Speaking of `.`, what about `..`? Does that give programs a way to break out of the sandbox? Let's see: ``` -$ wasmtime --dir=. --dir=/tmp a.out test.txt /tmp/../etc/passwd +$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/../etc/passwd error opening output /tmp/../etc/passwd: Capabilities insufficient ``` The sandbox says no. And note that this is the capabilities system saying no here ("Capabilities insufficient"), rather than Unix access controls -("Permission denied"). Even if the user running wasmtime had write access to +("Permission denied"). Even if the user running `wasmtime` had write access to `/etc/passwd`, WASI programs don't have the capability to access files outside of the directories they've been granted. This is true when resolving symbolic links as well. -Wasmtime also has the ability to remap directories, with the `--mapdir` +`wasmtime` also has the ability to remap directories, with the `--mapdir` command-line option: ``` -$ wasmtime --dir=. --mapdir=/tmp:/var/tmp a.out test.txt /tmp/somewhere.txt +$ wasmtime --dir=. --mapdir=/tmp:/var/tmp demo.wasm test.txt /tmp/somewhere.txt $ cat /var/tmp/somewhere.txt hello world ```