Removes experimental.WithFS (#922)

This removes the ability to override the current file system with Go
context, allowing us to simplify common paths and improve performance.

The context override was only used once in GitHub, in Trivy, and we
found another way to do that without it.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-12-14 15:00:17 +09:00
committed by GitHub
parent 9b503fba6d
commit 0ed4002549
21 changed files with 261 additions and 470 deletions

View File

@@ -2,6 +2,7 @@ package experimental_test
import (
"context"
_ "embed"
"log"
"os"
@@ -36,6 +37,13 @@ func Example_withCompilationCacheDirName() {
//
}
// fsWasm was generated by the following:
//
// cd testdata; wat2wasm --debug-names fs.wat
//
//go:embed testdata/fs.wasm
var fsWasm []byte
// newRuntimeCompileDestroy creates a new wazero.Runtime, compile a binary, and then delete the runtime.
func newRuntimeCompileClose(ctx context.Context) {
r := wazero.NewRuntime(ctx)

View File

@@ -1,24 +0,0 @@
package experimental
import (
"context"
"io/fs"
"github.com/tetratelabs/wazero/api"
internalfs "github.com/tetratelabs/wazero/internal/sys"
)
// WithFS overrides fs.FS in the context-based manner. Caller needs to take
// responsibility for closing the returned api.Closer.
//
// Note: This has the same effect as the same function on wazero.ModuleConfig.
func WithFS(ctx context.Context, fs fs.FS) (context.Context, api.Closer, error) {
if fs == nil {
fs = internalfs.EmptyFS
}
if fsCtx, err := internalfs.NewFSContext(fs); err != nil {
return nil, nil, err
} else {
return context.WithValue(ctx, internalfs.FSKey{}, fsCtx), fsCtx, err
}
}

View File

@@ -1,70 +0,0 @@
package experimental_test
import (
"context"
_ "embed"
"fmt"
"log"
"os"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)
// fsWasm was generated by the following:
//
// cd testdata; wat2wasm --debug-names fs.wat
//
//go:embed testdata/fs.wasm
var fsWasm []byte
// This is a basic example of overriding the file system via WithFS. The main
// goal is to show how it is configured.
func Example_withFS() {
ctx := context.Background()
r := wazero.NewRuntime(ctx)
defer r.Close(ctx) // This closes everything this Runtime created.
wasi_snapshot_preview1.MustInstantiate(ctx, r)
// Instantiate a module exporting a WASI function that uses the filesystem.
mod, err := r.InstantiateModuleFromBinary(ctx, fsWasm)
if err != nil {
log.Panicln(err)
}
// Configure the filesystem overlay, noting that it can fail if the
// directory is invalid. The closer must be closed.
ctx, closer, err := experimental.WithFS(ctx, os.DirFS("."))
if err != nil {
log.Panicln(err)
}
defer closer.Close(ctx)
fdPrestatDirName := mod.ExportedFunction("fd_prestat_dir_name")
fd := 3 // after stderr
pathLen := 1 // length we expect the path to be.
pathOffset := 0 // where to write pathLen bytes.
// By default, there are no pre-opened directories. If the configuration
// was wrong, this call would fail.
results, err := fdPrestatDirName.Call(ctx, uint64(fd), uint64(pathOffset), uint64(pathLen))
if err != nil {
log.Panicln(err)
}
if results[0] != 0 {
log.Panicf("received errno %d\n", results[0])
}
// Try to read the path!
if path, ok := mod.Memory().Read(ctx, uint32(pathOffset), uint32(pathLen)); !ok {
log.Panicln("out of memory reading path")
} else {
fmt.Println(string(path))
}
// Output:
// /
}

View File

@@ -1,45 +0,0 @@
package experimental_test
import (
"context"
_ "embed"
"testing"
"testing/fstest"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/testing/require"
)
// This is a very basic integration of fs config. The main goal is to show how it is configured.
func TestWithFS(t *testing.T) {
fileName := "animals.txt"
mapfs := fstest.MapFS{fileName: &fstest.MapFile{Data: []byte(`animals`)}}
// Set context to one that has experimental fs config
ctx, closer, err := experimental.WithFS(context.Background(), mapfs)
require.NoError(t, err)
defer closer.Close(ctx)
v := ctx.Value(sys.FSKey{})
require.NotNil(t, v)
fsCtx, ok := v.(*sys.FSContext)
require.True(t, ok)
entry, ok := fsCtx.OpenedFile(ctx, 3)
require.True(t, ok)
require.Equal(t, "/", entry.Name)
// Override to nil context, ex to block file access
ctx, closer, err = experimental.WithFS(ctx, nil)
require.NoError(t, err)
defer closer.Close(ctx)
v = ctx.Value(sys.FSKey{})
require.NotNil(t, v)
fsCtx, ok = v.(*sys.FSContext)
require.True(t, ok)
_, ok = fsCtx.OpenedFile(ctx, 3)
require.False(t, ok)
}

Binary file not shown.

View File

@@ -1,9 +0,0 @@
(module
(func ;; re-export fd_prestat_dir_name, imported from WASI
(export "clock_time_get")
(import "wasi_snapshot_preview1" "clock_time_get")
(param $id i32) (param $precision i64) (param $result.timestamp i32) (result (;errno;) i32)
)
(memory (export "memory") 1 1) ;; memory is required for WASI
)

View File

@@ -35,6 +35,7 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
@@ -155,16 +156,19 @@ var abortMessageDisabled = abortMessageEnabled.WithGoModuleFunc(abort)
// abortWithMessage implements functionAbort
func abortWithMessage(ctx context.Context, mod api.Module, stack []uint64) {
fsc := mod.(*wasm.CallContext).Sys.FS()
mem := mod.Memory()
message := uint32(stack[0])
fileName := uint32(stack[1])
lineNumber := uint32(stack[2])
columnNumber := uint32(stack[3])
sysCtx := mod.(*wasm.CallContext).Sys
mem := mod.Memory()
// Don't panic if there was a problem reading the message
stderr := fsc.FdWriter(internalsys.FdStderr)
if msg, msgOk := readAssemblyScriptString(ctx, mem, message); msgOk {
if fn, fnOk := readAssemblyScriptString(ctx, mem, fileName); fnOk {
_, _ = fmt.Fprintf(sysCtx.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
_, _ = fmt.Fprintf(stderr, "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
}
}
abort(ctx, mod, stack)
@@ -195,14 +199,18 @@ var traceStdout = &wasm.HostFunc{
Code: &wasm.Code{
IsHostFunction: true,
GoFunc: api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
traceTo(ctx, mod, stack, mod.(*wasm.CallContext).Sys.Stdout())
fsc := mod.(*wasm.CallContext).Sys.FS()
stdout := fsc.FdWriter(internalsys.FdStdout)
traceTo(ctx, mod, stack, stdout)
}),
},
}
// traceStderr implements trace to the configured Stderr.
var traceStderr = traceStdout.WithGoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
traceTo(ctx, mod, stack, mod.(*wasm.CallContext).Sys.Stderr())
fsc := mod.(*wasm.CallContext).Sys.FS()
stderr := fsc.FdWriter(internalsys.FdStderr)
traceTo(ctx, mod, stack, stderr)
})
// traceTo implements the function "trace" in AssemblyScript. e.g.

View File

@@ -86,11 +86,11 @@ var fdAllocate = stubFunction(
// and https://linux.die.net/man/3/close
var fdClose = newHostFunc(fdCloseName, fdCloseFn, []api.ValueType{i32}, "fd")
func fdCloseFn(ctx context.Context, mod api.Module, params []uint64) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
func fdCloseFn(_ context.Context, mod api.Module, params []uint64) Errno {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := uint32(params[0])
if ok := sysCtx.FS(ctx).CloseFile(ctx, fd); !ok {
if ok := fsc.CloseFile(fd); !ok {
return ErrnoBadf
}
return ErrnoSuccess
@@ -164,8 +164,8 @@ func fdFdstatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
}
// Otherwise, look up the file corresponding to the file descriptor.
sysCtx := mod.(*wasm.CallContext).Sys
file, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
fsc := mod.(*wasm.CallContext).Sys.FS()
file, ok := fsc.OpenedFile(fd)
if !ok {
return ErrnoBadf
}
@@ -326,8 +326,8 @@ func fdFilestatGetFunc(ctx context.Context, mod api.Module, fd, resultBuf uint32
}
// Otherwise, look up the file corresponding to the file descriptor.
sysCtx := mod.(*wasm.CallContext).Sys
file, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
fsc := mod.(*wasm.CallContext).Sys.FS()
file, ok := fsc.OpenedFile(fd)
if !ok {
return ErrnoBadf
}
@@ -458,8 +458,8 @@ var fdPrestatGet = proxyResultParams(&wasm.HostFunc{
Code: &wasm.Code{IsHostFunction: true, GoFunc: u64ResultParam(fdPrestatGetFn)},
}, fdPrestatGetName)
func fdPrestatGetFn(ctx context.Context, mod api.Module, stack []uint64) (prestat uint64, errno Errno) {
sysCtx := mod.(*wasm.CallContext).Sys
func fdPrestatGetFn(_ context.Context, mod api.Module, stack []uint64) (prestat uint64, errno Errno) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := uint32(stack[0])
// Currently, we only pre-open the root file descriptor.
@@ -467,7 +467,7 @@ func fdPrestatGetFn(ctx context.Context, mod api.Module, stack []uint64) (presta
return 0, ErrnoBadf
}
entry, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
entry, ok := fsc.OpenedFile(fd)
if !ok {
return 0, ErrnoBadf
}
@@ -516,7 +516,7 @@ var fdPrestatDirName = newHostFunc(
)
func fdPrestatDirNameFn(ctx context.Context, mod api.Module, params []uint64) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, path, pathLen := uint32(params[0]), uint32(params[1]), uint32(params[2])
// Currently, we only pre-open the root file descriptor.
@@ -524,7 +524,7 @@ func fdPrestatDirNameFn(ctx context.Context, mod api.Module, params []uint64) Er
return ErrnoBadf
}
f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
f, ok := fsc.OpenedFile(fd)
if !ok {
return ErrnoBadf
}
@@ -614,7 +614,7 @@ func fdReadFn(ctx context.Context, mod api.Module, stack []uint64) (nread uint32
func fdReadOrPread(ctx context.Context, mod api.Module, stack []uint64, isPread bool) (uint32, Errno) {
mem := mod.Memory()
sysCtx := mod.(*wasm.CallContext).Sys
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := uint32(stack[0])
iovs := uint32(stack[1])
@@ -625,7 +625,7 @@ func fdReadOrPread(ctx context.Context, mod api.Module, stack []uint64, isPread
offset = int64(stack[3])
}
r := internalsys.FdReader(ctx, sysCtx, fd)
r := fsc.FdReader(fd)
if r == nil {
return 0, ErrnoBadf
}
@@ -701,7 +701,7 @@ var fdReaddir = proxyResultParams(&wasm.HostFunc{
func fdReaddirFn(ctx context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
mem := mod.Memory()
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := uint32(stack[0])
buf := uint32(stack[1])
@@ -940,7 +940,7 @@ func writeDirent(buf []byte, dNext uint64, dNamlen uint32, dType bool) {
// openedDir returns the directory and ErrnoSuccess if the fd points to a readable directory.
func openedDir(ctx context.Context, fsc *internalsys.FSContext, fd uint32) (fs.ReadDirFile, *internalsys.ReadDir, Errno) {
if f, ok := fsc.OpenedFile(ctx, fd); !ok {
if f, ok := fsc.OpenedFile(fd); !ok {
return nil, nil, ErrnoBadf
} else if d, ok := f.File.(fs.ReadDirFile); !ok {
// fd_readdir docs don't indicate whether to return ErrnoNotdir or
@@ -1011,7 +1011,7 @@ var fdSeek = proxyResultParams(&wasm.HostFunc{
}, fdSeekName)
func fdSeekFn(ctx context.Context, mod api.Module, stack []uint64) (int64, Errno) {
sysCtx := mod.(*wasm.CallContext).Sys
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := uint32(stack[0])
offset := stack[1]
whence := uint32(stack[2])
@@ -1022,7 +1022,7 @@ func fdSeekFn(ctx context.Context, mod api.Module, stack []uint64) (int64, Errno
var seeker io.Seeker
// Check to see if the file descriptor is available
if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
if f, ok := fsc.OpenedFile(fd); !ok {
return 0, ErrnoBadf
// fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it.
} else if seeker, ok = f.File.(io.Seeker); !ok {
@@ -1122,13 +1122,13 @@ var fdWrite = proxyResultParams(&wasm.HostFunc{
func fdWriteFn(ctx context.Context, mod api.Module, stack []uint64) (uint32, Errno) {
mem := mod.Memory()
sysCtx := mod.(*wasm.CallContext).Sys
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := uint32(stack[0])
iovs := uint32(stack[1])
iovsCount := uint32(stack[2])
writer := internalsys.FdWriter(ctx, sysCtx, fd)
writer := fsc.FdWriter(fd)
if writer == nil {
return 0, ErrnoBadf
}
@@ -1210,8 +1210,7 @@ var pathFilestatGet = newHostFunc(
// pathFilestatGetFn cannot currently use proxyResultParams because filestat is
// larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64).
func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
fsc := sysCtx.FS(ctx)
fsc := mod.(*wasm.CallContext).Sys.FS()
dirfd := uint32(params[0])
@@ -1233,7 +1232,7 @@ func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Err
pathName := string(b)
// Prepend the path if necessary.
if dir, ok := fsc.OpenedFile(ctx, dirfd); !ok {
if dir, ok := fsc.OpenedFile(dirfd); !ok {
return ErrnoBadf
} else if _, ok := dir.File.(fs.ReadDirFile); !ok {
return ErrnoNotdir // TODO: cache filetype instead of poking.
@@ -1243,7 +1242,7 @@ func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Err
}
// Stat the file without allocating a file descriptor
stat, errnoResult := statFile(ctx, fsc, pathName)
stat, errnoResult := statFile(fsc, pathName)
if errnoResult != ErrnoSuccess {
return errnoResult
}
@@ -1356,8 +1355,7 @@ const (
)
func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) (uint32, Errno) {
sysCtx := mod.(*wasm.CallContext).Sys
fsc := sysCtx.FS(ctx)
fsc := mod.(*wasm.CallContext).Sys.FS()
dirfd := uint32(params[0])
@@ -1384,7 +1382,7 @@ func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) (uint32, E
// here in any way except assuming it is "/".
//
// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c#L24-L26
if _, ok := fsc.OpenedFile(ctx, dirfd); !ok {
if _, ok := fsc.OpenedFile(dirfd); !ok {
return 0, ErrnoBadf
}
@@ -1399,25 +1397,25 @@ func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) (uint32, E
// path="bar", this should open "/tmp/foo/bar" not "/bar".
//
// See https://linux.die.net/man/2/openat
newFD, errnoResult := openFile(ctx, fsc, string(b))
newFD, errnoResult := openFile(fsc, string(b))
if errnoResult != ErrnoSuccess {
return 0, errnoResult
}
// Check any flags that require the file to evaluate.
if oflags&wasiOflagsDirectory != 0 {
return newFD, failIfNotDirectory(ctx, fsc, newFD)
return newFD, failIfNotDirectory(fsc, newFD)
}
return newFD, ErrnoSuccess
}
func failIfNotDirectory(ctx context.Context, fsc *internalsys.FSContext, fd uint32) Errno {
func failIfNotDirectory(fsc *internalsys.FSContext, fd uint32) Errno {
// Lookup the previous file
if f, ok := fsc.OpenedFile(ctx, fd); !ok {
if f, ok := fsc.OpenedFile(fd); !ok {
return ErrnoBadf
} else if _, ok := f.File.(fs.ReadDirFile); !ok {
_ = fsc.CloseFile(ctx, fd)
_ = fsc.CloseFile(fd)
return ErrnoNotdir
}
return ErrnoSuccess
@@ -1473,8 +1471,8 @@ var pathUnlinkFile = stubFunction(
// openFile attempts to open the file at the given path. Errors coerce to WASI
// Errno.
func openFile(ctx context.Context, fsc *internalsys.FSContext, name string) (fd uint32, errno Errno) {
newFD, err := fsc.OpenFile(ctx, name)
func openFile(fsc *internalsys.FSContext, name string) (fd uint32, errno Errno) {
newFD, err := fsc.OpenFile(name)
if err == nil {
fd = newFD
errno = ErrnoSuccess
@@ -1486,8 +1484,8 @@ func openFile(ctx context.Context, fsc *internalsys.FSContext, name string) (fd
// statFile attempts to stat the file at the given path. Errors coerce to WASI
// Errno.
func statFile(ctx context.Context, fsc *internalsys.FSContext, name string) (stat fs.FileInfo, errno Errno) {
s, err := fsc.StatFile(ctx, name)
func statFile(fsc *internalsys.FSContext, name string) (stat fs.FileInfo, errno Errno) {
s, err := fsc.StatFile(name)
if err == nil {
stat = s
errno = ErrnoSuccess

View File

@@ -51,12 +51,12 @@ func Test_fdClose(t *testing.T) {
defer r.Close(testCtx)
// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS()
fdToClose, err := fsc.OpenFile(testCtx, path1)
fdToClose, err := fsc.OpenFile(path1)
require.NoError(t, err)
fdToKeep, err := fsc.OpenFile(testCtx, path2)
fdToKeep, err := fsc.OpenFile(path2)
require.NoError(t, err)
// Close
@@ -69,11 +69,11 @@ func Test_fdClose(t *testing.T) {
`, "\n"+log.String())
// Verify fdToClose is closed and removed from the opened FDs.
_, ok := fsc.OpenedFile(testCtx, fdToClose)
_, ok := fsc.OpenedFile(fdToClose)
require.False(t, ok)
// Verify fdToKeep is not closed
_, ok = fsc.OpenedFile(testCtx, fdToKeep)
_, ok = fsc.OpenedFile(fdToKeep)
require.True(t, ok)
log.Reset()
@@ -108,12 +108,12 @@ func Test_fdFdstatGet(t *testing.T) {
memorySize := mod.Memory().Size(testCtx)
// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS()
fileFd, err := fsc.OpenFile(testCtx, file)
fileFd, err := fsc.OpenFile(file)
require.NoError(t, err)
dirFd, err := fsc.OpenFile(testCtx, dir)
dirFd, err := fsc.OpenFile(dir)
require.NoError(t, err)
tests := []struct {
@@ -297,12 +297,12 @@ func Test_fdFilestatGet(t *testing.T) {
memorySize := mod.Memory().Size(testCtx)
// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS()
fileFd, err := fsc.OpenFile(testCtx, file)
fileFd, err := fsc.OpenFile(file)
require.NoError(t, err)
dirFd, err := fsc.OpenFile(testCtx, dir)
dirFd, err := fsc.OpenFile(dir)
require.NoError(t, err)
tests := []struct {
@@ -1242,9 +1242,9 @@ func Test_fdReaddir(t *testing.T) {
mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs))
defer r.Close(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(testCtx, "dir")
fd, err := fsc.OpenFile("dir")
require.NoError(t, err)
tests := []struct {
@@ -1481,7 +1481,7 @@ func Test_fdReaddir(t *testing.T) {
defer log.Reset()
// Assign the state we are testing
file, ok := fsc.OpenedFile(testCtx, fd)
file, ok := fsc.OpenedFile(fd)
require.True(t, ok)
dir := tc.dir()
defer dir.File.Close()
@@ -1521,12 +1521,12 @@ func Test_fdReaddir_Errors(t *testing.T) {
defer r.Close(testCtx)
memLen := mod.Memory().Size(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS()
dirFD, err := fsc.OpenFile(testCtx, "dir")
dirFD, err := fsc.OpenFile("dir")
require.NoError(t, err)
fileFD, err := fsc.OpenFile(testCtx, "notdir")
fileFD, err := fsc.OpenFile("notdir")
require.NoError(t, err)
tests := []struct {
@@ -1685,7 +1685,7 @@ func Test_fdReaddir_Errors(t *testing.T) {
defer log.Reset()
// Reset the directory so that tests don't taint each other.
if file, ok := fsc.OpenedFile(testCtx, tc.fd); ok && tc.fd == dirFD {
if file, ok := fsc.OpenedFile(tc.fd); ok && tc.fd == dirFD {
dir, err := fdReadDirFs.Open("dir")
require.NoError(t, err)
defer dir.Close()
@@ -2016,8 +2016,8 @@ func Test_fdSeek(t *testing.T) {
maskMemory(t, testCtx, mod, len(tc.expectedMemory))
// Since we initialized this file, we know it is a seeker (because it is a MapFile)
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
f, ok := fsc.OpenedFile(testCtx, fd)
fsc := mod.(*wasm.CallContext).Sys.FS()
f, ok := fsc.OpenedFile(fd)
require.True(t, ok)
seeker := f.File.(io.Seeker)
@@ -2375,14 +2375,14 @@ func Test_pathFilestatGet(t *testing.T) {
memorySize := mod.Memory().Size(testCtx)
// open both paths without using WASI
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fsc := mod.(*wasm.CallContext).Sys.FS()
rootFd := uint32(3) // after stderr
fileFd, err := fsc.OpenFile(testCtx, file)
fileFd, err := fsc.OpenFile(file)
require.NoError(t, err)
dirFd, err := fsc.OpenFile(testCtx, dir)
dirFd, err := fsc.OpenFile(dir)
require.NoError(t, err)
tests := []struct {
@@ -2649,8 +2649,8 @@ func Test_pathOpen(t *testing.T) {
require.Equal(t, expectedMemory, actual)
// verify the file was actually opened
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
f, ok := fsc.OpenedFile(testCtx, expectedFD)
fsc := mod.(*wasm.CallContext).Sys.FS()
f, ok := fsc.OpenedFile(expectedFD)
require.True(t, ok)
require.Equal(t, pathName, f.Name)
}
@@ -2860,8 +2860,8 @@ func requireOpenFile(t *testing.T, pathName string, data []byte) (api.Module, ui
}
testFS := fstest.MapFS{pathName[1:]: mapFile} // strip the leading slash
mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fd, err := fsc.OpenFile(testCtx, pathName)
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(pathName)
require.NoError(t, err)
return mod, fd, log, r
}
@@ -2870,12 +2870,12 @@ func requireOpenFile(t *testing.T, pathName string, data []byte) (api.Module, ui
func requireOpenWritableFile(t *testing.T, tmpDir string, pathName string) (api.Module, uint32, *bytes.Buffer, api.Closer) {
writeable, testFS := createWriteableFile(t, tmpDir, pathName, []byte{})
mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fd, err := fsc.OpenFile(testCtx, pathName)
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(pathName)
require.NoError(t, err)
// Swap the read-only file with a writeable one until #390
f, ok := fsc.OpenedFile(testCtx, fd)
f, ok := fsc.OpenedFile(fd)
require.True(t, ok)
f.File.Close()
f.File = writeable

View File

@@ -4,7 +4,6 @@ import (
"context"
"github.com/tetratelabs/wazero/api"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -133,16 +132,16 @@ func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno
// processFDEvent returns a validation error or ErrnoNotsup as file or socket
// subscriptions are not yet supported.
func processFDEvent(ctx context.Context, mod api.Module, eventType byte, inBuf []byte) Errno {
func processFDEvent(_ context.Context, mod api.Module, eventType byte, inBuf []byte) Errno {
fd := le.Uint32(inBuf)
sysCtx := mod.(*wasm.CallContext).Sys
fsc := mod.(*wasm.CallContext).Sys.FS()
// Choose the best error, which falls back to unsupported, until we support
// files.
errno := ErrnoNotsup
if eventType == eventTypeFdRead && internalsys.FdReader(ctx, sysCtx, fd) == nil {
if eventType == eventTypeFdRead && fsc.FdReader(fd) == nil {
errno = ErrnoBadf
} else if eventType == eventTypeFdWrite && internalsys.FdWriter(ctx, sysCtx, fd) == nil {
} else if eventType == eventTypeFdWrite && fsc.FdWriter(fd) == nil {
errno = ErrnoBadf
}

View File

@@ -171,16 +171,16 @@ func Benchmark_fdReaddir(b *testing.B) {
fn := mod.ExportedFunction(fdReaddirName)
// Open the root directory as a file-descriptor.
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fd, err := fsc.OpenFile(testCtx, ".")
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(".")
if err != nil {
b.Fatal(err)
}
f, ok := fsc.OpenedFile(testCtx, fd)
f, ok := fsc.OpenedFile(fd)
if !ok {
b.Fatal("couldn't open fd ", fd)
}
defer fsc.CloseFile(testCtx, fd)
defer fsc.CloseFile(fd)
b.ResetTimer()
b.ReportAllocs()
@@ -277,12 +277,12 @@ func Benchmark_pathFilestat(b *testing.B) {
// under a pre-determined directory: zig
fd := sys.FdRoot
if bc.fd != sys.FdRoot {
fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
fd, err = fsc.OpenFile(testCtx, "zig")
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err = fsc.OpenFile("zig")
if err != nil {
b.Fatal(err)
}
defer fsc.CloseFile(testCtx, fd)
defer fsc.CloseFile(fd)
}
fn := mod.ExportedFunction(pathFilestatGetName)

View File

@@ -9,6 +9,7 @@ import (
"syscall"
"github.com/tetratelabs/wazero/api"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -91,7 +92,7 @@ func (*jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface{}
perm := toUint32(args[2])
callback := args[3].(funcWrapper)
fd, err := syscallOpen(ctx, mod, name, flags, perm)
fd, err := syscallOpen(mod, name, flags, perm)
return callback.invoke(ctx, mod, refJsfs, err, fd) // note: error first
}
@@ -105,18 +106,18 @@ func (*jsfsStat) invoke(ctx context.Context, mod api.Module, args ...interface{}
name := args[0].(string)
callback := args[1].(funcWrapper)
stat, err := syscallStat(ctx, mod, name)
stat, err := syscallStat(mod, name)
return callback.invoke(ctx, mod, refJsfs, err, stat) // note: error first
}
// syscallStat is like syscall.Stat
func syscallStat(ctx context.Context, mod api.Module, name string) (*jsSt, error) {
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
if fd, err := fsc.OpenFile(ctx, name); err != nil {
func syscallStat(mod api.Module, name string) (*jsSt, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
if fd, err := fsc.OpenFile(name); err != nil {
return nil, err
} else {
defer fsc.CloseFile(ctx, fd)
return syscallFstat(ctx, mod, fd)
defer fsc.CloseFile(fd)
return syscallFstat(fsc, fd)
}
}
@@ -127,17 +128,18 @@ type jsfsFstat struct{}
// invoke implements jsFn.invoke
func (*jsfsFstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := toUint32(args[0])
callback := args[1].(funcWrapper)
fstat, err := syscallFstat(ctx, mod, fd)
fstat, err := syscallFstat(fsc, fd)
return callback.invoke(ctx, mod, refJsfs, err, fstat) // note: error first
}
// syscallFstat is like syscall.Fstat
func syscallFstat(ctx context.Context, mod api.Module, fd uint32) (*jsSt, error) {
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
if f, ok := fsc.OpenedFile(ctx, fd); !ok {
func syscallFstat(fsc *internalsys.FSContext, fd uint32) (*jsSt, error) {
if f, ok := fsc.OpenedFile(fd); !ok {
return nil, syscall.EBADF
} else if stat, err := f.File.Stat(); err != nil {
return nil, err
@@ -159,17 +161,18 @@ type jsfsClose struct{}
// invoke implements jsFn.invoke
func (*jsfsClose) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := toUint32(args[0])
callback := args[1].(funcWrapper)
err := syscallClose(ctx, mod, fd)
err := syscallClose(fsc, fd)
return callback.invoke(ctx, mod, refJsfs, err, true) // note: error first
}
// syscallClose is like syscall.Close
func syscallClose(ctx context.Context, mod api.Module, fd uint32) (err error) {
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
if ok := fsc.CloseFile(ctx, fd); !ok {
func syscallClose(fsc *internalsys.FSContext, fd uint32) (err error) {
if ok := fsc.CloseFile(fd); !ok {
err = syscall.EBADF // already closed
}
return
@@ -193,13 +196,15 @@ func (*jsfsRead) invoke(ctx context.Context, mod api.Module, args ...interface{}
fOffset := args[4] // nil unless Pread
callback := args[5].(funcWrapper)
n, err := syscallRead(ctx, mod, fd, fOffset, buf.slice[offset:offset+byteCount])
n, err := syscallRead(mod, fd, fOffset, buf.slice[offset:offset+byteCount])
return callback.invoke(ctx, mod, refJsfs, err, n) // note: error first
}
// syscallRead is like syscall.Read
func syscallRead(ctx context.Context, mod api.Module, fd uint32, offset interface{}, p []byte) (n uint32, err error) {
r := fdReader(ctx, mod, fd)
func syscallRead(mod api.Module, fd uint32, offset interface{}, p []byte) (n uint32, err error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
r := fsc.FdReader(fd)
if r == nil {
err = syscall.EBADF
}
@@ -244,15 +249,17 @@ func (*jsfsWrite) invoke(ctx context.Context, mod api.Module, args ...interface{
callback := args[5].(funcWrapper)
if byteCount > 0 { // empty is possible on EOF
n, err := syscallWrite(ctx, mod, fd, fOffset, buf.slice[offset:offset+byteCount])
n, err := syscallWrite(mod, fd, fOffset, buf.slice[offset:offset+byteCount])
return callback.invoke(ctx, mod, refJsfs, err, n) // note: error first
}
return callback.invoke(ctx, mod, refJsfs, nil, refValueZero)
}
// syscallWrite is like syscall.Write
func syscallWrite(ctx context.Context, mod api.Module, fd uint32, offset interface{}, p []byte) (n uint32, err error) {
if writer := fdWriter(ctx, mod, fd); writer == nil {
func syscallWrite(mod api.Module, fd uint32, offset interface{}, p []byte) (n uint32, err error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
if writer := fsc.FdWriter(fd); writer == nil {
err = syscall.EBADF
} else if nWritten, e := writer.Write(p); e == nil || e == io.EOF {
// fs_js.go cannot parse io.EOF so coerce it to nil.
@@ -279,15 +286,16 @@ func (*jsfsReaddir) invoke(ctx context.Context, mod api.Module, args ...interfac
return callback.invoke(ctx, mod, refJsfs, err, stat) // note: error first
}
func syscallReaddir(ctx context.Context, mod api.Module, name string) (*objectArray, error) {
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
fd, err := fsc.OpenFile(ctx, name)
func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArray, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(name)
if err != nil {
return nil, err
}
defer fsc.CloseFile(ctx, fd)
defer fsc.CloseFile(fd)
if f, ok := fsc.OpenedFile(ctx, fd); !ok {
if f, ok := fsc.OpenedFile(fd); !ok {
return nil, syscall.EBADF
} else if d, ok := f.File.(fs.ReadDirFile); !ok {
return nil, syscall.ENOTDIR
@@ -312,14 +320,14 @@ func (*returnZero) invoke(ctx context.Context, mod api.Module, args ...interface
type returnSliceOfZero struct{}
// invoke implements jsFn.invoke
func (*returnSliceOfZero) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
func (*returnSliceOfZero) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) {
return &objectArray{slice: []interface{}{refValueZero}}, nil
}
type returnArg0 struct{}
// invoke implements jsFn.invoke
func (*returnArg0) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
func (*returnArg0) invoke(_ context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
return args[0], nil
}
@@ -327,7 +335,7 @@ func (*returnArg0) invoke(ctx context.Context, mod api.Module, args ...interface
type cwd struct{}
// invoke implements jsFn.invoke
func (*cwd) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
func (*cwd) invoke(ctx context.Context, _ api.Module, _ ...interface{}) (interface{}, error) {
return getState(ctx).cwd, nil
}
@@ -336,19 +344,20 @@ type chdir struct{}
// invoke implements jsFn.invoke
func (*chdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
path := args[0].(string)
// TODO: refactor so that sys has path-based ops, also needed in WASI.
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
if fd, err := fsc.OpenFile(ctx, path); err != nil {
if fd, err := fsc.OpenFile(path); err != nil {
return nil, syscall.ENOENT
} else if f, ok := fsc.OpenedFile(ctx, fd); !ok {
} else if f, ok := fsc.OpenedFile(fd); !ok {
return nil, syscall.ENOENT
} else if s, err := f.File.Stat(); err != nil {
fsc.CloseFile(ctx, fd)
fsc.CloseFile(fd)
return nil, syscall.ENOENT
} else if !s.IsDir() {
fsc.CloseFile(ctx, fd)
fsc.CloseFile(fd)
return nil, syscall.ENOTDIR
} else {
getState(ctx).cwd = path

View File

@@ -3,7 +3,6 @@ package gojs
import (
"context"
"fmt"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/spfunc"
@@ -60,17 +59,12 @@ var WasmWrite = spfunc.MustCallFromSP(false, &wasm.HostFunc{
})
func wasmWrite(ctx context.Context, mod api.Module, stack []uint64) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, p, n := uint32(stack[0]), uint32(stack[1]), uint32(stack[2])
var writer io.Writer
switch fd {
case 1:
writer = mod.(*wasm.CallContext).Sys.Stdout()
case 2:
writer = mod.(*wasm.CallContext).Sys.Stderr()
default:
// Keep things simple by expecting nothing past 2
writer := fsc.FdWriter(fd)
if writer == nil {
panic(fmt.Errorf("unexpected fd %d", fd))
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"syscall"
@@ -565,49 +564,9 @@ func mapJSError(err error) *syscallErr {
}
// syscallOpen is like syscall.Open
func syscallOpen(ctx context.Context, mod api.Module, name string, flags, perm uint32) (uint32, error) {
fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
return fsc.OpenFile(ctx, name)
}
const (
fdStdin = iota
fdStdout
fdStderr
)
// fdReader returns a valid reader for the given file descriptor or nil if ErrnoBadf.
func fdReader(ctx context.Context, mod api.Module, fd uint32) io.Reader {
sysCtx := mod.(*wasm.CallContext).Sys
if fd == fdStdin {
return sysCtx.Stdin()
} else if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
return nil
} else {
return f.File
}
}
// fdWriter returns a valid writer for the given file descriptor or nil if ErrnoBadf.
func fdWriter(ctx context.Context, mod api.Module, fd uint32) io.Writer {
sysCtx := mod.(*wasm.CallContext).Sys
switch fd {
case fdStdout:
return sysCtx.Stdout()
case fdStderr:
return sysCtx.Stderr()
default:
// Check to see if the file descriptor is available
if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok || f.File == nil {
return nil
// fs.FS doesn't declare io.Writer, but implementations such as
// os.File implement it.
} else if writer, ok := f.File.(io.Writer); !ok {
return nil
} else {
return writer
}
}
func syscallOpen(mod api.Module, name string, flags, perm uint32) (uint32, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
return fsc.OpenFile(name)
}
// funcWrapper is the result of go's js.FuncOf ("_makeFuncWrapper" here).

View File

@@ -31,11 +31,6 @@ const (
FdRoot
)
// FSKey is a context.Context Value key. It allows overriding fs.FS for WASI.
//
// See https://github.com/tetratelabs/wazero/issues/491
type FSKey struct{}
// EmptyFS is exported to special-case an empty file system.
var EmptyFS = &emptyFS{}
@@ -104,6 +99,9 @@ type FSContext struct {
// fs is the root ("/") mount.
fs fs.FS
stdin io.Reader
stdout, stderr io.Writer
// openedFiles is a map of file descriptor numbers (>=FdRoot) to open files
// (or directories) and defaults to empty.
// TODO: This is unguarded, so not goroutine-safe!
@@ -113,15 +111,6 @@ type FSContext struct {
lastFD uint32
}
// emptyFSContext is the context associated with EmptyFS.
//
// Note: This is not mutable as operations functions do not affect field state.
var emptyFSContext = &FSContext{
fs: EmptyFS,
openedFiles: map[uint32]*FileEntry{},
lastFD: 2,
}
var errNotDir = errors.New("not a directory")
// NewFSContext creates a FSContext, using the `root` parameter for any paths
@@ -129,10 +118,30 @@ var errNotDir = errors.New("not a directory")
// Otherwise, `root` is assigned file descriptor FdRoot and the returned
// context can open files in that file system. Any error on opening "." is
// returned.
func NewFSContext(root fs.FS) (fsc *FSContext, err error) {
func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, root fs.FS) (fsc *FSContext, err error) {
if stdin == nil {
stdin = eofReader{}
}
if stdout == nil {
stdout = io.Discard
}
if stderr == nil {
stderr = io.Discard
}
fsc = &FSContext{
stdin: stdin,
stdout: stdout,
stderr: stderr,
fs: root,
openedFiles: map[uint32]*FileEntry{},
lastFD: FdStderr,
}
if root == EmptyFS {
fsc = emptyFSContext
return
return fsc, nil
}
// Open the root directory by using "." as "/" is not relevant in fs.FS.
@@ -158,13 +167,10 @@ func NewFSContext(root fs.FS) (fsc *FSContext, err error) {
return
}
return &FSContext{
fs: root,
openedFiles: map[uint32]*FileEntry{
FdRoot: {Name: "/", File: rootDir},
},
lastFD: FdRoot,
}, nil
fsc.openedFiles[FdRoot] = &FileEntry{Name: "/", File: rootDir}
fsc.lastFD = FdRoot
return fsc, nil
}
// nextFD gets the next file descriptor number in a goroutine safe way (monotonically) or zero if we ran out.
@@ -178,7 +184,7 @@ func (c *FSContext) nextFD() uint32 {
}
// OpenedFile returns a file and true if it was opened or nil and false, if syscall.EBADF.
func (c *FSContext) OpenedFile(_ context.Context, fd uint32) (*FileEntry, bool) {
func (c *FSContext) OpenedFile(fd uint32) (*FileEntry, bool) {
f, ok := c.openedFiles[fd]
return f, ok
}
@@ -188,7 +194,7 @@ func (c *FSContext) OpenedFile(_ context.Context, fd uint32) (*FileEntry, bool)
// TODO: Consider dirflags and oflags. Also, allow non-read-only open based on config about the mount.
// e.g. allow os.O_RDONLY, os.O_WRONLY, or os.O_RDWR either by config flag or pattern on filename
// See #390
func (c *FSContext) OpenFile(_ context.Context, name string /* TODO: flags int, perm int */) (uint32, error) {
func (c *FSContext) OpenFile(name string /* TODO: flags int, perm int */) (uint32, error) {
f, err := c.openFile(name)
if err != nil {
return 0, err
@@ -203,7 +209,7 @@ func (c *FSContext) OpenFile(_ context.Context, name string /* TODO: flags int,
return newFD, nil
}
func (c *FSContext) StatFile(_ context.Context, name string) (fs.FileInfo, error) {
func (c *FSContext) StatFile(name string) (fs.FileInfo, error) {
f, err := c.openFile(name)
if err != nil {
return nil, err
@@ -223,8 +229,43 @@ func (c *FSContext) openFile(name string) (fs.File, error) {
return c.fs.Open(fsOpenPath)
}
// FdWriter returns a valid writer for the given file descriptor or nil if syscall.EBADF.
func (c *FSContext) FdWriter(fd uint32) io.Writer {
switch fd {
case FdStdout:
return c.stdout
case FdStderr:
return c.stderr
case FdRoot:
return nil // directory, not a writeable file.
default:
// Check to see if the file descriptor is available
if f, ok := c.openedFiles[fd]; !ok {
return nil
} else if writer, ok := f.File.(io.Writer); !ok {
// Go's syscall.Write also returns EBADF if the FD is present, but not writeable
return nil
} else {
return writer
}
}
}
// FdReader returns a valid reader for the given file descriptor or nil if syscall.EBADF.
func (c *FSContext) FdReader(fd uint32) io.Reader {
if fd == FdStdin {
return c.stdin
} else if fd == FdRoot {
return nil // directory, not a readable file.
} else if f, ok := c.openedFiles[fd]; !ok {
return nil
} else {
return f.File
}
}
// CloseFile returns true if a file was opened and closed without error, or false if syscall.EBADF.
func (c *FSContext) CloseFile(_ context.Context, fd uint32) bool {
func (c *FSContext) CloseFile(fd uint32) bool {
f, ok := c.openedFiles[fd]
if !ok {
return false
@@ -237,7 +278,7 @@ func (c *FSContext) CloseFile(_ context.Context, fd uint32) bool {
return true
}
// Close implements io.Closer
// Close implements api.Closer
func (c *FSContext) Close(context.Context) (err error) {
// Close any files opened in this context
for fd, entry := range c.openedFiles {
@@ -248,38 +289,3 @@ func (c *FSContext) Close(context.Context) (err error) {
}
return
}
// FdWriter returns a valid writer for the given file descriptor or nil if syscall.EBADF.
func FdWriter(ctx context.Context, sysCtx *Context, fd uint32) io.Writer {
switch fd {
case FdStdout:
return sysCtx.Stdout()
case FdStderr:
return sysCtx.Stderr()
case FdRoot:
return nil // directory, not a writeable file.
default:
// Check to see if the file descriptor is available
if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
return nil
} else if writer, ok := f.File.(io.Writer); !ok {
// Go's syscall.Write also returns EBADF if the FD is present, but not writeable
return nil
} else {
return writer
}
}
}
// FdReader returns a valid reader for the given file descriptor or nil if syscall.EBADF.
func FdReader(ctx context.Context, sysCtx *Context, fd uint32) io.Reader {
if fd == FdStdin {
return sysCtx.Stdin()
} else if fd == FdRoot {
return nil // directory, not a readable file.
} else if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
return nil
} else {
return f.File
}
}

View File

@@ -52,7 +52,7 @@ func TestNewFSContext(t *testing.T) {
tc := tt
t.Run(tc.name, func(b *testing.T) {
fsc, err := NewFSContext(tc.fs)
fsc, err := NewFSContext(nil, nil, nil, tc.fs)
require.NoError(t, err)
defer fsc.Close(testCtx)
@@ -84,15 +84,20 @@ func TestEmptyFS(t *testing.T) {
}
func TestEmptyFSContext(t *testing.T) {
testFS := emptyFSContext
testFS, err := NewFSContext(nil, nil, nil, EmptyFS)
require.NoError(t, err)
expected := &FSContext{
stdin: eofReader{},
stdout: io.Discard,
stderr: io.Discard,
fs: EmptyFS,
openedFiles: map[uint32]*FileEntry{},
lastFD: 2,
}
t.Run("OpenFile doesn't affect state", func(t *testing.T) {
fd, err := testFS.OpenFile(testCtx, "foo.txt")
fd, err := testFS.OpenFile("foo.txt")
require.Zero(t, fd)
require.EqualError(t, err, "open foo.txt: file does not exist")
@@ -113,7 +118,7 @@ func TestContext_File(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err)
fsc, err := NewFSContext(embedFS)
fsc, err := NewFSContext(nil, nil, nil, embedFS)
require.NoError(t, err)
defer fsc.Close(testCtx)
@@ -142,11 +147,11 @@ func TestContext_File(t *testing.T) {
tc := tt
t.Run(tc.name, func(b *testing.T) {
fd, err := fsc.OpenFile(testCtx, tc.name)
fd, err := fsc.OpenFile(tc.name)
require.NoError(t, err)
defer fsc.CloseFile(testCtx, fd)
defer fsc.CloseFile(fd)
f, ok := fsc.OpenedFile(testCtx, fd)
f, ok := fsc.OpenedFile(fd)
require.True(t, ok)
stat, err := f.File.Stat()
@@ -169,12 +174,12 @@ func TestContext_File(t *testing.T) {
}
func TestContext_Close(t *testing.T) {
fsc, err := NewFSContext(testfs.FS{"foo": &testfs.File{}})
fsc, err := NewFSContext(nil, nil, nil, testfs.FS{"foo": &testfs.File{}})
require.NoError(t, err)
// Verify base case
require.Equal(t, 1, len(fsc.openedFiles))
_, err = fsc.OpenFile(testCtx, "foo")
_, err = fsc.OpenFile("foo")
require.NoError(t, err)
require.Equal(t, 2, len(fsc.openedFiles))
@@ -190,11 +195,11 @@ func TestContext_Close(t *testing.T) {
func TestContext_Close_Error(t *testing.T) {
file := &testfs.File{CloseErr: errors.New("error closing")}
fsc, err := NewFSContext(testfs.FS{"foo": file})
fsc, err := NewFSContext(nil, nil, nil, testfs.FS{"foo": file})
require.NoError(t, err)
// open another file
_, err = fsc.OpenFile(testCtx, "foo")
_, err = fsc.OpenFile("foo")
require.NoError(t, err)
require.EqualError(t, fsc.Close(testCtx), "error closing")

View File

@@ -17,8 +17,6 @@ import (
type Context struct {
args, environ [][]byte
argsSize, environSize uint32
stdin io.Reader
stdout, stderr io.Writer
// Note: Using function pointers here keeps them stable for tests.
@@ -65,24 +63,6 @@ func (c *Context) EnvironSize() uint32 {
return c.environSize
}
// Stdin is like exec.Cmd Stdin and defaults to a reader of os.DevNull.
// See wazero.ModuleConfig WithStdin
func (c *Context) Stdin() io.Reader {
return c.stdin
}
// Stdout is like exec.Cmd Stdout and defaults to io.Discard.
// See wazero.ModuleConfig WithStdout
func (c *Context) Stdout() io.Writer {
return c.stdout
}
// Stderr is like exec.Cmd Stderr and defaults to io.Discard.
// See wazero.ModuleConfig WithStderr
func (c *Context) Stderr() io.Writer {
return c.stderr
}
// Walltime implements sys.Walltime.
func (c *Context) Walltime(ctx context.Context) (sec int64, nsec int32) {
return (*(c.walltime))(ctx)
@@ -108,12 +88,8 @@ func (c *Context) Nanosleep(ctx context.Context, ns int64) {
(*(c.nanosleep))(ctx, ns)
}
// FS returns a possibly nil file system context.
func (c *Context) FS(ctx context.Context) *FSContext {
// Override Context when it is passed via context
if fsValue := ctx.Value(FSKey{}); fsValue != nil {
return fsValue.(*FSContext)
}
// FS returns the possibly empty (EmptyFS) file system context.
func (c *Context) FS() *FSContext {
return c.fsc
}
@@ -171,24 +147,6 @@ func NewContext(
return nil, fmt.Errorf("environ invalid: %w", err)
}
if stdin == nil {
sysCtx.stdin = eofReader{}
} else {
sysCtx.stdin = stdin
}
if stdout == nil {
sysCtx.stdout = io.Discard
} else {
sysCtx.stdout = stdout
}
if stderr == nil {
sysCtx.stderr = io.Discard
} else {
sysCtx.stderr = stderr
}
if randSource == nil {
sysCtx.randSource = platform.NewFakeRandSource()
} else {
@@ -224,9 +182,9 @@ func NewContext(
}
if fs != nil {
sysCtx.fsc, err = NewFSContext(fs)
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, fs)
} else {
sysCtx.fsc, err = NewFSContext(EmptyFS)
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, EmptyFS)
}
return

View File

@@ -16,16 +16,10 @@ import (
func TestContext_FS(t *testing.T) {
sysCtx := DefaultContext(testfs.FS{})
fsc1, err := NewFSContext(testfs.FS{})
fsc, err := NewFSContext(nil, nil, nil, testfs.FS{})
require.NoError(t, err)
require.Equal(t, fsc1, sysCtx.FS(testCtx))
fsc2, err := NewFSContext(testfs.FS{"foo": &testfs.File{}})
require.NoError(t, err)
// can override to something else
require.Equal(t, fsc2, sysCtx.FS(context.WithValue(testCtx, FSKey{}, fsc2)))
require.Equal(t, fsc, sysCtx.FS())
}
func TestDefaultSysContext(t *testing.T) {
@@ -48,9 +42,6 @@ func TestDefaultSysContext(t *testing.T) {
require.Zero(t, sysCtx.ArgsSize())
require.Nil(t, sysCtx.Environ())
require.Zero(t, sysCtx.EnvironSize())
require.Equal(t, eofReader{}, sysCtx.Stdin())
require.Equal(t, io.Discard, sysCtx.Stdout())
require.Equal(t, io.Discard, sysCtx.Stderr())
// To compare functions, we can only compare pointers, but the pointer will
// change. Hence, we have to compare the results instead.
sec, _ := sysCtx.Walltime(testCtx)
@@ -60,8 +51,12 @@ func TestDefaultSysContext(t *testing.T) {
require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
require.Equal(t, &ns, sysCtx.nanosleep)
require.Equal(t, platform.NewFakeRandSource(), sysCtx.RandSource())
expectedFS, _ := NewFSContext(testfs.FS{})
require.Equal(t, expectedFS, sysCtx.FS(testCtx))
expectedFS, _ := NewFSContext(nil, nil, nil, testfs.FS{})
require.Equal(t, eofReader{}, expectedFS.stdin)
require.Equal(t, io.Discard, expectedFS.stdout)
require.Equal(t, io.Discard, expectedFS.stderr)
require.Equal(t, expectedFS, sysCtx.FS())
}
func TestNewContext_Args(t *testing.T) {

View File

@@ -115,7 +115,7 @@ func (m *CallContext) close(ctx context.Context, exitCode uint32) (c bool, err e
}
c = true
if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder
err = sysCtx.FS(ctx).Close(ctx)
err = sysCtx.FS().Close(ctx)
}
return
}

View File

@@ -144,9 +144,9 @@ func TestCallContext_Close(t *testing.T) {
t.Run("calls Context.Close()", func(t *testing.T) {
sysCtx := sys.DefaultContext(testfs.FS{"foo": &testfs.File{}})
fsCtx := sysCtx.FS(testCtx)
fsCtx := sysCtx.FS()
_, err := fsCtx.OpenFile(testCtx, "/foo")
_, err := fsCtx.OpenFile("/foo")
require.NoError(t, err)
m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx)
@@ -154,14 +154,14 @@ func TestCallContext_Close(t *testing.T) {
// We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go).
// One side effect of Context.Close is that it clears the openedFiles map. Verify our base case.
_, ok := fsCtx.OpenedFile(testCtx, 3)
_, ok := fsCtx.OpenedFile(3)
require.True(t, ok, "sysCtx.openedFiles was empty")
// Closing should not err.
require.NoError(t, m.Close(testCtx))
// Verify our intended side-effect
_, ok = fsCtx.OpenedFile(testCtx, 3)
_, ok = fsCtx.OpenedFile(3)
require.False(t, ok, "expected no opened files")
// Verify no error closing again.
@@ -172,9 +172,9 @@ func TestCallContext_Close(t *testing.T) {
// Right now, the only way to err closing the sys context is if a File.Close erred.
testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}
sysCtx := sys.DefaultContext(testFS)
fsCtx := sysCtx.FS(testCtx)
fsCtx := sysCtx.FS()
_, err := fsCtx.OpenFile(testCtx, "/foo")
_, err := fsCtx.OpenFile("/foo")
require.NoError(t, err)
m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx)
@@ -183,7 +183,7 @@ func TestCallContext_Close(t *testing.T) {
require.EqualError(t, m.Close(testCtx), "error closing")
// Verify our intended side-effect
_, ok := fsCtx.OpenedFile(testCtx, 3)
_, ok := fsCtx.OpenedFile(3)
require.False(t, ok, "expected no opened files")
})
}
@@ -240,9 +240,9 @@ func TestCallContext_CallDynamic(t *testing.T) {
t.Run("calls Context.Close()", func(t *testing.T) {
sysCtx := sys.DefaultContext(testfs.FS{"foo": &testfs.File{}})
fsCtx := sysCtx.FS(testCtx)
fsCtx := sysCtx.FS()
_, err := fsCtx.OpenFile(testCtx, "/foo")
_, err := fsCtx.OpenFile("/foo")
require.NoError(t, err)
m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx)
@@ -250,14 +250,14 @@ func TestCallContext_CallDynamic(t *testing.T) {
// We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go).
// One side effect of Context.Close is that it clears the openedFiles map. Verify our base case.
_, ok := fsCtx.OpenedFile(testCtx, 3)
_, ok := fsCtx.OpenedFile(3)
require.True(t, ok, "sysCtx.openedFiles was empty")
// Closing should not err.
require.NoError(t, m.Close(testCtx))
// Verify our intended side-effect
_, ok = fsCtx.OpenedFile(testCtx, 3)
_, ok = fsCtx.OpenedFile(3)
require.False(t, ok, "expected no opened files")
// Verify no error closing again.
@@ -268,9 +268,9 @@ func TestCallContext_CallDynamic(t *testing.T) {
// Right now, the only way to err closing the sys context is if a File.Close erred.
testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}
sysCtx := sys.DefaultContext(testFS)
fsCtx := sysCtx.FS(testCtx)
fsCtx := sysCtx.FS()
_, err := fsCtx.OpenFile(testCtx, "/foo")
_, err := fsCtx.OpenFile("/foo")
require.NoError(t, err)
m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx)
@@ -279,7 +279,7 @@ func TestCallContext_CallDynamic(t *testing.T) {
require.EqualError(t, m.Close(testCtx), "error closing")
// Verify our intended side-effect
_, ok := fsCtx.OpenedFile(testCtx, 3)
_, ok := fsCtx.OpenedFile(3)
require.False(t, ok, "expected no opened files")
})
}

View File

@@ -235,9 +235,9 @@ func TestNamespace_CloseWithExitCode(t *testing.T) {
// Right now, the only way to err closing the sys context is if a File.Close erred.
testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}
sysCtx := sys.DefaultContext(testFS)
fsCtx := sysCtx.FS(testCtx)
fsCtx := sysCtx.FS()
_, err := fsCtx.OpenFile(testCtx, "/foo")
_, err := fsCtx.OpenFile("/foo")
require.NoError(t, err)
ns, m1, m2 := newTestNamespace()