LCOV - code coverage report
Current view: top level - pebble/vfs/errorfs - errorfs.go (source / functions) Hit Total Coverage
Test: 2024-03-15 08:15Z 65193e03 - tests only.lcov Lines: 193 259 74.5 %
Date: 2024-03-15 08:16:14 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           1 : func (o OpKind) ReadOrWrite() OpReadWrite {
      94           1 :         switch o {
      95           1 :         case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat:
      96           1 :                 return OpIsRead
      97           1 :         case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForWrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileSyncData, OpFileSyncTo, OpFileFlush, OpFilePreallocate:
      98           1 :                 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           1 : func Wrap(fs vfs.FS, inj Injector) *FS {
     281           1 :         return &FS{
     282           1 :                 fs:  fs,
     283           1 :                 inj: inj,
     284           1 :         }
     285           1 : }
     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           1 : func (fs *FS) Create(name string) (vfs.File, error) {
     303           1 :         if err := fs.inj.MaybeError(Op{Kind: OpCreate, Path: name}); err != nil {
     304           1 :                 return nil, err
     305           1 :         }
     306           1 :         f, err := fs.fs.Create(name)
     307           1 :         if err != nil {
     308           0 :                 return nil, err
     309           0 :         }
     310           1 :         return &errorFile{name, f, fs.inj}, nil
     311             : }
     312             : 
     313             : // Link implements FS.Link.
     314           1 : func (fs *FS) Link(oldname, newname string) error {
     315           1 :         if err := fs.inj.MaybeError(Op{Kind: OpLink, Path: oldname}); err != nil {
     316           1 :                 return err
     317           1 :         }
     318           1 :         return fs.fs.Link(oldname, newname)
     319             : }
     320             : 
     321             : // Open implements FS.Open.
     322           1 : func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
     323           1 :         if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
     324           1 :                 return nil, err
     325           1 :         }
     326           1 :         f, err := fs.fs.Open(name)
     327           1 :         if err != nil {
     328           0 :                 return nil, err
     329           0 :         }
     330           1 :         ef := &errorFile{name, f, fs.inj}
     331           1 :         for _, opt := range opts {
     332           1 :                 opt.Apply(ef)
     333           1 :         }
     334           1 :         return ef, nil
     335             : }
     336             : 
     337             : // OpenReadWrite implements FS.OpenReadWrite.
     338           1 : func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) {
     339           1 :         if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
     340           0 :                 return nil, err
     341           0 :         }
     342           1 :         f, err := fs.fs.OpenReadWrite(name)
     343           1 :         if err != nil {
     344           0 :                 return nil, err
     345           0 :         }
     346           1 :         ef := &errorFile{name, f, fs.inj}
     347           1 :         for _, opt := range opts {
     348           0 :                 opt.Apply(ef)
     349           0 :         }
     350           1 :         return ef, nil
     351             : }
     352             : 
     353             : // OpenDir implements FS.OpenDir.
     354           1 : func (fs *FS) OpenDir(name string) (vfs.File, error) {
     355           1 :         if err := fs.inj.MaybeError(Op{Kind: OpOpenDir, Path: name}); err != nil {
     356           1 :                 return nil, err
     357           1 :         }
     358           1 :         f, err := fs.fs.OpenDir(name)
     359           1 :         if err != nil {
     360           0 :                 return nil, err
     361           0 :         }
     362           1 :         return &errorFile{name, f, fs.inj}, nil
     363             : }
     364             : 
     365             : // GetDiskUsage implements FS.GetDiskUsage.
     366           1 : func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
     367           1 :         if err := fs.inj.MaybeError(Op{Kind: OpGetDiskUsage, Path: path}); err != nil {
     368           1 :                 return vfs.DiskUsage{}, err
     369           1 :         }
     370           1 :         return fs.fs.GetDiskUsage(path)
     371             : }
     372             : 
     373             : // PathBase implements FS.PathBase.
     374           1 : func (fs *FS) PathBase(p string) string {
     375           1 :         return fs.fs.PathBase(p)
     376           1 : }
     377             : 
     378             : // PathDir implements FS.PathDir.
     379           1 : func (fs *FS) PathDir(p string) string {
     380           1 :         return fs.fs.PathDir(p)
     381           1 : }
     382             : 
     383             : // PathJoin implements FS.PathJoin.
     384           1 : func (fs *FS) PathJoin(elem ...string) string {
     385           1 :         return fs.fs.PathJoin(elem...)
     386           1 : }
     387             : 
     388             : // Remove implements FS.Remove.
     389           1 : func (fs *FS) Remove(name string) error {
     390           1 :         if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) {
     391           1 :                 return nil
     392           1 :         }
     393             : 
     394           1 :         if err := fs.inj.MaybeError(Op{Kind: OpRemove, Path: name}); err != nil {
     395           1 :                 return err
     396           1 :         }
     397           1 :         return fs.fs.Remove(name)
     398             : }
     399             : 
     400             : // RemoveAll implements FS.RemoveAll.
     401           0 : func (fs *FS) RemoveAll(fullname string) error {
     402           0 :         if err := fs.inj.MaybeError(Op{Kind: OpRemoveAll, Path: fullname}); err != nil {
     403           0 :                 return err
     404           0 :         }
     405           0 :         return fs.fs.RemoveAll(fullname)
     406             : }
     407             : 
     408             : // Rename implements FS.Rename.
     409           1 : func (fs *FS) Rename(oldname, newname string) error {
     410           1 :         if err := fs.inj.MaybeError(Op{Kind: OpRename, Path: oldname}); err != nil {
     411           1 :                 return err
     412           1 :         }
     413           1 :         return fs.fs.Rename(oldname, newname)
     414             : }
     415             : 
     416             : // ReuseForWrite implements FS.ReuseForWrite.
     417           1 : func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
     418           1 :         if err := fs.inj.MaybeError(Op{Kind: OpReuseForWrite, Path: oldname}); err != nil {
     419           0 :                 return nil, err
     420           0 :         }
     421           1 :         return fs.fs.ReuseForWrite(oldname, newname)
     422             : }
     423             : 
     424             : // MkdirAll implements FS.MkdirAll.
     425           1 : func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
     426           1 :         if err := fs.inj.MaybeError(Op{Kind: OpMkdirAll, Path: dir}); err != nil {
     427           1 :                 return err
     428           1 :         }
     429           1 :         return fs.fs.MkdirAll(dir, perm)
     430             : }
     431             : 
     432             : // Lock implements FS.Lock.
     433           1 : func (fs *FS) Lock(name string) (io.Closer, error) {
     434           1 :         if err := fs.inj.MaybeError(Op{Kind: OpLock, Path: name}); err != nil {
     435           1 :                 return nil, err
     436           1 :         }
     437           1 :         return fs.fs.Lock(name)
     438             : }
     439             : 
     440             : // List implements FS.List.
     441           1 : func (fs *FS) List(dir string) ([]string, error) {
     442           1 :         if err := fs.inj.MaybeError(Op{Kind: OpList, Path: dir}); err != nil {
     443           1 :                 return nil, err
     444           1 :         }
     445           1 :         return fs.fs.List(dir)
     446             : }
     447             : 
     448             : // Stat implements FS.Stat.
     449           1 : func (fs *FS) Stat(name string) (os.FileInfo, error) {
     450           1 :         if err := fs.inj.MaybeError(Op{Kind: OpStat, Path: name}); err != nil {
     451           0 :                 return nil, err
     452           0 :         }
     453           1 :         return fs.fs.Stat(name)
     454             : }
     455             : 
     456             : // errorFile implements vfs.File. The interface is implemented on the pointer
     457             : // type to allow pointer equality comparisons.
     458             : type errorFile struct {
     459             :         path string
     460             :         file vfs.File
     461             :         inj  Injector
     462             : }
     463             : 
     464           1 : func (f *errorFile) Close() error {
     465           1 :         // We don't inject errors during close as those calls should never fail in
     466           1 :         // practice.
     467           1 :         return f.file.Close()
     468           1 : }
     469             : 
     470           1 : func (f *errorFile) Read(p []byte) (int, error) {
     471           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileRead, Path: f.path}); err != nil {
     472           0 :                 return 0, err
     473           0 :         }
     474           1 :         return f.file.Read(p)
     475             : }
     476             : 
     477           1 : func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
     478           1 :         if err := f.inj.MaybeError(Op{
     479           1 :                 Kind:   OpFileReadAt,
     480           1 :                 Path:   f.path,
     481           1 :                 Offset: off,
     482           1 :         }); err != nil {
     483           1 :                 return 0, err
     484           1 :         }
     485           1 :         return f.file.ReadAt(p, off)
     486             : }
     487             : 
     488           1 : func (f *errorFile) Write(p []byte) (int, error) {
     489           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileWrite, Path: f.path}); err != nil {
     490           1 :                 return 0, err
     491           1 :         }
     492           1 :         return f.file.Write(p)
     493             : }
     494             : 
     495           1 : func (f *errorFile) WriteAt(p []byte, off int64) (int, error) {
     496           1 :         if err := f.inj.MaybeError(Op{
     497           1 :                 Kind:   OpFileWriteAt,
     498           1 :                 Path:   f.path,
     499           1 :                 Offset: off,
     500           1 :         }); err != nil {
     501           0 :                 return 0, err
     502           0 :         }
     503           1 :         return f.file.WriteAt(p, off)
     504             : }
     505             : 
     506           1 : func (f *errorFile) Stat() (os.FileInfo, error) {
     507           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileStat, Path: f.path}); err != nil {
     508           1 :                 return nil, err
     509           1 :         }
     510           1 :         return f.file.Stat()
     511             : }
     512             : 
     513           1 : func (f *errorFile) Prefetch(offset, length int64) error {
     514           1 :         // TODO(radu): Consider error injection.
     515           1 :         return f.file.Prefetch(offset, length)
     516           1 : }
     517             : 
     518           1 : func (f *errorFile) Preallocate(offset, length int64) error {
     519           1 :         if err := f.inj.MaybeError(Op{Kind: OpFilePreallocate, Path: f.path}); err != nil {
     520           0 :                 return err
     521           0 :         }
     522           1 :         return f.file.Preallocate(offset, length)
     523             : }
     524             : 
     525           1 : func (f *errorFile) Sync() error {
     526           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileSync, Path: f.path}); err != nil {
     527           1 :                 return err
     528           1 :         }
     529           1 :         return f.file.Sync()
     530             : }
     531             : 
     532           1 : func (f *errorFile) SyncData() error {
     533           1 :         if err := f.inj.MaybeError(Op{Kind: OpFileSyncData, Path: f.path}); err != nil {
     534           1 :                 return err
     535           1 :         }
     536           1 :         return f.file.SyncData()
     537             : }
     538             : 
     539           0 : func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) {
     540           0 :         if err := f.inj.MaybeError(Op{Kind: OpFileSyncTo, Path: f.path}); err != nil {
     541           0 :                 return false, err
     542           0 :         }
     543           0 :         return f.file.SyncTo(length)
     544             : }
     545             : 
     546           1 : func (f *errorFile) Fd() uintptr {
     547           1 :         return f.file.Fd()
     548           1 : }

Generated by: LCOV version 1.14