LCOV - code coverage report
Current view: top level - pebble/vfs/errorfs - errorfs.go (source / functions) Hit Total Coverage
Test: 2024-01-09 08:16Z eeac388c - tests + meta.lcov Lines: 164 244 67.2 %
Date: 2024-01-09 08:17:13 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/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           1 : func (o OpKind) ReadOrWrite() OpReadWrite {
      89           1 :         switch o {
      90           1 :         case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat:
      91           1 :                 return OpIsRead
      92           1 :         case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForWrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileFlush, OpFilePreallocate:
      93           1 :                 return OpIsWrite
      94           0 :         default:
      95           0 :                 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           1 : func (kind OpReadWrite) String() string {
     112           1 :         switch kind {
     113           1 :         case OpIsRead:
     114           1 :                 return "Reads"
     115           1 :         case OpIsWrite:
     116           1 :                 return "Writes"
     117           0 :         default:
     118           0 :                 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           1 : func OnIndex(index int32) *InjectIndex {
     125           1 :         return &InjectIndex{dsl.OnIndex[Op](index)}
     126           1 : }
     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           0 : func (ii *InjectIndex) MaybeError(op Op) error {
     138           0 :         if !ii.Evaluate(op) {
     139           0 :                 return nil
     140           0 :         }
     141           0 :         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           0 : func (f InjectorFunc) String() string { return "<opaque func>" }
     150             : 
     151             : // MaybeError implements the Injector interface.
     152           1 : 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           1 : func Any(injectors ...Injector) Injector {
     168           1 :         return anyInjector(injectors)
     169           1 : }
     170             : 
     171             : type anyInjector []Injector
     172             : 
     173           0 : func (a anyInjector) String() string {
     174           0 :         var sb strings.Builder
     175           0 :         sb.WriteString("(Any")
     176           0 :         for _, inj := range a {
     177           0 :                 sb.WriteString(" ")
     178           0 :                 sb.WriteString(inj.String())
     179           0 :         }
     180           0 :         sb.WriteString(")")
     181           0 :         return sb.String()
     182             : }
     183             : 
     184           1 : func (a anyInjector) MaybeError(op Op) error {
     185           1 :         for _, inj := range a {
     186           1 :                 if err := inj.MaybeError(op); err != nil {
     187           1 :                         return err
     188           1 :                 }
     189             :         }
     190           1 :         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           0 : func (c *Counter) String() string {
     203           0 :         return c.Injector.String()
     204           0 : }
     205             : 
     206             : // MaybeError implements Injector.
     207           1 : func (c *Counter) MaybeError(op Op) error {
     208           1 :         err := c.Injector.MaybeError(op)
     209           1 :         if err != nil {
     210           1 :                 c.Uint64.Add(1)
     211           1 :         }
     212           1 :         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           0 : func (t *Toggle) String() string {
     225           0 :         return t.Injector.String()
     226           0 : }
     227             : 
     228             : // MaybeError implements Injector.
     229           1 : func (t *Toggle) MaybeError(op Op) error {
     230           1 :         if !t.on.Load() {
     231           1 :                 return nil
     232           1 :         }
     233           1 :         return t.Injector.MaybeError(op)
     234             : }
     235             : 
     236             : // On enables error injection.
     237           1 : func (t *Toggle) On() { t.on.Store(true) }
     238             : 
     239             : // Off disables error injection.
     240           0 : 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           2 : func Wrap(fs vfs.FS, inj Injector) *FS {
     255           2 :         return &FS{
     256           2 :                 fs:  fs,
     257           2 :                 inj: inj,
     258           2 :         }
     259           2 : }
     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           1 : func WrapFile(f vfs.File, inj Injector) vfs.File {
     266           1 :         return &errorFile{file: f, inj: inj}
     267           1 : }
     268             : 
     269             : // Unwrap returns the FS implementation underlying fs.
     270             : // See pebble/vfs.Root.
     271           0 : func (fs *FS) Unwrap() vfs.FS {
     272           0 :         return fs.fs
     273           0 : }
     274             : 
     275             : // Create implements FS.Create.
     276           1 : func (fs *FS) Create(name string) (vfs.File, error) {
     277           1 :         if err := fs.inj.MaybeError(Op{Kind: OpCreate, Path: name}); err != nil {
     278           1 :                 return nil, err
     279           1 :         }
     280           1 :         f, err := fs.fs.Create(name)
     281           1 :         if err != nil {
     282           0 :                 return nil, err
     283           0 :         }
     284           1 :         return &errorFile{name, f, fs.inj}, nil
     285             : }
     286             : 
     287             : // Link implements FS.Link.
     288           1 : func (fs *FS) Link(oldname, newname string) error {
     289           1 :         if err := fs.inj.MaybeError(Op{Kind: OpLink, Path: oldname}); err != nil {
     290           1 :                 return err
     291           1 :         }
     292           1 :         return fs.fs.Link(oldname, newname)
     293             : }
     294             : 
     295             : // Open implements FS.Open.
     296           1 : func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
     297           1 :         if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
     298           1 :                 return nil, err
     299           1 :         }
     300           1 :         f, err := fs.fs.Open(name)
     301           1 :         if err != nil {
     302           0 :                 return nil, err
     303           0 :         }
     304           1 :         ef := &errorFile{name, f, fs.inj}
     305           1 :         for _, opt := range opts {
     306           1 :                 opt.Apply(ef)
     307           1 :         }
     308           1 :         return ef, nil
     309             : }
     310             : 
     311             : // OpenReadWrite implements FS.OpenReadWrite.
     312           0 : func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) {
     313           0 :         if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
     314           0 :                 return nil, err
     315           0 :         }
     316           0 :         f, err := fs.fs.OpenReadWrite(name)
     317           0 :         if err != nil {
     318           0 :                 return nil, err
     319           0 :         }
     320           0 :         ef := &errorFile{name, f, fs.inj}
     321           0 :         for _, opt := range opts {
     322           0 :                 opt.Apply(ef)
     323           0 :         }
     324           0 :         return ef, nil
     325             : }
     326             : 
     327             : // OpenDir implements FS.OpenDir.
     328           1 : func (fs *FS) OpenDir(name string) (vfs.File, error) {
     329           1 :         if err := fs.inj.MaybeError(Op{Kind: OpOpenDir, Path: name}); err != nil {
     330           1 :                 return nil, err
     331           1 :         }
     332           1 :         f, err := fs.fs.OpenDir(name)
     333           1 :         if err != nil {
     334           0 :                 return nil, err
     335           0 :         }
     336           1 :         return &errorFile{name, f, fs.inj}, nil
     337             : }
     338             : 
     339             : // GetDiskUsage implements FS.GetDiskUsage.
     340           1 : func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
     341           1 :         if err := fs.inj.MaybeError(Op{Kind: OpGetDiskUsage, Path: path}); err != nil {
     342           1 :                 return vfs.DiskUsage{}, err
     343           1 :         }
     344           1 :         return fs.fs.GetDiskUsage(path)
     345             : }
     346             : 
     347             : // PathBase implements FS.PathBase.
     348           1 : func (fs *FS) PathBase(p string) string {
     349           1 :         return fs.fs.PathBase(p)
     350           1 : }
     351             : 
     352             : // PathDir implements FS.PathDir.
     353           0 : func (fs *FS) PathDir(p string) string {
     354           0 :         return fs.fs.PathDir(p)
     355           0 : }
     356             : 
     357             : // PathJoin implements FS.PathJoin.
     358           2 : func (fs *FS) PathJoin(elem ...string) string {
     359           2 :         return fs.fs.PathJoin(elem...)
     360           2 : }
     361             : 
     362             : // Remove implements FS.Remove.
     363           1 : func (fs *FS) Remove(name string) error {
     364           1 :         if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) {
     365           1 :                 return nil
     366           1 :         }
     367             : 
     368           1 :         if err := fs.inj.MaybeError(Op{Kind: OpRemove, Path: name}); err != nil {
     369           1 :                 return err
     370           1 :         }
     371           1 :         return fs.fs.Remove(name)
     372             : }
     373             : 
     374             : // RemoveAll implements FS.RemoveAll.
     375           0 : func (fs *FS) RemoveAll(fullname string) error {
     376           0 :         if err := fs.inj.MaybeError(Op{Kind: OpRemoveAll, Path: fullname}); err != nil {
     377           0 :                 return err
     378           0 :         }
     379           0 :         return fs.fs.RemoveAll(fullname)
     380             : }
     381             : 
     382             : // Rename implements FS.Rename.
     383           1 : func (fs *FS) Rename(oldname, newname string) error {
     384           1 :         if err := fs.inj.MaybeError(Op{Kind: OpRename, Path: oldname}); err != nil {
     385           1 :                 return err
     386           1 :         }
     387           1 :         return fs.fs.Rename(oldname, newname)
     388             : }
     389             : 
     390             : // ReuseForWrite implements FS.ReuseForWrite.
     391           1 : func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
     392           1 :         if err := fs.inj.MaybeError(Op{Kind: OpReuseForWrite, Path: oldname}); err != nil {
     393           0 :                 return nil, err
     394           0 :         }
     395           1 :         return fs.fs.ReuseForWrite(oldname, newname)
     396             : }
     397             : 
     398             : // MkdirAll implements FS.MkdirAll.
     399           1 : func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
     400           1 :         if err := fs.inj.MaybeError(Op{Kind: OpMkdirAll, Path: dir}); err != nil {
     401           1 :                 return err
     402           1 :         }
     403           1 :         return fs.fs.MkdirAll(dir, perm)
     404             : }
     405             : 
     406             : // Lock implements FS.Lock.
     407           1 : func (fs *FS) Lock(name string) (io.Closer, error) {
     408           1 :         if err := fs.inj.MaybeError(Op{Kind: OpLock, Path: name}); err != nil {
     409           1 :                 return nil, err
     410           1 :         }
     411           1 :         return fs.fs.Lock(name)
     412             : }
     413             : 
     414             : // List implements FS.List.
     415           1 : func (fs *FS) List(dir string) ([]string, error) {
     416           1 :         if err := fs.inj.MaybeError(Op{Kind: OpList, Path: dir}); err != nil {
     417           1 :                 return nil, err
     418           1 :         }
     419           1 :         return fs.fs.List(dir)
     420             : }
     421             : 
     422             : // Stat implements FS.Stat.
     423           1 : func (fs *FS) Stat(name string) (os.FileInfo, error) {
     424           1 :         if err := fs.inj.MaybeError(Op{Kind: OpStat, Path: name}); err != nil {
     425           0 :                 return nil, err
     426           0 :         }
     427           1 :         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           1 : func (f *errorFile) Close() error {
     439           1 :         // We don't inject errors during close as those calls should never fail in
     440           1 :         // practice.
     441           1 :         return f.file.Close()
     442           1 : }
     443             : 
     444           1 : func (f *errorFile) Read(p []byte) (int, error) {
     445           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileRead, Path: f.path}); err != nil {
     446           0 :                 return 0, err
     447           0 :         }
     448           1 :         return f.file.Read(p)
     449             : }
     450             : 
     451           1 : func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
     452           1 :         if err := f.inj.MaybeError(Op{
     453           1 :                 Kind:   OpFileReadAt,
     454           1 :                 Path:   f.path,
     455           1 :                 Offset: off,
     456           1 :         }); err != nil {
     457           1 :                 return 0, err
     458           1 :         }
     459           1 :         return f.file.ReadAt(p, off)
     460             : }
     461             : 
     462           1 : func (f *errorFile) Write(p []byte) (int, error) {
     463           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileWrite, Path: f.path}); err != nil {
     464           1 :                 return 0, err
     465           1 :         }
     466           1 :         return f.file.Write(p)
     467             : }
     468             : 
     469           0 : func (f *errorFile) WriteAt(p []byte, off int64) (int, error) {
     470           0 :         if err := f.inj.MaybeError(Op{
     471           0 :                 Kind:   OpFileWriteAt,
     472           0 :                 Path:   f.path,
     473           0 :                 Offset: off,
     474           0 :         }); err != nil {
     475           0 :                 return 0, err
     476           0 :         }
     477           0 :         return f.file.WriteAt(p, off)
     478             : }
     479             : 
     480           1 : func (f *errorFile) Stat() (os.FileInfo, error) {
     481           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileStat, Path: f.path}); err != nil {
     482           1 :                 return nil, err
     483           1 :         }
     484           1 :         return f.file.Stat()
     485             : }
     486             : 
     487           1 : func (f *errorFile) Prefetch(offset, length int64) error {
     488           1 :         // TODO(radu): Consider error injection.
     489           1 :         return f.file.Prefetch(offset, length)
     490           1 : }
     491             : 
     492           0 : func (f *errorFile) Preallocate(offset, length int64) error {
     493           0 :         if err := f.inj.MaybeError(Op{Kind: OpFilePreallocate, Path: f.path}); err != nil {
     494           0 :                 return err
     495           0 :         }
     496           0 :         return f.file.Preallocate(offset, length)
     497             : }
     498             : 
     499           1 : func (f *errorFile) Sync() error {
     500           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileSync, Path: f.path}); err != nil {
     501           1 :                 return err
     502           1 :         }
     503           1 :         return f.file.Sync()
     504             : }
     505             : 
     506           1 : func (f *errorFile) SyncData() error {
     507           1 :         // TODO(jackson): Consider error injection.
     508           1 :         return f.file.SyncData()
     509           1 : }
     510             : 
     511           0 : func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) {
     512           0 :         // TODO(jackson): Consider error injection.
     513           0 :         return f.file.SyncTo(length)
     514           0 : }
     515             : 
     516           1 : func (f *errorFile) Fd() uintptr {
     517           1 :         return f.file.Fd()
     518           1 : }

Generated by: LCOV version 1.14