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

Generated by: LCOV version 1.14