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 : "math/rand"
11 : "os"
12 : "sync"
13 : "sync/atomic"
14 : "time"
15 :
16 : "github.com/cockroachdb/errors"
17 : "github.com/cockroachdb/errors/oserror"
18 : "github.com/cockroachdb/pebble/vfs"
19 : )
20 :
21 : // ErrInjected is an error artificially injected for testing fs error paths.
22 : var ErrInjected = errors.New("injected error")
23 :
24 : // Op is an enum describing the type of operation.
25 : type Op int
26 :
27 : const (
28 : // OpCreate describes a create file operation.
29 : OpCreate Op = iota
30 : // OpLink describes a hardlink operation.
31 : OpLink
32 : // OpOpen describes a file open operation.
33 : OpOpen
34 : // OpOpenDir describes a directory open operation.
35 : OpOpenDir
36 : // OpRemove describes a remove file operation.
37 : OpRemove
38 : // OpRemoveAll describes a recursive remove operation.
39 : OpRemoveAll
40 : // OpRename describes a rename operation.
41 : OpRename
42 : // OpReuseForRewrite describes a reuse for rewriting operation.
43 : OpReuseForRewrite
44 : // OpMkdirAll describes a make directory including parents operation.
45 : OpMkdirAll
46 : // OpLock describes a lock file operation.
47 : OpLock
48 : // OpList describes a list directory operation.
49 : OpList
50 : // OpFilePreallocate describes a file preallocate operation.
51 : OpFilePreallocate
52 : // OpStat describes a path-based stat operation.
53 : OpStat
54 : // OpGetDiskUsage describes a disk usage operation.
55 : OpGetDiskUsage
56 : // OpFileClose describes a close file operation.
57 : OpFileClose
58 : // OpFileRead describes a file read operation.
59 : OpFileRead
60 : // OpFileReadAt describes a file seek read operation.
61 : OpFileReadAt
62 : // OpFileWrite describes a file write operation.
63 : OpFileWrite
64 : // OpFileWriteAt describes a file seek write operation.
65 : OpFileWriteAt
66 : // OpFileStat describes a file stat operation.
67 : OpFileStat
68 : // OpFileSync describes a file sync operation.
69 : OpFileSync
70 : // OpFileFlush describes a file flush operation.
71 : OpFileFlush
72 : )
73 :
74 : // OpKind returns the operation's kind.
75 1 : func (o Op) OpKind() OpKind {
76 1 : switch o {
77 1 : case OpOpen, OpOpenDir, OpList, OpStat, OpGetDiskUsage, OpFileRead, OpFileReadAt, OpFileStat:
78 1 : return OpKindRead
79 1 : case OpCreate, OpLink, OpRemove, OpRemoveAll, OpRename, OpReuseForRewrite, OpMkdirAll, OpLock, OpFileClose, OpFileWrite, OpFileWriteAt, OpFileSync, OpFileFlush, OpFilePreallocate:
80 1 : return OpKindWrite
81 0 : default:
82 0 : panic(fmt.Sprintf("unrecognized op %v\n", o))
83 : }
84 : }
85 :
86 : // OpKind is an enum describing whether an operation is a read or write
87 : // operation.
88 : type OpKind int
89 :
90 : const (
91 : // OpKindRead describes read operations.
92 : OpKindRead OpKind = iota
93 : // OpKindWrite describes write operations.
94 : OpKindWrite
95 : )
96 :
97 : // OnIndex constructs an injector that returns an error on
98 : // the (n+1)-th invocation of its MaybeError function. It
99 : // may be passed to Wrap to inject an error into an FS.
100 0 : func OnIndex(index int32) *InjectIndex {
101 0 : ii := &InjectIndex{}
102 0 : ii.index.Store(index)
103 0 : return ii
104 0 : }
105 :
106 : // InjectIndex implements Injector, injecting an error at a specific index.
107 : type InjectIndex struct {
108 : index atomic.Int32
109 : }
110 :
111 : // Index returns the index at which the error will be injected.
112 0 : func (ii *InjectIndex) Index() int32 { return ii.index.Load() }
113 :
114 : // SetIndex sets the index at which the error will be injected.
115 0 : func (ii *InjectIndex) SetIndex(v int32) { ii.index.Store(v) }
116 :
117 : // MaybeError implements the Injector interface.
118 0 : func (ii *InjectIndex) MaybeError(_ Op, _ string) error {
119 0 : if ii.index.Add(-1) == -1 {
120 0 : return errors.WithStack(ErrInjected)
121 0 : }
122 0 : return nil
123 : }
124 :
125 : // WithProbability returns a function that returns an error with the provided
126 : // probability when passed op. It may be passed to Wrap to inject an error
127 : // into an ErrFS with the provided probability. p should be within the range
128 : // [0.0,1.0].
129 1 : func WithProbability(op OpKind, p float64) Injector {
130 1 : mu := new(sync.Mutex)
131 1 : rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
132 1 : return InjectorFunc(func(currOp Op, _ string) error {
133 1 : mu.Lock()
134 1 : defer mu.Unlock()
135 1 : if currOp.OpKind() == op && rnd.Float64() < p {
136 0 : return errors.WithStack(ErrInjected)
137 0 : }
138 1 : return nil
139 : })
140 : }
141 :
142 : // InjectorFunc implements the Injector interface for a function with
143 : // MaybeError's signature.
144 : type InjectorFunc func(Op, string) error
145 :
146 : // MaybeError implements the Injector interface.
147 1 : func (f InjectorFunc) MaybeError(op Op, path string) error { return f(op, path) }
148 :
149 : // Injector injects errors into FS operations.
150 : type Injector interface {
151 : // MaybeError is invoked by an errorfs before an operation is executed. It
152 : // is passed an enum indicating the type of operation and a path of the
153 : // subject file or directory. If the operation takes two paths (eg,
154 : // Rename, Link), the original source path is provided.
155 : MaybeError(op Op, path string) error
156 : }
157 :
158 : // FS implements vfs.FS, injecting errors into
159 : // its operations.
160 : type FS struct {
161 : fs vfs.FS
162 : inj Injector
163 : }
164 :
165 : // Wrap wraps an existing vfs.FS implementation, returning a new
166 : // vfs.FS implementation that shadows operations to the provided FS.
167 : // It uses the provided Injector for deciding when to inject errors.
168 : // If an error is injected, FS propagates the error instead of
169 : // shadowing the operation.
170 1 : func Wrap(fs vfs.FS, inj Injector) *FS {
171 1 : return &FS{
172 1 : fs: fs,
173 1 : inj: inj,
174 1 : }
175 1 : }
176 :
177 : // WrapFile wraps an existing vfs.File, returning a new vfs.File that shadows
178 : // operations to the provided vfs.File. It uses the provided Injector for
179 : // deciding when to inject errors. If an error is injected, the file
180 : // propagates the error instead of shadowing the operation.
181 0 : func WrapFile(f vfs.File, inj Injector) vfs.File {
182 0 : return &errorFile{file: f, inj: inj}
183 0 : }
184 :
185 : // Unwrap returns the FS implementation underlying fs.
186 : // See pebble/vfs.Root.
187 1 : func (fs *FS) Unwrap() vfs.FS {
188 1 : return fs.fs
189 1 : }
190 :
191 : // Create implements FS.Create.
192 1 : func (fs *FS) Create(name string) (vfs.File, error) {
193 1 : if err := fs.inj.MaybeError(OpCreate, name); err != nil {
194 0 : return nil, err
195 0 : }
196 1 : f, err := fs.fs.Create(name)
197 1 : if err != nil {
198 0 : return nil, err
199 0 : }
200 1 : return &errorFile{name, f, fs.inj}, nil
201 : }
202 :
203 : // Link implements FS.Link.
204 1 : func (fs *FS) Link(oldname, newname string) error {
205 1 : if err := fs.inj.MaybeError(OpLink, oldname); err != nil {
206 0 : return err
207 0 : }
208 1 : return fs.fs.Link(oldname, newname)
209 : }
210 :
211 : // Open implements FS.Open.
212 1 : func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
213 1 : if err := fs.inj.MaybeError(OpOpen, name); err != nil {
214 0 : return nil, err
215 0 : }
216 1 : f, err := fs.fs.Open(name)
217 1 : if err != nil {
218 1 : return nil, err
219 1 : }
220 1 : ef := &errorFile{name, f, fs.inj}
221 1 : for _, opt := range opts {
222 1 : opt.Apply(ef)
223 1 : }
224 1 : return ef, nil
225 : }
226 :
227 : // OpenReadWrite implements FS.OpenReadWrite.
228 1 : func (fs *FS) OpenReadWrite(name string, opts ...vfs.OpenOption) (vfs.File, error) {
229 1 : if err := fs.inj.MaybeError(OpOpen, name); err != nil {
230 0 : return nil, err
231 0 : }
232 1 : f, err := fs.fs.OpenReadWrite(name)
233 1 : if err != nil {
234 0 : return nil, err
235 0 : }
236 1 : ef := &errorFile{name, f, fs.inj}
237 1 : for _, opt := range opts {
238 0 : opt.Apply(ef)
239 0 : }
240 1 : return ef, nil
241 : }
242 :
243 : // OpenDir implements FS.OpenDir.
244 1 : func (fs *FS) OpenDir(name string) (vfs.File, error) {
245 1 : if err := fs.inj.MaybeError(OpOpenDir, name); err != nil {
246 0 : return nil, err
247 0 : }
248 1 : f, err := fs.fs.OpenDir(name)
249 1 : if err != nil {
250 0 : return nil, err
251 0 : }
252 1 : return &errorFile{name, f, fs.inj}, nil
253 : }
254 :
255 : // GetDiskUsage implements FS.GetDiskUsage.
256 1 : func (fs *FS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
257 1 : if err := fs.inj.MaybeError(OpGetDiskUsage, path); err != nil {
258 0 : return vfs.DiskUsage{}, err
259 0 : }
260 1 : return fs.fs.GetDiskUsage(path)
261 : }
262 :
263 : // PathBase implements FS.PathBase.
264 1 : func (fs *FS) PathBase(p string) string {
265 1 : return fs.fs.PathBase(p)
266 1 : }
267 :
268 : // PathDir implements FS.PathDir.
269 1 : func (fs *FS) PathDir(p string) string {
270 1 : return fs.fs.PathDir(p)
271 1 : }
272 :
273 : // PathJoin implements FS.PathJoin.
274 1 : func (fs *FS) PathJoin(elem ...string) string {
275 1 : return fs.fs.PathJoin(elem...)
276 1 : }
277 :
278 : // Remove implements FS.Remove.
279 1 : func (fs *FS) Remove(name string) error {
280 1 : if _, err := fs.fs.Stat(name); oserror.IsNotExist(err) {
281 1 : return nil
282 1 : }
283 :
284 1 : if err := fs.inj.MaybeError(OpRemove, name); err != nil {
285 0 : return err
286 0 : }
287 1 : return fs.fs.Remove(name)
288 : }
289 :
290 : // RemoveAll implements FS.RemoveAll.
291 0 : func (fs *FS) RemoveAll(fullname string) error {
292 0 : if err := fs.inj.MaybeError(OpRemoveAll, fullname); err != nil {
293 0 : return err
294 0 : }
295 0 : return fs.fs.RemoveAll(fullname)
296 : }
297 :
298 : // Rename implements FS.Rename.
299 1 : func (fs *FS) Rename(oldname, newname string) error {
300 1 : if err := fs.inj.MaybeError(OpRename, oldname); err != nil {
301 0 : return err
302 0 : }
303 1 : return fs.fs.Rename(oldname, newname)
304 : }
305 :
306 : // ReuseForWrite implements FS.ReuseForWrite.
307 0 : func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
308 0 : if err := fs.inj.MaybeError(OpReuseForRewrite, oldname); err != nil {
309 0 : return nil, err
310 0 : }
311 0 : return fs.fs.ReuseForWrite(oldname, newname)
312 : }
313 :
314 : // MkdirAll implements FS.MkdirAll.
315 1 : func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
316 1 : if err := fs.inj.MaybeError(OpMkdirAll, dir); err != nil {
317 0 : return err
318 0 : }
319 1 : return fs.fs.MkdirAll(dir, perm)
320 : }
321 :
322 : // Lock implements FS.Lock.
323 1 : func (fs *FS) Lock(name string) (io.Closer, error) {
324 1 : if err := fs.inj.MaybeError(OpLock, name); err != nil {
325 0 : return nil, err
326 0 : }
327 1 : return fs.fs.Lock(name)
328 : }
329 :
330 : // List implements FS.List.
331 1 : func (fs *FS) List(dir string) ([]string, error) {
332 1 : if err := fs.inj.MaybeError(OpList, dir); err != nil {
333 0 : return nil, err
334 0 : }
335 1 : return fs.fs.List(dir)
336 : }
337 :
338 : // Stat implements FS.Stat.
339 1 : func (fs *FS) Stat(name string) (os.FileInfo, error) {
340 1 : if err := fs.inj.MaybeError(OpStat, name); err != nil {
341 0 : return nil, err
342 0 : }
343 1 : return fs.fs.Stat(name)
344 : }
345 :
346 : // errorFile implements vfs.File. The interface is implemented on the pointer
347 : // type to allow pointer equality comparisons.
348 : type errorFile struct {
349 : path string
350 : file vfs.File
351 : inj Injector
352 : }
353 :
354 1 : func (f *errorFile) Close() error {
355 1 : // We don't inject errors during close as those calls should never fail in
356 1 : // practice.
357 1 : return f.file.Close()
358 1 : }
359 :
360 1 : func (f *errorFile) Read(p []byte) (int, error) {
361 1 : if err := f.inj.MaybeError(OpFileRead, f.path); err != nil {
362 0 : return 0, err
363 0 : }
364 1 : return f.file.Read(p)
365 : }
366 :
367 1 : func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
368 1 : if err := f.inj.MaybeError(OpFileReadAt, f.path); err != nil {
369 0 : return 0, err
370 0 : }
371 1 : return f.file.ReadAt(p, off)
372 : }
373 :
374 1 : func (f *errorFile) Write(p []byte) (int, error) {
375 1 : if err := f.inj.MaybeError(OpFileWrite, f.path); err != nil {
376 0 : return 0, err
377 0 : }
378 1 : return f.file.Write(p)
379 : }
380 :
381 1 : func (f *errorFile) WriteAt(p []byte, ofs int64) (int, error) {
382 1 : if err := f.inj.MaybeError(OpFileWriteAt, f.path); err != nil {
383 0 : return 0, err
384 0 : }
385 1 : return f.file.WriteAt(p, ofs)
386 : }
387 :
388 1 : func (f *errorFile) Stat() (os.FileInfo, error) {
389 1 : if err := f.inj.MaybeError(OpFileStat, f.path); err != nil {
390 0 : return nil, err
391 0 : }
392 1 : return f.file.Stat()
393 : }
394 :
395 1 : func (f *errorFile) Prefetch(offset, length int64) error {
396 1 : // TODO(radu): Consider error injection.
397 1 : return f.file.Prefetch(offset, length)
398 1 : }
399 :
400 1 : func (f *errorFile) Preallocate(offset, length int64) error {
401 1 : if err := f.inj.MaybeError(OpFilePreallocate, f.path); err != nil {
402 0 : return err
403 0 : }
404 1 : return f.file.Preallocate(offset, length)
405 : }
406 :
407 1 : func (f *errorFile) Sync() error {
408 1 : if err := f.inj.MaybeError(OpFileSync, f.path); err != nil {
409 0 : return err
410 0 : }
411 1 : return f.file.Sync()
412 : }
413 :
414 1 : func (f *errorFile) SyncData() error {
415 1 : // TODO(jackson): Consider error injection.
416 1 : return f.file.SyncData()
417 1 : }
418 :
419 0 : func (f *errorFile) SyncTo(length int64) (fullSync bool, err error) {
420 0 : // TODO(jackson): Consider error injection.
421 0 : return f.file.SyncTo(length)
422 0 : }
423 :
424 1 : func (f *errorFile) Fd() uintptr {
425 1 : return f.file.Fd()
426 1 : }
|