github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/blunder/api.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  // Package blunder provides error-handling wrappers
     5  //
     6  // These wrappers allow callers to provide additional information in Go errors
     7  // while still conforming to the Go error interface.
     8  //
     9  // This package provides APIs to add errno and HTTP status information to regular Go errors.
    10  //
    11  // This package is currently implemented on top of the ansel1/merry package:
    12  //   https://github.com/ansel1/merry
    13  //
    14  //   merry comes with built-in support for adding information to errors:
    15  //    - stacktraces
    16  //    - overriding the error message
    17  //    - HTTP status codes
    18  //    - end user error messages
    19  //    - your own additional information
    20  //
    21  //   From merry godoc:
    22  //     You can add any context information to an error with `e = merry.WithValue(e, "code", 12345)`
    23  //     You can retrieve that value with `v, _ := merry.Value(e, "code").(int)`
    24  //
    25  // FUTURE: Create APIs that add error information without doing a stacktrace.
    26  //         We probably want to do this, but not for thin spike.
    27  //
    28  //         Currently, the merry package always adds a stack trace.
    29  //         However a recent change to the merry package permits disabling stacktrace.
    30  
    31  package blunder
    32  
    33  import (
    34  	"fmt"
    35  
    36  	"github.com/ansel1/merry"
    37  	"golang.org/x/sys/unix"
    38  
    39  	"github.com/swiftstack/ProxyFS/logger"
    40  )
    41  
    42  // Error constants to be used in the ProxyFS namespace.
    43  //
    44  // There are two groups of constants:
    45  //  - constants that correspond to linux/POSIX errnos as defined in errno.h
    46  //  - ProxyFS-specific constants for errors not covered in the errno space
    47  //
    48  // The linux/POSIX-related constants should be used in cases where there is a clear
    49  // mapping to these errors. Using these constants makes it easier to map errors for
    50  // use by our JSON RPC functionality.
    51  //
    52  //
    53  // NOTE: unix.Errno is used here because they are errno constants that exist in Go-land.
    54  //       This type consists of an unsigned number describing an error condition. It implements
    55  //       the error interface; we need to cast it to an int to get the errno value.
    56  //
    57  type FsError int
    58  
    59  // The following line of code is a directive to go generate that tells it to create a
    60  // file called fserror_string.go that implements the .String() method for type FsError.
    61  //go:generate stringer -type=FsError
    62  
    63  const (
    64  	// Errors that map to linux/POSIX errnos as defined in errno.h
    65  	//
    66  	NotPermError          FsError = FsError(int(unix.EPERM))        // Operation not permitted
    67  	NotFoundError         FsError = FsError(int(unix.ENOENT))       // No such file or directory
    68  	IOError               FsError = FsError(int(unix.EIO))          // I/O error
    69  	ReadOnlyError         FsError = FsError(int(unix.EROFS))        // Read-only file system
    70  	TooBigError           FsError = FsError(int(unix.E2BIG))        // Argument list too long
    71  	TooManyArgsError      FsError = FsError(int(unix.E2BIG))        // Arg list too long
    72  	BadFileError          FsError = FsError(int(unix.EBADF))        // Bad file number
    73  	TryAgainError         FsError = FsError(int(unix.EAGAIN))       // Try again
    74  	OutOfMemoryError      FsError = FsError(int(unix.ENOMEM))       // Out of memory
    75  	PermDeniedError       FsError = FsError(int(unix.EACCES))       // Permission denied
    76  	BadAddressError       FsError = FsError(int(unix.EFAULT))       // Bad address
    77  	DevBusyError          FsError = FsError(int(unix.EBUSY))        // Device or resource busy
    78  	FileExistsError       FsError = FsError(int(unix.EEXIST))       // File exists
    79  	NoDeviceError         FsError = FsError(int(unix.ENODEV))       // No such device
    80  	NotDirError           FsError = FsError(int(unix.ENOTDIR))      // Not a directory
    81  	IsDirError            FsError = FsError(int(unix.EISDIR))       // Is a directory
    82  	InvalidArgError       FsError = FsError(int(unix.EINVAL))       // Invalid argument
    83  	TableOverflowError    FsError = FsError(int(unix.ENFILE))       // File table overflow
    84  	TooManyOpenFilesError FsError = FsError(int(unix.EMFILE))       // Too many open files
    85  	FileTooLargeError     FsError = FsError(int(unix.EFBIG))        // File too large
    86  	NoSpaceError          FsError = FsError(int(unix.ENOSPC))       // No space left on device
    87  	BadSeekError          FsError = FsError(int(unix.ESPIPE))       // Illegal seek
    88  	TooManyLinksError     FsError = FsError(int(unix.EMLINK))       // Too many links
    89  	OutOfRangeError       FsError = FsError(int(unix.ERANGE))       // Math result not representable
    90  	NameTooLongError      FsError = FsError(int(unix.ENAMETOOLONG)) // File name too long
    91  	NoLocksError          FsError = FsError(int(unix.ENOLCK))       // No record locks available
    92  	NotImplementedError   FsError = FsError(int(unix.ENOSYS))       // Function not implemented
    93  	NotEmptyError         FsError = FsError(int(unix.ENOTEMPTY))    // Directory not empty
    94  	TooManySymlinksError  FsError = FsError(int(unix.ELOOP))        // Too many symbolic links encountered
    95  	NotSupportedError     FsError = FsError(int(unix.ENOTSUP))      // Operation not supported
    96  	NoDataError           FsError = FsError(int(unix.ENODATA))      // No data available
    97  	TimedOut              FsError = FsError(int(unix.ETIMEDOUT))    // Connection Timed Out
    98  )
    99  
   100  // Errors that map to constants already defined above
   101  const (
   102  	NotActiveError        FsError = NotFoundError
   103  	BadLeaseRequest       FsError = InvalidArgError
   104  	BadMountIDError       FsError = InvalidArgError
   105  	BadMountVolumeError   FsError = InvalidArgError
   106  	NotFileError          FsError = IsDirError
   107  	SegNumNotIntError     FsError = IOError
   108  	SegNotFoundError      FsError = IOError
   109  	SegReadError          FsError = IOError
   110  	InodeFlushError       FsError = IOError
   111  	BtreeDeleteError      FsError = IOError
   112  	BtreePutError         FsError = IOError
   113  	BtreeLenError         FsError = IOError
   114  	FileWriteError        FsError = IOError
   115  	GetMetadataError      FsError = IOError
   116  	NotSymlinkError       FsError = InvalidArgError
   117  	IsSymlinkError        FsError = InvalidArgError
   118  	LinkDirError          FsError = NotPermError
   119  	BadHTTPDeleteError    FsError = IOError
   120  	BadHTTPGetError       FsError = IOError
   121  	BadHTTPHeadError      FsError = IOError
   122  	BadHTTPPutError       FsError = IOError
   123  	InvalidInodeTypeError FsError = InvalidArgError
   124  	InvalidFileModeError  FsError = InvalidArgError
   125  	InvalidUserIDError    FsError = InvalidArgError
   126  	InvalidGroupIDError   FsError = InvalidArgError
   127  	StreamNotFound        FsError = NoDataError
   128  	AccountNotModifiable  FsError = NotPermError
   129  	OldMetaDataDifferent  FsError = TryAgainError
   130  )
   131  
   132  // Success error (sounds odd, no? - perhaps this could be renamed "NotAnError"?)
   133  const SuccessError FsError = 0
   134  
   135  const ( // reset iota to 0
   136  	// Errors that are internal/specific to ProxyFS
   137  	UnpackError FsError = 1000 + iota
   138  	PackError
   139  	CorruptInodeError
   140  	NotAnObjectError
   141  )
   142  
   143  // Default errno values for success and failure
   144  const successErrno = 0
   145  const failureErrno = -1
   146  
   147  // Value returns the int value for the specified FsError constant
   148  func (err FsError) Value() int {
   149  	return int(err)
   150  }
   151  
   152  // NewError creates a new merry/blunder.FsError-annotated error using the given
   153  // format string and arguments.
   154  func NewError(errValue FsError, format string, a ...interface{}) error {
   155  	return merry.WrapSkipping(fmt.Errorf(format, a...), 1).WithValue("errno", int(errValue))
   156  }
   157  
   158  // AddError is used to add FS error detail to a Go error.
   159  //
   160  // NOTE: Checks whether the error value has already been set
   161  //       Note that by default merry will replace the old with the new.
   162  //
   163  func AddError(e error, errValue FsError) error {
   164  	if e == nil {
   165  		// Error hasn't been allocated yet; need to create one
   166  		//
   167  		// Usually we wouldn't want to mess with a nil error, but the caller of
   168  		// this function obviously intends to make this a non-nil error.
   169  		//
   170  		// It's recommended that the caller create an error with some context
   171  		// in the error string first, but we don't want to silently not work
   172  		// if they forget to do that.
   173  		//
   174  		return merry.New("regular error").WithValue("errno", int(errValue))
   175  	}
   176  
   177  	// Make the error "merry", adding stack trace as well as errno value.
   178  	// This is done all in one line because the merry APIs create a new error each time.
   179  
   180  	// For now, check and log if an errno has already been added to
   181  	// this error, to help debugging in the cases where this was not intentional.
   182  	prevValue := Errno(e)
   183  	if prevValue != successErrno && prevValue != failureErrno {
   184  		logger.Warnf("replacing error value %v with value %v for error %v.\n", prevValue, int(errValue), e)
   185  	}
   186  
   187  	return merry.WrapSkipping(e, 1).WithValue("errno", int(errValue))
   188  }
   189  
   190  func hasErrnoValue(e error) bool {
   191  	// If the "errno" key/value was not present, merry.Value returns nil.
   192  	tmp := merry.Value(e, "errno")
   193  	if tmp != nil {
   194  		return true
   195  	}
   196  
   197  	return false
   198  }
   199  
   200  func AddHTTPCode(e error, statusCode int) error {
   201  	if e == nil {
   202  		// Error hasn't been allocated yet; need to create one
   203  		//
   204  		// Usually we wouldn't want to mess with a nil error, but the caller of
   205  		// this function obviously intends to make this a non-nil error.
   206  		//
   207  		// It's recommended that the caller create an error with some context
   208  		// in the error string first, but we don't want to silently not work
   209  		// if they forget to do that.
   210  		//
   211  		return merry.New("HTTP error").WithHTTPCode(statusCode)
   212  	}
   213  
   214  	// Make the error "merry", adding stack trace as well as errno value.
   215  	// This is done all in one line because the merry APIs create a new error each time.
   216  	return merry.WrapSkipping(e, 1).WithHTTPCode(statusCode)
   217  }
   218  
   219  // Errno extracts errno from the error, if it was previously wrapped.
   220  // Otherwise a default value is returned.
   221  //
   222  func Errno(e error) int {
   223  	if e == nil {
   224  		// nil error = success
   225  		return successErrno
   226  	}
   227  
   228  	// If the "errno" key/value was not present, merry.Value returns nil.
   229  	var errno = failureErrno
   230  	tmp := merry.Value(e, "errno")
   231  	if tmp != nil {
   232  		errno = tmp.(int)
   233  	}
   234  
   235  	return errno
   236  }
   237  
   238  func ErrorString(e error) string {
   239  	if e == nil {
   240  		return ""
   241  	}
   242  
   243  	// Get the regular error string
   244  	errPlusVal := e.Error()
   245  
   246  	// Add the error value to it, if set
   247  	var errno = failureErrno
   248  	tmp := merry.Value(e, "errno")
   249  	if tmp != nil {
   250  		errno = tmp.(int)
   251  		errPlusVal = fmt.Sprintf("%s. Error Value: %v\n", errPlusVal, errno)
   252  	}
   253  
   254  	return errPlusVal
   255  }
   256  
   257  // Check if an error matches a particular FsError
   258  //
   259  // NOTE: Because the value of the underlying errno is used to do this check, one cannot
   260  //       use this API to distinguish between FsErrors that use the same errno value.
   261  //       IOW, it can't tell the difference between InvalidFileModeError/BadMountIDError/InvalidArgError,
   262  //       since they all use unix.EINVAL as their underlying errno value.
   263  //
   264  func Is(e error, theError FsError) bool {
   265  	return Errno(e) == theError.Value()
   266  }
   267  
   268  // Check if an error is NOT a particular FsError
   269  func IsNot(e error, theError FsError) bool {
   270  	return Errno(e) != theError.Value()
   271  }
   272  
   273  // Check if an error is the success FsError
   274  func IsSuccess(e error) bool {
   275  	return Errno(e) == successErrno
   276  }
   277  
   278  // Check if an error is NOT the success FsError
   279  func IsNotSuccess(e error) bool {
   280  	return Errno(e) != successErrno
   281  }
   282  
   283  func ErrorUpdate(e error, currentVal FsError, changeToVal FsError) error {
   284  	errVal := Errno(e)
   285  
   286  	if errVal == int(currentVal) {
   287  		fmt.Printf("blunder.ErrorUpdate: errVal was %d, changing to %d.\n", errVal, int(changeToVal))
   288  		// Change to the new value
   289  		return merry.Wrap(e).WithValue("errno", int(changeToVal))
   290  	}
   291  
   292  	return e
   293  }
   294  
   295  // HTTPCode wraps merry.HTTPCode, which returns the HTTP status code. Default value is 500.
   296  func HTTPCode(e error) int {
   297  	return merry.HTTPCode(e)
   298  }
   299  
   300  // Location returns the file and line number of the code that generated the error.
   301  // Returns zero values if e has no stacktrace.
   302  func Location(e error) (file string, line int) {
   303  	file, line = merry.Location(e)
   304  	return
   305  }
   306  
   307  // SourceLine returns the string representation of Location's result
   308  // Returns empty stringif e has no stacktrace.
   309  func SourceLine(e error) string {
   310  	return merry.SourceLine(e)
   311  }
   312  
   313  // Details wraps merry.Details, which returns all error details including stacktrace in a string.
   314  func Details(e error) string {
   315  	return merry.Details(e)
   316  }
   317  
   318  // Stacktrace wraps merry.Stacktrace, which returns error stacktrace (if set) in a string.
   319  func Stacktrace(e error) string {
   320  	return merry.Stacktrace(e)
   321  }