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 }