github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/errorfs/errorfs.go (about) 1 // Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package errorfs 6 7 import ( 8 "fmt" 9 "io" 10 "math/rand" 11 "os" 12 "sync" 13 "sync/atomic" 14 "time" 15 16 "github.com/cockroachdb/errors" 17 "github.com/cockroachdb/errors/oserror" 18 "github.com/cockroachdb/pebble/vfs" 19 ) 20 21 // ErrInjected is an error artificially injected for testing fs error paths. 22 var ErrInjected = errors.New("injected error") 23 24 // Op is an enum describing the type of operation. 25 type Op int 26 27 const ( 28 // OpCreate describes a create file operation. 29 OpCreate Op = iota 30 // OpLink describes a hardlink operation. 31 OpLink 32 // OpOpen describes a file open operation. 33 OpOpen 34 // OpOpenDir describes a directory open operation. 35 OpOpenDir 36 // OpRemove describes a remove file operation. 37 OpRemove 38 // OpRemoveAll describes a recursive remove operation. 39 OpRemoveAll 40 // OpRename describes a rename operation. 41 OpRename 42 // OpReuseForRewrite describes a reuse for rewriting operation. 43 OpReuseForRewrite 44 // OpMkdirAll describes a make directory including parents operation. 45 OpMkdirAll 46 // OpLock describes a lock file operation. 47 OpLock 48 // OpList describes a list directory operation. 49 OpList 50 // OpFilePreallocate describes a file preallocate operation. 51 OpFilePreallocate 52 // OpStat describes a path-based stat operation. 53 OpStat 54 // OpGetDiskUsage describes a disk usage operation. 55 OpGetDiskUsage 56 // OpFileClose describes a close file operation. 57 OpFileClose 58 // OpFileRead describes a file read operation. 59 OpFileRead 60 // OpFileReadAt describes a file seek read operation. 61 OpFileReadAt 62 // OpFileWrite describes a file write operation. 63 OpFileWrite 64 // OpFileWriteAt describes a file seek write operation. 65 OpFileWriteAt 66 // OpFileStat describes a file stat operation. 67 OpFileStat 68 // OpFileSync describes a file sync operation. 69 OpFileSync 70 // OpFileFlush describes a file flush operation. 71 OpFileFlush 72 ) 73 74 // OpKind returns the operation's kind. 75 func (o Op) OpKind() OpKind { 76 switch o { 77 case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat: 78 return OpKindRead 79 case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForRewrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileFlush, OpFilePreallocate: 80 return OpKindWrite 81 default: 82 panic(fmt.Sprintf("unrecognized op %v\n", o)) 83 } 84 } 85 86 // OpKind is an enum describing whether an operation is a read or write 87 // operation. 88 type OpKind int 89 90 const ( 91 // OpKindRead describes read operations. 92 OpKindRead OpKind = iota 93 // OpKindWrite describes write operations. 94 OpKindWrite 95 ) 96 97 // OnIndex constructs an injector that returns an error on 98 // the (n+1)-th invocation of its MaybeError function. It 99 // may be passed to Wrap to inject an error into an FS. 100 func OnIndex(index int32) *InjectIndex { 101 ii := &InjectIndex{} 102 ii.index.Store(index) 103 return ii 104 } 105 106 // InjectIndex implements Injector, injecting an error at a specific index. 107 type InjectIndex struct { 108 index atomic.Int32 109 } 110 111 // Index returns the index at which the error will be injected. 112 func (ii *InjectIndex) Index() int32 { return ii.index.Load() } 113 114 // SetIndex sets the index at which the error will be injected. 115 func (ii *InjectIndex) SetIndex(v int32) { ii.index.Store(v) } 116 117 // MaybeError implements the Injector interface. 118 func (ii *InjectIndex) MaybeError(_ Op, _ string) error { 119 if ii.index.Add(-1) == -1 { 120 return errors.WithStack(ErrInjected) 121 } 122 return nil 123 } 124 125 // WithProbability returns a function that returns an error with the provided 126 // probability when passed op. It may be passed to Wrap to inject an error 127 // into an ErrFS with the provided probability. p should be within the range 128 // [0.0,1.0]. 129 func WithProbability(op OpKind, p float64) Injector { 130 mu := new(sync.Mutex) 131 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 132 return InjectorFunc(func(currOp Op, _ string) error { 133 mu.Lock() 134 defer mu.Unlock() 135 if currOp.OpKind() == op && rnd.Float64() < p { 136 return errors.WithStack(ErrInjected) 137 } 138 return nil 139 }) 140 } 141 142 // InjectorFunc implements the Injector interface for a function with 143 // MaybeError's signature. 144 type InjectorFunc func(Op, string) error 145 146 // MaybeError implements the Injector interface. 147 func (f InjectorFunc) MaybeError(op Op, path string) error { return f(op, path) } 148 149 // Injector injects errors into FS operations. 150 type Injector interface { 151 // MaybeError is invoked by an errorfs before an operation is executed. It 152 // is passed an enum indicating the type of operation and a path of the 153 // subject file or directory. If the operation takes two paths (eg, 154 // Rename, Link), the original source path is provided. 155 MaybeError(op Op, path string) error 156 } 157 158 // FS implements vfs.FS, injecting errors into 159 // its operations. 160 type FS struct { 161 fs vfs.FS 162 inj Injector 163 } 164 165 // Wrap wraps an existing vfs.FS implementation, returning a new 166 // vfs.FS implementation that shadows operations to the provided FS. 167 // It uses the provided Injector for deciding when to inject errors. 168 // If an error is injected, FS propagates the error instead of 169 // shadowing the operation. 170 func Wrap(fs vfs.FS, inj Injector) *FS { 171 return &FS{ 172 fs: fs, 173 inj: inj, 174 } 175 } 176 177 // WrapFile wraps an existing vfs.File, returning a new vfs.File that shadows 178 // operations to the provided vfs.File. It uses the provided Injector for 179 // deciding when to inject errors. If an error is injected, the file 180 // propagates the error instead of shadowing the operation. 181 func WrapFile(f vfs.File, inj Injector) vfs.File { 182 return &errorFile{file: f, inj: inj} 183 } 184 185 // Unwrap returns the FS implementation underlying fs. 186 // See pebble/vfs.Root. 187 func (fs *FS) Unwrap() vfs.FS { 188 return fs.fs 189 } 190 191 // Create implements FS.Create. 192 func (fs *FS) Create(name string) (vfs.File, error) { 193 if err := fs.inj.MaybeError(OpCreate, name); err != nil { 194 return nil, err 195 } 196 f, err := fs.fs.Create(name) 197 if err != nil { 198 return nil, err 199 } 200 return &errorFile{name, f, fs.inj}, nil 201 } 202 203 // Link implements FS.Link. 204 func (fs *FS) Link(oldname, newname string) error { 205 if err := fs.inj.MaybeError(OpLink, oldname); err != nil { 206 return err 207 } 208 return fs.fs.Link(oldname, newname) 209 } 210 211 // Open implements FS.Open. 212 func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) { 213 if err := fs.inj.MaybeError(OpOpen, name); err != nil { 214 return nil, err 215 } 216 f, err := fs.fs.Open(name) 217 if err != nil { 218 return nil, err 219 } 220 ef := &errorFile{name, f, fs.inj} 221 for _, opt := range opts { 222 opt.Apply(ef) 223 } 224 return ef, nil 225 } 226 227 // OpenReadWrite implements FS.OpenReadWrite. 228 func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) { 229 if err := fs.inj.MaybeError(OpOpen, name); err != nil { 230 return nil, err 231 } 232 f, err := fs.fs.OpenReadWrite(name) 233 if err != nil { 234 return nil, err 235 } 236 ef := &errorFile{name, f, fs.inj} 237 for _, opt := range opts { 238 opt.Apply(ef) 239 } 240 return ef, nil 241 } 242 243 // OpenDir implements FS.OpenDir. 244 func (fs *FS) OpenDir(name string) (vfs.File, error) { 245 if err := fs.inj.MaybeError(OpOpenDir, name); err != nil { 246 return nil, err 247 } 248 f, err := fs.fs.OpenDir(name) 249 if err != nil { 250 return nil, err 251 } 252 return &errorFile{name, f, fs.inj}, nil 253 } 254 255 // GetDiskUsage implements FS.GetDiskUsage. 256 func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) { 257 if err := fs.inj.MaybeError(OpGetDiskUsage, path); err != nil { 258 return vfs.DiskUsage{}, err 259 } 260 return fs.fs.GetDiskUsage(path) 261 } 262 263 // PathBase implements FS.PathBase. 264 func (fs *FS) PathBase(p string) string { 265 return fs.fs.PathBase(p) 266 } 267 268 // PathDir implements FS.PathDir. 269 func (fs *FS) PathDir(p string) string { 270 return fs.fs.PathDir(p) 271 } 272 273 // PathJoin implements FS.PathJoin. 274 func (fs *FS) PathJoin(elem ...string) string { 275 return fs.fs.PathJoin(elem...) 276 } 277 278 // Remove implements FS.Remove. 279 func (fs *FS) Remove(name string) error { 280 if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) { 281 return nil 282 } 283 284 if err := fs.inj.MaybeError(OpRemove, name); err != nil { 285 return err 286 } 287 return fs.fs.Remove(name) 288 } 289 290 // RemoveAll implements FS.RemoveAll. 291 func (fs *FS) RemoveAll(fullname string) error { 292 if err := fs.inj.MaybeError(OpRemoveAll, fullname); err != nil { 293 return err 294 } 295 return fs.fs.RemoveAll(fullname) 296 } 297 298 // Rename implements FS.Rename. 299 func (fs *FS) Rename(oldname, newname string) error { 300 if err := fs.inj.MaybeError(OpRename, oldname); err != nil { 301 return err 302 } 303 return fs.fs.Rename(oldname, newname) 304 } 305 306 // ReuseForWrite implements FS.ReuseForWrite. 307 func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) { 308 if err := fs.inj.MaybeError(OpReuseForRewrite, oldname); err != nil { 309 return nil, err 310 } 311 return fs.fs.ReuseForWrite(oldname, newname) 312 } 313 314 // MkdirAll implements FS.MkdirAll. 315 func (fs *FS) MkdirAll(dir string, perm os.FileMode) error { 316 if err := fs.inj.MaybeError(OpMkdirAll, dir); err != nil { 317 return err 318 } 319 return fs.fs.MkdirAll(dir, perm) 320 } 321 322 // Lock implements FS.Lock. 323 func (fs *FS) Lock(name string) (io.Closer, error) { 324 if err := fs.inj.MaybeError(OpLock, name); err != nil { 325 return nil, err 326 } 327 return fs.fs.Lock(name) 328 } 329 330 // List implements FS.List. 331 func (fs *FS) List(dir string) ([]string, error) { 332 if err := fs.inj.MaybeError(OpList, dir); err != nil { 333 return nil, err 334 } 335 return fs.fs.List(dir) 336 } 337 338 // Stat implements FS.Stat. 339 func (fs *FS) Stat(name string) (os.FileInfo, error) { 340 if err := fs.inj.MaybeError(OpStat, name); err != nil { 341 return nil, err 342 } 343 return fs.fs.Stat(name) 344 } 345 346 // errorFile implements vfs.File. The interface is implemented on the pointer 347 // type to allow pointer equality comparisons. 348 type errorFile struct { 349 path string 350 file vfs.File 351 inj Injector 352 } 353 354 func (f *errorFile) Close() error { 355 // We don't inject errors during close as those calls should never fail in 356 // practice. 357 return f.file.Close() 358 } 359 360 func (f *errorFile) Read(p []byte) (int, error) { 361 if err := f.inj.MaybeError(OpFileRead, f.path); err != nil { 362 return 0, err 363 } 364 return f.file.Read(p) 365 } 366 367 func (f *errorFile) ReadAt(p []byte, off int64) (int, error) { 368 if err := f.inj.MaybeError(OpFileReadAt, f.path); err != nil { 369 return 0, err 370 } 371 return f.file.ReadAt(p, off) 372 } 373 374 func (f *errorFile) Write(p []byte) (int, error) { 375 if err := f.inj.MaybeError(OpFileWrite, f.path); err != nil { 376 return 0, err 377 } 378 return f.file.Write(p) 379 } 380 381 func (f *errorFile) WriteAt(p []byte, ofs int64) (int, error) { 382 if err := f.inj.MaybeError(OpFileWriteAt, f.path); err != nil { 383 return 0, err 384 } 385 return f.file.WriteAt(p, ofs) 386 } 387 388 func (f *errorFile) Stat() (os.FileInfo, error) { 389 if err := f.inj.MaybeError(OpFileStat, f.path); err != nil { 390 return nil, err 391 } 392 return f.file.Stat() 393 } 394 395 func (f *errorFile) Prefetch(offset, length int64) error { 396 // TODO(radu): Consider error injection. 397 return f.file.Prefetch(offset, length) 398 } 399 400 func (f *errorFile) Preallocate(offset, length int64) error { 401 if err := f.inj.MaybeError(OpFilePreallocate, f.path); err != nil { 402 return err 403 } 404 return f.file.Preallocate(offset, length) 405 } 406 407 func (f *errorFile) Sync() error { 408 if err := f.inj.MaybeError(OpFileSync, f.path); err != nil { 409 return err 410 } 411 return f.file.Sync() 412 } 413 414 func (f *errorFile) SyncData() error { 415 // TODO(jackson): Consider error injection. 416 return f.file.SyncData() 417 } 418 419 func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) { 420 // TODO(jackson): Consider error injection. 421 return f.file.SyncTo(length) 422 } 423 424 func (f *errorFile) Fd() uintptr { 425 return f.file.Fd() 426 }