-
Notifications
You must be signed in to change notification settings - Fork 269
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cli: add mount option to expose paths to wasm app (#816)
Signed-off-by: Anuraag Agrawal <[email protected]>
- Loading branch information
Showing
13 changed files
with
620 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package main | ||
|
||
import ( | ||
"io/fs" | ||
"strings" | ||
) | ||
|
||
type compositeFS struct { | ||
paths map[string]fs.FS | ||
} | ||
|
||
func (c *compositeFS) Open(name string) (fs.File, error) { | ||
if !fs.ValidPath(name) { | ||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} | ||
} | ||
for path, f := range c.paths { | ||
if !strings.HasPrefix(name, path) { | ||
continue | ||
} | ||
rest := name[len(path):] | ||
if len(rest) == 0 { | ||
// Special case reading directory | ||
rest = "." | ||
} else { | ||
// fs.Open requires a relative path | ||
if rest[0] == '/' { | ||
rest = rest[1:] | ||
} | ||
} | ||
file, err := f.Open(rest) | ||
if err == nil { | ||
return file, err | ||
} | ||
} | ||
|
||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"embed" | ||
"fmt" | ||
"io/fs" | ||
"testing" | ||
"testing/fstest" | ||
|
||
"github.com/tetratelabs/wazero/internal/testing/require" | ||
) | ||
|
||
//go:embed testdata/fs | ||
var testFS embed.FS | ||
|
||
func TestCompositeFS(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
fs *compositeFS | ||
path string | ||
content string | ||
}{ | ||
{ | ||
name: "empty", | ||
fs: &compositeFS{}, | ||
path: "bear.txt", | ||
}, | ||
{ | ||
name: "single mount to root", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"": testFSSub(""), | ||
}, | ||
}, | ||
path: "bear.txt", | ||
content: "pooh", | ||
}, | ||
{ | ||
name: "single mount to root", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"": testFSSub(""), | ||
}, | ||
}, | ||
path: "fish/clownfish.txt", | ||
content: "nemo", | ||
}, | ||
{ | ||
name: "single mount to root", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"": testFSSub(""), | ||
}, | ||
}, | ||
path: "mammals/primates/ape.txt", | ||
content: "king kong", | ||
}, | ||
{ | ||
name: "single mount to path", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"mammals": testFSSub(""), | ||
}, | ||
}, | ||
path: "mammals/bear.txt", | ||
content: "pooh", | ||
}, | ||
{ | ||
name: "single mount to path", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"mammals": testFSSub(""), | ||
}, | ||
}, | ||
path: "mammals/whale.txt", | ||
}, | ||
{ | ||
name: "single mount to path", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"mammals": testFSSub(""), | ||
}, | ||
}, | ||
path: "bear.txt", | ||
}, | ||
{ | ||
name: "non-overlapping mounts", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"fish": testFSSub("fish"), | ||
"mammals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "fish/clownfish.txt", | ||
content: "nemo", | ||
}, | ||
{ | ||
name: "non-overlapping mounts", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"fish": testFSSub("fish"), | ||
"mammals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "mammals/whale.txt", | ||
content: "moby dick", | ||
}, | ||
{ | ||
name: "non-overlapping mounts", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"fish": testFSSub("fish"), | ||
"mammals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "mammals/primates/ape.txt", | ||
content: "king kong", | ||
}, | ||
{ | ||
name: "non-overlapping mounts", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"fish": testFSSub("fish"), | ||
"mammals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "bear.txt", | ||
}, | ||
{ | ||
name: "overlapping mounts, deep first", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"animals/fish": testFSSub("fish"), | ||
"animals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "animals/fish/clownfish.txt", | ||
content: "nemo", | ||
}, | ||
{ | ||
name: "overlapping mounts, deep first", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"animals/fish": testFSSub("fish"), | ||
"animals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "animals/whale.txt", | ||
content: "moby dick", | ||
}, | ||
{ | ||
name: "overlapping mounts, deep first", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"animals/fish": testFSSub("fish"), | ||
"animals": testFSSub("mammals"), | ||
}, | ||
}, | ||
path: "animals/bear.txt", | ||
}, | ||
{ | ||
name: "overlapping mounts, shallow first", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"animals": testFSSub("mammals"), | ||
"animals/fish": testFSSub("fish"), | ||
}, | ||
}, | ||
path: "animals/fish/clownfish.txt", | ||
content: "nemo", | ||
}, | ||
{ | ||
name: "overlapping mounts, shallow first", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"animals": testFSSub("mammals"), | ||
"animals/fish": testFSSub("fish"), | ||
}, | ||
}, | ||
path: "animals/whale.txt", | ||
content: "moby dick", | ||
}, | ||
{ | ||
name: "overlapping mounts, shallow first", | ||
fs: &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
"animals": testFSSub("mammals"), | ||
"animals/fish": testFSSub("fish"), | ||
}, | ||
}, | ||
path: "animals/bear.txt", | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
tt := tc | ||
t.Run(fmt.Sprintf("%s - %s", tt.name, tt.path), func(t *testing.T) { | ||
content, err := fs.ReadFile(tt.fs, tt.path) | ||
if tt.content != "" { | ||
require.NoError(t, err) | ||
require.Equal(t, tt.content, string(bytes.TrimSpace(content))) | ||
} else { | ||
require.ErrorIs(t, err, fs.ErrNotExist) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestFSTest(t *testing.T) { | ||
fs := &compositeFS{ | ||
paths: map[string]fs.FS{ | ||
// TestFS requires non-rooted paths to be read from current directory so we mount | ||
// both with . and no prefix. | ||
".": testFSSub(""), | ||
"": testFSSub(""), | ||
}, | ||
} | ||
|
||
require.NoError(t, fstest.TestFS(fs, "bear.txt")) | ||
} | ||
|
||
func testFSSub(path string) fs.FS { | ||
// Can't use filepath.Join because we need unix behavior even on Windows. | ||
p := "testdata/fs" | ||
if len(path) > 0 { | ||
p = fmt.Sprintf("%s/%s", p, path) | ||
} | ||
f, err := fs.Sub(testFS, p) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return f | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pooh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
nemo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
king kong |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
moby dick |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
;; $wasi_env is a WASI command which copies null-terminated environ to stdout. | ||
(module $wasi_env | ||
;; environ_get reads environment variables. | ||
;; | ||
;; See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-environ_getenviron-pointerpointeru8-environ_buf-pointeru8---errno | ||
(import "wasi_snapshot_preview1" "environ_get" | ||
(func $wasi.environ_get (param $environ i32) (param $environ_buf i32) (result (;errno;) i32))) | ||
|
||
;; environ_sizes_get returns environment variables sizes. | ||
;; | ||
;; See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-environ_sizes_get---errno-size-size | ||
(import "wasi_snapshot_preview1" "environ_sizes_get" | ||
(func $wasi.environ_sizes_get (param $result.environc i32) (param $result.environ_buf_size i32) (result (;errno;) i32))) | ||
|
||
;; fd_write write bytes to a file descriptor. | ||
;; | ||
;; See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write | ||
(import "wasi_snapshot_preview1" "fd_write" | ||
(func $wasi.fd_write (param $fd i32) (param $iovs i32) (param $iovs_len i32) (param $result.size i32) (result (;errno;) i32))) | ||
|
||
;; WASI commands are required to export "memory". Particularly, imported functions mutate this. | ||
;; | ||
;; Note: 1 is the size in pages (64KB), not bytes! | ||
;; See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memories%E2%91%A7 | ||
(memory (export "memory") 1) | ||
|
||
;; $iovs are offset/length pairs in memory fd_write copies to the file descriptor. | ||
;; $main will only write one offset/length pair, corresponding to null-terminated environ. | ||
(global $iovs i32 i32.const 1024) ;; 1024 is an arbitrary offset larger than the environ. | ||
|
||
;; WASI parameters are usually memory offsets, you can ignore values by writing them to an unread offset. | ||
(global $ignored i32 i32.const 32768) | ||
|
||
;; _start is a special function defined by a WASI Command that runs like a main function would. | ||
;; | ||
;; See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi | ||
(func $main (export "_start") | ||
;; To copy an env to a file, we first need to load it into memory. | ||
(call $wasi.environ_get | ||
(global.get $ignored) ;; ignore $environ as we only read the environ_buf | ||
(i32.const 0) ;; Write $environ_buf (null-terminated environ) to memory offset zero. | ||
) | ||
drop ;; ignore the errno returned | ||
|
||
;; Next, we need to know how many bytes were loaded, as that's how much we'll copy to the file. | ||
(call $wasi.environ_sizes_get | ||
(global.get $ignored) ;; ignore $result.environc as we only read the environ_buf. | ||
(i32.add (global.get $iovs) (i32.const 4)) ;; store $result.environ_buf_size as the length to copy | ||
) | ||
drop ;; ignore the errno returned | ||
|
||
;; Finally, write the memory region to the file. | ||
(call $wasi.fd_write | ||
(i32.const 1) ;; $fd is a file descriptor and 1 is stdout (console). | ||
(global.get $iovs) ;; $iovs is the start offset of the IO vectors to copy. | ||
(i32.const 1) ;; $iovs_len is the count of offset/length pairs to copy to memory. | ||
(global.get $ignored) ;; ignore $result.size as we aren't verifying it. | ||
) | ||
drop ;; ignore the errno returned | ||
) | ||
) |
Binary file not shown.
Oops, something went wrong.