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