Skip to content

Commit

Permalink
sysfs: Makes ReadFS and AdaptFS embeddable (#1607)
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Cole <[email protected]>
  • Loading branch information
codefromthecrypt authored Aug 2, 2023
1 parent 1f8c908 commit 2f2b6a9
Show file tree
Hide file tree
Showing 21 changed files with 245 additions and 267 deletions.
4 changes: 2 additions & 2 deletions cmd/wazero/wazero.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,9 @@ func validateMounts(mounts sliceFlag, stdErr logging.Writer) (rc int, rootPath s
fmt.Fprintf(stdErr, "invalid mount: path %q is not a directory\n", dir)
}

root := sysfs.NewDirFS(dir)
root := sysfs.DirFS(dir)
if readOnly {
root = sysfs.NewReadFS(root)
root = &sysfs.ReadFS{FS: root}
}

config = config.(sysfs.FSConfig).WithSysFSMount(root, guestPath)
Expand Down
7 changes: 4 additions & 3 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/tetratelabs/wazero/api"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fstest"
"github.com/tetratelabs/wazero/internal/platform"
internalsys "github.com/tetratelabs/wazero/internal/sys"
Expand Down Expand Up @@ -325,7 +326,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
config := base.WithFS(testFS)
return config, func(t *testing.T, sys *internalsys.Context) {
rootfs := sys.FS().RootFS()
require.Equal(t, sysfs.Adapt(testFS), rootfs)
require.Equal(t, &sysfs.AdaptFS{FS: testFS}, rootfs)
}
},
},
Expand All @@ -336,7 +337,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
config := base.WithFS(testFS).WithFS(testFS2)
return config, func(t *testing.T, sys *internalsys.Context) {
rootfs := sys.FS().RootFS()
require.Equal(t, sysfs.Adapt(testFS2), rootfs)
require.Equal(t, &sysfs.AdaptFS{FS: testFS2}, rootfs)
}
},
},
Expand All @@ -346,7 +347,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
config := base.WithFS(nil)
return config, func(t *testing.T, sys *internalsys.Context) {
rootfs := sys.FS().RootFS()
require.Equal(t, sysfs.Adapt(nil), rootfs)
require.Equal(t, experimentalsys.UnimplementedFS{}, rootfs)
}
},
},
Expand Down
15 changes: 0 additions & 15 deletions experimental/sys/unimplemented.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ import (
// This should be embedded to have forward compatible implementations.
type UnimplementedFS struct{}

// String implements fmt.Stringer
func (UnimplementedFS) String() string {
return "Unimplemented:/"
}

// Open implements the same method as documented on fs.FS
func (UnimplementedFS) Open(name string) (fs.File, error) {
return nil, &fs.PathError{Op: "open", Path: name, Err: ENOSYS}
}

// OpenFile implements FS.OpenFile
func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno) {
return nil, ENOSYS
Expand Down Expand Up @@ -80,11 +70,6 @@ func (UnimplementedFS) Utimens(path string, atim, mtim int64) Errno {
return ENOSYS
}

// Truncate implements FS.Truncate
func (UnimplementedFS) Truncate(string, int64) Errno {
return ENOSYS
}

// UnimplementedFile is a File that returns ENOSYS for all functions,
// except where no-op are otherwise documented.
//
Expand Down
32 changes: 16 additions & 16 deletions experimental/sysfs/config_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,31 @@ import (

var moduleConfig wazero.ModuleConfig

// This example shows how to configure a sysfs.NewDirFS
func ExampleNewDirFS() {
root := sysfs.NewDirFS(".")
// This example shows how to adapt a fs.FS to a sys.FS
func ExampleAdaptFS() {
m := fstest.MapFS{
"a/b.txt": &fstest.MapFile{Mode: 0o666},
".": &fstest.MapFile{Mode: 0o777 | fs.ModeDir},
}
root := &sysfs.AdaptFS{FS: m}

moduleConfig = wazero.NewModuleConfig().
WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(root, "/"))
}

// This example shows how to configure a sysfs.NewReadFS
func ExampleNewReadFS() {
root := sysfs.NewDirFS(".")
readOnly := sysfs.NewReadFS(root)
// This example shows how to configure a sysfs.DirFS
func ExampleDirFS() {
root := sysfs.DirFS(".")

moduleConfig = wazero.NewModuleConfig().
WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(readOnly, "/"))
WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(root, "/"))
}

// This example shows how to adapt a fs.FS as a sys.FS
func ExampleAdapt() {
m := fstest.MapFS{
"a/b.txt": &fstest.MapFile{Mode: 0o666},
".": &fstest.MapFile{Mode: 0o777 | fs.ModeDir},
}
root := sysfs.Adapt(m)
// This example shows how to configure a sysfs.ReadFS
func ExampleReadFS() {
root := sysfs.DirFS(".")
readOnly := &sysfs.ReadFS{FS: root}

moduleConfig = wazero.NewModuleConfig().
WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(root, "/"))
WithFSConfig(wazero.NewFSConfig().(sysfs.FSConfig).WithSysFSMount(readOnly, "/"))
}
31 changes: 14 additions & 17 deletions experimental/sysfs/sysfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,29 @@
package sysfs

import (
"io/fs"

experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/sysfs"
)

// Adapt adapts the input to sys.FS unless it is already one. Use NewDirFS
// instead of os.DirFS as it handles interop issues such as windows support.
// AdaptFS adapts the input to sys.FS. Use DirFS instead of adapting an
// os.DirFS as it handles interop issues such as windows support.
//
// Note: This performs no flag verification on OpenFile. fs.FS cannot read
// flags as there is no parameter to pass them through with. Moreover, fs.FS
// Note: This performs no flag verification on OpenFile. sys.FS cannot read
// flags as there is no parameter to pass them through with. Moreover, sys.FS
// documentation does not require the file to be present. In summary, we can't
// enforce flag behavior.
func Adapt(fs fs.FS) experimentalsys.FS {
return sysfs.Adapt(fs)
type AdaptFS = sysfs.AdaptFS

// DirFS is like os.DirFS except it returns sys.FS, which has more features.
func DirFS(dir string) experimentalsys.FS {
return sysfs.DirFS(dir)
}

// NewReadFS is used to mask an existing sys.FS for reads. Notably, this allows
// ReadFS is used to mask an existing sys.FS for reads. Notably, this allows
// the CLI to do read-only mounts of directories the host user can write, but
// doesn't want the guest wasm to. For example, Python libraries shouldn't be
// written to at runtime by the python wasm file.
func NewReadFS(fs experimentalsys.FS) experimentalsys.FS {
return sysfs.NewReadFS(fs)
}

// NewDirFS is like os.DirFS except it returns sys.FS, which has more features.
func NewDirFS(dir string) experimentalsys.FS {
return sysfs.NewDirFS(dir)
}
//
// Note: This implements read-only by returning sys.EROFS or sys.EBADF,
// depending on the operation that require write access.
type ReadFS = sysfs.ReadFS
12 changes: 8 additions & 4 deletions fsconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,21 @@ func (c *fsConfig) clone() *fsConfig {

// WithDirMount implements FSConfig.WithDirMount
func (c *fsConfig) WithDirMount(dir, guestPath string) FSConfig {
return c.WithSysFSMount(sysfs.NewDirFS(dir), guestPath)
return c.WithSysFSMount(sysfs.DirFS(dir), guestPath)
}

// WithReadOnlyDirMount implements FSConfig.WithReadOnlyDirMount
func (c *fsConfig) WithReadOnlyDirMount(dir, guestPath string) FSConfig {
return c.WithSysFSMount(sysfs.NewReadFS(sysfs.NewDirFS(dir)), guestPath)
return c.WithSysFSMount(&sysfs.ReadFS{FS: sysfs.DirFS(dir)}, guestPath)
}

// WithFSMount implements FSConfig.WithFSMount
func (c *fsConfig) WithFSMount(fs fs.FS, guestPath string) FSConfig {
return c.WithSysFSMount(sysfs.Adapt(fs), guestPath)
var adapted experimentalsys.FS
if fs != nil {
adapted = &sysfs.AdaptFS{FS: fs}
}
return c.WithSysFSMount(adapted, guestPath)
}

// WithSysFSMount implements sysfs.FSConfig
Expand All @@ -188,7 +192,7 @@ func (c *fsConfig) WithSysFSMount(fs experimentalsys.FS, guestPath string) FSCon
if i, ok := ret.guestPathToFS[cleaned]; ok {
ret.fs[i] = fs
ret.guestPaths[i] = guestPath
} else {
} else if fs != nil {
ret.guestPathToFS[cleaned] = len(ret.fs)
ret.fs = append(ret.fs, fs)
ret.guestPaths = append(ret.guestPaths, guestPath)
Expand Down
8 changes: 4 additions & 4 deletions fsconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ func TestFSConfig(t *testing.T) {
{
name: "WithFSMount",
input: base.WithFSMount(testFS, "/"),
expectedFS: []sys.FS{sysfs.Adapt(testFS)},
expectedFS: []sys.FS{&sysfs.AdaptFS{FS: testFS}},
expectedGuestPaths: []string{"/"},
},
{
name: "WithFSMount overwrites",
input: base.WithFSMount(testFS, "/").WithFSMount(testFS2, "/"),
expectedFS: []sys.FS{sysfs.Adapt(testFS2)},
expectedFS: []sys.FS{&sysfs.AdaptFS{FS: testFS2}},
expectedGuestPaths: []string{"/"},
},
{
Expand All @@ -45,13 +45,13 @@ func TestFSConfig(t *testing.T) {
{
name: "WithDirMount overwrites",
input: base.WithFSMount(testFS, "/").WithDirMount(".", "/"),
expectedFS: []sys.FS{sysfs.NewDirFS(".")},
expectedFS: []sys.FS{sysfs.DirFS(".")},
expectedGuestPaths: []string{"/"},
},
{
name: "multiple",
input: base.WithReadOnlyDirMount(".", "/").WithDirMount("/tmp", "/tmp"),
expectedFS: []sys.FS{sysfs.NewReadFS(sysfs.NewDirFS(".")), sysfs.NewDirFS("/tmp")},
expectedFS: []sys.FS{&sysfs.ReadFS{FS: sysfs.DirFS(".")}, sysfs.DirFS("/tmp")},
expectedGuestPaths: []string{"/", "/tmp"},
},
}
Expand Down
8 changes: 4 additions & 4 deletions imports/wasi_snapshot_preview1/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ func Test_fdFdstatGet(t *testing.T) {
// replace stdin with a fake TTY file.
// TODO: Make this easier once we have in-memory sys.File
stdin, _ := fsc.LookupFile(sys.FdStdin)
stdinFile, errno := sysfs.Adapt(&gofstest.MapFS{"stdin": &gofstest.MapFile{
stdinFile, errno := (&sysfs.AdaptFS{FS: &gofstest.MapFS{"stdin": &gofstest.MapFile{
Mode: fs.ModeDevice | fs.ModeCharDevice | 0o600,
}}).OpenFile("stdin", 0, 0)
}}}).OpenFile("stdin", 0, 0)
require.EqualErrno(t, 0, errno)

stdin.File = stdinFile
Expand Down Expand Up @@ -3648,8 +3648,8 @@ func Test_pathLink(t *testing.T) {

func Test_pathOpen(t *testing.T) {
dir := t.TempDir() // open before loop to ensure no locking problems.
writeFS := sysfs.NewDirFS(dir)
readFS := sysfs.NewReadFS(writeFS)
writeFS := sysfs.DirFS(dir)
readFS := &sysfs.ReadFS{FS: writeFS}

fileName := "file"
fileContents := []byte("012")
Expand Down
24 changes: 12 additions & 12 deletions internal/sys/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestNewFSContext(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err)

dirfs := sysfs.NewDirFS(".")
dirfs := sysfs.DirFS(".")

// Test various usual configuration for the file system.
tests := []struct {
Expand All @@ -33,21 +33,21 @@ func TestNewFSContext(t *testing.T) {
}{
{
name: "embed.FS",
fs: sysfs.Adapt(embedFS),
fs: &sysfs.AdaptFS{FS: embedFS},
},
{
name: "NewDirFS",
name: "DirFS",
// Don't use "testdata" because it may not be present in
// cross-architecture (a.k.a. scratch) build containers.
fs: dirfs,
},
{
name: "NewReadFS",
fs: sysfs.NewReadFS(dirfs),
name: "ReadFS",
fs: &sysfs.ReadFS{FS: dirfs},
},
{
name: "fstest.MapFS",
fs: sysfs.Adapt(gofstest.MapFS{}),
fs: &sysfs.AdaptFS{FS: gofstest.MapFS{}},
},
}

Expand Down Expand Up @@ -96,7 +96,7 @@ func TestNewFSContext(t *testing.T) {
func TestFSContext_CloseFile(t *testing.T) {
embedFS, err := fs.Sub(testdata, "testdata")
require.NoError(t, err)
testFS := sysfs.Adapt(embedFS)
testFS := &sysfs.AdaptFS{FS: embedFS}

c := Context{}
err = c.InitFSContext(nil, nil, nil, []sys.FS{testFS}, []string{"/"}, nil)
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestFSContext_noPreopens(t *testing.T) {
}

func TestContext_Close(t *testing.T) {
testFS := sysfs.Adapt(testfs.FS{"foo": &testfs.File{}})
testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{}}}

c := Context{}
err := c.InitFSContext(nil, nil, nil, []sys.FS{testFS}, []string{"/"}, nil)
Expand All @@ -181,7 +181,7 @@ func TestContext_Close(t *testing.T) {
func TestContext_Close_Error(t *testing.T) {
file := &testfs.File{CloseErr: errors.New("error closing")}

testFS := sysfs.Adapt(testfs.FS{"foo": file})
testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": file}}

c := Context{}
err := c.InitFSContext(nil, nil, nil, []sys.FS{testFS}, []string{"/"}, nil)
Expand All @@ -201,7 +201,7 @@ func TestContext_Close_Error(t *testing.T) {

func TestFSContext_Renumber(t *testing.T) {
tmpDir := t.TempDir()
dirFS := sysfs.NewDirFS(tmpDir)
dirFS := sysfs.DirFS(tmpDir)

const dirName = "dir"
errno := dirFS.Mkdir(dirName, 0o700)
Expand Down Expand Up @@ -252,7 +252,7 @@ func TestFSContext_Renumber(t *testing.T) {

func TestDirentCache_Read(t *testing.T) {
c := Context{}
err := c.InitFSContext(nil, nil, nil, []sys.FS{sysfs.Adapt(fstest.FS)}, []string{"/"}, nil)
err := c.InitFSContext(nil, nil, nil, []sys.FS{&sysfs.AdaptFS{FS: fstest.FS}}, []string{"/"}, nil)
require.NoError(t, err)
fsc := c.fsc
defer fsc.Close()
Expand Down Expand Up @@ -430,7 +430,7 @@ func TestDirentCache_ReadNewFile(t *testing.T) {
tmpDir := t.TempDir()

c := Context{}
err := c.InitFSContext(nil, nil, nil, []sys.FS{sysfs.NewDirFS(tmpDir)}, []string{"/"}, nil)
err := c.InitFSContext(nil, nil, nil, []sys.FS{sysfs.DirFS(tmpDir)}, []string{"/"}, nil)
require.NoError(t, err)
fsc := c.fsc
defer fsc.Close()
Expand Down
2 changes: 1 addition & 1 deletion internal/sys/sys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestContext_WalltimeNanos(t *testing.T) {
}

func TestDefaultSysContext(t *testing.T) {
testFS := sysfs.Adapt(fstest.FS)
testFS := &sysfs.AdaptFS{FS: fstest.FS}

sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, []experimentalsys.FS{testFS}, []string{"/"}, nil)
require.NoError(t, err)
Expand Down
Loading

0 comments on commit 2f2b6a9

Please sign in to comment.