xref: /wasmtime-44.0.1/docs/WASI-tutorial.md (revision 770c5d0f)
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 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 {}: {}", input_fname, 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!("usage: {} <from> <to>", 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: No such file or directory
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
220error opening input test.txt: No such file or directory
221```
222
223So, we always have to use `.` to refer to the current directory.
224
225Speaking of `.`, what about `..`? Does that give programs a way to break
226out of the sandbox? Let's see:
227
228```
229$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/../etc/passwd
230error opening output /tmp/../etc/passwd: Operation not permitted
231```
232
233The sandbox says no. And note that this is the capabilities system saying no
234here ("Operation not permitted"), rather than Unix access controls
235("Permission denied"). Even if the user running `wasmtime` had write access to
236`/etc/passwd`, WASI programs don't have the capability to access files outside
237of the directories they've been granted. This is true when resolving symbolic
238links as well.
239
240`wasmtime` also has the ability to remap directories, with the `--mapdir`
241command-line option:
242
243```
244$ wasmtime --dir=. --mapdir=/tmp::/var/tmp demo.wasm test.txt /tmp/somewhere.txt
245$ cat /var/tmp/somewhere.txt
246hello world
247```
248
249This maps the name `/tmp` within the WebAssembly program to `/var/tmp` in the
250host filesystem. So the WebAssembly program itself never sees the `/var/tmp` path,
251but that's where the output file goes.
252
253See [here](WASI-capabilities.md) for more information on the capability-based
254security model.
255
256The capability model is very powerful, and what's shown here is just the beginning.
257In the future, we'll be exposing much more functionality, including finer-grained
258capabilities, capabilities for network ports, and the ability for applications to
259explicitly request capabilities.
260
261## Web assembly text example
262
263In this example we will look at compiling the WebAssembly text format into wasm, and
264running the compiled WebAssembly module using the `wasmtime` runtime. This example
265makes use of WASI's `fd_write` implementation to write `hello world` to stdout.
266
267First, create a new `demo.wat` file:
268
269```wat
270(module
271    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
272    ;; The function signature for fd_write is:
273    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
274    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
275
276    (memory 1)
277    (export "memory" (memory 0))
278
279    ;; Write 'hello world\n' to memory at an offset of 8 bytes
280    ;; Note the trailing newline which is required for the text to appear
281    (data (i32.const 8) "hello world\n")
282
283    (func $main (export "_start")
284        ;; Creating a new io vector within linear memory
285        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
286        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string
287
288        (call $fd_write
289            (i32.const 1) ;; file_descriptor - 1 for stdout
290            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
291            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
292            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
293        )
294        drop ;; Discard the number of bytes written from the top of the stack
295    )
296)
297```
298
299`wasmtime` can directly execute `.wat` files:
300
301```
302$ wasmtime demo.wat
303hello world
304```
305
306Or, you can compile the `.wat` WebAssembly text format into the wasm binary format
307yourself using the [wabt] command line tools:
308
309```
310$ wat2wasm demo.wat
311```
312
313The created `.wasm` file can now be executed with `wasmtime` directly like so:
314
315```
316$ wasmtime demo.wasm
317hello world
318```
319
320To run this example within the browser, simply upload the compiled `.wasm` file to
321the [WASI browser polyfill].
322
323[wabt]: https://github.com/WebAssembly/wabt
324[WASI browser polyfill]: https://wasi.dev/polyfill/
325