diff options
Diffstat (limited to 'pkg/syserror')
-rw-r--r-- | pkg/syserror/BUILD | 18 | ||||
-rw-r--r-- | pkg/syserror/syserror.go | 151 | ||||
-rw-r--r-- | pkg/syserror/syserror_test.go | 136 |
3 files changed, 305 insertions, 0 deletions
diff --git a/pkg/syserror/BUILD b/pkg/syserror/BUILD new file mode 100644 index 000000000..68ddec786 --- /dev/null +++ b/pkg/syserror/BUILD @@ -0,0 +1,18 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "syserror", + srcs = ["syserror.go"], + importpath = "gvisor.googlesource.com/gvisor/pkg/syserror", + visibility = ["//visibility:public"], +) + +go_test( + name = "syserror_test", + srcs = ["syserror_test.go"], + deps = [ + ":syserror", + ], +) diff --git a/pkg/syserror/syserror.go b/pkg/syserror/syserror.go new file mode 100644 index 000000000..6f8a7a319 --- /dev/null +++ b/pkg/syserror/syserror.go @@ -0,0 +1,151 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package syserror contains syscall error codes exported as error interface +// instead of Errno. This allows for fast comparison and returns when the +// comparand or return value is of type error because there is no need to +// convert from Errno to an interface, i.e., runtime.convT2I isn't called. +package syserror + +import ( + "errors" + "syscall" +) + +// The following variables have the same meaning as their syscall equivalent. +var ( + E2BIG = error(syscall.E2BIG) + EACCES = error(syscall.EACCES) + EAGAIN = error(syscall.EAGAIN) + EBADF = error(syscall.EBADF) + EBUSY = error(syscall.EBUSY) + ECHILD = error(syscall.ECHILD) + ECONNREFUSED = error(syscall.ECONNREFUSED) + ECONNRESET = error(syscall.ECONNRESET) + EEXIST = error(syscall.EEXIST) + EFAULT = error(syscall.EFAULT) + EFBIG = error(syscall.EFBIG) + EIDRM = error(syscall.EIDRM) + EINTR = error(syscall.EINTR) + EINVAL = error(syscall.EINVAL) + EIO = error(syscall.EIO) + EISDIR = error(syscall.EISDIR) + ELIBBAD = error(syscall.ELIBBAD) + ELOOP = error(syscall.ELOOP) + EMFILE = error(syscall.EMFILE) + ENAMETOOLONG = error(syscall.ENAMETOOLONG) + ENOATTR = ENODATA + ENODATA = error(syscall.ENODATA) + ENODEV = error(syscall.ENODEV) + ENOENT = error(syscall.ENOENT) + ENOEXEC = error(syscall.ENOEXEC) + ENOLCK = error(syscall.ENOLCK) + ENOLINK = error(syscall.ENOLINK) + ENOMEM = error(syscall.ENOMEM) + ENOSPC = error(syscall.ENOSPC) + ENOSYS = error(syscall.ENOSYS) + ENOTDIR = error(syscall.ENOTDIR) + ENOTEMPTY = error(syscall.ENOTEMPTY) + ENOTSUP = error(syscall.ENOTSUP) + ENOTTY = error(syscall.ENOTTY) + ENXIO = error(syscall.ENXIO) + EOPNOTSUPP = error(syscall.EOPNOTSUPP) + EOVERFLOW = error(syscall.EOVERFLOW) + EPERM = error(syscall.EPERM) + EPIPE = error(syscall.EPIPE) + ERANGE = error(syscall.ERANGE) + EROFS = error(syscall.EROFS) + ESPIPE = error(syscall.ESPIPE) + ESRCH = error(syscall.ESRCH) + ETIMEDOUT = error(syscall.ETIMEDOUT) + EUSERS = error(syscall.EUSERS) + EWOULDBLOCK = error(syscall.EWOULDBLOCK) + EXDEV = error(syscall.EXDEV) +) + +var ( + // ErrWouldBlock is an internal error used to indicate that an operation + // cannot be satisfied immediately, and should be retried at a later + // time, possibly when the caller has received a notification that the + // operation may be able to complete. It is used by implementations of + // the kio.File interface. + ErrWouldBlock = errors.New("request would block") + + // ErrInterrupted is returned if a request is interrupted before it can + // complete. + ErrInterrupted = errors.New("request was interrupted") + + // ErrExceedsFileSizeLimit is returned if a request would exceed the + // file's size limit. + ErrExceedsFileSizeLimit = errors.New("exceeds file size limit") +) + +// errorMap is the map used to convert generic errors into errnos. +var errorMap = map[error]syscall.Errno{} + +// errorUnwrappers is an array of unwrap functions to extract typed errors. +var errorUnwrappers = []func(error) (syscall.Errno, bool){} + +// AddErrorTranslation allows modules to populate the error map by adding their +// own translations during initialization. Returns if the error translation is +// accepted or not. A pre-existing translation will not be overwritten by the +// new translation. +func AddErrorTranslation(from error, to syscall.Errno) bool { + if _, ok := errorMap[from]; ok { + return false + } + + errorMap[from] = to + return true +} + +// AddErrorUnwrapper registers an unwrap method that can extract a concrete error +// from a typed, but not initialized, error. +func AddErrorUnwrapper(unwrap func(e error) (syscall.Errno, bool)) { + errorUnwrappers = append(errorUnwrappers, unwrap) +} + +// TranslateError translates errors to errnos, it will return false if +// the error was not registered. +func TranslateError(from error) (syscall.Errno, bool) { + err, ok := errorMap[from] + if ok { + return err, ok + } + // Try to unwrap the error if we couldn't match an error + // exactly. This might mean that a package has its own + // error type. + for _, unwrap := range errorUnwrappers { + err, ok := unwrap(from) + if ok { + return err, ok + } + } + return 0, false +} + +// ConvertIntr converts the provided error code (err) to another one (intr) if +// the first error corresponds to an interrupted operation. +func ConvertIntr(err, intr error) error { + if err == ErrInterrupted { + return intr + } + return err +} + +func init() { + AddErrorTranslation(ErrWouldBlock, syscall.EWOULDBLOCK) + AddErrorTranslation(ErrInterrupted, syscall.EINTR) + AddErrorTranslation(ErrExceedsFileSizeLimit, syscall.EFBIG) +} diff --git a/pkg/syserror/syserror_test.go b/pkg/syserror/syserror_test.go new file mode 100644 index 000000000..fb7d8d5ee --- /dev/null +++ b/pkg/syserror/syserror_test.go @@ -0,0 +1,136 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syserror_test + +import ( + "errors" + "syscall" + "testing" + + "gvisor.googlesource.com/gvisor/pkg/syserror" +) + +var globalError error + +func returnErrnoAsError() error { + return syscall.EINVAL +} + +func returnError() error { + return syserror.EINVAL +} + +func BenchmarkReturnErrnoAsError(b *testing.B) { + for i := b.N; i > 0; i-- { + returnErrnoAsError() + } +} + +func BenchmarkReturnError(b *testing.B) { + for i := b.N; i > 0; i-- { + returnError() + } +} + +func BenchmarkCompareErrno(b *testing.B) { + j := 0 + for i := b.N; i > 0; i-- { + if globalError == syscall.EINVAL { + j++ + } + } +} + +func BenchmarkCompareError(b *testing.B) { + j := 0 + for i := b.N; i > 0; i-- { + if globalError == syserror.EINVAL { + j++ + } + } +} + +func BenchmarkSwitchErrno(b *testing.B) { + j := 0 + for i := b.N; i > 0; i-- { + switch globalError { + case syscall.EINVAL: + j += 1 + case syscall.EINTR: + j += 2 + case syscall.EAGAIN: + j += 3 + } + } +} + +func BenchmarkSwitchError(b *testing.B) { + j := 0 + for i := b.N; i > 0; i-- { + switch globalError { + case syserror.EINVAL: + j += 1 + case syserror.EINTR: + j += 2 + case syserror.EAGAIN: + j += 3 + } + } +} + +type translationTestTable struct { + fn string + errIn error + syscallErrorIn syscall.Errno + expectedBool bool + expectedTranslation syscall.Errno +} + +func TestErrorTranslation(t *testing.T) { + myError := errors.New("My test error") + myError2 := errors.New("Another test error") + testTable := []translationTestTable{ + {"TranslateError", myError, 0, false, 0}, + {"TranslateError", myError2, 0, false, 0}, + {"AddErrorTranslation", myError, syscall.EAGAIN, true, 0}, + {"AddErrorTranslation", myError, syscall.EAGAIN, false, 0}, + {"AddErrorTranslation", myError, syscall.EPERM, false, 0}, + {"TranslateError", myError, 0, true, syscall.EAGAIN}, + {"TranslateError", myError2, 0, false, 0}, + {"AddErrorTranslation", myError2, syscall.EPERM, true, 0}, + {"AddErrorTranslation", myError2, syscall.EPERM, false, 0}, + {"AddErrorTranslation", myError2, syscall.EAGAIN, false, 0}, + {"TranslateError", myError, 0, true, syscall.EAGAIN}, + {"TranslateError", myError2, 0, true, syscall.EPERM}, + } + for _, tt := range testTable { + switch tt.fn { + case "TranslateError": + err, ok := syserror.TranslateError(tt.errIn) + if ok != tt.expectedBool { + t.Fatalf("%v(%v) => %v expected %v", tt.fn, tt.errIn, ok, tt.expectedBool) + } else if err != tt.expectedTranslation { + t.Fatalf("%v(%v) (error) => %v expected %v", tt.fn, tt.errIn, err, tt.expectedTranslation) + } + case "AddErrorTranslation": + ok := syserror.AddErrorTranslation(tt.errIn, tt.syscallErrorIn) + if ok != tt.expectedBool { + t.Fatalf("%v(%v) => %v expected %v", tt.fn, tt.errIn, ok, tt.expectedBool) + } + default: + t.Fatalf("Unknown function %v", tt.fn) + } + } +} |