Update WASI tutorial with Rust howto as well
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user