github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/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 "os" 11 "strings" 12 "sync/atomic" 13 14 "github.com/cockroachdb/errors" 15 "github.com/cockroachdb/errors/oserror" 16 "github.com/cockroachdb/pebble/internal/dsl" 17 "github.com/cockroachdb/pebble/vfs" 18 ) 19 20 // ErrInjected is an error artificially injected for testing fs error paths. 21 var ErrInjected = LabelledError{ 22 error: errors.New("injected error"), 23 Label: "ErrInjected", 24 } 25 26 // Op describes a filesystem operation. 27 type Op struct { 28 // Kind describes the particular kind of operation being performed. 29 Kind OpKind 30 // Path is the path of the file of the file being operated on. 31 Path string 32 // Offset is the offset of an operation. It's set for OpFileReadAt and 33 // OpFileWriteAt operations. 34 Offset int64 35 } 36 37 // OpKind is an enum describing the type of operation. 38 type OpKind int 39 40 const ( 41 // OpCreate describes a create file operation. 42 OpCreate OpKind = iota 43 // OpLink describes a hardlink operation. 44 OpLink 45 // OpOpen describes a file open operation. 46 OpOpen 47 // OpOpenDir describes a directory open operation. 48 OpOpenDir 49 // OpRemove describes a remove file operation. 50 OpRemove 51 // OpRemoveAll describes a recursive remove operation. 52 OpRemoveAll 53 // OpRename describes a rename operation. 54 OpRename 55 // OpReuseForWrite describes a reuse for write operation. 56 OpReuseForWrite 57 // OpMkdirAll describes a make directory including parents operation. 58 OpMkdirAll 59 // OpLock describes a lock file operation. 60 OpLock 61 // OpList describes a list directory operation. 62 OpList 63 // OpFilePreallocate describes a file preallocate operation. 64 OpFilePreallocate 65 // OpStat describes a path-based stat operation. 66 OpStat 67 // OpGetDiskUsage describes a disk usage operation. 68 OpGetDiskUsage 69 // OpFileClose describes a close file operation. 70 OpFileClose 71 // OpFileRead describes a file read operation. 72 OpFileRead 73 // OpFileReadAt describes a file seek read operation. 74 OpFileReadAt 75 // OpFileWrite describes a file write operation. 76 OpFileWrite 77 // OpFileWriteAt describes a file seek write operation. 78 OpFileWriteAt 79 // OpFileStat describes a file stat operation. 80 OpFileStat 81 // OpFileSync describes a file sync operation. 82 OpFileSync 83 // OpFileFlush describes a file flush operation. 84 OpFileFlush 85 ) 86 87 // ReadOrWrite returns the operation's kind. 88 func (o OpKind) ReadOrWrite() OpReadWrite { 89 switch o { 90 case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat: 91 return OpIsRead 92 case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForWrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileFlush, OpFilePreallocate: 93 return OpIsWrite 94 default: 95 panic(fmt.Sprintf("unrecognized op %v\n", o)) 96 } 97 } 98 99 // OpReadWrite is an enum describing whether an operation is a read or write 100 // operation. 101 type OpReadWrite int 102 103 const ( 104 // OpIsRead describes read operations. 105 OpIsRead OpReadWrite = iota 106 // OpIsWrite describes write operations. 107 OpIsWrite 108 ) 109 110 // String implements fmt.Stringer. 111 func (kind OpReadWrite) String() string { 112 switch kind { 113 case OpIsRead: 114 return "Reads" 115 case OpIsWrite: 116 return "Writes" 117 default: 118 panic(fmt.Sprintf("unrecognized OpKind %d", kind)) 119 } 120 } 121 122 // OnIndex is a convenience function for constructing a dsl.OnIndex for use with 123 // an error-injecting filesystem. 124 func OnIndex(index int32) *InjectIndex { 125 return &InjectIndex{dsl.OnIndex[Op](index)} 126 } 127 128 // InjectIndex implements Injector, injecting an error at a specific index. 129 type InjectIndex struct { 130 *dsl.Index[Op] 131 } 132 133 // MaybeError implements the Injector interface. 134 // 135 // TODO(jackson): Remove this implementation and update callers to compose it 136 // with other injectors. 137 func (ii *InjectIndex) MaybeError(op Op) error { 138 if !ii.Evaluate(op) { 139 return nil 140 } 141 return ErrInjected 142 } 143 144 // InjectorFunc implements the Injector interface for a function with 145 // MaybeError's signature. 146 type InjectorFunc func(Op) error 147 148 // String implements fmt.Stringer. 149 func (f InjectorFunc) String() string { return "<opaque func>" } 150 151 // MaybeError implements the Injector interface. 152 func (f InjectorFunc) MaybeError(op Op) error { return f(op) } 153 154 // Injector injects errors into FS operations. 155 type Injector interface { 156 fmt.Stringer 157 // MaybeError is invoked by an errorfs before an operation is executed. It 158 // is passed an enum indicating the type of operation and a path of the 159 // subject file or directory. If the operation takes two paths (eg, 160 // Rename, Link), the original source path is provided. 161 MaybeError(op Op) error 162 } 163 164 // Any returns an injector that injects an error if any of the provided 165 // injectors inject an error. The error returned by the first injector to return 166 // an error is used. 167 func Any(injectors ...Injector) Injector { 168 return anyInjector(injectors) 169 } 170 171 type anyInjector []Injector 172 173 func (a anyInjector) String() string { 174 var sb strings.Builder 175 sb.WriteString("(Any") 176 for _, inj := range a { 177 sb.WriteString(" ") 178 sb.WriteString(inj.String()) 179 } 180 sb.WriteString(")") 181 return sb.String() 182 } 183 184 func (a anyInjector) MaybeError(op Op) error { 185 for _, inj := range a { 186 if err := inj.MaybeError(op); err != nil { 187 return err 188 } 189 } 190 return nil 191 } 192 193 // Counter wraps an Injector, counting the number of errors injected. It may be 194 // used in tests to ensure that when an error is injected, the error is 195 // surfaced through the user interface. 196 type Counter struct { 197 Injector 198 atomic.Uint64 199 } 200 201 // String implements fmt.Stringer. 202 func (c *Counter) String() string { 203 return c.Injector.String() 204 } 205 206 // MaybeError implements Injector. 207 func (c *Counter) MaybeError(op Op) error { 208 err := c.Injector.MaybeError(op) 209 if err != nil { 210 c.Uint64.Add(1) 211 } 212 return err 213 } 214 215 // Toggle wraps an Injector. By default, Toggle injects nothing. When toggled on 216 // through its On method, it begins injecting errors when the contained injector 217 // injects them. It may be returned to its original state through Off. 218 type Toggle struct { 219 Injector 220 on atomic.Bool 221 } 222 223 // String implements fmt.Stringer. 224 func (t *Toggle) String() string { 225 return t.Injector.String() 226 } 227 228 // MaybeError implements Injector. 229 func (t *Toggle) MaybeError(op Op) error { 230 if !t.on.Load() { 231 return nil 232 } 233 return t.Injector.MaybeError(op) 234 } 235 236 // On enables error injection. 237 func (t *Toggle) On() { t.on.Store(true) } 238 239 // Off disables error injection. 240 func (t *Toggle) Off() { t.on.Store(false) } 241 242 // FS implements vfs.FS, injecting errors into 243 // its operations. 244 type FS struct { 245 fs vfs.FS 246 inj Injector 247 } 248 249 // Wrap wraps an existing vfs.FS implementation, returning a new 250 // vfs.FS implementation that shadows operations to the provided FS. 251 // It uses the provided Injector for deciding when to inject errors. 252 // If an error is injected, FS propagates the error instead of 253 // shadowing the operation. 254 func Wrap(fs vfs.FS, inj Injector) *FS { 255 return &FS{ 256 fs: fs, 257 inj: inj, 258 } 259 } 260 261 // WrapFile wraps an existing vfs.File, returning a new vfs.File that shadows 262 // operations to the provided vfs.File. It uses the provided Injector for 263 // deciding when to inject errors. If an error is injected, the file 264 // propagates the error instead of shadowing the operation. 265 func WrapFile(f vfs.File, inj Injector) vfs.File { 266 return &errorFile{file: f, inj: inj} 267 } 268 269 // Unwrap returns the FS implementation underlying fs. 270 // See pebble/vfs.Root. 271 func (fs *FS) Unwrap() vfs.FS { 272 return fs.fs 273 } 274 275 // Create implements FS.Create. 276 func (fs *FS) Create(name string) (vfs.File, error) { 277 if err := fs.inj.MaybeError(Op{Kind: OpCreate, Path: name}); err != nil { 278 return nil, err 279 } 280 f, err := fs.fs.Create(name) 281 if err != nil { 282 return nil, err 283 } 284 return &errorFile{name, f, fs.inj}, nil 285 } 286 287 // Link implements FS.Link. 288 func (fs *FS) Link(oldname, newname string) error { 289 if err := fs.inj.MaybeError(Op{Kind: OpLink, Path: oldname}); err != nil { 290 return err 291 } 292 return fs.fs.Link(oldname, newname) 293 } 294 295 // Open implements FS.Open. 296 func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) { 297 if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil { 298 return nil, err 299 } 300 f, err := fs.fs.Open(name) 301 if err != nil { 302 return nil, err 303 } 304 ef := &errorFile{name, f, fs.inj} 305 for _, opt := range opts { 306 opt.Apply(ef) 307 } 308 return ef, nil 309 } 310 311 // OpenReadWrite implements FS.OpenReadWrite. 312 func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) { 313 if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil { 314 return nil, err 315 } 316 f, err := fs.fs.OpenReadWrite(name) 317 if err != nil { 318 return nil, err 319 } 320 ef := &errorFile{name, f, fs.inj} 321 for _, opt := range opts { 322 opt.Apply(ef) 323 } 324 return ef, nil 325 } 326 327 // OpenDir implements FS.OpenDir. 328 func (fs *FS) OpenDir(name string) (vfs.File, error) { 329 if err := fs.inj.MaybeError(Op{Kind: OpOpenDir, Path: name}); err != nil { 330 return nil, err 331 } 332 f, err := fs.fs.OpenDir(name) 333 if err != nil { 334 return nil, err 335 } 336 return &errorFile{name, f, fs.inj}, nil 337 } 338 339 // GetDiskUsage implements FS.GetDiskUsage. 340 func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) { 341 if err := fs.inj.MaybeError(Op{Kind: OpGetDiskUsage, Path: path}); err != nil { 342 return vfs.DiskUsage{}, err 343 } 344 return fs.fs.GetDiskUsage(path) 345 } 346 347 // PathBase implements FS.PathBase. 348 func (fs *FS) PathBase(p string) string { 349 return fs.fs.PathBase(p) 350 } 351 352 // PathDir implements FS.PathDir. 353 func (fs *FS) PathDir(p string) string { 354 return fs.fs.PathDir(p) 355 } 356 357 // PathJoin implements FS.PathJoin. 358 func (fs *FS) PathJoin(elem ...string) string { 359 return fs.fs.PathJoin(elem...) 360 } 361 362 // Remove implements FS.Remove. 363 func (fs *FS) Remove(name string) error { 364 if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) { 365 return nil 366 } 367 368 if err := fs.inj.MaybeError(Op{Kind: OpRemove, Path: name}); err != nil { 369 return err 370 } 371 return fs.fs.Remove(name) 372 } 373 374 // RemoveAll implements FS.RemoveAll. 375 func (fs *FS) RemoveAll(fullname string) error { 376 if err := fs.inj.MaybeError(Op{Kind: OpRemoveAll, Path: fullname}); err != nil { 377 return err 378 } 379 return fs.fs.RemoveAll(fullname) 380 } 381 382 // Rename implements FS.Rename. 383 func (fs *FS) Rename(oldname, newname string) error { 384 if err := fs.inj.MaybeError(Op{Kind: OpRename, Path: oldname}); err != nil { 385 return err 386 } 387 return fs.fs.Rename(oldname, newname) 388 } 389 390 // ReuseForWrite implements FS.ReuseForWrite. 391 func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) { 392 if err := fs.inj.MaybeError(Op{Kind: OpReuseForWrite, Path: oldname}); err != nil { 393 return nil, err 394 } 395 return fs.fs.ReuseForWrite(oldname, newname) 396 } 397 398 // MkdirAll implements FS.MkdirAll. 399 func (fs *FS) MkdirAll(dir string, perm os.FileMode) error { 400 if err := fs.inj.MaybeError(Op{Kind: OpMkdirAll, Path: dir}); err != nil { 401 return err 402 } 403 return fs.fs.MkdirAll(dir, perm) 404 } 405 406 // Lock implements FS.Lock. 407 func (fs *FS) Lock(name string) (io.Closer, error) { 408 if err := fs.inj.MaybeError(Op{Kind: OpLock, Path: name}); err != nil { 409 return nil, err 410 } 411 return fs.fs.Lock(name) 412 } 413 414 // List implements FS.List. 415 func (fs *FS) List(dir string) ([]string, error) { 416 if err := fs.inj.MaybeError(Op{Kind: OpList, Path: dir}); err != nil { 417 return nil, err 418 } 419 return fs.fs.List(dir) 420 } 421 422 // Stat implements FS.Stat. 423 func (fs *FS) Stat(name string) (os.FileInfo, error) { 424 if err := fs.inj.MaybeError(Op{Kind: OpStat, Path: name}); err != nil { 425 return nil, err 426 } 427 return fs.fs.Stat(name) 428 } 429 430 // errorFile implements vfs.File. The interface is implemented on the pointer 431 // type to allow pointer equality comparisons. 432 type errorFile struct { 433 path string 434 file vfs.File 435 inj Injector 436 } 437 438 func (f *errorFile) Close() error { 439 // We don't inject errors during close as those calls should never fail in 440 // practice. 441 return f.file.Close() 442 } 443 444 func (f *errorFile) Read(p []byte) (int, error) { 445 if err := f.inj.MaybeError(Op{Kind: OpFileRead, Path: f.path}); err != nil { 446 return 0, err 447 } 448 return f.file.Read(p) 449 } 450 451 func (f *errorFile) ReadAt(p []byte, off int64) (int, error) { 452 if err := f.inj.MaybeError(Op{ 453 Kind: OpFileReadAt, 454 Path: f.path, 455 Offset: off, 456 }); err != nil { 457 return 0, err 458 } 459 return f.file.ReadAt(p, off) 460 } 461 462 func (f *errorFile) Write(p []byte) (int, error) { 463 if err := f.inj.MaybeError(Op{Kind: OpFileWrite, Path: f.path}); err != nil { 464 return 0, err 465 } 466 return f.file.Write(p) 467 } 468 469 func (f *errorFile) WriteAt(p []byte, off int64) (int, error) { 470 if err := f.inj.MaybeError(Op{ 471 Kind: OpFileWriteAt, 472 Path: f.path, 473 Offset: off, 474 }); err != nil { 475 return 0, err 476 } 477 return f.file.WriteAt(p, off) 478 } 479 480 func (f *errorFile) Stat() (os.FileInfo, error) { 481 if err := f.inj.MaybeError(Op{Kind: OpFileStat, Path: f.path}); err != nil { 482 return nil, err 483 } 484 return f.file.Stat() 485 } 486 487 func (f *errorFile) Prefetch(offset, length int64) error { 488 // TODO(radu): Consider error injection. 489 return f.file.Prefetch(offset, length) 490 } 491 492 func (f *errorFile) Preallocate(offset, length int64) error { 493 if err := f.inj.MaybeError(Op{Kind: OpFilePreallocate, Path: f.path}); err != nil { 494 return err 495 } 496 return f.file.Preallocate(offset, length) 497 } 498 499 func (f *errorFile) Sync() error { 500 if err := f.inj.MaybeError(Op{Kind: OpFileSync, Path: f.path}); err != nil { 501 return err 502 } 503 return f.file.Sync() 504 } 505 506 func (f *errorFile) SyncData() error { 507 // TODO(jackson): Consider error injection. 508 return f.file.SyncData() 509 } 510 511 func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) { 512 // TODO(jackson): Consider error injection. 513 return f.file.SyncTo(length) 514 } 515 516 func (f *errorFile) Fd() uintptr { 517 return f.file.Fd() 518 }