diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go index 7866a01372..b92a910aec 100644 --- a/cmd/wazero/wazero.go +++ b/cmd/wazero/wazero.go @@ -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) diff --git a/config_test.go b/config_test.go index c8f2419e82..8b73dd7dc5 100644 --- a/config_test.go +++ b/config_test.go @@ -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" @@ -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) } }, }, @@ -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) } }, }, @@ -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) } }, }, diff --git a/experimental/sys/unimplemented.go b/experimental/sys/unimplemented.go index 1099eb12eb..764fba6a24 100644 --- a/experimental/sys/unimplemented.go +++ b/experimental/sys/unimplemented.go @@ -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 @@ -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. // diff --git a/experimental/sysfs/config_example_test.go b/experimental/sysfs/config_example_test.go index 91ce29a42a..6c54a449fe 100644 --- a/experimental/sysfs/config_example_test.go +++ b/experimental/sysfs/config_example_test.go @@ -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, "/")) } diff --git a/experimental/sysfs/sysfs.go b/experimental/sysfs/sysfs.go index c2a18fd5ee..a5d90f7e65 100644 --- a/experimental/sysfs/sysfs.go +++ b/experimental/sysfs/sysfs.go @@ -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 diff --git a/fsconfig.go b/fsconfig.go index 5576e7cc0e..c28909e19e 100644 --- a/fsconfig.go +++ b/fsconfig.go @@ -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 @@ -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) diff --git a/fsconfig_test.go b/fsconfig_test.go index 716120098e..d4b6c76768 100644 --- a/fsconfig_test.go +++ b/fsconfig_test.go @@ -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{"/"}, }, { @@ -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"}, }, } diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go index ee5ad2d740..e8883b2169 100644 --- a/imports/wasi_snapshot_preview1/fs_test.go +++ b/imports/wasi_snapshot_preview1/fs_test.go @@ -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 @@ -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") diff --git a/internal/sys/fs_test.go b/internal/sys/fs_test.go index 33031b9018..1719dae391 100644 --- a/internal/sys/fs_test.go +++ b/internal/sys/fs_test.go @@ -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 { @@ -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{}}, }, } @@ -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) @@ -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) @@ -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) @@ -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) @@ -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() @@ -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() diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go index ae6270a181..5949d27bf2 100644 --- a/internal/sys/sys_test.go +++ b/internal/sys/sys_test.go @@ -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) diff --git a/internal/sysfs/adapter.go b/internal/sysfs/adapter.go index bacc0b18f6..51a9a54804 100644 --- a/internal/sysfs/adapter.go +++ b/internal/sysfs/adapter.go @@ -9,40 +9,35 @@ import ( "github.com/tetratelabs/wazero/sys" ) -// 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. -// -// 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 { - if fs == nil { - return experimentalsys.UnimplementedFS{} - } - if sys, ok := fs.(experimentalsys.FS); ok { - return sys - } - return &adapter{fs: fs} -} - -type adapter struct { - experimentalsys.UnimplementedFS - fs fs.FS +type AdaptFS struct { + FS fs.FS } // String implements fmt.Stringer -func (a *adapter) String() string { - return fmt.Sprintf("%v", a.fs) +func (a *AdaptFS) String() string { + return fmt.Sprintf("%v", a.FS) } // OpenFile implements the same method as documented on sys.FS -func (a *adapter) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { - return OpenFSFile(a.fs, cleanPath(path), flag, perm) +func (a *AdaptFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + return OpenFSFile(a.FS, cleanPath(path), flag, perm) +} + +// Lstat implements the same method as documented on sys.FS +func (a *AdaptFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) { + // At this time, we make the assumption sys.FS instances do not support + // symbolic links, therefore Lstat is the same as Stat. This is obviously + // not true, but until FS.FS has a solid story for how to handle symlinks, + // we are better off not making a decision that would be difficult to + // revert later on. + // + // For further discussions on the topic, see: + // https://github.com/golang/go/issues/49580 + return a.Stat(path) } // Stat implements the same method as documented on sys.FS -func (a *adapter) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { +func (a *AdaptFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { f, errno := a.OpenFile(path, experimentalsys.O_RDONLY, 0) if errno != 0 { return sys.Stat_t{}, errno @@ -51,17 +46,49 @@ func (a *adapter) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { return f.Stat() } -// Lstat implements the same method as documented on sys.FS -func (a *adapter) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) { - // At this time, we make the assumption that sys.FS instances do not support - // symbolic links, therefore Lstat is the same as Stat. This is obviously - // not true but until sys.FS has a solid story for how to handle symlinks we - // are better off not making a decision that would be difficult to revert - // later on. - // - // For further discussions on the topic, see: - // https://github.com/golang/go/issues/49580 - return a.Stat(path) +// Readlink implements the same method as documented on sys.FS +func (a *AdaptFS) Readlink(string) (string, experimentalsys.Errno) { + return "", experimentalsys.ENOSYS +} + +// Mkdir implements the same method as documented on sys.FS +func (a *AdaptFS) Mkdir(string, fs.FileMode) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Chmod implements the same method as documented on sys.FS +func (a *AdaptFS) Chmod(string, fs.FileMode) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Rename implements the same method as documented on sys.FS +func (a *AdaptFS) Rename(string, string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Rmdir implements the same method as documented on sys.FS +func (a *AdaptFS) Rmdir(string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Link implements the same method as documented on sys.FS +func (a *AdaptFS) Link(string, string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Symlink implements the same method as documented on sys.FS +func (a *AdaptFS) Symlink(string, string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Unlink implements the same method as documented on sys.FS +func (a *AdaptFS) Unlink(string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Utimens implements the same method as documented on sys.FS +func (a *AdaptFS) Utimens(string, int64, int64) experimentalsys.Errno { + return experimentalsys.ENOSYS } func cleanPath(name string) string { diff --git a/internal/sysfs/adapter_test.go b/internal/sysfs/adapter_test.go index fe3d491663..75b4f3b199 100644 --- a/internal/sysfs/adapter_test.go +++ b/internal/sysfs/adapter_test.go @@ -17,29 +17,23 @@ import ( "github.com/tetratelabs/wazero/sys" ) -func TestAdapt_nil(t *testing.T) { - testFS := Adapt(nil) - _, ok := testFS.(experimentalsys.UnimplementedFS) - require.True(t, ok) -} - -func TestAdapt_MkDir(t *testing.T) { - testFS := Adapt(os.DirFS(t.TempDir())) +func TestAdaptFS_MkDir(t *testing.T) { + testFS := &AdaptFS{FS: os.DirFS(t.TempDir())} err := testFS.Mkdir("mkdir", fs.ModeDir) require.EqualErrno(t, experimentalsys.ENOSYS, err) } -func TestAdapt_Chmod(t *testing.T) { - testFS := Adapt(os.DirFS(t.TempDir())) +func TestAdaptFS_Chmod(t *testing.T) { + testFS := &AdaptFS{FS: os.DirFS(t.TempDir())} err := testFS.Chmod("chmod", fs.ModeDir) require.EqualErrno(t, experimentalsys.ENOSYS, err) } -func TestAdapt_Rename(t *testing.T) { +func TestAdaptFS_Rename(t *testing.T) { tmpDir := t.TempDir() - testFS := Adapt(os.DirFS(tmpDir)) + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} file1 := "file1" file1Path := joinPath(tmpDir, file1) @@ -57,9 +51,9 @@ func TestAdapt_Rename(t *testing.T) { require.EqualErrno(t, experimentalsys.ENOSYS, err) } -func TestAdapt_Rmdir(t *testing.T) { +func TestAdaptFS_Rmdir(t *testing.T) { tmpDir := t.TempDir() - testFS := Adapt(os.DirFS(tmpDir)) + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} path := "rmdir" realPath := joinPath(tmpDir, path) @@ -69,9 +63,9 @@ func TestAdapt_Rmdir(t *testing.T) { require.EqualErrno(t, experimentalsys.ENOSYS, err) } -func TestAdapt_Unlink(t *testing.T) { +func TestAdaptFS_Unlink(t *testing.T) { tmpDir := t.TempDir() - testFS := Adapt(os.DirFS(tmpDir)) + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} path := "unlink" realPath := joinPath(tmpDir, path) @@ -81,9 +75,9 @@ func TestAdapt_Unlink(t *testing.T) { require.EqualErrno(t, experimentalsys.ENOSYS, err) } -func TestAdapt_UtimesNano(t *testing.T) { +func TestAdaptFS_UtimesNano(t *testing.T) { tmpDir := t.TempDir() - testFS := Adapt(os.DirFS(tmpDir)) + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} path := "utimes" realPath := joinPath(tmpDir, path) @@ -93,13 +87,13 @@ func TestAdapt_UtimesNano(t *testing.T) { require.EqualErrno(t, experimentalsys.ENOSYS, err) } -func TestAdapt_Open_Read(t *testing.T) { - // Create a subdirectory, so we can test reads outside the sys.FS root. +func TestAdaptFS_Open_Read(t *testing.T) { tmpDir := t.TempDir() + // Create a subdirectory, so we can test reads outside the sys.FS root. tmpDir = joinPath(tmpDir, t.Name()) require.NoError(t, os.Mkdir(tmpDir, 0o700)) require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := Adapt(os.DirFS(tmpDir)) + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} // We can't correct operating system portability issues with os.DirFS on // windows. Use syscall.DirFS instead! @@ -113,10 +107,10 @@ func TestAdapt_Open_Read(t *testing.T) { }) } -func TestAdapt_Lstat(t *testing.T) { +func TestAdaptFS_Lstat(t *testing.T) { tmpDir := t.TempDir() + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := Adapt(os.DirFS(tmpDir)) for _, path := range []string{"animals.txt", "sub", "sub-link"} { fullPath := joinPath(tmpDir, path) @@ -128,15 +122,15 @@ func TestAdapt_Lstat(t *testing.T) { } } -func TestAdapt_Stat(t *testing.T) { +func TestAdaptFS_Stat(t *testing.T) { tmpDir := t.TempDir() + testFS := &AdaptFS{FS: os.DirFS(tmpDir)} require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := Adapt(os.DirFS(tmpDir)) testStat(t, testFS) } -// hackFS cheats the api.FS contract by opening for write (sys.O_RDWR). +// hackFS cheats the fs.FS contract by opening for write (sys.O_RDWR). // // Until we have an alternate public interface for filesystems, some users will // rely on this. Via testing, we ensure we don't accidentally break them. @@ -156,11 +150,11 @@ func (dir hackFS) Open(name string) (fs.File, error) { } } -// TestAdapt_HackedWrites ensures we allow writes even if they violate the +// TestAdaptFS_HackedWrites ensures we allow writes even if they violate the // api.FS contract. -func TestAdapt_HackedWrites(t *testing.T) { +func TestAdaptFS_HackedWrites(t *testing.T) { tmpDir := t.TempDir() - testFS := Adapt(hackFS(tmpDir)) + testFS := &AdaptFS{FS: hackFS(tmpDir)} testOpen_O_RDWR(t, tmpDir, testFS) } @@ -237,7 +231,7 @@ func (i *sysFileInfo) Sys() any { // to zero). // // A fs.File implementing this should be functionally equivalent to an os.File, -// even if both are less ideal than using NewDirFS directly, especially on +// even if both are less ideal than using DirFS directly, especially on // Windows. // // For example, on Windows, we cannot reliably read the inode for a diff --git a/internal/sysfs/dirfs.go b/internal/sysfs/dirfs.go index 3fc13d8e2e..05d5b647ea 100644 --- a/internal/sysfs/dirfs.go +++ b/internal/sysfs/dirfs.go @@ -9,7 +9,7 @@ import ( "github.com/tetratelabs/wazero/sys" ) -func NewDirFS(dir string) experimentalsys.FS { +func DirFS(dir string) experimentalsys.FS { return &dirFS{ dir: dir, cleanedDir: ensureTrailingPathSeparator(dir), @@ -23,8 +23,11 @@ func ensureTrailingPathSeparator(dir string) string { return dir } +// dirFS is not exported because the input fields must be maintained together. +// This is likely why os.DirFS doesn't, either! type dirFS struct { experimentalsys.UnimplementedFS + dir string // cleanedDir is for easier OS-specific concatenation, as it always has // a trailing path separator. diff --git a/internal/sysfs/dirfs_test.go b/internal/sysfs/dirfs_test.go index 08b7a78220..d2826898ba 100644 --- a/internal/sysfs/dirfs_test.go +++ b/internal/sysfs/dirfs_test.go @@ -16,8 +16,8 @@ import ( "github.com/tetratelabs/wazero/internal/testing/require" ) -func TestNewDirFS(t *testing.T) { - testFS := NewDirFS(".") +func TestDirFS(t *testing.T) { + testFS := DirFS(".") // Guest can look up / f, errno := testFS.OpenFile("/", sys.O_RDONLY, 0) @@ -25,14 +25,14 @@ func TestNewDirFS(t *testing.T) { require.EqualErrno(t, 0, f.Close()) t.Run("host path not found", func(t *testing.T) { - testFS := NewDirFS("a") + testFS := DirFS("a") _, errno = testFS.OpenFile(".", sys.O_RDONLY, 0) require.EqualErrno(t, sys.ENOENT, errno) }) t.Run("host path not a directory", func(t *testing.T) { arg0 := os.Args[0] // should be safe in scratch tests which don't have the source mounted. - testFS := NewDirFS(arg0) + testFS := DirFS(arg0) d, errno := testFS.OpenFile(".", sys.O_RDONLY, 0) require.EqualErrno(t, 0, errno) _, errno = d.Readdir(-1) @@ -41,13 +41,13 @@ func TestNewDirFS(t *testing.T) { } func TestDirFS_join(t *testing.T) { - testFS := NewDirFS("/").(*dirFS) + testFS := DirFS("/").(*dirFS) require.Equal(t, "/", testFS.join("")) require.Equal(t, "/", testFS.join(".")) require.Equal(t, "/", testFS.join("/")) require.Equal(t, "/tmp", testFS.join("tmp")) - testFS = NewDirFS(".").(*dirFS) + testFS = DirFS(".").(*dirFS) require.Equal(t, ".", testFS.join("")) require.Equal(t, ".", testFS.join(".")) require.Equal(t, ".", testFS.join("/")) @@ -55,7 +55,7 @@ func TestDirFS_join(t *testing.T) { } func TestDirFS_String(t *testing.T) { - testFS := NewDirFS(".") + testFS := DirFS(".") // String has the name of the path entered require.Equal(t, ".", testFS.(fmt.Stringer).String()) @@ -65,7 +65,7 @@ func TestDirFS_Lstat(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) for _, path := range []string{"animals.txt", "sub", "sub-link"} { require.EqualErrno(t, 0, testFS.Symlink(path, path+"-link")) } @@ -75,7 +75,7 @@ func TestDirFS_Lstat(t *testing.T) { func TestDirFS_MkDir(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "mkdir" realPath := path.Join(tmpDir, name) @@ -152,7 +152,7 @@ func requireMode(t *testing.T, testFS sys.FS, path string, mode fs.FileMode) { func TestDirFS_Rename(t *testing.T) { t.Run("from doesn't exist", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file1 := "file1" file1Path := path.Join(tmpDir, file1) @@ -164,7 +164,7 @@ func TestDirFS_Rename(t *testing.T) { }) t.Run("file to non-exist", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file1 := "file1" file1Path := path.Join(tmpDir, file1) @@ -187,7 +187,7 @@ func TestDirFS_Rename(t *testing.T) { }) t.Run("dir to non-exist", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) dir1 := "dir1" dir1Path := path.Join(tmpDir, dir1) @@ -208,7 +208,7 @@ func TestDirFS_Rename(t *testing.T) { }) t.Run("dir to file", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) dir1 := "dir1" dir1Path := path.Join(tmpDir, dir1) @@ -227,7 +227,7 @@ func TestDirFS_Rename(t *testing.T) { }) t.Run("file to dir", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file1 := "file1" file1Path := path.Join(tmpDir, file1) @@ -246,7 +246,7 @@ func TestDirFS_Rename(t *testing.T) { // Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L567-L582 t.Run("dir to empty dir should be fine", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) dir1 := "dir1" dir1Path := path.Join(tmpDir, dir1) @@ -279,7 +279,7 @@ func TestDirFS_Rename(t *testing.T) { // Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L584-L604 t.Run("dir to non empty dir should be EXIST", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) dir1 := "dir1" dir1Path := path.Join(tmpDir, dir1) @@ -306,7 +306,7 @@ func TestDirFS_Rename(t *testing.T) { t.Run("file to file", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file1 := "file1" file1Path := path.Join(tmpDir, file1) @@ -334,7 +334,7 @@ func TestDirFS_Rename(t *testing.T) { }) t.Run("dir to itself", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) dir1 := "dir1" dir1Path := path.Join(tmpDir, dir1) @@ -349,7 +349,7 @@ func TestDirFS_Rename(t *testing.T) { }) t.Run("file to itself", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file1 := "file1" file1Path := path.Join(tmpDir, file1) @@ -369,7 +369,7 @@ func TestDirFS_Rename(t *testing.T) { func TestDirFS_Rmdir(t *testing.T) { t.Run("doesn't exist", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "rmdir" @@ -379,7 +379,7 @@ func TestDirFS_Rmdir(t *testing.T) { t.Run("dir not empty", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "rmdir" realPath := path.Join(tmpDir, name) @@ -396,7 +396,7 @@ func TestDirFS_Rmdir(t *testing.T) { t.Run("dir previously not empty", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "rmdir" realPath := path.Join(tmpDir, name) @@ -414,7 +414,7 @@ func TestDirFS_Rmdir(t *testing.T) { t.Run("dir empty", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "rmdir" realPath := path.Join(tmpDir, name) @@ -426,7 +426,7 @@ func TestDirFS_Rmdir(t *testing.T) { t.Run("dir empty while opening", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "rmdir" realPath := path.Join(tmpDir, name) @@ -443,7 +443,7 @@ func TestDirFS_Rmdir(t *testing.T) { t.Run("not directory", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "rmdir" realPath := path.Join(tmpDir, name) @@ -460,7 +460,7 @@ func TestDirFS_Rmdir(t *testing.T) { func TestDirFS_Unlink(t *testing.T) { t.Run("doesn't exist", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "unlink" err := testFS.Unlink(name) @@ -469,7 +469,7 @@ func TestDirFS_Unlink(t *testing.T) { t.Run("target: dir", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) dir := "dir" realPath := path.Join(tmpDir, dir) @@ -484,7 +484,7 @@ func TestDirFS_Unlink(t *testing.T) { t.Run("target: symlink to dir", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) // Create link target dir. subDirName := "subdir" @@ -502,7 +502,7 @@ func TestDirFS_Unlink(t *testing.T) { t.Run("file exists", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "unlink" realPath := path.Join(tmpDir, name) @@ -518,7 +518,7 @@ func TestDirFS_Unlink(t *testing.T) { func TestDirFS_Utimesns(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file := "file" err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700) @@ -574,7 +574,7 @@ func TestDirFS_Utimesns(t *testing.T) { t.Run(name, func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) file := path.Join(tmpDir, "file") errno := os.WriteFile(file, []byte{}, 0o700) @@ -638,7 +638,7 @@ func TestDirFS_OpenFile(t *testing.T) { require.NoError(t, os.Mkdir(tmpDir, 0o700)) require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) testOpen_Read(t, testFS, statSetsIno(), true) @@ -656,7 +656,7 @@ func TestDirFS_Stat(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) testStat(t, testFS) // from os.TestDirFSPathsValid @@ -673,7 +673,7 @@ func TestDirFS_Stat(t *testing.T) { func TestDirFS_Readdir(t *testing.T) { root := t.TempDir() - testFS := NewDirFS(root) + testFS := DirFS(root) const readDirTarget = "dir" errno := testFS.Mkdir(readDirTarget, 0o700) @@ -728,7 +728,7 @@ func TestDirFS_Link(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) require.EqualErrno(t, testFS.Link("cat", ""), sys.ENOENT) require.EqualErrno(t, testFS.Link("sub/test.txt", "sub/test.txt"), sys.EEXIST) @@ -745,7 +745,7 @@ func TestDirFS_Symlink(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) require.EqualErrno(t, sys.EEXIST, testFS.Symlink("sub/test.txt", "sub/test.txt")) // Non-existing old name is allowed. @@ -767,6 +767,6 @@ func TestDirFS_Readlink(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) testReadlink(t, testFS, testFS) } diff --git a/internal/sysfs/file.go b/internal/sysfs/file.go index dc686014f1..a2258c3fff 100644 --- a/internal/sysfs/file.go +++ b/internal/sysfs/file.go @@ -60,7 +60,7 @@ func OpenFSFile(fs fs.FS, path string, flag experimentalsys.Oflag, perm fs.FileM return nil, errno } // Don't return an os.File because the path is not absolute. osFile needs - // the path to be real and certain fs.File impls are subrooted. + // the path to be real and certain FS.File impls are subrooted. return &fsFile{fs: fs, name: path, file: f}, 0 } @@ -261,7 +261,7 @@ func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno experime // Notably, this uses readdirFile or fs.ReadDirFile if available. This does not // return inodes on windows. func (f *fsFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { - // Windows lets you Readdir after close, fs.File also may not implement + // Windows lets you Readdir after close, FS.File also may not implement // close in a meaningful way. read our closed field to return consistent // results. if f.closed { @@ -286,8 +286,8 @@ func (f *fsFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experim return } - // Try with fs.ReadDirFile which is available on api.FS implementations - // like embed:fs. + // Try with FS.ReadDirFile which is available on api.FS implementations + // like embed:FS. if rdf, ok := f.file.(fs.ReadDirFile); ok { entries, e := rdf.ReadDir(n) if errno = adjustReaddirErr(f, f.closed, e); errno != 0 { diff --git a/internal/sysfs/file_test.go b/internal/sysfs/file_test.go index d50093301a..00501154a0 100644 --- a/internal/sysfs/file_test.go +++ b/internal/sysfs/file_test.go @@ -832,7 +832,7 @@ func testSync_NoError(t *testing.T, sync func(experimentalsys.File) experimental f experimentalsys.File }{ {name: "UnimplementedFile", f: experimentalsys.UnimplementedFile{}}, - {name: "File of read-only fs.File", f: ro}, + {name: "File of read-only FS.File", f: ro}, {name: "File of os.File", f: rw}, } diff --git a/internal/sysfs/ino_windows.go b/internal/sysfs/ino_windows.go index b880e3b0c3..d163b3601e 100644 --- a/internal/sysfs/ino_windows.go +++ b/internal/sysfs/ino_windows.go @@ -14,7 +14,7 @@ func inoFromFileInfo(dirPath string, info fs.FileInfo) (ino sys.Inode, errno exp return v.Ino, 0 } if dirPath == "" { - // This is a fs.File backed implementation which doesn't have access to + // This is a FS.File backed implementation which doesn't have access to // the original file path. return } diff --git a/internal/sysfs/readfs.go b/internal/sysfs/readfs.go index a81b64c0bf..59e331a298 100644 --- a/internal/sysfs/readfs.go +++ b/internal/sysfs/readfs.go @@ -6,51 +6,21 @@ import ( experimentalsys "github.com/tetratelabs/wazero/experimental/sys" ) -// NewReadFS 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 { - if _, ok := fs.(*readFS); ok { - return fs - } else if _, ok = fs.(experimentalsys.UnimplementedFS); ok { - return fs // unimplemented is read-only - } - return &readFS{fs} -} - -type readFS struct { +type ReadFS struct { experimentalsys.FS } // OpenFile implements the same method as documented on sys.FS -func (r *readFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { - // TODO: Once the real implementation is complete, move the below to - // /RATIONALE.md. Doing this while the type is unstable creates - // documentation drift as we expect a lot of reshaping meanwhile. - // - // Callers of this function expect to either open a valid file handle, or - // get an error, if they can't. We want to return ENOSYS if opened for - // anything except reads. - // - // Instead, we could return a fake no-op file on O_WRONLY. However, this - // hurts observability because a later write error to that file will be on - // a different source code line than the root cause which is opening with - // an unsupported flag. - // - // The tricky part is os.RD_ONLY is typically defined as zero, so while the - // parameter is named flag, the part about opening read vs write isn't a - // typical bitflag. We can't compare against zero anyway, because even if - // there isn't a current flag to OR in with that, there may be in the - // future. What we do instead is mask the flags about read/write mode and - // check if they are the opposite of read or not. +func (r *ReadFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + // Mask the mutually exclusive bits as they determine write mode. switch flag & (experimentalsys.O_RDONLY | experimentalsys.O_WRONLY | experimentalsys.O_RDWR) { case experimentalsys.O_WRONLY, experimentalsys.O_RDWR: + // Return the correct error if a directory was opened for write. if flag&experimentalsys.O_DIRECTORY != 0 { return nil, experimentalsys.EISDIR } return nil, experimentalsys.ENOSYS - default: // sys.O_RDONLY (or no flag) so we are ok! + default: // sys.O_RDONLY (integer zero) so we are ok! } f, errno := r.FS.OpenFile(path, flag, perm) @@ -61,42 +31,42 @@ func (r *readFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileM } // Mkdir implements the same method as documented on sys.FS -func (r *readFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno { +func (r *ReadFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno { return experimentalsys.EROFS } // Chmod implements the same method as documented on sys.FS -func (r *readFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { +func (r *ReadFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { return experimentalsys.EROFS } // Rename implements the same method as documented on sys.FS -func (r *readFS) Rename(from, to string) experimentalsys.Errno { +func (r *ReadFS) Rename(from, to string) experimentalsys.Errno { return experimentalsys.EROFS } // Rmdir implements the same method as documented on sys.FS -func (r *readFS) Rmdir(path string) experimentalsys.Errno { +func (r *ReadFS) Rmdir(path string) experimentalsys.Errno { return experimentalsys.EROFS } // Link implements the same method as documented on sys.FS -func (r *readFS) Link(_, _ string) experimentalsys.Errno { +func (r *ReadFS) Link(_, _ string) experimentalsys.Errno { return experimentalsys.EROFS } // Symlink implements the same method as documented on sys.FS -func (r *readFS) Symlink(_, _ string) experimentalsys.Errno { +func (r *ReadFS) Symlink(_, _ string) experimentalsys.Errno { return experimentalsys.EROFS } // Unlink implements the same method as documented on sys.FS -func (r *readFS) Unlink(path string) experimentalsys.Errno { +func (r *ReadFS) Unlink(path string) experimentalsys.Errno { return experimentalsys.EROFS } // Utimens implements the same method as documented on sys.FS -func (r *readFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno { +func (r *ReadFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno { return experimentalsys.EROFS } diff --git a/internal/sysfs/readfs_test.go b/internal/sysfs/readfs_test.go index 25a47fa318..a8e21f1d9c 100644 --- a/internal/sysfs/readfs_test.go +++ b/internal/sysfs/readfs_test.go @@ -14,16 +14,13 @@ import ( func TestNewReadFS(t *testing.T) { tmpDir := t.TempDir() - // Doesn't double-wrap file systems that are already read-only - require.Equal(t, sys.UnimplementedFS{}, NewReadFS(sys.UnimplementedFS{})) - // Wraps a sys.FS because it allows access to Write - adapted := Adapt(os.DirFS(tmpDir)) - require.NotEqual(t, adapted, NewReadFS(adapted)) + adapted := &AdaptFS{FS: os.DirFS(tmpDir)} + require.NotEqual(t, adapted, &ReadFS{FS: adapted}) // Wraps a writeable file system - writeable := NewDirFS(tmpDir) - readFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + readFS := &ReadFS{FS: writeable} require.NotEqual(t, writeable, readFS) } @@ -31,27 +28,27 @@ func TestReadFS_Lstat(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - writeable := NewDirFS(tmpDir) + writeable := DirFS(tmpDir) for _, path := range []string{"animals.txt", "sub", "sub-link"} { require.EqualErrno(t, 0, writeable.Symlink(path, path+"-link")) } - testFS := NewReadFS(writeable) + testFS := &ReadFS{FS: writeable} testLstat(t, testFS) } func TestReadFS_MkDir(t *testing.T) { - writeable := NewDirFS(t.TempDir()) - testFS := NewReadFS(writeable) + writeable := DirFS(t.TempDir()) + testFS := &ReadFS{FS: writeable} err := testFS.Mkdir("mkdir", fs.ModeDir) require.EqualErrno(t, sys.EROFS, err) } func TestReadFS_Chmod(t *testing.T) { - writeable := NewDirFS(t.TempDir()) - testFS := NewReadFS(writeable) + writeable := DirFS(t.TempDir()) + testFS := &ReadFS{FS: writeable} err := testFS.Chmod("chmod", fs.ModeDir) require.EqualErrno(t, sys.EROFS, err) @@ -59,8 +56,8 @@ func TestReadFS_Chmod(t *testing.T) { func TestReadFS_Rename(t *testing.T) { tmpDir := t.TempDir() - writeable := NewDirFS(tmpDir) - testFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + testFS := &ReadFS{FS: writeable} file1 := "file1" file1Path := joinPath(tmpDir, file1) @@ -80,8 +77,8 @@ func TestReadFS_Rename(t *testing.T) { func TestReadFS_Rmdir(t *testing.T) { tmpDir := t.TempDir() - writeable := NewDirFS(tmpDir) - testFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + testFS := &ReadFS{FS: writeable} path := "rmdir" realPath := joinPath(tmpDir, path) @@ -93,8 +90,8 @@ func TestReadFS_Rmdir(t *testing.T) { func TestReadFS_Unlink(t *testing.T) { tmpDir := t.TempDir() - writeable := NewDirFS(tmpDir) - testFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + testFS := &ReadFS{FS: writeable} path := "unlink" realPath := joinPath(tmpDir, path) @@ -106,8 +103,8 @@ func TestReadFS_Unlink(t *testing.T) { func TestReadFS_UtimesNano(t *testing.T) { tmpDir := t.TempDir() - writeable := NewDirFS(tmpDir) - testFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + testFS := &ReadFS{FS: writeable} path := "utimes" realPath := joinPath(tmpDir, path) @@ -129,7 +126,7 @@ func TestReadFS_Open_Read(t *testing.T) { { name: "DirFS", fs: func(tmpDir string) sys.FS { - return NewDirFS(tmpDir) + return DirFS(tmpDir) }, expectFileIno: true, expectDirIno: true, @@ -137,7 +134,7 @@ func TestReadFS_Open_Read(t *testing.T) { { name: "fstest.MapFS", fs: func(tmpDir string) sys.FS { - return Adapt(fstest.FS) + return &AdaptFS{FS: fstest.FS} }, expectFileIno: false, expectDirIno: false, @@ -145,7 +142,7 @@ func TestReadFS_Open_Read(t *testing.T) { { name: "os.DirFS", fs: func(tmpDir string) sys.FS { - return Adapt(os.DirFS(tmpDir)) + return &AdaptFS{FS: os.DirFS(tmpDir)} }, expectFileIno: statSetsIno(), expectDirIno: runtime.GOOS != "windows", @@ -153,7 +150,7 @@ func TestReadFS_Open_Read(t *testing.T) { { name: "mask(os.DirFS)", fs: func(tmpDir string) sys.FS { - return Adapt(&MaskOsFS{Fs: os.DirFS(tmpDir)}) + return &AdaptFS{FS: &MaskOsFS{Fs: os.DirFS(tmpDir)}} }, expectFileIno: statSetsIno(), expectDirIno: runtime.GOOS != "windows", @@ -161,7 +158,7 @@ func TestReadFS_Open_Read(t *testing.T) { { name: "mask(os.DirFS) ZeroIno", fs: func(tmpDir string) sys.FS { - return Adapt(&MaskOsFS{Fs: os.DirFS(tmpDir), ZeroIno: true}) + return &AdaptFS{FS: &MaskOsFS{Fs: os.DirFS(tmpDir), ZeroIno: true}} }, expectFileIno: false, expectDirIno: false, @@ -178,7 +175,7 @@ func TestReadFS_Open_Read(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - testOpen_Read(t, NewReadFS(tc.fs(tmpDir)), tc.expectFileIno, tc.expectDirIno) + testOpen_Read(t, &ReadFS{FS: tc.fs(tmpDir)}, tc.expectFileIno, tc.expectDirIno) }) } } @@ -187,8 +184,8 @@ func TestReadFS_Stat(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - writeable := NewDirFS(tmpDir) - testFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + testFS := &ReadFS{FS: writeable} testStat(t, testFS) } @@ -196,7 +193,7 @@ func TestReadFS_Readlink(t *testing.T) { tmpDir := t.TempDir() require.NoError(t, fstest.WriteTestFiles(tmpDir)) - writeable := NewDirFS(tmpDir) - testFS := NewReadFS(writeable) + writeable := DirFS(tmpDir) + testFS := &ReadFS{FS: writeable} testReadlink(t, testFS, writeable) } diff --git a/internal/sysfs/sysfs_test.go b/internal/sysfs/sysfs_test.go index 0411b8dc4e..75526e565f 100644 --- a/internal/sysfs/sysfs_test.go +++ b/internal/sysfs/sysfs_test.go @@ -70,7 +70,7 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS experimentalsys.FS) { t.Run("O_TRUNC", func(t *testing.T) { tmpDir := t.TempDir() - testFS := NewDirFS(tmpDir) + testFS := DirFS(tmpDir) name := "truncate" realPath := path.Join(tmpDir, name) diff --git a/internal/wasm/module_instance_test.go b/internal/wasm/module_instance_test.go index 7be778546f..4fdb4cc045 100644 --- a/internal/wasm/module_instance_test.go +++ b/internal/wasm/module_instance_test.go @@ -112,7 +112,7 @@ func TestModuleInstance_Close(t *testing.T) { } t.Run("calls Context.Close()", func(t *testing.T) { - testFS := sysfs.Adapt(testfs.FS{"foo": &testfs.File{}}) + testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{}}} sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS() @@ -147,7 +147,7 @@ func TestModuleInstance_Close(t *testing.T) { t.Run("error closing", func(t *testing.T) { // Right now, the only way to err closing the sys context is if a File.Close erred. - testFS := sysfs.Adapt(testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}) + testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}} sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS() @@ -217,7 +217,7 @@ func TestModuleInstance_CallDynamic(t *testing.T) { } t.Run("calls Context.Close()", func(t *testing.T) { - testFS := sysfs.Adapt(testfs.FS{"foo": &testfs.File{}}) + testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{}}} sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS() @@ -245,7 +245,7 @@ func TestModuleInstance_CallDynamic(t *testing.T) { t.Run("error closing", func(t *testing.T) { // Right now, the only way to err closing the sys context is if a File.Close erred. - testFS := sysfs.Adapt(testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}) + testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}} sysCtx := internalsys.DefaultContext(testFS) fsCtx := sysCtx.FS()