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