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