Update WASI tutorial with Rust howto as well

This commit is contained in:
Jakub Konka
2019-05-02 22:02:19 +02:00
parent e12f797c2c
commit e41d333878
2 changed files with 98 additions and 21 deletions

View File

@@ -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,

View File

@@ -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<String> = env::args().collect();
let program = args[0].clone();
if args.len() < 3 {
eprintln!("{} <input_file> <output_file>", 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 <from> <to>
$ wasmtime demo.wasm
usage: demo.wasm <from> <to>
```
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
```