xref: /wasmtime-44.0.1/docs/WASI-tutorial.md (revision 3b7cb6ee)
1# WASI tutorial
2We'll split the tutorial into two parts: in the first part we'll walk through
3compiling C and Rust programs to WASI and executing the compiled WebAssembly module
4using `wasmtime` runtime. In the second part we will discuss the compilation of a
5simpler WebAssembly program written using the WebAssembly text format, and executing
6this using the `wasmtime` runtime.
7
8- [WASI tutorial](#wasi-tutorial)
9  - [Running common languages with WASI](#running-common-languages-with-wasi)
10    - [Compiling to WASI](#compiling-to-wasi)
11        - [From C](#from-c)
12        - [From Rust](#from-rust)
13    - [Executing in `wasmtime` runtime](#executing-in-wasmtime-runtime)
14  - [Web assembly text example](#web-assembly-text-example)
15
16## Running common languages with WASI
17## Compiling to WASI
18#### From C
19Let's start with a simple C program which performs a file copy, which will
20show to compile and run programs, as well as perform simple sandbox
21configuration. The C code here uses standard POSIX APIs, and doesn't have
22any knowledge of WASI, WebAssembly, or sandboxing.
23
24```c
25#include <stdio.h>
26#include <string.h>
27#include <stdlib.h>
28#include <unistd.h>
29#include <fcntl.h>
30#include <errno.h>
31
32int main(int argc, char **argv) {
33    ssize_t n, m;
34    char buf[BUFSIZ];
35
36    if (argc != 3) {
37        fprintf(stderr, "usage: %s <from> <to>\n", argv[0]);
38        exit(1);
39    }
40
41    int in = open(argv[1], O_RDONLY);
42    if (in < 0) {
43        fprintf(stderr, "error opening input %s: %s\n", argv[1], strerror(errno));
44        exit(1);
45    }
46
47    int out = open(argv[2], O_WRONLY | O_CREAT, 0660);
48    if (out < 0) {
49        fprintf(stderr, "error opening output %s: %s\n", argv[2], strerror(errno));
50        exit(1);
51    }
52
53    while ((n = read(in, buf, BUFSIZ)) > 0) {
54        char *ptr = buf;
55        while (n > 0) {
56            m = write(out, ptr, (size_t)n);
57            if (m < 0) {
58                fprintf(stderr, "write error: %s\n", strerror(errno));
59                exit(1);
60            }
61            n -= m;
62            ptr += m;
63        }
64    }
65
66    if (n < 0) {
67        fprintf(stderr, "read error: %s\n", strerror(errno));
68        exit(1);
69    }
70
71    return EXIT_SUCCESS;
72}
73```
74
75We'll put this source in a file called `demo.c`.
76
77The [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases) provides a clang
78which is configured to target WASI and use the WASI sysroot by default if you put the extracted tree into `/`, so we can
79compile our program like so:
80
81```
82$ clang demo.c -o demo.wasm
83```
84
85If you would want to extract it elsewhere, you can specify the sysroot directory like so
86
87```
88$ clang demo.c --sysroot <path to sysroot> -o demo.wasm
89```
90
91If you're using the wasi-sdk, the sysroot directory is located in `opt/wasi-sdk/share/sysroot/` on Linux and mac.
92
93This is just regular clang, configured to use
94a WebAssembly target and sysroot. The output name specified with the "-o"
95flag can be anything you want, and *does not* need to contain the `.wasm` extension.
96In fact, the output of clang here is a standard WebAssembly module:
97
98```
99$ file demo.wasm
100demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
101```
102
103
104#### From Rust
105The same effect can be achieved with Rust. Firstly, go ahead and create a new
106binary crate:
107
108```
109$ cargo new --bin demo
110```
111
112You can also clone the Rust code with the crate preset for you from
113[here](https://github.com/kubkon/rust-wasi-tutorial).
114
115Now, let's port the C program defined in [From C](#from-c) section to Rust:
116
117```rust
118use std::env;
119use std::fs;
120use std::io::{Read, Write};
121
122fn process(input_fname: &str, output_fname: &str) -> Result<(), String> {
123    let mut input_file =
124        fs::File::open(input_fname).map_err(|err| format!("error opening input: {}", err))?;
125    let mut contents = Vec::new();
126    input_file
127        .read_to_end(&mut contents)
128        .map_err(|err| format!("read error: {}", err))?;
129
130    let mut output_file = fs::File::create(output_fname)
131        .map_err(|err| format!("error opening output '{}': {}", output_fname, err))?;
132    output_file
133        .write_all(&contents)
134        .map_err(|err| format!("write error: {}", err))
135}
136
137fn main() {
138    let args: Vec<String> = env::args().collect();
139    let program = args[0].clone();
140
141    if args.len() < 3 {
142        eprintln!("{} <input_file> <output_file>", program);
143        return;
144    }
145
146    if let Err(err) = process(&args[1], &args[2]) {
147        eprintln!("{}", err)
148    }
149}
150```
151
152Let's put this source in the main file of our crate `src/main.rs`.
153
154In order to build it, we first need to install a WASI-enabled Rust toolchain:
155
156```
157$ rustup target add wasm32-wasi
158$ cargo build --target wasm32-wasi
159```
160
161We should now have the WebAssembly module created in `target/wasm32-wasi/debug`:
162
163```
164$ file target/wasm32-wasi/debug/demo.wasm
165demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
166```
167
168## Executing in `wasmtime` runtime
169The resultant WebAssembly module `demo.wasm` compiled either from C or Rust is simply
170a single file containing a self-contained wasm module, that doesn't require
171any supporting JS code.
172
173We can execute it with `wasmtime` directly, like so:
174
175```
176$ wasmtime demo.wasm
177usage: demo.wasm <from> <to>
178```
179
180Ok, this program needs some command-line arguments. So let's give it some:
181
182```
183$ echo hello world > test.txt
184$ wasmtime demo.wasm test.txt /tmp/somewhere.txt
185error opening input test.txt: Capabilities insufficient
186```
187
188Aha, now we're seeing the sandboxing in action. This program is attempting to
189access a file by the name of `test.txt`, however it hasn't been given the
190capability to do so.
191
192So let's give it capabilities to access files in the requisite directories:
193
194```
195$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt
196$ cat /tmp/somewhere.txt
197hello world
198```
199
200Now our program runs as expected!
201
202What's going on under the covers? The `--dir=` option instructs `wasmtime`
203to *preopen* a directory, and make it available to the program as a capability
204which can be used to open files inside that directory. Now when the program
205calls the C/Rust `open` function, passing it either an absolute or relative path,
206the WASI libc transparently translates that path into a path that's relative to
207one of the given preopened directories, if possible (using a technique based
208on [libpreopen](https://github.com/musec/libpreopen)). This way, we can have a
209simple capability-oriented model at the system call level, while portable
210application code doesn't have to do anything special.
211
212As a brief aside, note that we used the path `.` above to grant the program
213access to the current directory. This is needed because the mapping from
214paths to associated capabilities is performed by libc, so it's part of the
215WebAssembly program, and we don't expose the actual current working
216directory to the WebAssembly program. So providing a full path doesn't work:
217
218```
219$ wasmtime --dir=$PWD --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt
220$ cat /tmp/somewhere.txt
221error opening input test.txt: Capabilities insufficient
222```
223
224So, we always have to use `.` to refer to the current directory.
225
226Speaking of `.`, what about `..`? Does that give programs a way to break
227out of the sandbox? Let's see:
228
229```
230$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/../etc/passwd
231error opening output /tmp/../etc/passwd: Capabilities insufficient
232```
233
234The sandbox says no. And note that this is the capabilities system saying no
235here ("Capabilities insufficient"), rather than Unix access controls
236("Permission denied"). Even if the user running `wasmtime` had write access to
237`/etc/passwd`, WASI programs don't have the capability to access files outside
238of the directories they've been granted. This is true when resolving symbolic
239links as well.
240
241`wasmtime` also has the ability to remap directories, with the `--mapdir`
242command-line option:
243
244```
245$ wasmtime --dir=. --mapdir=/tmp::/var/tmp demo.wasm test.txt /tmp/somewhere.txt
246$ cat /var/tmp/somewhere.txt
247hello world
248```
249
250This maps the name `/tmp` within the WebAssembly program to `/var/tmp` in the
251host filesystem. So the WebAssembly program itself never sees the `/var/tmp` path,
252but that's where the output file goes.
253
254See [here](WASI-capabilities.md) for more information on the capability-based
255security model.
256
257The capability model is very powerful, and what's shown here is just the beginning.
258In the future, we'll be exposing much more functionality, including finer-grained
259capabilities, capabilities for network ports, and the ability for applications to
260explicitly request capabilities.
261
262## Web assembly text example
263
264In this example we will look at compiling the WebAssembly text format into wasm, and
265running the compiled WebAssembly module using the `wasmtime` runtime. This example
266makes use of WASI's `fd_write` implementation to write `hello world` to stdout.
267
268First, create a new `demo.wat` file:
269
270```wat
271(module
272    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
273    ;; The function signature for fd_write is:
274    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
275    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
276
277    (memory 1)
278    (export "memory" (memory 0))
279
280    ;; Write 'hello world\n' to memory at an offset of 8 bytes
281    ;; Note the trailing newline which is required for the text to appear
282    (data (i32.const 8) "hello world\n")
283
284    (func $main (export "_start")
285        ;; Creating a new io vector within linear memory
286        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
287        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string
288
289        (call $fd_write
290            (i32.const 1) ;; file_descriptor - 1 for stdout
291            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
292            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
293            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
294        )
295        drop ;; Discard the number of bytes written from the top of the stack
296    )
297)
298```
299
300`wasmtime` can directly execute `.wat` files:
301
302```
303$ wasmtime demo.wat
304hello world
305```
306
307Or, you can compile the `.wat` WebAssembly text format into the wasm binary format
308yourself using the [wabt] command line tools:
309
310```
311$ wat2wasm demo.wat
312```
313
314The created `.wasm` file can now be executed with `wasmtime` directly like so:
315
316```
317$ wasmtime demo.wasm
318hello world
319```
320
321To run this example within the browser, simply upload the compiled `.wasm` file to
322the [WASI browser polyfill].
323
324[wabt]: https://github.com/WebAssembly/wabt
325[WASI browser polyfill]: https://wasi.dev/polyfill/
326