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