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 2 : func (o OpKind) ReadOrWrite() OpReadWrite {
94 2 : switch o {
95 2 : case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat:
96 2 : return OpIsRead
97 2 : case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForWrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileSyncData, OpFileSyncTo, OpFileFlush, OpFilePreallocate:
98 2 : 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 2 : func Wrap(fs vfs.FS, inj Injector) *FS {
281 2 : return &FS{
282 2 : fs: fs,
283 2 : inj: inj,
284 2 : }
285 2 : }
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 2 : func (fs *FS) Create(name string, category vfs.DiskWriteCategory) (vfs.File, error) {
303 2 : if err := fs.inj.MaybeError(Op{Kind: OpCreate, Path: name}); err != nil {
304 1 : return nil, err
305 1 : }
306 2 : f, err := fs.fs.Create(name, category)
307 2 : if err != nil {
308 0 : return nil, err
309 0 : }
310 2 : return &errorFile{name, f, fs.inj}, nil
311 : }
312 :
313 : // Link implements FS.Link.
314 2 : func (fs *FS) Link(oldname, newname string) error {
315 2 : if err := fs.inj.MaybeError(Op{Kind: OpLink, Path: oldname}); err != nil {
316 1 : return err
317 1 : }
318 2 : return fs.fs.Link(oldname, newname)
319 : }
320 :
321 : // Open implements FS.Open.
322 2 : func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
323 2 : if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
324 1 : return nil, err
325 1 : }
326 2 : f, err := fs.fs.Open(name)
327 2 : if err != nil {
328 0 : return nil, err
329 0 : }
330 2 : ef := &errorFile{name, f, fs.inj}
331 2 : for _, opt := range opts {
332 2 : opt.Apply(ef)
333 2 : }
334 2 : return ef, nil
335 : }
336 :
337 : // OpenReadWrite implements FS.OpenReadWrite.
338 : func (fs *FS) OpenReadWrite(
339 : name string, category vfs.DiskWriteCategory, opts ...vfs.OpenOption,
340 1 : ) (vfs.File, error) {
341 1 : if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
342 0 : return nil, err
343 0 : }
344 1 : f, err := fs.fs.OpenReadWrite(name, category)
345 1 : if err != nil {
346 0 : return nil, err
347 0 : }
348 1 : ef := &errorFile{name, f, fs.inj}
349 1 : for _, opt := range opts {
350 0 : opt.Apply(ef)
351 0 : }
352 1 : return ef, nil
353 : }
354 :
355 : // OpenDir implements FS.OpenDir.
356 2 : func (fs *FS) OpenDir(name string) (vfs.File, error) {
357 2 : if err := fs.inj.MaybeError(Op{Kind: OpOpenDir, Path: name}); err != nil {
358 1 : return nil, err
359 1 : }
360 2 : f, err := fs.fs.OpenDir(name)
361 2 : if err != nil {
362 0 : return nil, err
363 0 : }
364 2 : return &errorFile{name, f, fs.inj}, nil
365 : }
366 :
367 : // GetDiskUsage implements FS.GetDiskUsage.
368 2 : func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
369 2 : if err := fs.inj.MaybeError(Op{Kind: OpGetDiskUsage, Path: path}); err != nil {
370 1 : return vfs.DiskUsage{}, err
371 1 : }
372 2 : return fs.fs.GetDiskUsage(path)
373 : }
374 :
375 : // PathBase implements FS.PathBase.
376 2 : func (fs *FS) PathBase(p string) string {
377 2 : return fs.fs.PathBase(p)
378 2 : }
379 :
380 : // PathDir implements FS.PathDir.
381 2 : func (fs *FS) PathDir(p string) string {
382 2 : return fs.fs.PathDir(p)
383 2 : }
384 :
385 : // PathJoin implements FS.PathJoin.
386 2 : func (fs *FS) PathJoin(elem ...string) string {
387 2 : return fs.fs.PathJoin(elem...)
388 2 : }
389 :
390 : // Remove implements FS.Remove.
391 2 : func (fs *FS) Remove(name string) error {
392 2 : if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) {
393 2 : return nil
394 2 : }
395 :
396 2 : if err := fs.inj.MaybeError(Op{Kind: OpRemove, Path: name}); err != nil {
397 1 : return err
398 1 : }
399 2 : return fs.fs.Remove(name)
400 : }
401 :
402 : // RemoveAll implements FS.RemoveAll.
403 0 : func (fs *FS) RemoveAll(fullname string) error {
404 0 : if err := fs.inj.MaybeError(Op{Kind: OpRemoveAll, Path: fullname}); err != nil {
405 0 : return err
406 0 : }
407 0 : return fs.fs.RemoveAll(fullname)
408 : }
409 :
410 : // Rename implements FS.Rename.
411 2 : func (fs *FS) Rename(oldname, newname string) error {
412 2 : if err := fs.inj.MaybeError(Op{Kind: OpRename, Path: oldname}); err != nil {
413 1 : return err
414 1 : }
415 2 : return fs.fs.Rename(oldname, newname)
416 : }
417 :
418 : // ReuseForWrite implements FS.ReuseForWrite.
419 : func (fs *FS) ReuseForWrite(
420 : oldname, newname string, category vfs.DiskWriteCategory,
421 1 : ) (vfs.File, error) {
422 1 : if err := fs.inj.MaybeError(Op{Kind: OpReuseForWrite, Path: oldname}); err != nil {
423 0 : return nil, err
424 0 : }
425 1 : return fs.fs.ReuseForWrite(oldname, newname, category)
426 : }
427 :
428 : // MkdirAll implements FS.MkdirAll.
429 2 : func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
430 2 : if err := fs.inj.MaybeError(Op{Kind: OpMkdirAll, Path: dir}); err != nil {
431 1 : return err
432 1 : }
433 2 : return fs.fs.MkdirAll(dir, perm)
434 : }
435 :
436 : // Lock implements FS.Lock.
437 2 : func (fs *FS) Lock(name string) (io.Closer, error) {
438 2 : if err := fs.inj.MaybeError(Op{Kind: OpLock, Path: name}); err != nil {
439 1 : return nil, err
440 1 : }
441 2 : return fs.fs.Lock(name)
442 : }
443 :
444 : // List implements FS.List.
445 2 : func (fs *FS) List(dir string) ([]string, error) {
446 2 : if err := fs.inj.MaybeError(Op{Kind: OpList, Path: dir}); err != nil {
447 1 : return nil, err
448 1 : }
449 2 : return fs.fs.List(dir)
450 : }
451 :
452 : // Stat implements FS.Stat.
453 2 : func (fs *FS) Stat(name string) (os.FileInfo, error) {
454 2 : if err := fs.inj.MaybeError(Op{Kind: OpStat, Path: name}); err != nil {
455 0 : return nil, err
456 0 : }
457 2 : return fs.fs.Stat(name)
458 : }
459 :
460 : // errorFile implements vfs.File. The interface is implemented on the pointer
461 : // type to allow pointer equality comparisons.
462 : type errorFile struct {
463 : path string
464 : file vfs.File
465 : inj Injector
466 : }
467 :
468 2 : func (f *errorFile) Close() error {
469 2 : // We don't inject errors during close as those calls should never fail in
470 2 : // practice.
471 2 : return f.file.Close()
472 2 : }
473 :
474 2 : func (f *errorFile) Read(p []byte) (int, error) {
475 2 : if err := f.inj.MaybeError(Op{Kind: OpFileRead, Path: f.path}); err != nil {
476 0 : return 0, err
477 0 : }
478 2 : return f.file.Read(p)
479 : }
480 :
481 2 : func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
482 2 : if err := f.inj.MaybeError(Op{
483 2 : Kind: OpFileReadAt,
484 2 : Path: f.path,
485 2 : Offset: off,
486 2 : }); err != nil {
487 1 : return 0, err
488 1 : }
489 2 : return f.file.ReadAt(p, off)
490 : }
491 :
492 2 : func (f *errorFile) Write(p []byte) (int, error) {
493 2 : if err := f.inj.MaybeError(Op{Kind: OpFileWrite, Path: f.path}); err != nil {
494 1 : return 0, err
495 1 : }
496 2 : return f.file.Write(p)
497 : }
498 :
499 1 : func (f *errorFile) WriteAt(p []byte, off int64) (int, error) {
500 1 : if err := f.inj.MaybeError(Op{
501 1 : Kind: OpFileWriteAt,
502 1 : Path: f.path,
503 1 : Offset: off,
504 1 : }); err != nil {
505 0 : return 0, err
506 0 : }
507 1 : return f.file.WriteAt(p, off)
508 : }
509 :
510 2 : func (f *errorFile) Stat() (os.FileInfo, error) {
511 2 : if err := f.inj.MaybeError(Op{Kind: OpFileStat, Path: f.path}); err != nil {
512 1 : return nil, err
513 1 : }
514 2 : return f.file.Stat()
515 : }
516 :
517 2 : func (f *errorFile) Prefetch(offset, length int64) error {
518 2 : // TODO(radu): Consider error injection.
519 2 : return f.file.Prefetch(offset, length)
520 2 : }
521 :
522 1 : func (f *errorFile) Preallocate(offset, length int64) error {
523 1 : if err := f.inj.MaybeError(Op{Kind: OpFilePreallocate, Path: f.path}); err != nil {
524 0 : return err
525 0 : }
526 1 : return f.file.Preallocate(offset, length)
527 : }
528 :
529 2 : func (f *errorFile) Sync() error {
530 2 : if err := f.inj.MaybeError(Op{Kind: OpFileSync, Path: f.path}); err != nil {
531 1 : return err
532 1 : }
533 2 : return f.file.Sync()
534 : }
535 :
536 2 : func (f *errorFile) SyncData() error {
537 2 : if err := f.inj.MaybeError(Op{Kind: OpFileSyncData, Path: f.path}); err != nil {
538 1 : return err
539 1 : }
540 2 : return f.file.SyncData()
541 : }
542 :
543 0 : func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) {
544 0 : if err := f.inj.MaybeError(Op{Kind: OpFileSyncTo, Path: f.path}); err != nil {
545 0 : return false, err
546 0 : }
547 0 : return f.file.SyncTo(length)
548 : }
549 :
550 2 : func (f *errorFile) Fd() uintptr {
551 2 : return f.file.Fd()
552 2 : }
|