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/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 1 : func (o OpKind) ReadOrWrite() OpReadWrite {
89 1 : switch o {
90 1 : case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat:
91 1 : return OpIsRead
92 1 : case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForWrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileFlush, OpFilePreallocate:
93 1 : return OpIsWrite
94 0 : default:
95 0 : 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 1 : func (kind OpReadWrite) String() string {
112 1 : switch kind {
113 1 : case OpIsRead:
114 1 : return "Reads"
115 1 : case OpIsWrite:
116 1 : return "Writes"
117 0 : default:
118 0 : 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 1 : func OnIndex(index int32) *InjectIndex {
125 1 : return &InjectIndex{dsl.OnIndex[Op](index)}
126 1 : }
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 0 : func (ii *InjectIndex) MaybeError(op Op) error {
138 0 : if !ii.Evaluate(op) {
139 0 : return nil
140 0 : }
141 0 : 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 0 : func (f InjectorFunc) String() string { return "<opaque func>" }
150 :
151 : // MaybeError implements the Injector interface.
152 1 : 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 1 : func Any(injectors ...Injector) Injector {
168 1 : return anyInjector(injectors)
169 1 : }
170 :
171 : type anyInjector []Injector
172 :
173 0 : func (a anyInjector) String() string {
174 0 : var sb strings.Builder
175 0 : sb.WriteString("(Any")
176 0 : for _, inj := range a {
177 0 : sb.WriteString(" ")
178 0 : sb.WriteString(inj.String())
179 0 : }
180 0 : sb.WriteString(")")
181 0 : return sb.String()
182 : }
183 :
184 1 : func (a anyInjector) MaybeError(op Op) error {
185 1 : for _, inj := range a {
186 1 : if err := inj.MaybeError(op); err != nil {
187 1 : return err
188 1 : }
189 : }
190 1 : 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 0 : func (c *Counter) String() string {
203 0 : return c.Injector.String()
204 0 : }
205 :
206 : // MaybeError implements Injector.
207 1 : func (c *Counter) MaybeError(op Op) error {
208 1 : err := c.Injector.MaybeError(op)
209 1 : if err != nil {
210 1 : c.Uint64.Add(1)
211 1 : }
212 1 : 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 0 : func (t *Toggle) String() string {
225 0 : return t.Injector.String()
226 0 : }
227 :
228 : // MaybeError implements Injector.
229 1 : func (t *Toggle) MaybeError(op Op) error {
230 1 : if !t.on.Load() {
231 1 : return nil
232 1 : }
233 1 : return t.Injector.MaybeError(op)
234 : }
235 :
236 : // On enables error injection.
237 1 : func (t *Toggle) On() { t.on.Store(true) }
238 :
239 : // Off disables error injection.
240 0 : 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 2 : func Wrap(fs vfs.FS, inj Injector) *FS {
255 2 : return &FS{
256 2 : fs: fs,
257 2 : inj: inj,
258 2 : }
259 2 : }
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 1 : func WrapFile(f vfs.File, inj Injector) vfs.File {
266 1 : return &errorFile{file: f, inj: inj}
267 1 : }
268 :
269 : // Unwrap returns the FS implementation underlying fs.
270 : // See pebble/vfs.Root.
271 0 : func (fs *FS) Unwrap() vfs.FS {
272 0 : return fs.fs
273 0 : }
274 :
275 : // Create implements FS.Create.
276 1 : func (fs *FS) Create(name string) (vfs.File, error) {
277 1 : if err := fs.inj.MaybeError(Op{Kind: OpCreate, Path: name}); err != nil {
278 1 : return nil, err
279 1 : }
280 1 : f, err := fs.fs.Create(name)
281 1 : if err != nil {
282 0 : return nil, err
283 0 : }
284 1 : return &errorFile{name, f, fs.inj}, nil
285 : }
286 :
287 : // Link implements FS.Link.
288 1 : func (fs *FS) Link(oldname, newname string) error {
289 1 : if err := fs.inj.MaybeError(Op{Kind: OpLink, Path: oldname}); err != nil {
290 1 : return err
291 1 : }
292 1 : return fs.fs.Link(oldname, newname)
293 : }
294 :
295 : // Open implements FS.Open.
296 1 : func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
297 1 : if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
298 1 : return nil, err
299 1 : }
300 1 : f, err := fs.fs.Open(name)
301 1 : if err != nil {
302 0 : return nil, err
303 0 : }
304 1 : ef := &errorFile{name, f, fs.inj}
305 1 : for _, opt := range opts {
306 1 : opt.Apply(ef)
307 1 : }
308 1 : return ef, nil
309 : }
310 :
311 : // OpenReadWrite implements FS.OpenReadWrite.
312 0 : func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) {
313 0 : if err := fs.inj.MaybeError(Op{Kind: OpOpen, Path: name}); err != nil {
314 0 : return nil, err
315 0 : }
316 0 : f, err := fs.fs.OpenReadWrite(name)
317 0 : if err != nil {
318 0 : return nil, err
319 0 : }
320 0 : ef := &errorFile{name, f, fs.inj}
321 0 : for _, opt := range opts {
322 0 : opt.Apply(ef)
323 0 : }
324 0 : return ef, nil
325 : }
326 :
327 : // OpenDir implements FS.OpenDir.
328 1 : func (fs *FS) OpenDir(name string) (vfs.File, error) {
329 1 : if err := fs.inj.MaybeError(Op{Kind: OpOpenDir, Path: name}); err != nil {
330 1 : return nil, err
331 1 : }
332 1 : f, err := fs.fs.OpenDir(name)
333 1 : if err != nil {
334 0 : return nil, err
335 0 : }
336 1 : return &errorFile{name, f, fs.inj}, nil
337 : }
338 :
339 : // GetDiskUsage implements FS.GetDiskUsage.
340 1 : func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
341 1 : if err := fs.inj.MaybeError(Op{Kind: OpGetDiskUsage, Path: path}); err != nil {
342 1 : return vfs.DiskUsage{}, err
343 1 : }
344 1 : return fs.fs.GetDiskUsage(path)
345 : }
346 :
347 : // PathBase implements FS.PathBase.
348 1 : func (fs *FS) PathBase(p string) string {
349 1 : return fs.fs.PathBase(p)
350 1 : }
351 :
352 : // PathDir implements FS.PathDir.
353 0 : func (fs *FS) PathDir(p string) string {
354 0 : return fs.fs.PathDir(p)
355 0 : }
356 :
357 : // PathJoin implements FS.PathJoin.
358 2 : func (fs *FS) PathJoin(elem ...string) string {
359 2 : return fs.fs.PathJoin(elem...)
360 2 : }
361 :
362 : // Remove implements FS.Remove.
363 1 : func (fs *FS) Remove(name string) error {
364 1 : if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) {
365 1 : return nil
366 1 : }
367 :
368 1 : if err := fs.inj.MaybeError(Op{Kind: OpRemove, Path: name}); err != nil {
369 1 : return err
370 1 : }
371 1 : return fs.fs.Remove(name)
372 : }
373 :
374 : // RemoveAll implements FS.RemoveAll.
375 0 : func (fs *FS) RemoveAll(fullname string) error {
376 0 : if err := fs.inj.MaybeError(Op{Kind: OpRemoveAll, Path: fullname}); err != nil {
377 0 : return err
378 0 : }
379 0 : return fs.fs.RemoveAll(fullname)
380 : }
381 :
382 : // Rename implements FS.Rename.
383 1 : func (fs *FS) Rename(oldname, newname string) error {
384 1 : if err := fs.inj.MaybeError(Op{Kind: OpRename, Path: oldname}); err != nil {
385 1 : return err
386 1 : }
387 1 : return fs.fs.Rename(oldname, newname)
388 : }
389 :
390 : // ReuseForWrite implements FS.ReuseForWrite.
391 1 : func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
392 1 : if err := fs.inj.MaybeError(Op{Kind: OpReuseForWrite, Path: oldname}); err != nil {
393 0 : return nil, err
394 0 : }
395 1 : return fs.fs.ReuseForWrite(oldname, newname)
396 : }
397 :
398 : // MkdirAll implements FS.MkdirAll.
399 1 : func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
400 1 : if err := fs.inj.MaybeError(Op{Kind: OpMkdirAll, Path: dir}); err != nil {
401 1 : return err
402 1 : }
403 1 : return fs.fs.MkdirAll(dir, perm)
404 : }
405 :
406 : // Lock implements FS.Lock.
407 1 : func (fs *FS) Lock(name string) (io.Closer, error) {
408 1 : if err := fs.inj.MaybeError(Op{Kind: OpLock, Path: name}); err != nil {
409 1 : return nil, err
410 1 : }
411 1 : return fs.fs.Lock(name)
412 : }
413 :
414 : // List implements FS.List.
415 1 : func (fs *FS) List(dir string) ([]string, error) {
416 1 : if err := fs.inj.MaybeError(Op{Kind: OpList, Path: dir}); err != nil {
417 1 : return nil, err
418 1 : }
419 1 : return fs.fs.List(dir)
420 : }
421 :
422 : // Stat implements FS.Stat.
423 1 : func (fs *FS) Stat(name string) (os.FileInfo, error) {
424 1 : if err := fs.inj.MaybeError(Op{Kind: OpStat, Path: name}); err != nil {
425 0 : return nil, err
426 0 : }
427 1 : 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 1 : func (f *errorFile) Close() error {
439 1 : // We don't inject errors during close as those calls should never fail in
440 1 : // practice.
441 1 : return f.file.Close()
442 1 : }
443 :
444 1 : func (f *errorFile) Read(p []byte) (int, error) {
445 1 : if err := f.inj.MaybeError(Op{Kind: OpFileRead, Path: f.path}); err != nil {
446 0 : return 0, err
447 0 : }
448 1 : return f.file.Read(p)
449 : }
450 :
451 1 : func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
452 1 : if err := f.inj.MaybeError(Op{
453 1 : Kind: OpFileReadAt,
454 1 : Path: f.path,
455 1 : Offset: off,
456 1 : }); err != nil {
457 1 : return 0, err
458 1 : }
459 1 : return f.file.ReadAt(p, off)
460 : }
461 :
462 1 : func (f *errorFile) Write(p []byte) (int, error) {
463 1 : if err := f.inj.MaybeError(Op{Kind: OpFileWrite, Path: f.path}); err != nil {
464 1 : return 0, err
465 1 : }
466 1 : return f.file.Write(p)
467 : }
468 :
469 0 : func (f *errorFile) WriteAt(p []byte, off int64) (int, error) {
470 0 : if err := f.inj.MaybeError(Op{
471 0 : Kind: OpFileWriteAt,
472 0 : Path: f.path,
473 0 : Offset: off,
474 0 : }); err != nil {
475 0 : return 0, err
476 0 : }
477 0 : return f.file.WriteAt(p, off)
478 : }
479 :
480 1 : func (f *errorFile) Stat() (os.FileInfo, error) {
481 1 : if err := f.inj.MaybeError(Op{Kind: OpFileStat, Path: f.path}); err != nil {
482 1 : return nil, err
483 1 : }
484 1 : return f.file.Stat()
485 : }
486 :
487 1 : func (f *errorFile) Prefetch(offset, length int64) error {
488 1 : // TODO(radu): Consider error injection.
489 1 : return f.file.Prefetch(offset, length)
490 1 : }
491 :
492 0 : func (f *errorFile) Preallocate(offset, length int64) error {
493 0 : if err := f.inj.MaybeError(Op{Kind: OpFilePreallocate, Path: f.path}); err != nil {
494 0 : return err
495 0 : }
496 0 : return f.file.Preallocate(offset, length)
497 : }
498 :
499 1 : func (f *errorFile) Sync() error {
500 1 : if err := f.inj.MaybeError(Op{Kind: OpFileSync, Path: f.path}); err != nil {
501 1 : return err
502 1 : }
503 1 : return f.file.Sync()
504 : }
505 :
506 1 : func (f *errorFile) SyncData() error {
507 1 : // TODO(jackson): Consider error injection.
508 1 : return f.file.SyncData()
509 1 : }
510 :
511 0 : func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) {
512 0 : // TODO(jackson): Consider error injection.
513 0 : return f.file.SyncTo(length)
514 0 : }
515 :
516 1 : func (f *errorFile) Fd() uintptr {
517 1 : return f.file.Fd()
518 1 : }
|