diff --git a/internal/sys/fs.go b/internal/sys/fs.go index cfea4ee5..dd5cf7c0 100644 --- a/internal/sys/fs.go +++ b/internal/sys/fs.go @@ -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 } diff --git a/internal/sys/fs_test.go b/internal/sys/fs_test.go index fc16c565..5beb9b39 100644 --- a/internal/sys/fs_test.go +++ b/internal/sys/fs_test.go @@ -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) }) } diff --git a/internal/sys/sys.go b/internal/sys/sys.go index a94f5049..b0d6e3be 100644 --- a/internal/sys/sys.go +++ b/internal/sys/sys.go @@ -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 diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go index 01cbacc2..50b26f45 100644 --- a/internal/sys/sys_test.go +++ b/internal/sys/sys_test.go @@ -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()) diff --git a/internal/syscallfs/adapter.go b/internal/syscallfs/adapter.go index aff4b8ce..a1474eb1 100644 --- a/internal/syscallfs/adapter.go +++ b/internal/syscallfs/adapter.go @@ -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 -} diff --git a/internal/syscallfs/adapter_test.go b/internal/syscallfs/adapter_test.go index 6801d38e..5824e9d0 100644 --- a/internal/syscallfs/adapter_test.go +++ b/internal/syscallfs/adapter_test.go @@ -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) }) } diff --git a/internal/syscallfs/dirfs.go b/internal/syscallfs/dirfs.go index d3c5608a..57daea42 100644 --- a/internal/syscallfs/dirfs.go +++ b/internal/syscallfs/dirfs.go @@ -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) } diff --git a/internal/syscallfs/dirfs_test.go b/internal/syscallfs/dirfs_test.go index 20fdd1d3..b6bd1b9a 100644 --- a/internal/syscallfs/dirfs_test.go +++ b/internal/syscallfs/dirfs_test.go @@ -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) diff --git a/internal/syscallfs/emptyfs.go b/internal/syscallfs/emptyfs.go deleted file mode 100644 index 14df204f..00000000 --- a/internal/syscallfs/emptyfs.go +++ /dev/null @@ -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 -} diff --git a/internal/syscallfs/emptyfs_test.go b/internal/syscallfs/emptyfs_test.go deleted file mode 100644 index a2b5c287..00000000 --- a/internal/syscallfs/emptyfs_test.go +++ /dev/null @@ -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()) -} diff --git a/internal/syscallfs/readfs.go b/internal/syscallfs/readfs.go index 8811993e..e99b954d 100644 --- a/internal/syscallfs/readfs.go +++ b/internal/syscallfs/readfs.go @@ -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 -} diff --git a/internal/syscallfs/readfs_test.go b/internal/syscallfs/readfs_test.go index b27badd9..dcc4a27d 100644 --- a/internal/syscallfs/readfs_test.go +++ b/internal/syscallfs/readfs_test.go @@ -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") diff --git a/internal/syscallfs/rootfs.go b/internal/syscallfs/rootfs.go index 041b5304..faa0142a 100644 --- a/internal/syscallfs/rootfs.go +++ b/internal/syscallfs/rootfs.go @@ -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) diff --git a/internal/syscallfs/rootfs_test.go b/internal/syscallfs/rootfs_test.go index a958c441..08a56038 100644 --- a/internal/syscallfs/rootfs_test.go +++ b/internal/syscallfs/rootfs_test.go @@ -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) } }) } diff --git a/internal/syscallfs/syscall_windows.go b/internal/syscallfs/syscall_windows.go index 5883feac..303b08fd 100644 --- a/internal/syscallfs/syscall_windows.go +++ b/internal/syscallfs/syscall_windows.go @@ -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 } diff --git a/internal/syscallfs/syscallfs.go b/internal/syscallfs/syscallfs.go index 2046a829..bcd36c10 100644 --- a/internal/syscallfs/syscallfs.go +++ b/internal/syscallfs/syscallfs.go @@ -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 +} diff --git a/internal/syscallfs/syscallfs_test.go b/internal/syscallfs/syscallfs_test.go index 64ed1f41..d22a9f4d 100644 --- a/internal/syscallfs/syscallfs_test.go +++ b/internal/syscallfs/syscallfs_test.go @@ -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) { diff --git a/internal/syscallfs/unsupported.go b/internal/syscallfs/unsupported.go new file mode 100644 index 00000000..991bb155 --- /dev/null +++ b/internal/syscallfs/unsupported.go @@ -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 +}