From a37919dfeaebde2267cd4a3bb1effd88c579e90d Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Mon, 27 Jan 2025 07:03:22 -0800 Subject: [PATCH] internal/unix: add Errno wrapper for Windows eBPF for Windows has historically aimed to be source compatible with libbpf API. As part of this, POSIX error codes are found in errno.h are used to indicate errors of all kinds. eBPF for Windows so far aimed to be source compatible with libbpf. This includes using the same error constants (EINVAL, etc.) as libbpf. It does this using the errno.h header supplied by the Microsoft C runtime. Unfortunately, Windows error codes do not always use the same value as Linux does. Introduce a dedicated Errno type on Windows which maps errors to the correct constant for the platform. This means that syscall.Errno is not an alias for unix.Errno any more. Signed-off-by: Lorenz Bauer --- internal/sys/syscall.go | 11 ++-- internal/unix/errno_linux.go | 29 +++++++++++ internal/unix/errno_linux_test.go | 13 +++++ internal/unix/errno_other.go | 29 +++++++++++ internal/unix/errno_string_windows.go | 59 ++++++++++++++++++++++ internal/unix/errno_test.go | 12 +++++ internal/unix/errno_windows.go | 72 +++++++++++++++++++++++++++ internal/unix/types_linux.go | 20 -------- internal/unix/types_other.go | 24 +-------- 9 files changed, 221 insertions(+), 48 deletions(-) create mode 100644 internal/unix/errno_linux.go create mode 100644 internal/unix/errno_linux_test.go create mode 100644 internal/unix/errno_other.go create mode 100644 internal/unix/errno_string_windows.go create mode 100644 internal/unix/errno_test.go create mode 100644 internal/unix/errno_windows.go diff --git a/internal/sys/syscall.go b/internal/sys/syscall.go index e37f4cf67..4fdb74c57 100644 --- a/internal/sys/syscall.go +++ b/internal/sys/syscall.go @@ -2,7 +2,6 @@ package sys import ( "runtime" - "syscall" "unsafe" "github.com/cilium/ebpf/internal/unix" @@ -11,7 +10,7 @@ import ( // ENOTSUPP is a Linux internal error code that has leaked into UAPI. // // It is not the same as ENOTSUP or EOPNOTSUPP. -const ENOTSUPP = syscall.Errno(524) +const ENOTSUPP = unix.Errno(524) // BPF wraps SYS_BPF. // @@ -179,12 +178,12 @@ const ( const BPF_TAG_SIZE = 8 const BPF_OBJ_NAME_LEN = 16 -// wrappedErrno wraps syscall.Errno to prevent direct comparisons with +// wrappedErrno wraps [unix.Errno] to prevent direct comparisons with // syscall.E* or unix.E* constants. // // You should never export an error of this type. type wrappedErrno struct { - syscall.Errno + unix.Errno } func (we wrappedErrno) Unwrap() error { @@ -200,10 +199,10 @@ func (we wrappedErrno) Error() string { type syscallError struct { error - errno syscall.Errno + errno unix.Errno } -func Error(err error, errno syscall.Errno) error { +func Error(err error, errno unix.Errno) error { return &syscallError{err, errno} } diff --git a/internal/unix/errno_linux.go b/internal/unix/errno_linux.go new file mode 100644 index 000000000..0c4886bd1 --- /dev/null +++ b/internal/unix/errno_linux.go @@ -0,0 +1,29 @@ +package unix + +import ( + "syscall" + + linux "golang.org/x/sys/unix" +) + +type Errno = syscall.Errno + +const ( + E2BIG = linux.E2BIG + EACCES = linux.EACCES + EAGAIN = linux.EAGAIN + EBADF = linux.EBADF + EEXIST = linux.EEXIST + EFAULT = linux.EFAULT + EILSEQ = linux.EILSEQ + EINTR = linux.EINTR + EINVAL = linux.EINVAL + ENODEV = linux.ENODEV + ENOENT = linux.ENOENT + ENOSPC = linux.ENOSPC + EOPNOTSUPP = linux.EOPNOTSUPP + EPERM = linux.EPERM + EPOLLIN = linux.EPOLLIN + ESRCH = linux.ESRCH + ESTALE = linux.ESTALE +) diff --git a/internal/unix/errno_linux_test.go b/internal/unix/errno_linux_test.go new file mode 100644 index 000000000..32bf69e37 --- /dev/null +++ b/internal/unix/errno_linux_test.go @@ -0,0 +1,13 @@ +package unix + +import ( + "testing" + + "github.com/go-quicktest/qt" + "golang.org/x/sys/unix" +) + +func TestErrnoIsUnix(t *testing.T) { + qt.Assert(t, qt.ErrorIs(EPERM, unix.EPERM)) + qt.Assert(t, qt.ErrorIs(ENOENT, unix.ENOENT)) +} diff --git a/internal/unix/errno_other.go b/internal/unix/errno_other.go new file mode 100644 index 000000000..fc2b042b5 --- /dev/null +++ b/internal/unix/errno_other.go @@ -0,0 +1,29 @@ +//go:build !linux && !windows + +package unix + +import "syscall" + +type Errno = syscall.Errno + +// Errnos are distinct and non-zero. +const ( + E2BIG Errno = iota + 1 + EACCES + EAGAIN + EBADF + EEXIST + EFAULT + EILSEQ + EINTR + EINVAL + ENODEV + ENOENT + ENOSPC + ENOTSUP + ENOTSUPP + EOPNOTSUPP + EPERM + ESRCH + ESTALE +) diff --git a/internal/unix/errno_string_windows.go b/internal/unix/errno_string_windows.go new file mode 100644 index 000000000..6077e983f --- /dev/null +++ b/internal/unix/errno_string_windows.go @@ -0,0 +1,59 @@ +// Code generated by "stringer -type=Errno -tags=windows -output=errno_string_windows.go"; DO NOT EDIT. + +package unix + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[EPERM-1] + _ = x[ENOENT-2] + _ = x[ESRCH-3] + _ = x[EINTR-4] + _ = x[E2BIG-7] + _ = x[EBADF-9] + _ = x[EAGAIN-11] + _ = x[EACCES-13] + _ = x[EFAULT-14] + _ = x[EEXIST-17] + _ = x[ENODEV-19] + _ = x[EINVAL-22] + _ = x[ENOSPC-28] + _ = x[EILSEQ-42] + _ = x[ENOTSUP-129] + _ = x[EOPNOTSUPP-130] + _ = x[ENOTSUPP-536870912] + _ = x[ESTALE-536870913] +} + +const _Errno_name = "EPERMENOENTESRCHEINTRE2BIGEBADFEAGAINEACCESEFAULTEEXISTENODEVEINVALENOSPCEILSEQENOTSUPEOPNOTSUPPENOTSUPPESTALE" + +var _Errno_map = map[Errno]string{ + 1: _Errno_name[0:5], + 2: _Errno_name[5:11], + 3: _Errno_name[11:16], + 4: _Errno_name[16:21], + 7: _Errno_name[21:26], + 9: _Errno_name[26:31], + 11: _Errno_name[31:37], + 13: _Errno_name[37:43], + 14: _Errno_name[43:49], + 17: _Errno_name[49:55], + 19: _Errno_name[55:61], + 22: _Errno_name[61:67], + 28: _Errno_name[67:73], + 42: _Errno_name[73:79], + 129: _Errno_name[79:86], + 130: _Errno_name[86:96], + 536870912: _Errno_name[96:104], + 536870913: _Errno_name[104:110], +} + +func (i Errno) String() string { + if str, ok := _Errno_map[i]; ok { + return str + } + return "Errno(" + strconv.FormatInt(int64(i), 10) + ")" +} diff --git a/internal/unix/errno_test.go b/internal/unix/errno_test.go new file mode 100644 index 000000000..84991f957 --- /dev/null +++ b/internal/unix/errno_test.go @@ -0,0 +1,12 @@ +package unix + +import ( + "os" + "testing" + + "github.com/go-quicktest/qt" +) + +func TestErrno(t *testing.T) { + qt.Assert(t, qt.ErrorIs(ENOENT, os.ErrNotExist)) +} diff --git a/internal/unix/errno_windows.go b/internal/unix/errno_windows.go new file mode 100644 index 000000000..574a70222 --- /dev/null +++ b/internal/unix/errno_windows.go @@ -0,0 +1,72 @@ +package unix + +// The code in this file is derived from syscall_unix.go in the Go source code, +// licensed under the MIT license. + +import ( + "errors" + "os" + "syscall" +) + +//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Errno -tags=windows -output=errno_string_windows.go + +// Windows specific constants for Unix errnos. +// +// The values do not always match Linux, for example EILSEQ and EOPNOTSUPP. +// +// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/errno-constants?view=msvc-170 +const ( + EPERM Errno = 1 + ENOENT Errno = 2 + ESRCH Errno = 3 + EINTR Errno = 4 + E2BIG Errno = 7 + EBADF Errno = 9 + EAGAIN Errno = 11 + EACCES Errno = 13 + EFAULT Errno = 14 + EEXIST Errno = 17 + ENODEV Errno = 19 + EINVAL Errno = 22 + ENOSPC Errno = 28 + EILSEQ Errno = 42 + ENOTSUP Errno = 129 + EOPNOTSUPP Errno = 130 +) + +// These constants do not exist on Windows and therefore have a non-zero +// dummy value. +const ( + ENOTSUPP Errno = Errno(syscall.APPLICATION_ERROR) + iota + ESTALE +) + +// Errno is a Windows compatibility shim for Unix errnos. +type Errno uintptr + +func (e Errno) Error() string { + return e.String() +} + +func (e Errno) Is(target error) bool { + switch target { + case os.ErrPermission: + return e == EACCES || e == EPERM + case os.ErrExist: + return e == EEXIST /* || e == ENOTEMPTY */ + case os.ErrNotExist: + return e == ENOENT + case errors.ErrUnsupported: + return /* e == ENOSYS || e == ENOTSUP || */ e == EOPNOTSUPP + } + return false +} + +func (e Errno) Temporary() bool { + return e == EINTR || /* e == EMFILE || e == ENFILE || */ e.Timeout() +} + +func (e Errno) Timeout() bool { + return e == EAGAIN /* || e == EWOULDBLOCK || e == ETIMEDOUT */ +} diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go index 144e608d1..1421addac 100644 --- a/internal/unix/types_linux.go +++ b/internal/unix/types_linux.go @@ -8,26 +8,6 @@ import ( linux "golang.org/x/sys/unix" ) -const ( - ENOENT = linux.ENOENT - EEXIST = linux.EEXIST - EAGAIN = linux.EAGAIN - ENOSPC = linux.ENOSPC - EINVAL = linux.EINVAL - EPOLLIN = linux.EPOLLIN - EINTR = linux.EINTR - EPERM = linux.EPERM - ESRCH = linux.ESRCH - ENODEV = linux.ENODEV - EBADF = linux.EBADF - E2BIG = linux.E2BIG - EFAULT = linux.EFAULT - EACCES = linux.EACCES - EILSEQ = linux.EILSEQ - EOPNOTSUPP = linux.EOPNOTSUPP - ESTALE = linux.ESTALE -) - const ( BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go index 806c6e567..768737315 100644 --- a/internal/unix/types_other.go +++ b/internal/unix/types_other.go @@ -6,26 +6,6 @@ import ( "syscall" ) -// Errnos are distinct and non-zero. -const ( - ENOENT syscall.Errno = iota + 1 - EEXIST - EAGAIN - ENOSPC - EINVAL - EINTR - EPERM - ESRCH - ENODEV - EBADF - E2BIG - EFAULT - EACCES - EILSEQ - EOPNOTSUPP - ESTALE -) - // Constants are distinct to avoid breaking switch statements. const ( BPF_F_NO_PREALLOC = iota @@ -133,8 +113,8 @@ type Sigset_t struct { Val [4]uint64 } -func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { - return 0, 0, syscall.ENOTSUP +func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { + return 0, 0, ENOTSUP } func PthreadSigmask(how int, set, oldset *Sigset_t) error {