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

Generated by: LCOV version 1.14