// 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 syserr contains sandbox-internal errors. These errors are distinct
// from both the errors returned by host system calls and the errors returned
// to sandboxed applications.
package syserr

import (
	"fmt"
	"syscall"

	"gvisor.googlesource.com/gvisor/pkg/abi/linux"
	"gvisor.googlesource.com/gvisor/pkg/syserror"
)

// Error represents an internal error.
type Error struct {
	string
}

// New creates a new Error and adds a translation for it.
//
// New must only be called at init.
func New(message string, linuxTranslation *linux.Errno) *Error {
	err := &Error{message}
	linuxABITranslations[err] = linuxTranslation

	// TODO: Remove this.
	if linuxTranslation == nil {
		linuxBackwardsTranslations[err] = nil
	} else {
		e := error(syscall.Errno(linuxTranslation.Number()))
		// syserror.ErrWouldBlock gets translated to syserror.EWOULDBLOCK and
		// enables proper blocking semantics. This should temporary address the
		// class of blocking bugs that keep popping up with the current state of
		// the error space.
		if e == syserror.EWOULDBLOCK {
			e = syserror.ErrWouldBlock
		}
		linuxBackwardsTranslations[err] = e
	}

	return err
}

// NewWithoutTranslation creates a new Error. If translation is attempted on
// the error, translation will fail.
func NewWithoutTranslation(message string) *Error {
	return &Error{message}
}

// String implements fmt.Stringer.String.
func (e *Error) String() string {
	if e == nil {
		return "<nil>"
	}
	return e.string
}

// TODO: Remove this.
var linuxBackwardsTranslations = map[*Error]error{}

// ToError translates an Error to a corresponding error value.
//
// TODO: Remove this.
func (e *Error) ToError() error {
	if e == nil {
		return nil
	}
	err, ok := linuxBackwardsTranslations[e]
	if !ok {
		panic(fmt.Sprintf("unknown error: %q", e.string))
	}
	return err
}

// TODO: Remove or replace most of these errors.
//
// Some of the errors should be replaced with package specific errors and
// others should be removed entirely.
var (
	ErrNotPermitted               = New("operation not permitted", linux.EPERM)
	ErrNoFileOrDir                = New("no such file or directory", linux.ENOENT)
	ErrNoProcess                  = New("no such process", linux.ESRCH)
	ErrInterrupted                = New("interrupted system call", linux.EINTR)
	ErrIO                         = New("I/O error", linux.EIO)
	ErrDeviceOrAddress            = New("no such device or address", linux.ENXIO)
	ErrTooManyArgs                = New("argument list too long", linux.E2BIG)
	ErrEcec                       = New("exec format error", linux.ENOEXEC)
	ErrBadFD                      = New("bad file number", linux.EBADF)
	ErrNoChild                    = New("no child processes", linux.ECHILD)
	ErrTryAgain                   = New("try again", linux.EAGAIN)
	ErrNoMemory                   = New("out of memory", linux.ENOMEM)
	ErrPermissionDenied           = New("permission denied", linux.EACCES)
	ErrBadAddress                 = New("bad address", linux.EFAULT)
	ErrNotBlockDevice             = New("block device required", linux.ENOTBLK)
	ErrBusy                       = New("device or resource busy", linux.EBUSY)
	ErrExists                     = New("file exists", linux.EEXIST)
	ErrCrossDeviceLink            = New("cross-device link", linux.EXDEV)
	ErrNoDevice                   = New("no such device", linux.ENODEV)
	ErrNotDir                     = New("not a directory", linux.ENOTDIR)
	ErrIsDir                      = New("is a directory", linux.EISDIR)
	ErrInvalidArgument            = New("invalid argument", linux.EINVAL)
	ErrFileTableOverflow          = New("file table overflow", linux.ENFILE)
	ErrTooManyOpenFiles           = New("too many open files", linux.EMFILE)
	ErrNotTTY                     = New("not a typewriter", linux.ENOTTY)
	ErrTestFileBusy               = New("text file busy", linux.ETXTBSY)
	ErrFileTooBig                 = New("file too large", linux.EFBIG)
	ErrNoSpace                    = New("no space left on device", linux.ENOSPC)
	ErrIllegalSeek                = New("illegal seek", linux.ESPIPE)
	ErrReadOnlyFS                 = New("read-only file system", linux.EROFS)
	ErrTooManyLinks               = New("too many links", linux.EMLINK)
	ErrBrokenPipe                 = New("broken pipe", linux.EPIPE)
	ErrDomain                     = New("math argument out of domain of func", linux.EDOM)
	ErrRange                      = New("math result not representable", linux.ERANGE)
	ErrDeadlock                   = New("resource deadlock would occur", linux.EDEADLOCK)
	ErrNameTooLong                = New("file name too long", linux.ENAMETOOLONG)
	ErrNoLocksAvailable           = New("no record locks available", linux.ENOLCK)
	ErrInvalidSyscall             = New("invalid system call number", linux.ENOSYS)
	ErrDirNotEmpty                = New("directory not empty", linux.ENOTEMPTY)
	ErrLinkLoop                   = New("too many symbolic links encountered", linux.ELOOP)
	ErrWouldBlock                 = New("operation would block", linux.EWOULDBLOCK)
	ErrNoMessage                  = New("no message of desired type", linux.ENOMSG)
	ErrIdentifierRemoved          = New("identifier removed", linux.EIDRM)
	ErrChannelOutOfRange          = New("channel number out of range", linux.ECHRNG)
	ErrLevelTwoNotSynced          = New("level 2 not synchronized", linux.EL2NSYNC)
	ErrLevelThreeHalted           = New("level 3 halted", linux.EL3HLT)
	ErrLevelThreeReset            = New("level 3 reset", linux.EL3RST)
	ErrLinkNumberOutOfRange       = New("link number out of range", linux.ELNRNG)
	ErrProtocolDriverNotAttached  = New("protocol driver not attached", linux.EUNATCH)
	ErrNoCSIAvailable             = New("no CSI structure available", linux.ENOCSI)
	ErrLevelTwoHalted             = New("level 2 halted", linux.EL2HLT)
	ErrInvalidExchange            = New("invalid exchange", linux.EBADE)
	ErrInvalidRequestDescriptor   = New("invalid request descriptor", linux.EBADR)
	ErrExchangeFull               = New("exchange full", linux.EXFULL)
	ErrNoAnode                    = New("no anode", linux.ENOANO)
	ErrInvalidRequestCode         = New("invalid request code", linux.EBADRQC)
	ErrInvalidSlot                = New("invalid slot", linux.EBADSLT)
	ErrBadFontFile                = New("bad font file format", linux.EBFONT)
	ErrNotStream                  = New("device not a stream", linux.ENOSTR)
	ErrNoDataAvailable            = New("no data available", linux.ENODATA)
	ErrTimerExpired               = New("timer expired", linux.ETIME)
	ErrStreamsResourceDepleted    = New("out of streams resources", linux.ENOSR)
	ErrMachineNotOnNetwork        = New("machine is not on the network", linux.ENONET)
	ErrPackageNotInstalled        = New("package not installed", linux.ENOPKG)
	ErrIsRemote                   = New("object is remote", linux.EREMOTE)
	ErrNoLink                     = New("link has been severed", linux.ENOLINK)
	ErrAdvertise                  = New("advertise error", linux.EADV)
	ErrSRMount                    = New("srmount error", linux.ESRMNT)
	ErrSendCommunication          = New("communication error on send", linux.ECOMM)
	ErrProtocol                   = New("protocol error", linux.EPROTO)
	ErrMultihopAttempted          = New("multihop attempted", linux.EMULTIHOP)
	ErrRFS                        = New("RFS specific error", linux.EDOTDOT)
	ErrInvalidDataMessage         = New("not a data message", linux.EBADMSG)
	ErrOverflow                   = New("value too large for defined data type", linux.EOVERFLOW)
	ErrNetworkNameNotUnique       = New("name not unique on network", linux.ENOTUNIQ)
	ErrFDInBadState               = New("file descriptor in bad state", linux.EBADFD)
	ErrRemoteAddressChanged       = New("remote address changed", linux.EREMCHG)
	ErrSharedLibraryInaccessible  = New("can not access a needed shared library", linux.ELIBACC)
	ErrCorruptedSharedLibrary     = New("accessing a corrupted shared library", linux.ELIBBAD)
	ErrLibSectionCorrupted        = New(".lib section in a.out corrupted", linux.ELIBSCN)
	ErrTooManySharedLibraries     = New("attempting to link in too many shared libraries", linux.ELIBMAX)
	ErrSharedLibraryExeced        = New("cannot exec a shared library directly", linux.ELIBEXEC)
	ErrIllegalByteSequence        = New("illegal byte sequence", linux.EILSEQ)
	ErrShouldRestart              = New("interrupted system call should be restarted", linux.ERESTART)
	ErrStreamPipe                 = New("streams pipe error", linux.ESTRPIPE)
	ErrTooManyUsers               = New("too many users", linux.EUSERS)
	ErrNotASocket                 = New("socket operation on non-socket", linux.ENOTSOCK)
	ErrDestinationAddressRequired = New("destination address required", linux.EDESTADDRREQ)
	ErrMessageTooLong             = New("message too long", linux.EMSGSIZE)
	ErrWrongProtocolForSocket     = New("protocol wrong type for socket", linux.EPROTOTYPE)
	ErrProtocolNotAvailable       = New("protocol not available", linux.ENOPROTOOPT)
	ErrProtocolNotSupported       = New("protocol not supported", linux.EPROTONOSUPPORT)
	ErrSocketNotSupported         = New("socket type not supported", linux.ESOCKTNOSUPPORT)
	ErrEndpointOperation          = New("operation not supported on transport endpoint", linux.EOPNOTSUPP)
	ErrProtocolFamilyNotSupported = New("protocol family not supported", linux.EPFNOSUPPORT)
	ErrAddressFamilyNotSupported  = New("address family not supported by protocol", linux.EAFNOSUPPORT)
	ErrAddressInUse               = New("address already in use", linux.EADDRINUSE)
	ErrAddressNotAvailable        = New("cannot assign requested address", linux.EADDRNOTAVAIL)
	ErrNetworkDown                = New("network is down", linux.ENETDOWN)
	ErrNetworkUnreachable         = New("network is unreachable", linux.ENETUNREACH)
	ErrNetworkReset               = New("network dropped connection because of reset", linux.ENETRESET)
	ErrConnectionAborted          = New("software caused connection abort", linux.ECONNABORTED)
	ErrConnectionReset            = New("connection reset by peer", linux.ECONNRESET)
	ErrNoBufferSpace              = New("no buffer space available", linux.ENOBUFS)
	ErrAlreadyConnected           = New("transport endpoint is already connected", linux.EISCONN)
	ErrNotConnected               = New("transport endpoint is not connected", linux.ENOTCONN)
	ErrShutdown                   = New("cannot send after transport endpoint shutdown", linux.ESHUTDOWN)
	ErrTooManyRefs                = New("too many references: cannot splice", linux.ETOOMANYREFS)
	ErrTimedOut                   = New("connection timed out", linux.ETIMEDOUT)
	ErrConnectionRefused          = New("connection refused", linux.ECONNREFUSED)
	ErrHostDown                   = New("host is down", linux.EHOSTDOWN)
	ErrNoRoute                    = New("no route to host", linux.EHOSTUNREACH)
	ErrAlreadyInProgress          = New("operation already in progress", linux.EALREADY)
	ErrInProgress                 = New("operation now in progress", linux.EINPROGRESS)
	ErrStaleFileHandle            = New("stale file handle", linux.ESTALE)
	ErrStructureNeedsCleaning     = New("structure needs cleaning", linux.EUCLEAN)
	ErrIsNamedFile                = New("is a named type file", linux.ENOTNAM)
	ErrRemoteIO                   = New("remote I/O error", linux.EREMOTEIO)
	ErrQuotaExceeded              = New("quota exceeded", linux.EDQUOT)
	ErrNoMedium                   = New("no medium found", linux.ENOMEDIUM)
	ErrWrongMediumType            = New("wrong medium type", linux.EMEDIUMTYPE)
	ErrCanceled                   = New("operation Canceled", linux.ECANCELED)
	ErrNoKey                      = New("required key not available", linux.ENOKEY)
	ErrKeyExpired                 = New("key has expired", linux.EKEYEXPIRED)
	ErrKeyRevoked                 = New("key has been revoked", linux.EKEYREVOKED)
	ErrKeyRejected                = New("key was rejected by service", linux.EKEYREJECTED)
	ErrOwnerDied                  = New("owner died", linux.EOWNERDEAD)
	ErrNotRecoverable             = New("state not recoverable", linux.ENOTRECOVERABLE)
)

// FromError converts a generic error to an *Error.
//
// TODO: Remove this function.
func FromError(err error) *Error {
	if err == nil {
		return nil
	}
	if errno, ok := err.(syscall.Errno); ok {
		return FromHost(errno)
	}
	if errno, ok := syserror.TranslateError(err); ok {
		return FromHost(errno)
	}
	panic("unknown error: " + err.Error())
}