github.com/rohankumardubey/proxyfs@v0.0.0-20210108201508-653efa9ab00e/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 }