Skip to content

Commit

Permalink
wasi: replaces existing filesystem apis with fs.FS
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Cole <[email protected]>
  • Loading branch information
Adrian Cole committed Mar 23, 2022
1 parent 59617a2 commit 84a369e
Show file tree
Hide file tree
Showing 12 changed files with 429 additions and 550 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,22 @@ on your machine unless you explicitly allow it.

System access is defined by an emerging specification called WebAssembly
System Interface ([WASI](https://github.com/WebAssembly/WASI)). WASI defines
how WebAssembly programs interact with the host embedding them. For example,
WASI defines functions for reading the time, or a random number.
how WebAssembly programs interact with the host embedding them.

This repository includes several [examples](examples) that expose system
interfaces, via the module `wazero.WASISnapshotPreview1`. These examples are
tested and a good way to learn what's possible with wazero.
For example, here's how you can allow WebAssembly modules to read
"/work/home/a.txt" as "/a.txt" or "./a.txt":
```go
wasi, err := r.InstantiateModule(wazero.WASISnapshotPreview1())
defer wasi.Close()

sysConfig := wazero.NewSysConfig().WithFS(os.DirFS("/work/home"))
module, err := wazero.StartWASICommandWithConfig(r, compiled, sysConfig)
defer module.Close()
...
```

The best way to learn this and other features you get with wazero is by trying
[examples](examples).

## Runtime

Expand Down
20 changes: 10 additions & 10 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"math"

internalwasm "github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasm/interpreter"
"github.com/tetratelabs/wazero/internal/wasm/jit"
"github.com/tetratelabs/wazero/wasi"
)

// NewRuntimeConfigJIT compiles WebAssembly modules into runtime.GOARCH-specific assembly for optimal performance.
Expand Down Expand Up @@ -217,20 +217,20 @@ func (c *SysConfig) WithEnv(key, value string) *SysConfig {
// WithFS assigns the file system to use for any paths beginning at "/". Defaults to not found.
//
// Note: This sets WithWorkDirFS to the same file-system unless already set.
func (c *SysConfig) WithFS(fs wasi.FS) *SysConfig {
func (c *SysConfig) WithFS(fs fs.FS) *SysConfig {
c.setFS("/", fs)
return c
}

// WithWorkDirFS indicates the file system to use for any paths beginning at ".". Defaults to the same as WithFS.
func (c *SysConfig) WithWorkDirFS(fs wasi.FS) *SysConfig {
// WithWorkDirFS indicates the file system to use for any paths beginning at "./". Defaults to the same as WithFS.
func (c *SysConfig) WithWorkDirFS(fs fs.FS) *SysConfig {
c.setFS(".", fs)
return c
}

// withFS is hidden especially until #394 as existing use cases should be possible by composing file systems.
// TODO: in #394 add examples on WithFS to accomplish this.
func (c *SysConfig) setFS(path string, fs wasi.FS) {
func (c *SysConfig) setFS(path string, fs fs.FS) {
// Check to see if this key already exists and update it.
entry := &internalwasm.FileEntry{Path: path, FS: fs}
if fd, ok := c.preopenPaths[path]; ok {
Expand Down Expand Up @@ -265,13 +265,13 @@ func (c *SysConfig) toSysContext() (sys *internalwasm.SysContext, err error) {
rootFD := uint32(0) // zero is invalid
setWorkDirFS := false
preopens := c.preopens
for fd, fs := range preopens {
if fs.FS == nil {
err = fmt.Errorf("FS for %s is nil", fs.Path)
for fd, entry := range preopens {
if entry.FS == nil {
err = fmt.Errorf("FS for %s is nil", entry.Path)
return
} else if fs.Path == "/" {
} else if entry.Path == "/" {
rootFD = fd
} else if fs.Path == "." {
} else if entry.Path == "." {
setWorkDirFS = true
}
}
Expand Down
33 changes: 17 additions & 16 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io"
"math"
"testing"
"testing/fstest"

"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -58,8 +59,8 @@ func TestRuntimeConfig_Features(t *testing.T) {
}

func TestSysConfig_toSysContext(t *testing.T) {
memFS := WASIMemFS()
memFS2 := WASIMemFS()
testFS := fstest.MapFS{}
testFS2 := fstest.MapFS{}

tests := []struct {
name string
Expand Down Expand Up @@ -186,7 +187,7 @@ func TestSysConfig_toSysContext(t *testing.T) {
},
{
name: "WithFS",
input: NewSysConfig().WithFS(memFS),
input: NewSysConfig().WithFS(testFS),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
Expand All @@ -195,14 +196,14 @@ func TestSysConfig_toSysContext(t *testing.T) {
nil, // stdout
nil, // stderr
map[uint32]*internalwasm.FileEntry{ // openedFiles
3: {Path: "/", FS: memFS},
4: {Path: ".", FS: memFS},
3: {Path: "/", FS: testFS},
4: {Path: ".", FS: testFS},
},
),
},
{
name: "WithFS - overwrites",
input: NewSysConfig().WithFS(memFS).WithFS(memFS2),
input: NewSysConfig().WithFS(testFS).WithFS(testFS2),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
Expand All @@ -211,14 +212,14 @@ func TestSysConfig_toSysContext(t *testing.T) {
nil, // stdout
nil, // stderr
map[uint32]*internalwasm.FileEntry{ // openedFiles
3: {Path: "/", FS: memFS2},
4: {Path: ".", FS: memFS2},
3: {Path: "/", FS: testFS2},
4: {Path: ".", FS: testFS2},
},
),
},
{
name: "WithWorkDirFS",
input: NewSysConfig().WithWorkDirFS(memFS),
input: NewSysConfig().WithWorkDirFS(testFS),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
Expand All @@ -227,13 +228,13 @@ func TestSysConfig_toSysContext(t *testing.T) {
nil, // stdout
nil, // stderr
map[uint32]*internalwasm.FileEntry{ // openedFiles
3: {Path: ".", FS: memFS},
3: {Path: ".", FS: testFS},
},
),
},
{
name: "WithFS and WithWorkDirFS",
input: NewSysConfig().WithFS(memFS).WithWorkDirFS(memFS2),
input: NewSysConfig().WithFS(testFS).WithWorkDirFS(testFS2),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
Expand All @@ -242,14 +243,14 @@ func TestSysConfig_toSysContext(t *testing.T) {
nil, // stdout
nil, // stderr
map[uint32]*internalwasm.FileEntry{ // openedFiles
3: {Path: "/", FS: memFS},
4: {Path: ".", FS: memFS2},
3: {Path: "/", FS: testFS},
4: {Path: ".", FS: testFS2},
},
),
},
{
name: "WithWorkDirFS and WithFS",
input: NewSysConfig().WithWorkDirFS(memFS).WithFS(memFS2),
input: NewSysConfig().WithWorkDirFS(testFS).WithFS(testFS2),
expected: requireSysContext(t,
math.MaxUint32, // max
nil, // args
Expand All @@ -258,8 +259,8 @@ func TestSysConfig_toSysContext(t *testing.T) {
nil, // stdout
nil, // stderr
map[uint32]*internalwasm.FileEntry{ // openedFiles
3: {Path: ".", FS: memFS},
4: {Path: "/", FS: memFS2},
3: {Path: ".", FS: testFS},
4: {Path: "/", FS: testFS2},
},
),
},
Expand Down
33 changes: 10 additions & 23 deletions examples/file_system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package examples

import (
"bytes"
"embed"
_ "embed"
"io"
"io/fs"
"testing"

"github.com/stretchr/testify/require"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/wasi"
)

// catFS is an embedded filesystem limited to cat.go
//go:embed testdata/cat.go
var catFS embed.FS

// catGo is the TinyGo source
//go:embed testdata/cat.go
var catGo []byte
Expand All @@ -31,16 +35,13 @@ func Test_Cat(t *testing.T) {
stdoutBuf := bytes.NewBuffer(nil)
sysConfig := wazero.NewSysConfig().WithStdout(stdoutBuf)

// Next, configure a sandboxed filesystem to include one file.
file := "cat.go" // arbitrary file
memFS := wazero.WASIMemFS()
err := writeFile(memFS, file, catGo)
// Since wazero uses fs.FS we can use standard libraries to do things like trim the leading path.
rooted, err := fs.Sub(catFS, "testdata")
require.NoError(t, err)
sysConfig.WithWorkDirFS(memFS)

// Since this runs a main function (_start in WASI), configure the arguments.
// Remember, arg[0] is the program name!
sysConfig.WithArgs("cat", file)
sysConfig = sysConfig.WithFS(rooted).WithArgs("cat", "/cat.go")

// Compile the `cat` module.
compiled, err := r.CompileModule(catWasm)
Expand All @@ -53,23 +54,9 @@ func Test_Cat(t *testing.T) {

// StartWASICommand runs the "_start" function which is what TinyGo compiles "main" to.
cat, err := wazero.StartWASICommandWithConfig(r, compiled, sysConfig)

require.NoError(t, err)
defer cat.Close()

// To ensure it worked, verify stdout from WebAssembly had what we expected.
require.Equal(t, string(catGo), stdoutBuf.String())
}

func writeFile(fs wasi.FS, path string, data []byte) error {
f, err := fs.OpenWASI(0, path, wasi.O_CREATE|wasi.O_TRUNC, wasi.R_FD_WRITE, 0, 0)
if err != nil {
return err
}

if _, err := io.Copy(f, bytes.NewBuffer(data)); err != nil {
return err
}

return f.Close()
require.Equal(t, catGo, stdoutBuf.Bytes())
}
111 changes: 0 additions & 111 deletions internal/wasi/fs.go

This file was deleted.

21 changes: 0 additions & 21 deletions internal/wasi/fs_test.go

This file was deleted.

Loading

0 comments on commit 84a369e

Please sign in to comment.