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 : }
|