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
|
See the [WASI Overview](WASI-overview.md) for more detailed background
|
||||||
information, and the [WASI Tutorial](WASI-tutorial.md) for a walkthrough
|
information, and the [WASI Tutorial](WASI-tutorial.md) for a walkthrough
|
||||||
showing how various pieces fit together, written in C. For Rust version,
|
showing how various pieces fit together.
|
||||||
see [rust-wasi-tutorial](https://github.com/kubkon/rust-wasi-tutorial).
|
|
||||||
|
|
||||||
Note that everything here is a prototype, and while a lot of stuff works,
|
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,
|
there are numerous missing features and some rough edges. For example,
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
# WASI tutorial
|
# 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
|
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
|
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
|
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:
|
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 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
|
a WebAssembly target and sysroot. Second, the output name specified with the "-o"
|
||||||
output name that C compilers use, and can be overridden with the "-o" flag in the
|
flag can be anything you want, and *does not* need to contain the `.wasm` extension.
|
||||||
usual way. And, the output of clang here is a standard WebAssembly module:
|
In fact, the output of clang here is a standard WebAssembly module:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ file a.out
|
$ file demo.wasm
|
||||||
a.out: WebAssembly (wasm) binary module version 0x1 (MVP)
|
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.
|
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
|
$ wasmtime demo.wasm
|
||||||
usage: a.out <from> <to>
|
usage: demo.wasm <from> <to>
|
||||||
```
|
```
|
||||||
|
|
||||||
Ok, this program needs some command-line arguments. So let's give it some:
|
Ok, this program needs some command-line arguments. So let's give it some:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ echo hello world > test.txt
|
$ 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
|
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:
|
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
|
$ cat /tmp/somewhere.txt
|
||||||
hello world
|
hello world
|
||||||
```
|
```
|
||||||
|
|
||||||
Now our program runs as expected!
|
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
|
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
|
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
|
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
|
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
|
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:
|
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
|
$ cat /tmp/somewhere.txt
|
||||||
error opening input test.txt: Capabilities insufficient
|
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:
|
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
|
error opening output /tmp/../etc/passwd: Capabilities insufficient
|
||||||
```
|
```
|
||||||
|
|
||||||
The sandbox says no. And note that this is the capabilities system saying no
|
The sandbox says no. And note that this is the capabilities system saying no
|
||||||
here ("Capabilities insufficient"), rather than Unix access controls
|
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
|
`/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
|
of the directories they've been granted. This is true when resolving symbolic
|
||||||
links as well.
|
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:
|
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
|
$ cat /var/tmp/somewhere.txt
|
||||||
hello world
|
hello world
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user