xref: /wasmtime-44.0.1/docs/WASI-tutorial.md (revision 3f2febf0)
1b2fefe77SDan Gohman# WASI tutorial
2e41d3338SJakub KonkaWe'll split the tutorial into two parts: in the first part we'll walk through
31c0efd03SAlan Fostercompiling C and Rust programs to WASI and executing the compiled WebAssembly module
41c0efd03SAlan Fosterusing `wasmtime` runtime. In the second part we will discuss the compilation of a
51c0efd03SAlan Fostersimpler WebAssembly program written using the WebAssembly text format, and executing
61c0efd03SAlan Fosterthis using the `wasmtime` runtime.
7b2fefe77SDan Gohman
8e41d3338SJakub Konka- [WASI tutorial](#wasi-tutorial)
91c0efd03SAlan Foster  - [Running common languages with WASI](#running-common-languages-with-wasi)
10e41d3338SJakub Konka    - [Compiling to WASI](#compiling-to-wasi)
11e41d3338SJakub Konka        - [From C](#from-c)
12e41d3338SJakub Konka        - [From Rust](#from-rust)
1372004aadSNick Fitzgerald    - [Executing in Wasmtime](#executing-in-wasmtime)
141c0efd03SAlan Foster  - [Web assembly text example](#web-assembly-text-example)
15e41d3338SJakub Konka
161c0efd03SAlan Foster## Running common languages with WASI
1772004aadSNick Fitzgerald### Compiling to WASI
181c0efd03SAlan Foster#### From C
19b2fefe77SDan GohmanLet's start with a simple C program which performs a file copy, which will
20b2fefe77SDan Gohmanshow to compile and run programs, as well as perform simple sandbox
21b2fefe77SDan Gohmanconfiguration. The C code here uses standard POSIX APIs, and doesn't have
22b2fefe77SDan Gohmanany knowledge of WASI, WebAssembly, or sandboxing.
23b2fefe77SDan Gohman
24b2fefe77SDan Gohman```c
25b2fefe77SDan Gohman#include <stdio.h>
26b2fefe77SDan Gohman#include <string.h>
27b2fefe77SDan Gohman#include <stdlib.h>
28b2fefe77SDan Gohman#include <unistd.h>
29b2fefe77SDan Gohman#include <fcntl.h>
30b2fefe77SDan Gohman#include <errno.h>
31b2fefe77SDan Gohman
32658b5aabSVan der Auwermeulen Grégoireint main(int argc, char **argv) {
33e41cae7dSDan Gohman    ssize_t n, m;
34b2fefe77SDan Gohman    char buf[BUFSIZ];
35b2fefe77SDan Gohman
36b2fefe77SDan Gohman    if (argc != 3) {
37b2fefe77SDan Gohman        fprintf(stderr, "usage: %s <from> <to>\n", argv[0]);
38b2fefe77SDan Gohman        exit(1);
39b2fefe77SDan Gohman    }
40b2fefe77SDan Gohman
41b2fefe77SDan Gohman    int in = open(argv[1], O_RDONLY);
42b2fefe77SDan Gohman    if (in < 0) {
43b2fefe77SDan Gohman        fprintf(stderr, "error opening input %s: %s\n", argv[1], strerror(errno));
44b2fefe77SDan Gohman        exit(1);
45b2fefe77SDan Gohman    }
46b2fefe77SDan Gohman
47b2fefe77SDan Gohman    int out = open(argv[2], O_WRONLY | O_CREAT, 0660);
48b2fefe77SDan Gohman    if (out < 0) {
49b2fefe77SDan Gohman        fprintf(stderr, "error opening output %s: %s\n", argv[2], strerror(errno));
50b2fefe77SDan Gohman        exit(1);
51b2fefe77SDan Gohman    }
52b2fefe77SDan Gohman
53b2fefe77SDan Gohman    while ((n = read(in, buf, BUFSIZ)) > 0) {
54e41cae7dSDan Gohman        char *ptr = buf;
55b2fefe77SDan Gohman        while (n > 0) {
56e41cae7dSDan Gohman            m = write(out, ptr, (size_t)n);
57b2fefe77SDan Gohman            if (m < 0) {
58b2fefe77SDan Gohman                fprintf(stderr, "write error: %s\n", strerror(errno));
59b2fefe77SDan Gohman                exit(1);
60b2fefe77SDan Gohman            }
61b2fefe77SDan Gohman            n -= m;
62e41cae7dSDan Gohman            ptr += m;
63b2fefe77SDan Gohman        }
64b2fefe77SDan Gohman    }
65b2fefe77SDan Gohman
66b2fefe77SDan Gohman    if (n < 0) {
67b2fefe77SDan Gohman        fprintf(stderr, "read error: %s\n", strerror(errno));
68b2fefe77SDan Gohman        exit(1);
69b2fefe77SDan Gohman    }
70b2fefe77SDan Gohman
71b2fefe77SDan Gohman    return EXIT_SUCCESS;
72b2fefe77SDan Gohman}
73b2fefe77SDan Gohman```
74b2fefe77SDan Gohman
75b2fefe77SDan GohmanWe'll put this source in a file called `demo.c`.
76b2fefe77SDan Gohman
77fbe29da5SDan GohmanThe [wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases) provides a clang
783425553dSVan der Auwermeulen Grégoirewhich is configured to target WASI and use the WASI sysroot by default if you put the extracted tree into `/`, so we can
79b2fefe77SDan Gohmancompile our program like so:
80b2fefe77SDan Gohman
81b2fefe77SDan Gohman```
823425553dSVan der Auwermeulen Grégoire$ clang demo.c -o demo.wasm
833425553dSVan der Auwermeulen Grégoire```
843425553dSVan der Auwermeulen Grégoire
85d900a5f6SVan der Auwermeulen GrégoireIf you would want to extract it elsewhere, you can specify the sysroot directory like so
863425553dSVan der Auwermeulen Grégoire
873425553dSVan der Auwermeulen Grégoire```
8815b85dc2SVan der Auwermeulen Grégoire$ clang demo.c --sysroot <path to sysroot> -o demo.wasm
89b2fefe77SDan Gohman```
90b2fefe77SDan Gohman
91d900a5f6SVan der Auwermeulen GrégoireIf you're using the wasi-sdk, the sysroot directory is located in `opt/wasi-sdk/share/sysroot/` on Linux and mac.
9215b85dc2SVan der Auwermeulen Grégoire
93d900a5f6SVan der Auwermeulen GrégoireThis is just regular clang, configured to use
94d900a5f6SVan der Auwermeulen Grégoirea WebAssembly target and sysroot. The output name specified with the "-o"
95e41d3338SJakub Konkaflag can be anything you want, and *does not* need to contain the `.wasm` extension.
96e41d3338SJakub KonkaIn fact, the output of clang here is a standard WebAssembly module:
97b2fefe77SDan Gohman
98b2fefe77SDan Gohman```
99e41d3338SJakub Konka$ file demo.wasm
100e41d3338SJakub Konkademo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
101b2fefe77SDan Gohman```
102b2fefe77SDan Gohman
103e41d3338SJakub Konka
1041c0efd03SAlan Foster#### From Rust
105e41d3338SJakub KonkaThe same effect can be achieved with Rust. Firstly, go ahead and create a new
106e41d3338SJakub Konkabinary crate:
107e41d3338SJakub Konka
108e41d3338SJakub Konka```
109a3e08ee5STshepang Lekhonkhobe$ cargo new demo
110e41d3338SJakub Konka```
111e41d3338SJakub Konka
112e41d3338SJakub KonkaYou can also clone the Rust code with the crate preset for you from
113e41d3338SJakub Konka[here](https://github.com/kubkon/rust-wasi-tutorial).
114e41d3338SJakub Konka
115e41d3338SJakub KonkaNow, let's port the C program defined in [From C](#from-c) section to Rust:
116e41d3338SJakub Konka
117e41d3338SJakub Konka```rust
118e41d3338SJakub Konkause std::env;
119e41d3338SJakub Konkause std::fs;
120e41d3338SJakub Konkause std::io::{Read, Write};
121e41d3338SJakub Konka
122e41d3338SJakub Konkafn process(input_fname: &str, output_fname: &str) -> Result<(), String> {
123e41d3338SJakub Konka    let mut input_file =
1244c7e66e5Shayasshi        fs::File::open(input_fname).map_err(|err| format!("error opening input {}: {}", input_fname, err))?;
125e41d3338SJakub Konka    let mut contents = Vec::new();
126e41d3338SJakub Konka    input_file
127e41d3338SJakub Konka        .read_to_end(&mut contents)
128e41d3338SJakub Konka        .map_err(|err| format!("read error: {}", err))?;
129e41d3338SJakub Konka
130e41d3338SJakub Konka    let mut output_file = fs::File::create(output_fname)
1314c7e66e5Shayasshi        .map_err(|err| format!("error opening output {}: {}", output_fname, err))?;
132e41d3338SJakub Konka    output_file
133e41d3338SJakub Konka        .write_all(&contents)
134e41d3338SJakub Konka        .map_err(|err| format!("write error: {}", err))
135e41d3338SJakub Konka}
136e41d3338SJakub Konka
137e41d3338SJakub Konkafn main() {
138e41d3338SJakub Konka    let args: Vec<String> = env::args().collect();
139e41d3338SJakub Konka    let program = args[0].clone();
140e41d3338SJakub Konka
141e41d3338SJakub Konka    if args.len() < 3 {
142ebbe3997SPat Hickey        eprintln!("usage: {} <from> <to>", program);
143e41d3338SJakub Konka        return;
144e41d3338SJakub Konka    }
145e41d3338SJakub Konka
146e41d3338SJakub Konka    if let Err(err) = process(&args[1], &args[2]) {
147e41d3338SJakub Konka        eprintln!("{}", err)
148e41d3338SJakub Konka    }
149e41d3338SJakub Konka}
150e41d3338SJakub Konka```
151e41d3338SJakub Konka
152e41d3338SJakub KonkaLet's put this source in the main file of our crate `src/main.rs`.
153e41d3338SJakub Konka
154e41d3338SJakub KonkaIn order to build it, we first need to install a WASI-enabled Rust toolchain:
155e41d3338SJakub Konka
156e41d3338SJakub Konka```
15705095c18SAlex Crichton$ rustup target add wasm32-wasip1
15805095c18SAlex Crichton$ cargo build --target wasm32-wasip1
159e41d3338SJakub Konka```
160e41d3338SJakub Konka
16105095c18SAlex CrichtonWe should now have the WebAssembly module created in `target/wasm32-wasip1/debug`:
162e41d3338SJakub Konka
163e41d3338SJakub Konka```
16405095c18SAlex Crichton$ file target/wasm32-wasip1/debug/demo.wasm
165e41d3338SJakub Konkademo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
166e41d3338SJakub Konka```
167e41d3338SJakub Konka
16872004aadSNick Fitzgerald### Executing in Wasmtime
169e41d3338SJakub KonkaThe resultant WebAssembly module `demo.wasm` compiled either from C or Rust is simply
170e41d3338SJakub Konkaa single file containing a self-contained wasm module, that doesn't require
171b2fefe77SDan Gohmanany supporting JS code.
172b2fefe77SDan Gohman
173e41d3338SJakub KonkaWe can execute it with `wasmtime` directly, like so:
174b2fefe77SDan Gohman
175b2fefe77SDan Gohman```
176e41d3338SJakub Konka$ wasmtime demo.wasm
177e41d3338SJakub Konkausage: demo.wasm <from> <to>
178b2fefe77SDan Gohman```
179b2fefe77SDan Gohman
180b2fefe77SDan GohmanOk, this program needs some command-line arguments. So let's give it some:
181b2fefe77SDan Gohman
182b2fefe77SDan Gohman```
183b2fefe77SDan Gohman$ echo hello world > test.txt
184e41d3338SJakub Konka$ wasmtime demo.wasm test.txt /tmp/somewhere.txt
18579f33451SJia Tanerror opening input test.txt: No such file or directory
186b2fefe77SDan Gohman```
187b2fefe77SDan Gohman
188b2fefe77SDan GohmanAha, now we're seeing the sandboxing in action. This program is attempting to
189b2fefe77SDan Gohmanaccess a file by the name of `test.txt`, however it hasn't been given the
190b2fefe77SDan Gohmancapability to do so.
191b2fefe77SDan Gohman
192b2fefe77SDan GohmanSo let's give it capabilities to access files in the requisite directories:
193b2fefe77SDan Gohman
194b2fefe77SDan Gohman```
195e41d3338SJakub Konka$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt
196b2fefe77SDan Gohman$ cat /tmp/somewhere.txt
197b2fefe77SDan Gohmanhello world
198b2fefe77SDan Gohman```
199b2fefe77SDan Gohman
200b2fefe77SDan GohmanNow our program runs as expected!
201b2fefe77SDan Gohman
202e41d3338SJakub KonkaWhat's going on under the covers? The `--dir=` option instructs `wasmtime`
203d8242bc6SDan Gohmanto *preopen* a directory, and make it available to the program as a capability
204d8242bc6SDan Gohmanwhich can be used to open files inside that directory. Now when the program
205e41d3338SJakub Konkacalls the C/Rust `open` function, passing it either an absolute or relative path,
206d8242bc6SDan Gohmanthe WASI libc transparently translates that path into a path that's relative to
207d8242bc6SDan Gohmanone of the given preopened directories, if possible (using a technique based
208002a61c3SDan Gohmanon [libpreopen](https://github.com/musec/libpreopen)). This way, we can have a
209d8242bc6SDan Gohmansimple capability-oriented model at the system call level, while portable
210d8242bc6SDan Gohmanapplication code doesn't have to do anything special.
211d8242bc6SDan Gohman
212b2fefe77SDan GohmanAs a brief aside, note that we used the path `.` above to grant the program
213b2fefe77SDan Gohmanaccess to the current directory. This is needed because the mapping from
214b2fefe77SDan Gohmanpaths to associated capabilities is performed by libc, so it's part of the
215b2fefe77SDan GohmanWebAssembly program, and we don't expose the actual current working
216b2fefe77SDan Gohmandirectory to the WebAssembly program. So providing a full path doesn't work:
217b2fefe77SDan Gohman
218b2fefe77SDan Gohman```
219e41d3338SJakub Konka$ wasmtime --dir=$PWD --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt
22079f33451SJia Tanerror opening input test.txt: No such file or directory
221b2fefe77SDan Gohman```
222b2fefe77SDan Gohman
223b2fefe77SDan GohmanSo, we always have to use `.` to refer to the current directory.
224b2fefe77SDan Gohman
225b2fefe77SDan GohmanSpeaking of `.`, what about `..`? Does that give programs a way to break
226b2fefe77SDan Gohmanout of the sandbox? Let's see:
227b2fefe77SDan Gohman
228b2fefe77SDan Gohman```
229e41d3338SJakub Konka$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/../etc/passwd
23079f33451SJia Tanerror opening output /tmp/../etc/passwd: Operation not permitted
231b2fefe77SDan Gohman```
232b2fefe77SDan Gohman
233b2fefe77SDan GohmanThe sandbox says no. And note that this is the capabilities system saying no
23479f33451SJia Tanhere ("Operation not permitted"), rather than Unix access controls
235e41d3338SJakub Konka("Permission denied"). Even if the user running `wasmtime` had write access to
236b2fefe77SDan Gohman`/etc/passwd`, WASI programs don't have the capability to access files outside
237b2fefe77SDan Gohmanof the directories they've been granted. This is true when resolving symbolic
238b2fefe77SDan Gohmanlinks as well.
239b2fefe77SDan Gohman
2408995750aSAlex Crichton`wasmtime` also has the ability to remap directories:
241b2fefe77SDan Gohman
242b2fefe77SDan Gohman```
243183cb0f2SAlex Crichton$ wasmtime --dir=. --dir=/var/tmp::/tmp demo.wasm test.txt /tmp/somewhere.txt
244b2fefe77SDan Gohman$ cat /var/tmp/somewhere.txt
245b2fefe77SDan Gohmanhello world
246b2fefe77SDan Gohman```
247b2fefe77SDan Gohman
248b2fefe77SDan GohmanThis maps the name `/tmp` within the WebAssembly program to `/var/tmp` in the
249b2fefe77SDan Gohmanhost filesystem. So the WebAssembly program itself never sees the `/var/tmp` path,
250b2fefe77SDan Gohmanbut that's where the output file goes.
251b2fefe77SDan Gohman
252b2fefe77SDan GohmanSee [here](WASI-capabilities.md) for more information on the capability-based
253b2fefe77SDan Gohmansecurity model.
254b2fefe77SDan Gohman
255b2fefe77SDan GohmanThe capability model is very powerful, and what's shown here is just the beginning.
256b2fefe77SDan GohmanIn the future, we'll be exposing much more functionality, including finer-grained
257b2fefe77SDan Gohmancapabilities, capabilities for network ports, and the ability for applications to
258b2fefe77SDan Gohmanexplicitly request capabilities.
2591c0efd03SAlan Foster
2601c0efd03SAlan Foster## Web assembly text example
2611c0efd03SAlan Foster
2621c0efd03SAlan FosterIn this example we will look at compiling the WebAssembly text format into wasm, and
2631c0efd03SAlan Fosterrunning the compiled WebAssembly module using the `wasmtime` runtime. This example
2641c0efd03SAlan Fostermakes use of WASI's `fd_write` implementation to write `hello world` to stdout.
2651c0efd03SAlan Foster
2661c0efd03SAlan FosterFirst, create a new `demo.wat` file:
2671c0efd03SAlan Foster
2681c0efd03SAlan Foster```wat
2691c0efd03SAlan Foster(module
2701c0efd03SAlan Foster    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
2711c0efd03SAlan Foster    ;; The function signature for fd_write is:
2729be5dd7cSKevin Gibbons    ;; (File Descriptor, *iovs, iovs_len, *nwritten) -> Returns 0 on success, nonzero on error
273af38ee09SMatthew Phillips    (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
2741c0efd03SAlan Foster
2751c0efd03SAlan Foster    (memory 1)
2761c0efd03SAlan Foster    (export "memory" (memory 0))
2771c0efd03SAlan Foster
2781c0efd03SAlan Foster    ;; Write 'hello world\n' to memory at an offset of 8 bytes
2791c0efd03SAlan Foster    ;; Note the trailing newline which is required for the text to appear
2801c0efd03SAlan Foster    (data (i32.const 8) "hello world\n")
2811c0efd03SAlan Foster
2821c0efd03SAlan Foster    (func $main (export "_start")
2831c0efd03SAlan Foster        ;; Creating a new io vector within linear memory
2841c0efd03SAlan Foster        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
2851c0efd03SAlan Foster        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string
2861c0efd03SAlan Foster
2871c0efd03SAlan Foster        (call $fd_write
2881c0efd03SAlan Foster            (i32.const 1) ;; file_descriptor - 1 for stdout
2891c0efd03SAlan Foster            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
2901c0efd03SAlan Foster            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
291038a3874SDaniel Salvadori            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
2921c0efd03SAlan Foster        )
2930e3dcaebSMischa Spiegelmock        drop ;; Discard the number of bytes written from the top of the stack
2941c0efd03SAlan Foster    )
2951c0efd03SAlan Foster)
2961c0efd03SAlan Foster```
2971c0efd03SAlan Foster
2981c0efd03SAlan Foster`wasmtime` can directly execute `.wat` files:
2991c0efd03SAlan Foster
3001c0efd03SAlan Foster```
3011c0efd03SAlan Foster$ wasmtime demo.wat
3021c0efd03SAlan Fosterhello world
3031c0efd03SAlan Foster```
3041c0efd03SAlan Foster
3051c0efd03SAlan FosterOr, you can compile the `.wat` WebAssembly text format into the wasm binary format
306*3f2febf0SDan Gohmanyourself using the [wasm-tools] command line tools:
3071c0efd03SAlan Foster
3081c0efd03SAlan Foster```
309*3f2febf0SDan Gohman$ wasm-tools parse demo.wat -o demo.wasm
3101c0efd03SAlan Foster```
3111c0efd03SAlan Foster
3121c0efd03SAlan FosterThe created `.wasm` file can now be executed with `wasmtime` directly like so:
3131c0efd03SAlan Foster
3141c0efd03SAlan Foster```
3151c0efd03SAlan Foster$ wasmtime demo.wasm
3161c0efd03SAlan Fosterhello world
3171c0efd03SAlan Foster```
3181c0efd03SAlan Foster
319*3f2febf0SDan GohmanTo run this example within the browser, use [jco].
3201c0efd03SAlan Foster
321*3f2febf0SDan Gohman[wasm-tools]: https://github.com/bytecodealliance/wasm-tools
322*3f2febf0SDan Gohman[jco]: https://github.com/bytecodealliance/jco
323