LCOV - code coverage report
Current view: top level - pebble/vfs/errorfs - errorfs.go (source / functions) Hit Total Coverage
Test: 2024-05-18 08:15Z 6195a2cb - tests + meta.lcov Lines: 193 259 74.5 %
Date: 2024-05-18 08:16:58 Functions: 0 0 -

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

Generated by: LCOV version 1.14