github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/errorfs/errorfs.go (about)

     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  func (o Op) OpKind() OpKind {
    76  	switch o {
    77  	case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat:
    78  		return OpKindRead
    79  	case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForRewrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileFlush, OpFilePreallocate:
    80  		return OpKindWrite
    81  	default:
    82  		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  func OnIndex(index int32) *InjectIndex {
   101  	ii := &InjectIndex{}
   102  	ii.index.Store(index)
   103  	return ii
   104  }
   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  func (ii *InjectIndex) Index() int32 { return ii.index.Load() }
   113  
   114  // SetIndex sets the index at which the error will be injected.
   115  func (ii *InjectIndex) SetIndex(v int32) { ii.index.Store(v) }
   116  
   117  // MaybeError implements the Injector interface.
   118  func (ii *InjectIndex) MaybeError(_ Op, _ string) error {
   119  	if ii.index.Add(-1) == -1 {
   120  		return errors.WithStack(ErrInjected)
   121  	}
   122  	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  func WithProbability(op OpKind, p float64) Injector {
   130  	mu := new(sync.Mutex)
   131  	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
   132  	return InjectorFunc(func(currOp Op, _ string) error {
   133  		mu.Lock()
   134  		defer mu.Unlock()
   135  		if currOp.OpKind() == op && rnd.Float64() < p {
   136  			return errors.WithStack(ErrInjected)
   137  		}
   138  		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  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  func Wrap(fs vfs.FS, inj Injector) *FS {
   171  	return &FS{
   172  		fs:  fs,
   173  		inj: inj,
   174  	}
   175  }
   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  func WrapFile(f vfs.File, inj Injector) vfs.File {
   182  	return &errorFile{file: f, inj: inj}
   183  }
   184  
   185  // Unwrap returns the FS implementation underlying fs.
   186  // See pebble/vfs.Root.
   187  func (fs *FS) Unwrap() vfs.FS {
   188  	return fs.fs
   189  }
   190  
   191  // Create implements FS.Create.
   192  func (fs *FS) Create(name string) (vfs.File, error) {
   193  	if err := fs.inj.MaybeError(OpCreate, name); err != nil {
   194  		return nil, err
   195  	}
   196  	f, err := fs.fs.Create(name)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	return &errorFile{name, f, fs.inj}, nil
   201  }
   202  
   203  // Link implements FS.Link.
   204  func (fs *FS) Link(oldname, newname string) error {
   205  	if err := fs.inj.MaybeError(OpLink, oldname); err != nil {
   206  		return err
   207  	}
   208  	return fs.fs.Link(oldname, newname)
   209  }
   210  
   211  // Open implements FS.Open.
   212  func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
   213  	if err := fs.inj.MaybeError(OpOpen, name); err != nil {
   214  		return nil, err
   215  	}
   216  	f, err := fs.fs.Open(name)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	ef := &errorFile{name, f, fs.inj}
   221  	for _, opt := range opts {
   222  		opt.Apply(ef)
   223  	}
   224  	return ef, nil
   225  }
   226  
   227  // OpenReadWrite implements FS.OpenReadWrite.
   228  func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) {
   229  	if err := fs.inj.MaybeError(OpOpen, name); err != nil {
   230  		return nil, err
   231  	}
   232  	f, err := fs.fs.OpenReadWrite(name)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	ef := &errorFile{name, f, fs.inj}
   237  	for _, opt := range opts {
   238  		opt.Apply(ef)
   239  	}
   240  	return ef, nil
   241  }
   242  
   243  // OpenDir implements FS.OpenDir.
   244  func (fs *FS) OpenDir(name string) (vfs.File, error) {
   245  	if err := fs.inj.MaybeError(OpOpenDir, name); err != nil {
   246  		return nil, err
   247  	}
   248  	f, err := fs.fs.OpenDir(name)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	return &errorFile{name, f, fs.inj}, nil
   253  }
   254  
   255  // GetDiskUsage implements FS.GetDiskUsage.
   256  func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
   257  	if err := fs.inj.MaybeError(OpGetDiskUsage, path); err != nil {
   258  		return vfs.DiskUsage{}, err
   259  	}
   260  	return fs.fs.GetDiskUsage(path)
   261  }
   262  
   263  // PathBase implements FS.PathBase.
   264  func (fs *FS) PathBase(p string) string {
   265  	return fs.fs.PathBase(p)
   266  }
   267  
   268  // PathDir implements FS.PathDir.
   269  func (fs *FS) PathDir(p string) string {
   270  	return fs.fs.PathDir(p)
   271  }
   272  
   273  // PathJoin implements FS.PathJoin.
   274  func (fs *FS) PathJoin(elem ...string) string {
   275  	return fs.fs.PathJoin(elem...)
   276  }
   277  
   278  // Remove implements FS.Remove.
   279  func (fs *FS) Remove(name string) error {
   280  	if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) {
   281  		return nil
   282  	}
   283  
   284  	if err := fs.inj.MaybeError(OpRemove, name); err != nil {
   285  		return err
   286  	}
   287  	return fs.fs.Remove(name)
   288  }
   289  
   290  // RemoveAll implements FS.RemoveAll.
   291  func (fs *FS) RemoveAll(fullname string) error {
   292  	if err := fs.inj.MaybeError(OpRemoveAll, fullname); err != nil {
   293  		return err
   294  	}
   295  	return fs.fs.RemoveAll(fullname)
   296  }
   297  
   298  // Rename implements FS.Rename.
   299  func (fs *FS) Rename(oldname, newname string) error {
   300  	if err := fs.inj.MaybeError(OpRename, oldname); err != nil {
   301  		return err
   302  	}
   303  	return fs.fs.Rename(oldname, newname)
   304  }
   305  
   306  // ReuseForWrite implements FS.ReuseForWrite.
   307  func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
   308  	if err := fs.inj.MaybeError(OpReuseForRewrite, oldname); err != nil {
   309  		return nil, err
   310  	}
   311  	return fs.fs.ReuseForWrite(oldname, newname)
   312  }
   313  
   314  // MkdirAll implements FS.MkdirAll.
   315  func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
   316  	if err := fs.inj.MaybeError(OpMkdirAll, dir); err != nil {
   317  		return err
   318  	}
   319  	return fs.fs.MkdirAll(dir, perm)
   320  }
   321  
   322  // Lock implements FS.Lock.
   323  func (fs *FS) Lock(name string) (io.Closer, error) {
   324  	if err := fs.inj.MaybeError(OpLock, name); err != nil {
   325  		return nil, err
   326  	}
   327  	return fs.fs.Lock(name)
   328  }
   329  
   330  // List implements FS.List.
   331  func (fs *FS) List(dir string) ([]string, error) {
   332  	if err := fs.inj.MaybeError(OpList, dir); err != nil {
   333  		return nil, err
   334  	}
   335  	return fs.fs.List(dir)
   336  }
   337  
   338  // Stat implements FS.Stat.
   339  func (fs *FS) Stat(name string) (os.FileInfo, error) {
   340  	if err := fs.inj.MaybeError(OpStat, name); err != nil {
   341  		return nil, err
   342  	}
   343  	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  func (f *errorFile) Close() error {
   355  	// We don't inject errors during close as those calls should never fail in
   356  	// practice.
   357  	return f.file.Close()
   358  }
   359  
   360  func (f *errorFile) Read(p []byte) (int, error) {
   361  	if err := f.inj.MaybeError(OpFileRead, f.path); err != nil {
   362  		return 0, err
   363  	}
   364  	return f.file.Read(p)
   365  }
   366  
   367  func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
   368  	if err := f.inj.MaybeError(OpFileReadAt, f.path); err != nil {
   369  		return 0, err
   370  	}
   371  	return f.file.ReadAt(p, off)
   372  }
   373  
   374  func (f *errorFile) Write(p []byte) (int, error) {
   375  	if err := f.inj.MaybeError(OpFileWrite, f.path); err != nil {
   376  		return 0, err
   377  	}
   378  	return f.file.Write(p)
   379  }
   380  
   381  func (f *errorFile) WriteAt(p []byte, ofs int64) (int, error) {
   382  	if err := f.inj.MaybeError(OpFileWriteAt, f.path); err != nil {
   383  		return 0, err
   384  	}
   385  	return f.file.WriteAt(p, ofs)
   386  }
   387  
   388  func (f *errorFile) Stat() (os.FileInfo, error) {
   389  	if err := f.inj.MaybeError(OpFileStat, f.path); err != nil {
   390  		return nil, err
   391  	}
   392  	return f.file.Stat()
   393  }
   394  
   395  func (f *errorFile) Prefetch(offset, length int64) error {
   396  	// TODO(radu): Consider error injection.
   397  	return f.file.Prefetch(offset, length)
   398  }
   399  
   400  func (f *errorFile) Preallocate(offset, length int64) error {
   401  	if err := f.inj.MaybeError(OpFilePreallocate, f.path); err != nil {
   402  		return err
   403  	}
   404  	return f.file.Preallocate(offset, length)
   405  }
   406  
   407  func (f *errorFile) Sync() error {
   408  	if err := f.inj.MaybeError(OpFileSync, f.path); err != nil {
   409  		return err
   410  	}
   411  	return f.file.Sync()
   412  }
   413  
   414  func (f *errorFile) SyncData() error {
   415  	// TODO(jackson): Consider error injection.
   416  	return f.file.SyncData()
   417  }
   418  
   419  func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) {
   420  	// TODO(jackson): Consider error injection.
   421  	return f.file.SyncTo(length)
   422  }
   423  
   424  func (f *errorFile) Fd() uintptr {
   425  	return f.file.Fd()
   426  }