github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/blunder/api.go (about)

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