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 sysroot 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 directory is located in `opt/wasi-sdk/share/sysroot/` on Linux and mac. 90 91This is just regular clang, configured to use 92a WebAssembly target and sysroot. 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