xref: /wasmtime-44.0.1/docs/WASI-tutorial.md (revision 3f2febf0)
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](#executing-in-wasmtime)
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-wasip1
158$ cargo build --target wasm32-wasip1
159```
160
161We should now have the WebAssembly module created in `target/wasm32-wasip1/debug`:
162
163```
164$ file target/wasm32-wasip1/debug/demo.wasm
165demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
166```
167
168### Executing in Wasmtime
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:
241
242```
243$ wasmtime --dir=. --dir=/var/tmp::/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 0 on success, nonzero on error
273    (import "wasi_snapshot_preview1" "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 written
292        )
293        drop ;; Discard the number of bytes written from the top of 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 [wasm-tools] command line tools:
307
308```
309$ wasm-tools parse demo.wat -o demo.wasm
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, use [jco].
320
321[wasm-tools]: https://github.com/bytecodealliance/wasm-tools
322[jco]: https://github.com/bytecodealliance/jco
323