Skip to content

Commit

Permalink
internal/unix: add Errno wrapper for Windows
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
lmb committed Jan 27, 2025
1 parent 9f20115 commit a37919d
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 48 deletions.
11 changes: 5 additions & 6 deletions internal/sys/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package sys

import (
"runtime"
"syscall"
"unsafe"

"github.com/cilium/ebpf/internal/unix"
Expand All @@ -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.
//
Expand Down Expand Up @@ -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 {
Expand All @@ -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}
}

Expand Down
29 changes: 29 additions & 0 deletions internal/unix/errno_linux.go
Original file line number Diff line number Diff line change
@@ -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
)
13 changes: 13 additions & 0 deletions internal/unix/errno_linux_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
29 changes: 29 additions & 0 deletions internal/unix/errno_other.go
Original file line number Diff line number Diff line change
@@ -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
)
59 changes: 59 additions & 0 deletions internal/unix/errno_string_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions internal/unix/errno_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
72 changes: 72 additions & 0 deletions internal/unix/errno_windows.go
Original file line number Diff line number Diff line change
@@ -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 */
}
20 changes: 0 additions & 20 deletions internal/unix/types_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 2 additions & 22 deletions internal/unix/types_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit a37919d

Please sign in to comment.