adds experimental sys.Errno to begin decoupling from the syscall package (#1582)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-07-17 08:13:29 +08:00
committed by GitHub
parent 1dafce0b2a
commit 2f8dd23097
94 changed files with 1591 additions and 1374 deletions

View File

@@ -1,9 +0,0 @@
//go:build !windows
package platform
import "syscall"
func adjustErrno(err syscall.Errno) syscall.Errno {
return err
}

View File

@@ -1,72 +0,0 @@
package platform
import "syscall"
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
const (
// ERROR_ACCESS_DENIED is a Windows error returned by syscall.Unlink
// instead of syscall.EACCES
ERROR_ACCESS_DENIED = syscall.Errno(5)
// ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
// instead of syscall.EBADF
ERROR_INVALID_HANDLE = syscall.Errno(6)
// ERROR_FILE_EXISTS is a Windows error returned by os.OpenFile
// instead of syscall.EEXIST
ERROR_FILE_EXISTS = syscall.Errno(0x50)
// ERROR_INVALID_NAME is a Windows error returned by open when a file
// path has a trailing slash
ERROR_INVALID_NAME = syscall.Errno(0x7B)
// ERROR_NEGATIVE_SEEK is a Windows error returned by os.Truncate
// instead of syscall.EINVAL
ERROR_NEGATIVE_SEEK = syscall.Errno(0x83)
// ERROR_DIR_NOT_EMPTY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTEMPTY
ERROR_DIR_NOT_EMPTY = syscall.Errno(0x91)
// ERROR_ALREADY_EXISTS is a Windows error returned by os.Mkdir
// instead of syscall.EEXIST
ERROR_ALREADY_EXISTS = syscall.Errno(0xB7)
// ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTDIR
ERROR_DIRECTORY = syscall.Errno(0x10B)
)
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-
const (
// ERROR_PRIVILEGE_NOT_HELD is a Windows error returned by os.Symlink
// instead of syscall.EPERM.
//
// Note: This can happen when trying to create symlinks w/o admin perms.
ERROR_PRIVILEGE_NOT_HELD = syscall.Errno(0x522)
)
func adjustErrno(err syscall.Errno) syscall.Errno {
// Note: In windows, ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
switch err {
case ERROR_ALREADY_EXISTS:
return syscall.EEXIST
case ERROR_DIRECTORY:
return syscall.ENOTDIR
case ERROR_DIR_NOT_EMPTY:
return syscall.ENOTEMPTY
case ERROR_FILE_EXISTS:
return syscall.EEXIST
case ERROR_INVALID_HANDLE:
return syscall.EBADF
case ERROR_ACCESS_DENIED:
// POSIX read and write functions expect EBADF, not EACCES when not
// open for reading or writing.
return syscall.EBADF
case ERROR_PRIVILEGE_NOT_HELD:
return syscall.EPERM
case ERROR_NEGATIVE_SEEK, ERROR_INVALID_NAME:
return syscall.EINVAL
}
return err
}

View File

@@ -1,52 +0,0 @@
package platform
import (
"io"
"io/fs"
"os"
"syscall"
)
// UnwrapOSError returns a syscall.Errno or zero if the input is nil.
func UnwrapOSError(err error) syscall.Errno {
if err == nil {
return 0
}
err = underlyingError(err)
if se, ok := err.(syscall.Errno); ok {
return adjustErrno(se)
}
// Below are all the fs.ErrXXX in fs.go.
//
// Note: Once we have our own file type, we should never see these.
switch err {
case nil, io.EOF:
return 0 // EOF is not a syscall.Errno
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 syscall.EIO
}
// underlyingError returns the underlying error if a well-known OS error type.
//
// This impl is basically the same as os.underlyingError in os/error.go
func underlyingError(err error) error {
switch err := err.(type) {
case *os.PathError:
return err.Err
case *os.LinkError:
return err.Err
case *os.SyscallError:
return err.Err
}
return err
}

View File

@@ -1,89 +0,0 @@
package platform
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"syscall"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestUnwrapOSError(t *testing.T) {
tests := []struct {
name string
input error
expected syscall.Errno
}{
{
name: "io.EOF is not an error",
input: io.EOF,
expected: 0,
},
{
name: "LinkError ErrInvalid",
input: &os.LinkError{Err: fs.ErrInvalid},
expected: syscall.EINVAL,
},
{
name: "PathError ErrInvalid",
input: &os.PathError{Err: fs.ErrInvalid},
expected: syscall.EINVAL,
},
{
name: "SyscallError ErrInvalid",
input: &os.SyscallError{Err: fs.ErrInvalid},
expected: syscall.EINVAL,
},
{
name: "PathError ErrPermission",
input: &os.PathError{Err: os.ErrPermission},
expected: syscall.EPERM,
},
{
name: "PathError ErrExist",
input: &os.PathError{Err: os.ErrExist},
expected: syscall.EEXIST,
},
{
name: "PathError syscall.ErrnotExist",
input: &os.PathError{Err: os.ErrNotExist},
expected: syscall.ENOENT,
},
{
name: "PathError ErrClosed",
input: &os.PathError{Err: os.ErrClosed},
expected: syscall.EBADF,
},
{
name: "PathError unknown == syscall.EIO",
input: &os.PathError{Err: errors.New("ice cream")},
expected: syscall.EIO,
},
{
name: "unknown == syscall.EIO",
input: errors.New("ice cream"),
expected: syscall.EIO,
},
{
name: "very wrapped unknown == syscall.EIO",
input: fmt.Errorf("%w", fmt.Errorf("%w", fmt.Errorf("%w", errors.New("ice cream")))),
expected: syscall.EIO,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := UnwrapOSError(tc.input)
require.EqualErrno(t, tc.expected, errno)
})
}
t.Run("nil -> zero", func(t *testing.T) {
require.Zero(t, UnwrapOSError(nil))
})
}