fs: adds base UnimplementedFS type and unwraps PathError (#1046)
This reduces some boilerplate by extracting UnimplementedFS from the existing FS implementations, such that it returns ENOSYS. This also removes inconsistency where some methods on FS returned syscall.Errno and others PathError. Note: this doesn't get rid of all PathError, yet. We still need to create a syscallfs.File type which would be able to do that. This is just one preliminary cleanup before refactoring out the `fs.FS` embedding from `syscallfs.DS`. P.S. naming convention is arbitrary, so I took UnimplementedXXX from grpc. This pattern is used a lot of places, also proxy-wasm-go-sdk, e.g. `DefaultVMContext`. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -210,15 +210,15 @@ type FSContext struct {
|
||||
// NewFSContext creates a FSContext with stdio streams and an optional
|
||||
// pre-opened filesystem.
|
||||
//
|
||||
// If `preopened` is not syscallfs.EmptyFS, it is inserted into the file
|
||||
// descriptor table as FdPreopen.
|
||||
// If `preopened` is not syscallfs.UnimplementedFS, it is inserted into
|
||||
// the file descriptor table as FdPreopen.
|
||||
func NewFSContext(stdin io.Reader, stdout, stderr io.Writer, preopened syscallfs.FS) (fsc *FSContext, err error) {
|
||||
fsc = &FSContext{fs: preopened}
|
||||
fsc.openedFiles.Insert(stdinReader(stdin))
|
||||
fsc.openedFiles.Insert(stdioWriter(stdout, noopStdoutStat))
|
||||
fsc.openedFiles.Insert(stdioWriter(stderr, noopStderrStat))
|
||||
|
||||
if preopened == syscallfs.EmptyFS {
|
||||
if _, ok := preopened.(syscallfs.UnimplementedFS); ok {
|
||||
return fsc, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -95,11 +95,11 @@ func TestNewFSContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyFSContext(t *testing.T) {
|
||||
testFS, err := NewFSContext(nil, nil, nil, syscallfs.EmptyFS)
|
||||
func TestUnimplementedFSContext(t *testing.T) {
|
||||
testFS, err := NewFSContext(nil, nil, nil, syscallfs.UnimplementedFS{})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &FSContext{fs: syscallfs.EmptyFS}
|
||||
expected := &FSContext{fs: syscallfs.UnimplementedFS{}}
|
||||
expected.openedFiles.Insert(noopStdin)
|
||||
expected.openedFiles.Insert(noopStdout)
|
||||
expected.openedFiles.Insert(noopStderr)
|
||||
@@ -109,7 +109,7 @@ func TestEmptyFSContext(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Closes opened files
|
||||
require.Equal(t, &FSContext{fs: syscallfs.EmptyFS}, testFS)
|
||||
require.Equal(t, &FSContext{fs: syscallfs.UnimplementedFS{}}, testFS)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ func (c *Context) Nanosleep(ns int64) {
|
||||
(*(c.nanosleep))(ns)
|
||||
}
|
||||
|
||||
// FS returns the possibly empty (EmptyFS) file system context.
|
||||
// FS returns the possibly empty (syscallfs.UnimplementedFS) file system context.
|
||||
func (c *Context) FS() *FSContext {
|
||||
return c.fsc
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func NewContext(
|
||||
if fs != nil {
|
||||
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, syscallfs.Adapt(fs, "/"))
|
||||
} else {
|
||||
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, syscallfs.EmptyFS)
|
||||
sysCtx.fsc, err = NewFSContext(stdin, stdout, stderr, syscallfs.UnimplementedFS{})
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
func TestContext_FS(t *testing.T) {
|
||||
sysCtx := DefaultContext(syscallfs.EmptyFS)
|
||||
sysCtx := DefaultContext(syscallfs.UnimplementedFS{})
|
||||
|
||||
fsc, err := NewFSContext(nil, nil, nil, syscallfs.EmptyFS)
|
||||
fsc, err := NewFSContext(nil, nil, nil, syscallfs.UnimplementedFS{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, fsc, sysCtx.FS())
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
pathutil "path"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Adapt adapts the input to FS unless it is already one. NewDirFS should be
|
||||
@@ -19,10 +18,11 @@ func Adapt(fs fs.FS, guestDir string) FS {
|
||||
if sys, ok := fs.(FS); ok {
|
||||
return sys
|
||||
}
|
||||
return &adapter{fs, guestDir}
|
||||
return &adapter{fs: fs, guestDir: guestDir}
|
||||
}
|
||||
|
||||
type adapter struct {
|
||||
UnimplementedFS
|
||||
fs fs.FS
|
||||
guestDir string
|
||||
}
|
||||
@@ -32,11 +32,6 @@ func (a *adapter) String() string {
|
||||
return fmt.Sprintf("%v:%s:ro", a.fs, a.guestDir)
|
||||
}
|
||||
|
||||
// Open implements the same method as documented on fs.FS
|
||||
func (a *adapter) Open(name string) (fs.File, error) {
|
||||
panic(fmt.Errorf("unexpected to call fs.FS.Open(%s)", name))
|
||||
}
|
||||
|
||||
// GuestDir implements FS.GuestDir
|
||||
func (a *adapter) GuestDir() string {
|
||||
return a.guestDir
|
||||
@@ -48,15 +43,7 @@ func (a *adapter) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, er
|
||||
f, err := a.fs.Open(path)
|
||||
|
||||
if err != nil {
|
||||
if pe, ok := err.(*fs.PathError); ok {
|
||||
switch pe.Err {
|
||||
case os.ErrInvalid:
|
||||
pe.Err = syscall.EINVAL // adjust it
|
||||
case os.ErrNotExist:
|
||||
pe.Err = syscall.ENOENT // adjust it
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
return nil, unwrapPathError(err)
|
||||
} else if osF, ok := f.(*os.File); ok {
|
||||
// If this is an OS file, it has same portability issues as dirFS.
|
||||
return maybeWrapFile(osF), nil
|
||||
@@ -76,28 +63,3 @@ func cleanPath(name string) string {
|
||||
cleaned = pathutil.Clean(cleaned) // e.g. "sub/." -> "sub"
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (a *adapter) Mkdir(path string, perm fs.FileMode) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rename implements FS.Rename
|
||||
func (a *adapter) Rename(from, to string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rmdir implements FS.Rmdir
|
||||
func (a *adapter) Rmdir(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Unlink implements FS.Unlink
|
||||
func (a *adapter) Unlink(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Utimes implements FS.Utimes
|
||||
func (a *adapter) Utimes(path string, atimeNsec, mtimeNsec int64) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestAdapt_Open_Read(t *testing.T) {
|
||||
_, err := testFS.OpenFile("../foo", os.O_RDONLY, 0)
|
||||
|
||||
// fs.FS doesn't allow relative path lookups
|
||||
requireErrno(t, syscall.EINVAL, err)
|
||||
require.Equal(t, syscall.EINVAL, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ func ensureTrailingPathSeparator(dir string) string {
|
||||
}
|
||||
|
||||
type dirFS struct {
|
||||
UnimplementedFS
|
||||
hostDir, guestDir string
|
||||
// cleanedHostDir is for easier OS-specific concatenation, as it always has
|
||||
// a trailing path separator.
|
||||
@@ -42,11 +43,6 @@ func (d *dirFS) String() string {
|
||||
return d.hostDir + ":" + d.guestDir
|
||||
}
|
||||
|
||||
// Open implements the same method as documented on fs.FS
|
||||
func (d *dirFS) Open(name string) (fs.File, error) {
|
||||
panic(fmt.Errorf("unexpected to call fs.FS.Open(%s)", name))
|
||||
}
|
||||
|
||||
// GuestDir implements FS.GuestDir
|
||||
func (d *dirFS) GuestDir() string {
|
||||
return d.guestDir
|
||||
@@ -56,7 +52,7 @@ func (d *dirFS) GuestDir() string {
|
||||
func (d *dirFS) OpenFile(name string, flag int, perm fs.FileMode) (fs.File, error) {
|
||||
f, err := os.OpenFile(d.join(name), flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, unwrapPathError(err)
|
||||
}
|
||||
return maybeWrapFile(f), nil
|
||||
}
|
||||
@@ -64,6 +60,7 @@ func (d *dirFS) OpenFile(name string, flag int, perm fs.FileMode) (fs.File, erro
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (d *dirFS) Mkdir(name string, perm fs.FileMode) error {
|
||||
err := os.Mkdir(d.join(name), perm)
|
||||
err = unwrapPathError(err)
|
||||
return adjustMkdirError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestDirFS_MkDir(t *testing.T) {
|
||||
|
||||
t.Run("dir exists", func(t *testing.T) {
|
||||
err := testFS.Mkdir(name, fs.ModeDir)
|
||||
requireErrno(t, syscall.EEXIST, err)
|
||||
require.Equal(t, syscall.EEXIST, err)
|
||||
})
|
||||
|
||||
t.Run("file exists", func(t *testing.T) {
|
||||
@@ -76,7 +76,7 @@ func TestDirFS_MkDir(t *testing.T) {
|
||||
require.NoError(t, os.Mkdir(realPath, 0o700))
|
||||
|
||||
err := testFS.Mkdir(name, fs.ModeDir)
|
||||
requireErrno(t, syscall.EEXIST, err)
|
||||
require.Equal(t, syscall.EEXIST, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestDirFS_Rename(t *testing.T) {
|
||||
|
||||
// Show the prior path no longer exists
|
||||
_, err = os.Stat(file1Path)
|
||||
requireErrno(t, syscall.ENOENT, err)
|
||||
require.Equal(t, syscall.ENOENT, errors.Unwrap(err))
|
||||
|
||||
s, err := os.Stat(file2Path)
|
||||
require.NoError(t, err)
|
||||
@@ -134,7 +134,7 @@ func TestDirFS_Rename(t *testing.T) {
|
||||
|
||||
// Show the prior path no longer exists
|
||||
_, err = os.Stat(dir1Path)
|
||||
requireErrno(t, syscall.ENOENT, err)
|
||||
require.Equal(t, syscall.ENOENT, errors.Unwrap(err))
|
||||
|
||||
s, err := os.Stat(dir2Path)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package syscallfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// EmptyFS is an FS that returns syscall.ENOENT for all read functions, and
|
||||
// syscall.ENOSYS otherwise.
|
||||
var EmptyFS FS = empty{}
|
||||
|
||||
type empty struct{}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (empty) String() string {
|
||||
return "empty:/:ro"
|
||||
}
|
||||
|
||||
// Open implements the same method as documented on fs.FS
|
||||
func (empty) Open(name string) (fs.File, error) {
|
||||
panic(fmt.Errorf("unexpected to call fs.FS.Open(%s)", name))
|
||||
}
|
||||
|
||||
// GuestDir implements FS.GuestDir
|
||||
func (empty) GuestDir() string {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// OpenFile implements FS.OpenFile
|
||||
func (empty) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
|
||||
return nil, &fs.PathError{Op: "open", Path: path, Err: syscall.ENOENT}
|
||||
}
|
||||
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (empty) Mkdir(path string, perm fs.FileMode) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rename implements FS.Rename
|
||||
func (empty) Rename(from, to string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rmdir implements FS.Rmdir
|
||||
func (empty) Rmdir(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Unlink implements FS.Unlink
|
||||
func (empty) Unlink(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Utimes implements FS.Utimes
|
||||
func (empty) Utimes(path string, atimeNsec, mtimeNsec int64) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package syscallfs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
func TestEmptyFS_String(t *testing.T) {
|
||||
require.Equal(t, "empty:/:ro", EmptyFS.String())
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package syscallfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
@@ -17,24 +16,22 @@ func NewReadFS(fs FS) FS {
|
||||
return fs
|
||||
} else if _, ok = fs.(*adapter); ok {
|
||||
return fs // fs.FS is always read-only
|
||||
} else if _, ok = fs.(empty); ok {
|
||||
return fs // empty is always read-only
|
||||
} else if _, ok = fs.(UnimplementedFS); ok {
|
||||
return fs // unimplemented is read-only
|
||||
}
|
||||
return &readFS{fs}
|
||||
return &readFS{fs: fs}
|
||||
}
|
||||
|
||||
type readFS struct{ fs FS }
|
||||
type readFS struct {
|
||||
UnimplementedFS
|
||||
fs FS
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (r *readFS) String() string {
|
||||
return r.fs.String() + ":ro"
|
||||
}
|
||||
|
||||
// Open implements the same method as documented on fs.FS
|
||||
func (r *readFS) Open(name string) (fs.File, error) {
|
||||
panic(fmt.Errorf("unexpected to call fs.FS.Open(%s)", name))
|
||||
}
|
||||
|
||||
// GuestDir implements FS.GuestDir
|
||||
func (r *readFS) GuestDir() string {
|
||||
return r.fs.GuestDir()
|
||||
@@ -128,28 +125,3 @@ func maskForReads(f fs.File) fs.File {
|
||||
panic("BUG: unhandled pattern")
|
||||
}
|
||||
}
|
||||
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (r *readFS) Mkdir(path string, perm fs.FileMode) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rename implements FS.Rename
|
||||
func (r *readFS) Rename(from, to string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rmdir implements FS.Rmdir
|
||||
func (r *readFS) Rmdir(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Unlink implements FS.Unlink
|
||||
func (r *readFS) Unlink(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Utimes implements FS.Utimes
|
||||
func (r *readFS) Utimes(path string, atimeNsec, mtimeNsec int64) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestNewReadFS(t *testing.T) {
|
||||
// Doesn't double-wrap file systems that are already read-only
|
||||
adapted := Adapt(os.DirFS(tmpDir), "/")
|
||||
require.Equal(t, adapted, NewReadFS(adapted))
|
||||
require.Equal(t, EmptyFS, NewReadFS(EmptyFS))
|
||||
require.Equal(t, UnimplementedFS{}, NewReadFS(UnimplementedFS{}))
|
||||
|
||||
// Wraps a writeable file system
|
||||
writeable, err := NewDirFS(tmpDir, "/tmp")
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func NewRootFS(fs ...FS) (FS, error) {
|
||||
switch len(fs) {
|
||||
case 0:
|
||||
return EmptyFS, nil
|
||||
return UnimplementedFS{}, nil
|
||||
case 1:
|
||||
if fs[0].GuestDir() == "/" {
|
||||
return fs[0], nil
|
||||
@@ -54,14 +54,15 @@ func NewRootFS(fs ...FS) (FS, error) {
|
||||
// Ensure there is always a root match to keep runtime logic simpler.
|
||||
if ret.rootIndex == -1 {
|
||||
// TODO: Make a fake root filesystem that can do a directory listing of
|
||||
// any existing prefixes. We can't use EmptyFS as the pre-open for root
|
||||
// must work.
|
||||
// any existing prefixes. We can't use UnimplementedFS as the pre-open
|
||||
// for root must work.
|
||||
return nil, fmt.Errorf("you must supply a root filesystem: %s", fsString(fs))
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type CompositeFS struct {
|
||||
UnimplementedFS
|
||||
// prefixes to match in precedence order
|
||||
prefixes []string
|
||||
// rootPrefixes are prefixes that exist directly under root, such as "tmp".
|
||||
@@ -89,23 +90,13 @@ func fsString(fs []FS) string {
|
||||
func (c *CompositeFS) Unwrap() []FS {
|
||||
result := make([]FS, 0, len(c.fs))
|
||||
for i := len(c.fs) - 1; i >= 0; i-- {
|
||||
if fs := c.fs[i]; fs != EmptyFS {
|
||||
if fs := c.fs[i]; fs != (UnimplementedFS{}) {
|
||||
result = append(result, fs)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Open implements the same method as documented on fs.FS
|
||||
func (c *CompositeFS) Open(name string) (fs.File, error) {
|
||||
panic(fmt.Errorf("unexpected to call fs.FS.Open(%s)", name))
|
||||
}
|
||||
|
||||
// GuestDir implements FS.GuestDir
|
||||
func (c *CompositeFS) GuestDir() string {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// OpenFile implements FS.OpenFile
|
||||
func (c *CompositeFS) OpenFile(path string, flag int, perm fs.FileMode) (f fs.File, err error) {
|
||||
matchIndex, relativePath := c.chooseFS(path)
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestNewRootFS(t *testing.T) {
|
||||
rootFS, err := NewRootFS()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, EmptyFS, rootFS)
|
||||
require.Equal(t, UnimplementedFS{}, rootFS)
|
||||
})
|
||||
t.Run("only root", func(t *testing.T) {
|
||||
testFS, err := NewDirFS(t.TempDir(), "/")
|
||||
@@ -183,8 +183,8 @@ func TestRootFS_examples(t *testing.T) {
|
||||
{
|
||||
name: "go test text/template",
|
||||
fs: []FS{
|
||||
&adapter{testfs.FS{"go-example-stdout-ExampleTemplate-0.txt": &testfs.File{}}, "/tmp"},
|
||||
&adapter{testfs.FS{"testdata/file1.tmpl": &testfs.File{}}, "."},
|
||||
&adapter{fs: testfs.FS{"go-example-stdout-ExampleTemplate-0.txt": &testfs.File{}}, guestDir: "/tmp"},
|
||||
&adapter{fs: testfs.FS{"testdata/file1.tmpl": &testfs.File{}}, guestDir: "."},
|
||||
},
|
||||
expected: []string{"/tmp/go-example-stdout-ExampleTemplate-0.txt", "testdata/file1.tmpl"},
|
||||
unexpected: []string{"DOES NOT EXIST"},
|
||||
@@ -195,9 +195,9 @@ func TestRootFS_examples(t *testing.T) {
|
||||
{
|
||||
name: "tinygo test compress/flate",
|
||||
fs: []FS{
|
||||
&adapter{testfs.FS{}, "/"},
|
||||
&adapter{testfs.FS{"testdata/e.txt": &testfs.File{}}, "../"},
|
||||
&adapter{testfs.FS{"testdata/Isaac.Newton-Opticks.txt": &testfs.File{}}, "../../"},
|
||||
&adapter{fs: testfs.FS{}, guestDir: "/"},
|
||||
&adapter{fs: testfs.FS{"testdata/e.txt": &testfs.File{}}, guestDir: "../"},
|
||||
&adapter{fs: testfs.FS{"testdata/Isaac.Newton-Opticks.txt": &testfs.File{}}, guestDir: "../../"},
|
||||
},
|
||||
expected: []string{"../testdata/e.txt", "../../testdata/Isaac.Newton-Opticks.txt"},
|
||||
unexpected: []string{"../../testdata/e.txt"},
|
||||
@@ -208,8 +208,8 @@ func TestRootFS_examples(t *testing.T) {
|
||||
{
|
||||
name: "go test net",
|
||||
fs: []FS{
|
||||
&adapter{testfs.FS{"services": &testfs.File{}}, "/etc"},
|
||||
&adapter{testfs.FS{"testdata/aliases": &testfs.File{}}, "/"},
|
||||
&adapter{fs: testfs.FS{"services": &testfs.File{}}, guestDir: "/etc"},
|
||||
&adapter{fs: testfs.FS{"testdata/aliases": &testfs.File{}}, guestDir: "/"},
|
||||
},
|
||||
expected: []string{"/etc/services", "testdata/aliases"},
|
||||
unexpected: []string{"services"},
|
||||
@@ -221,10 +221,10 @@ func TestRootFS_examples(t *testing.T) {
|
||||
{
|
||||
name: "python",
|
||||
fs: []FS{
|
||||
&adapter{gofstest.MapFS{ // to allow resolution of "."
|
||||
&adapter{fs: gofstest.MapFS{ // to allow resolution of "."
|
||||
"pybuilddir.txt": &gofstest.MapFile{},
|
||||
"opt/wasi-python/lib/python3.11/__phello__/__init__.py": &gofstest.MapFile{},
|
||||
}, "/"},
|
||||
}, guestDir: "/"},
|
||||
},
|
||||
expected: []string{
|
||||
".",
|
||||
@@ -238,8 +238,8 @@ func TestRootFS_examples(t *testing.T) {
|
||||
{
|
||||
name: "zig",
|
||||
fs: []FS{
|
||||
&adapter{testfs.FS{"zig-cache": &testfs.File{}}, "/"},
|
||||
&adapter{testfs.FS{"qSQRrUkgJX9L20mr": &testfs.File{}}, "/tmp"},
|
||||
&adapter{fs: testfs.FS{"zig-cache": &testfs.File{}}, guestDir: "/"},
|
||||
&adapter{fs: testfs.FS{"qSQRrUkgJX9L20mr": &testfs.File{}}, guestDir: "/tmp"},
|
||||
},
|
||||
expected: []string{"zig-cache", "/tmp/qSQRrUkgJX9L20mr"},
|
||||
unexpected: []string{"/qSQRrUkgJX9L20mr"},
|
||||
@@ -261,7 +261,7 @@ func TestRootFS_examples(t *testing.T) {
|
||||
|
||||
for _, p := range tc.unexpected {
|
||||
_, err := root.OpenFile(p, os.O_RDONLY, 0)
|
||||
requireErrno(t, syscall.ENOENT, err)
|
||||
require.Equal(t, syscall.ENOENT, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,9 +32,8 @@ const (
|
||||
)
|
||||
|
||||
func adjustMkdirError(err error) error {
|
||||
// os.Mkdir wraps the syscall error in a path error
|
||||
if pe, ok := err.(*fs.PathError); ok && pe.Err == ERROR_ALREADY_EXISTS {
|
||||
pe.Err = syscall.EEXIST // adjust it
|
||||
if err == ERROR_ALREADY_EXISTS {
|
||||
return syscall.EEXIST
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
// FS is a writeable fs.FS bridge backed by syscall functions needed for ABI
|
||||
// including WASI and runtime.GOOS=js.
|
||||
//
|
||||
// Any unsupported method should return syscall.ENOSYS.
|
||||
// Implementations should embed UnimplementedFS for forward compatability. Any
|
||||
// unsupported method or parameter should return syscall.ENOSYS.
|
||||
//
|
||||
// See https://github.com/golang/go/issues/45757
|
||||
type FS interface {
|
||||
@@ -52,14 +53,21 @@ type FS interface {
|
||||
Open(name string) (fs.File, error)
|
||||
|
||||
// OpenFile is similar to os.OpenFile, except the path is relative to this
|
||||
// file system.
|
||||
// file system, and syscall.Errno are returned instead of a os.PathError.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// The following errors are expected:
|
||||
// - syscall.EINVAL: `path` or `flag` is invalid.
|
||||
// - syscall.ENOENT: `path` doesn't exist and `flag` doesn't contain
|
||||
// os.O_CREATE.
|
||||
//
|
||||
// # Constraints on the returned file
|
||||
//
|
||||
// Implementations that can read flags should enforce them regardless of
|
||||
// the type returned. For example, while os.File implements io.Writer,
|
||||
// attempts to write to a directory or a file opened with os.O_RDONLY fail
|
||||
// with an os.PathError of syscall.EBADF.
|
||||
// with a syscall.EBADF.
|
||||
//
|
||||
// Some implementations choose whether to enforce read-only opens, namely
|
||||
// fs.FS. While fs.FS is supported (Adapt), wazero cannot runtime enforce
|
||||
@@ -70,7 +78,15 @@ type FS interface {
|
||||
// coercing flags and perms similar to what is done in os.OpenFile.
|
||||
|
||||
// Mkdir is similar to os.Mkdir, except the path is relative to this file
|
||||
// system.
|
||||
// system, and syscall.Errno are returned instead of a os.PathError.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// The following errors are expected:
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.EEXIST: `path` exists and is a directory.
|
||||
// - syscall.ENOTDIR: `path` exists and is a file.
|
||||
//
|
||||
Mkdir(path string, perm fs.FileMode) error
|
||||
// ^^ TODO: Consider syscall.Mkdir, though this implies defining and
|
||||
// coercing flags and perms similar to what is done in os.Mkdir.
|
||||
@@ -272,3 +288,22 @@ func (r *writerAtOffset) Write(p []byte) (int, error) {
|
||||
r.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func unwrapPathError(err error) error {
|
||||
if pe, ok := err.(*fs.PathError); ok {
|
||||
err = pe.Err
|
||||
}
|
||||
switch err {
|
||||
case fs.ErrInvalid:
|
||||
return syscall.EINVAL
|
||||
case fs.ErrPermission:
|
||||
return syscall.EPERM
|
||||
case fs.ErrExist:
|
||||
return syscall.EEXIST
|
||||
case fs.ErrNotExist:
|
||||
return syscall.ENOENT
|
||||
case fs.ErrClosed:
|
||||
return syscall.EBADF
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func testOpen_Read(t *testing.T, tmpDir string, testFS FS) {
|
||||
_, err := testFS.OpenFile("nope", os.O_RDONLY, 0)
|
||||
|
||||
// We currently follow os.Open not syscall.Open, so the error is wrapped.
|
||||
requireErrno(t, syscall.ENOENT, err)
|
||||
require.Equal(t, syscall.ENOENT, err)
|
||||
})
|
||||
|
||||
t.Run(". opens root", func(t *testing.T) {
|
||||
|
||||
56
internal/syscallfs/unsupported.go
Normal file
56
internal/syscallfs/unsupported.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package syscallfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// UnimplementedFS is an FS that returns syscall.ENOSYS for all functions,
|
||||
// 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) {
|
||||
panic(fmt.Errorf("unexpected to call fs.FS.Open(%s)", name))
|
||||
}
|
||||
|
||||
// GuestDir implements FS.GuestDir
|
||||
func (UnimplementedFS) GuestDir() string {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// OpenFile implements FS.OpenFile
|
||||
func (UnimplementedFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
|
||||
return nil, syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rename implements FS.Rename
|
||||
func (UnimplementedFS) Rename(from, to string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Rmdir implements FS.Rmdir
|
||||
func (UnimplementedFS) Rmdir(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Unlink implements FS.Unlink
|
||||
func (UnimplementedFS) Unlink(path string) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Utimes implements FS.Utimes
|
||||
func (UnimplementedFS) Utimes(path string, atimeNsec, mtimeNsec int64) error {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
Reference in New Issue
Block a user