blob: d2e3e4ab0b17fadbfbc7639d05173bb50e6da617 [file] [log] [blame]
Jeff Gaston9f6da312017-08-09 18:35:15 -07001// Copyright 2017 Google Inc. All rights reserved.
Jeff Gastonf1fd45e2017-08-09 18:25:28 -07002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package fs
16
17import (
18 "bytes"
19 "errors"
20 "fmt"
21 "io"
22 "io/ioutil"
23 "os"
24 "os/user"
25 "path/filepath"
26 "sync"
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070027 "time"
28)
29
30var OsFs FileSystem = osFs{}
31
32func NewMockFs(files map[string][]byte) *MockFs {
33 workDir := "/cwd"
34 fs := &MockFs{
35 Clock: NewClock(time.Unix(2, 2)),
36 workDir: workDir,
37 }
38 fs.root = *fs.newDir()
39 fs.MkDirs(workDir)
40
41 for path, bytes := range files {
42 dir := filepath.Dir(path)
43 fs.MkDirs(dir)
44 fs.WriteFile(path, bytes, 0777)
45 }
46
47 return fs
48}
49
50type FileSystem interface {
51 // getting information about files
52 Open(name string) (file io.ReadCloser, err error)
53 Lstat(path string) (stats os.FileInfo, err error)
Colin Cross7cdad452020-06-29 23:01:52 -070054 Stat(path string) (stats os.FileInfo, err error)
Colin Crossa88c8832017-12-21 15:39:26 -080055 ReadDir(path string) (contents []DirEntryInfo, err error)
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070056
57 InodeNumber(info os.FileInfo) (number uint64, err error)
58 DeviceNumber(info os.FileInfo) (number uint64, err error)
59 PermTime(info os.FileInfo) (time time.Time, err error)
60
61 // changing contents of the filesystem
62 Rename(oldPath string, newPath string) (err error)
63 WriteFile(path string, data []byte, perm os.FileMode) (err error)
64 Remove(path string) (err error)
65 RemoveAll(path string) (err error)
66
67 // metadata about the filesystem
68 ViewId() (id string) // Some unique id of the user accessing the filesystem
69}
70
Colin Crossa88c8832017-12-21 15:39:26 -080071// DentryInfo is a subset of the functionality available through os.FileInfo that might be able
72// to be gleaned through only a syscall.Getdents without requiring a syscall.Lstat of every file.
73type DirEntryInfo interface {
74 Name() string
75 Mode() os.FileMode // the file type encoded as an os.FileMode
76 IsDir() bool
77}
78
Colin Crossae7fd6b2017-12-21 16:44:26 -080079type dirEntryInfo struct {
80 name string
81 mode os.FileMode
82 modeExists bool
83}
84
Colin Crossa88c8832017-12-21 15:39:26 -080085var _ DirEntryInfo = os.FileInfo(nil)
86
Colin Crossae7fd6b2017-12-21 16:44:26 -080087func (d *dirEntryInfo) Name() string { return d.name }
88func (d *dirEntryInfo) Mode() os.FileMode { return d.mode }
89func (d *dirEntryInfo) IsDir() bool { return d.mode.IsDir() }
90func (d *dirEntryInfo) String() string { return d.name + ": " + d.mode.String() }
91
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070092// osFs implements FileSystem using the local disk.
93type osFs struct{}
94
Colin Crossa88c8832017-12-21 15:39:26 -080095var _ FileSystem = (*osFs)(nil)
96
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070097func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
98
99func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
100 return os.Lstat(path)
101}
102
Colin Cross7cdad452020-06-29 23:01:52 -0700103func (osFs) Stat(path string) (stats os.FileInfo, err error) {
104 return os.Stat(path)
105}
106
Colin Crossa88c8832017-12-21 15:39:26 -0800107func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
Colin Crossae7fd6b2017-12-21 16:44:26 -0800108 entries, err := readdir(path)
Colin Crossa88c8832017-12-21 15:39:26 -0800109 if err != nil {
110 return nil, err
111 }
112 for _, entry := range entries {
113 contents = append(contents, entry)
114 }
115
116 return contents, nil
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700117}
Colin Crossa88c8832017-12-21 15:39:26 -0800118
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700119func (osFs) Rename(oldPath string, newPath string) error {
120 return os.Rename(oldPath, newPath)
121}
122
123func (osFs) WriteFile(path string, data []byte, perm os.FileMode) error {
124 return ioutil.WriteFile(path, data, perm)
125}
126
127func (osFs) Remove(path string) error {
128 return os.Remove(path)
129}
130
131func (osFs) RemoveAll(path string) error {
132 return os.RemoveAll(path)
133}
134
135func (osFs) ViewId() (id string) {
136 user, err := user.Current()
137 if err != nil {
138 return ""
139 }
140 username := user.Username
141
142 hostname, err := os.Hostname()
143 if err != nil {
144 return ""
145 }
146
147 return username + "@" + hostname
148}
149
150type Clock struct {
151 time time.Time
152}
153
154func NewClock(startTime time.Time) *Clock {
155 return &Clock{time: startTime}
156
157}
158
159func (c *Clock) Tick() {
160 c.time = c.time.Add(time.Microsecond)
161}
162
163func (c *Clock) Time() time.Time {
164 return c.time
165}
166
167// given "/a/b/c/d", pathSplit returns ("/a/b/c", "d")
168func pathSplit(path string) (dir string, leaf string) {
169 dir, leaf = filepath.Split(path)
170 if dir != "/" && len(dir) > 0 {
171 dir = dir[:len(dir)-1]
172 }
173 return dir, leaf
174}
175
176// MockFs supports singlethreaded writes and multithreaded reads
177type MockFs struct {
178 // configuration
179 viewId string //
180 deviceNumber uint64
181
182 // implementation
183 root mockDir
184 Clock *Clock
185 workDir string
186 nextInodeNumber uint64
187
188 // history of requests, for tests to check
189 StatCalls []string
190 ReadDirCalls []string
191 aggregatesLock sync.Mutex
192}
193
Colin Crossa88c8832017-12-21 15:39:26 -0800194var _ FileSystem = (*MockFs)(nil)
195
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700196type mockInode struct {
197 modTime time.Time
198 permTime time.Time
199 sys interface{}
200 inodeNumber uint64
Jeff Gastonb629e182017-08-14 16:49:18 -0700201 readErr error
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700202}
203
204func (m mockInode) ModTime() time.Time {
205 return m.modTime
206}
207
208func (m mockInode) Sys() interface{} {
209 return m.sys
210}
211
212type mockFile struct {
213 bytes []byte
214
215 mockInode
216}
217
218type mockLink struct {
219 target string
220
221 mockInode
222}
223
224type mockDir struct {
225 mockInode
226
227 subdirs map[string]*mockDir
228 files map[string]*mockFile
229 symlinks map[string]*mockLink
230}
231
232func (m *MockFs) resolve(path string, followLastLink bool) (result string, err error) {
233 if !filepath.IsAbs(path) {
234 path = filepath.Join(m.workDir, path)
235 }
236 path = filepath.Clean(path)
237
238 return m.followLinks(path, followLastLink, 10)
239}
240
241// note that followLinks can return a file path that doesn't exist
242func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canonicalPath string, err error) {
243 if path == "/" {
244 return path, nil
245 }
246
247 parentPath, leaf := pathSplit(path)
248 if parentPath == path {
249 err = fmt.Errorf("Internal error: %v yields itself as a parent", path)
250 panic(err.Error())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700251 }
252
253 parentPath, err = m.followLinks(parentPath, true, count)
254 if err != nil {
255 return "", err
256 }
257
258 parentNode, err := m.getDir(parentPath, false)
259 if err != nil {
260 return "", err
261 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700262 if parentNode.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700263 return "", &os.PathError{
264 Op: "read",
265 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700266 Err: parentNode.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700267 }
268 }
269
270 link, isLink := parentNode.symlinks[leaf]
271 if isLink && followLastLink {
272 if count <= 0 {
273 // probably a loop
274 return "", &os.PathError{
275 Op: "read",
276 Path: path,
277 Err: fmt.Errorf("too many levels of symbolic links"),
278 }
279 }
280
Jeff Gastonb629e182017-08-14 16:49:18 -0700281 if link.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700282 return "", &os.PathError{
283 Op: "read",
284 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700285 Err: link.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700286 }
287 }
288
289 target := m.followLink(link, parentPath)
290 return m.followLinks(target, followLastLink, count-1)
291 }
292 return path, nil
293}
294
295func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) {
296 return filepath.Clean(filepath.Join(parentPath, link.target))
297}
298
299func (m *MockFs) getFile(parentDir *mockDir, fileName string) (file *mockFile, err error) {
300 file, isFile := parentDir.files[fileName]
301 if !isFile {
302 _, isDir := parentDir.subdirs[fileName]
303 _, isLink := parentDir.symlinks[fileName]
304 if isDir || isLink {
305 return nil, &os.PathError{
306 Op: "open",
307 Path: fileName,
308 Err: os.ErrInvalid,
309 }
310 }
311
312 return nil, &os.PathError{
313 Op: "open",
314 Path: fileName,
315 Err: os.ErrNotExist,
316 }
317 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700318 if file.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700319 return nil, &os.PathError{
320 Op: "open",
321 Path: fileName,
Jeff Gastonb629e182017-08-14 16:49:18 -0700322 Err: file.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700323 }
324 }
325 return file, nil
326}
327
328func (m *MockFs) getInode(parentDir *mockDir, name string) (inode *mockInode, err error) {
329 file, isFile := parentDir.files[name]
330 if isFile {
331 return &file.mockInode, nil
332 }
333 link, isLink := parentDir.symlinks[name]
334 if isLink {
335 return &link.mockInode, nil
336 }
337 dir, isDir := parentDir.subdirs[name]
338 if isDir {
339 return &dir.mockInode, nil
340 }
341 return nil, &os.PathError{
342 Op: "stat",
343 Path: name,
344 Err: os.ErrNotExist,
345 }
346
347}
348
349func (m *MockFs) Open(path string) (io.ReadCloser, error) {
350 path, err := m.resolve(path, true)
351 if err != nil {
352 return nil, err
353 }
354
355 if err != nil {
356 return nil, err
357 }
358
359 parentPath, base := pathSplit(path)
360 parentDir, err := m.getDir(parentPath, false)
361 if err != nil {
362 return nil, err
363 }
364 file, err := m.getFile(parentDir, base)
365 if err != nil {
366 return nil, err
367 }
368 return struct {
369 io.Closer
370 *bytes.Reader
371 }{
372 ioutil.NopCloser(nil),
373 bytes.NewReader(file.bytes),
374 }, nil
375
376}
377
378// a mockFileInfo is for exporting file stats in a way that satisfies the FileInfo interface
379type mockFileInfo struct {
380 path string
381 size int64
382 modTime time.Time // time at which the inode's contents were modified
383 permTime time.Time // time at which the inode's permissions were modified
Colin Cross7cdad452020-06-29 23:01:52 -0700384 mode os.FileMode
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700385 inodeNumber uint64
386 deviceNumber uint64
387}
388
389func (m *mockFileInfo) Name() string {
390 return m.path
391}
392
393func (m *mockFileInfo) Size() int64 {
394 return m.size
395}
396
397func (m *mockFileInfo) Mode() os.FileMode {
Colin Cross7cdad452020-06-29 23:01:52 -0700398 return m.mode
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700399}
400
401func (m *mockFileInfo) ModTime() time.Time {
402 return m.modTime
403}
404
405func (m *mockFileInfo) IsDir() bool {
Colin Cross7cdad452020-06-29 23:01:52 -0700406 return m.mode&os.ModeDir != 0
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700407}
408
409func (m *mockFileInfo) Sys() interface{} {
410 return nil
411}
412
413func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
414 return &mockFileInfo{
Colin Cross7cdad452020-06-29 23:01:52 -0700415 path: filepath.Base(path),
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700416 size: 1,
417 modTime: d.modTime,
418 permTime: d.permTime,
Colin Cross7cdad452020-06-29 23:01:52 -0700419 mode: os.ModeDir,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700420 inodeNumber: d.inodeNumber,
421 deviceNumber: m.deviceNumber,
422 }
423
424}
425
426func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
427 return &mockFileInfo{
Colin Cross7cdad452020-06-29 23:01:52 -0700428 path: filepath.Base(path),
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700429 size: 1,
430 modTime: f.modTime,
431 permTime: f.permTime,
Colin Cross7cdad452020-06-29 23:01:52 -0700432 mode: 0,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700433 inodeNumber: f.inodeNumber,
434 deviceNumber: m.deviceNumber,
435 }
436}
437
438func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
439 return &mockFileInfo{
Colin Cross7cdad452020-06-29 23:01:52 -0700440 path: filepath.Base(path),
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700441 size: 1,
442 modTime: l.modTime,
443 permTime: l.permTime,
Colin Cross7cdad452020-06-29 23:01:52 -0700444 mode: os.ModeSymlink,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700445 inodeNumber: l.inodeNumber,
446 deviceNumber: m.deviceNumber,
447 }
448}
449
450func (m *MockFs) Lstat(path string) (stats os.FileInfo, err error) {
451 // update aggregates
452 m.aggregatesLock.Lock()
453 m.StatCalls = append(m.StatCalls, path)
454 m.aggregatesLock.Unlock()
455
456 // resolve symlinks
457 path, err = m.resolve(path, false)
458 if err != nil {
459 return nil, err
460 }
461
462 // special case for root dir
463 if path == "/" {
464 return m.dirToFileInfo(&m.root, "/"), nil
465 }
466
467 // determine type and handle appropriately
468 parentPath, baseName := pathSplit(path)
469 dir, err := m.getDir(parentPath, false)
470 if err != nil {
471 return nil, err
472 }
473 subdir, subdirExists := dir.subdirs[baseName]
474 if subdirExists {
475 return m.dirToFileInfo(subdir, path), nil
476 }
477 file, fileExists := dir.files[baseName]
478 if fileExists {
479 return m.fileToFileInfo(file, path), nil
480 }
481 link, linkExists := dir.symlinks[baseName]
482 if linkExists {
483 return m.linkToFileInfo(link, path), nil
484 }
485 // not found
486 return nil, &os.PathError{
487 Op: "stat",
488 Path: path,
489 Err: os.ErrNotExist,
490 }
491}
492
Colin Cross7cdad452020-06-29 23:01:52 -0700493func (m *MockFs) Stat(path string) (stats os.FileInfo, err error) {
494 // resolve symlinks
495 path, err = m.resolve(path, true)
496 if err != nil {
497 return nil, err
498 }
499
500 return m.Lstat(path)
501}
502
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700503func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
504 mockInfo, ok := info.(*mockFileInfo)
505 if ok {
506 return mockInfo.inodeNumber, nil
507 }
508 return 0, fmt.Errorf("%v is not a mockFileInfo", info)
509}
510func (m *MockFs) DeviceNumber(info os.FileInfo) (number uint64, err error) {
511 mockInfo, ok := info.(*mockFileInfo)
512 if ok {
513 return mockInfo.deviceNumber, nil
514 }
515 return 0, fmt.Errorf("%v is not a mockFileInfo", info)
516}
517func (m *MockFs) PermTime(info os.FileInfo) (when time.Time, err error) {
518 mockInfo, ok := info.(*mockFileInfo)
519 if ok {
520 return mockInfo.permTime, nil
521 }
522 return time.Date(0, 0, 0, 0, 0, 0, 0, nil),
523 fmt.Errorf("%v is not a mockFileInfo", info)
524}
525
Colin Crossa88c8832017-12-21 15:39:26 -0800526func (m *MockFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700527 // update aggregates
528 m.aggregatesLock.Lock()
529 m.ReadDirCalls = append(m.ReadDirCalls, path)
530 m.aggregatesLock.Unlock()
531
532 // locate directory
533 path, err = m.resolve(path, true)
534 if err != nil {
535 return nil, err
536 }
Colin Crossa88c8832017-12-21 15:39:26 -0800537 results := []DirEntryInfo{}
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700538 dir, err := m.getDir(path, false)
539 if err != nil {
540 return nil, err
541 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700542 if dir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700543 return nil, &os.PathError{
544 Op: "read",
545 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700546 Err: dir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700547 }
548 }
549 // describe its contents
550 for name, subdir := range dir.subdirs {
551 dirInfo := m.dirToFileInfo(subdir, name)
552 results = append(results, dirInfo)
553 }
554 for name, file := range dir.files {
555 info := m.fileToFileInfo(file, name)
556 results = append(results, info)
557 }
558 for name, link := range dir.symlinks {
559 info := m.linkToFileInfo(link, name)
560 results = append(results, info)
561 }
562 return results, nil
563}
564
565func (m *MockFs) Rename(sourcePath string, destPath string) error {
566 // validate source parent exists
567 sourcePath, err := m.resolve(sourcePath, false)
568 if err != nil {
569 return err
570 }
571 sourceParentPath := filepath.Dir(sourcePath)
572 sourceParentDir, err := m.getDir(sourceParentPath, false)
573 if err != nil {
574 return err
575 }
576 if sourceParentDir == nil {
577 return &os.PathError{
578 Op: "move",
579 Path: sourcePath,
580 Err: os.ErrNotExist,
581 }
582 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700583 if sourceParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700584 return &os.PathError{
585 Op: "move",
586 Path: sourcePath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700587 Err: sourceParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700588 }
589 }
590
591 // validate dest parent exists
592 destPath, err = m.resolve(destPath, false)
593 destParentPath := filepath.Dir(destPath)
594 destParentDir, err := m.getDir(destParentPath, false)
595 if err != nil {
596 return err
597 }
598 if destParentDir == nil {
599 return &os.PathError{
600 Op: "move",
601 Path: destParentPath,
602 Err: os.ErrNotExist,
603 }
604 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700605 if destParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700606 return &os.PathError{
607 Op: "move",
608 Path: destParentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700609 Err: destParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700610 }
611 }
612 // check the source and dest themselves
613 sourceBase := filepath.Base(sourcePath)
614 destBase := filepath.Base(destPath)
615
616 file, sourceIsFile := sourceParentDir.files[sourceBase]
617 dir, sourceIsDir := sourceParentDir.subdirs[sourceBase]
618 link, sourceIsLink := sourceParentDir.symlinks[sourceBase]
619
620 // validate that the source exists
621 if !sourceIsFile && !sourceIsDir && !sourceIsLink {
622 return &os.PathError{
623 Op: "move",
624 Path: sourcePath,
625 Err: os.ErrNotExist,
626 }
627
628 }
629
630 // validate the destination doesn't already exist as an incompatible type
631 _, destWasFile := destParentDir.files[destBase]
632 _, destWasDir := destParentDir.subdirs[destBase]
633 _, destWasLink := destParentDir.symlinks[destBase]
634
635 if destWasDir {
636 return &os.PathError{
637 Op: "move",
638 Path: destPath,
639 Err: errors.New("destination exists as a directory"),
640 }
641 }
642
643 if sourceIsDir && (destWasFile || destWasLink) {
644 return &os.PathError{
645 Op: "move",
646 Path: destPath,
647 Err: errors.New("destination exists as a file"),
648 }
649 }
650
651 if destWasFile {
652 delete(destParentDir.files, destBase)
653 }
654 if destWasDir {
655 delete(destParentDir.subdirs, destBase)
656 }
657 if destWasLink {
658 delete(destParentDir.symlinks, destBase)
659 }
660
661 if sourceIsFile {
662 destParentDir.files[destBase] = file
663 delete(sourceParentDir.files, sourceBase)
664 }
665 if sourceIsDir {
666 destParentDir.subdirs[destBase] = dir
667 delete(sourceParentDir.subdirs, sourceBase)
668 }
669 if sourceIsLink {
670 destParentDir.symlinks[destBase] = link
671 delete(destParentDir.symlinks, sourceBase)
672 }
673
674 destParentDir.modTime = m.Clock.Time()
675 sourceParentDir.modTime = m.Clock.Time()
676 return nil
677}
678
679func (m *MockFs) newInodeNumber() uint64 {
680 result := m.nextInodeNumber
681 m.nextInodeNumber++
682 return result
683}
684
685func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error {
686 filePath, err := m.resolve(filePath, true)
687 if err != nil {
688 return err
689 }
690 parentPath := filepath.Dir(filePath)
691 parentDir, err := m.getDir(parentPath, false)
692 if err != nil || parentDir == nil {
693 return &os.PathError{
694 Op: "write",
695 Path: parentPath,
696 Err: os.ErrNotExist,
697 }
698 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700699 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700700 return &os.PathError{
701 Op: "write",
702 Path: parentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700703 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700704 }
705 }
706
707 baseName := filepath.Base(filePath)
708 _, exists := parentDir.files[baseName]
709 if !exists {
710 parentDir.modTime = m.Clock.Time()
711 parentDir.files[baseName] = m.newFile()
712 } else {
Jeff Gastonb629e182017-08-14 16:49:18 -0700713 readErr := parentDir.files[baseName].readErr
714 if readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700715 return &os.PathError{
716 Op: "write",
717 Path: filePath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700718 Err: readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700719 }
720 }
721 }
722 file := parentDir.files[baseName]
723 file.bytes = data
724 file.modTime = m.Clock.Time()
725 return nil
726}
727
728func (m *MockFs) newFile() *mockFile {
729 newFile := &mockFile{}
730 newFile.inodeNumber = m.newInodeNumber()
731 newFile.modTime = m.Clock.Time()
732 newFile.permTime = newFile.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700733 return newFile
734}
735
736func (m *MockFs) newDir() *mockDir {
737 newDir := &mockDir{
738 subdirs: make(map[string]*mockDir, 0),
739 files: make(map[string]*mockFile, 0),
740 symlinks: make(map[string]*mockLink, 0),
741 }
742 newDir.inodeNumber = m.newInodeNumber()
743 newDir.modTime = m.Clock.Time()
744 newDir.permTime = newDir.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700745 return newDir
746}
747
748func (m *MockFs) newLink(target string) *mockLink {
749 newLink := &mockLink{
750 target: target,
751 }
752 newLink.inodeNumber = m.newInodeNumber()
753 newLink.modTime = m.Clock.Time()
754 newLink.permTime = newLink.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700755
756 return newLink
757}
758func (m *MockFs) MkDirs(path string) error {
759 _, err := m.getDir(path, true)
760 return err
761}
762
763// getDir doesn't support symlinks
764func (m *MockFs) getDir(path string, createIfMissing bool) (dir *mockDir, err error) {
765 cleanedPath := filepath.Clean(path)
766 if cleanedPath == "/" {
767 return &m.root, nil
768 }
769
770 parentPath, leaf := pathSplit(cleanedPath)
771 if len(parentPath) >= len(path) {
772 return &m.root, nil
773 }
774 parent, err := m.getDir(parentPath, createIfMissing)
775 if err != nil {
776 return nil, err
777 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700778 if parent.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700779 return nil, &os.PathError{
780 Op: "stat",
781 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700782 Err: parent.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700783 }
784 }
785 childDir, dirExists := parent.subdirs[leaf]
786 if !dirExists {
787 if createIfMissing {
788 // confirm that a file with the same name doesn't already exist
789 _, fileExists := parent.files[leaf]
790 if fileExists {
791 return nil, &os.PathError{
792 Op: "mkdir",
793 Path: path,
794 Err: os.ErrExist,
795 }
796 }
797 // create this directory
798 childDir = m.newDir()
799 parent.subdirs[leaf] = childDir
800 parent.modTime = m.Clock.Time()
801 } else {
802 return nil, &os.PathError{
803 Op: "stat",
804 Path: path,
805 Err: os.ErrNotExist,
806 }
807 }
808 }
809 return childDir, nil
810
811}
812
813func (m *MockFs) Remove(path string) (err error) {
814 path, err = m.resolve(path, false)
815 parentPath, leaf := pathSplit(path)
816 if len(leaf) == 0 {
817 return fmt.Errorf("Cannot remove %v\n", path)
818 }
819 parentDir, err := m.getDir(parentPath, false)
820 if err != nil {
821 return err
822 }
823 if parentDir == nil {
824 return &os.PathError{
825 Op: "remove",
826 Path: path,
827 Err: os.ErrNotExist,
828 }
829 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700830 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700831 return &os.PathError{
832 Op: "remove",
833 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700834 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700835 }
836 }
837 _, isDir := parentDir.subdirs[leaf]
838 if isDir {
839 return &os.PathError{
840 Op: "remove",
841 Path: path,
842 Err: os.ErrInvalid,
843 }
844 }
845 _, isLink := parentDir.symlinks[leaf]
846 if isLink {
847 delete(parentDir.symlinks, leaf)
848 } else {
849 _, isFile := parentDir.files[leaf]
850 if !isFile {
851 return &os.PathError{
852 Op: "remove",
853 Path: path,
854 Err: os.ErrNotExist,
855 }
856 }
857 delete(parentDir.files, leaf)
858 }
859 parentDir.modTime = m.Clock.Time()
860 return nil
861}
862
863func (m *MockFs) Symlink(oldPath string, newPath string) (err error) {
864 newPath, err = m.resolve(newPath, false)
865 if err != nil {
866 return err
867 }
868
869 newParentPath, leaf := pathSplit(newPath)
870 newParentDir, err := m.getDir(newParentPath, false)
Jeff Gastonb629e182017-08-14 16:49:18 -0700871 if newParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700872 return &os.PathError{
873 Op: "link",
874 Path: newPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700875 Err: newParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700876 }
877 }
878 if err != nil {
879 return err
880 }
881 newParentDir.symlinks[leaf] = m.newLink(oldPath)
882 return nil
883}
884
885func (m *MockFs) RemoveAll(path string) (err error) {
886 path, err = m.resolve(path, false)
887 if err != nil {
888 return err
889 }
890 parentPath, leaf := pathSplit(path)
891 if len(leaf) == 0 {
892 return fmt.Errorf("Cannot remove %v\n", path)
893 }
894 parentDir, err := m.getDir(parentPath, false)
895 if err != nil {
896 return err
897 }
898 if parentDir == nil {
899 return &os.PathError{
900 Op: "removeAll",
901 Path: path,
902 Err: os.ErrNotExist,
903 }
904 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700905 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700906 return &os.PathError{
907 Op: "removeAll",
908 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700909 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700910 }
911
912 }
913 _, isFile := parentDir.files[leaf]
914 _, isLink := parentDir.symlinks[leaf]
915 if isFile || isLink {
916 return m.Remove(path)
917 }
918 _, isDir := parentDir.subdirs[leaf]
919 if !isDir {
920 if !isDir {
921 return &os.PathError{
922 Op: "removeAll",
923 Path: path,
924 Err: os.ErrNotExist,
925 }
926 }
927 }
928
929 delete(parentDir.subdirs, leaf)
930 parentDir.modTime = m.Clock.Time()
931 return nil
932}
933
934func (m *MockFs) SetReadable(path string, readable bool) error {
Jeff Gastonb629e182017-08-14 16:49:18 -0700935 var readErr error
936 if !readable {
937 readErr = os.ErrPermission
938 }
939 return m.SetReadErr(path, readErr)
940}
941
942func (m *MockFs) SetReadErr(path string, readErr error) error {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700943 path, err := m.resolve(path, false)
944 if err != nil {
945 return err
946 }
947 parentPath, leaf := filepath.Split(path)
948 parentDir, err := m.getDir(parentPath, false)
949 if err != nil {
950 return err
951 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700952 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700953 return &os.PathError{
954 Op: "chmod",
955 Path: parentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700956 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700957 }
958 }
959
960 inode, err := m.getInode(parentDir, leaf)
961 if err != nil {
962 return err
963 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700964 inode.readErr = readErr
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700965 inode.permTime = m.Clock.Time()
966 return nil
967}
968
969func (m *MockFs) ClearMetrics() {
970 m.ReadDirCalls = []string{}
971 m.StatCalls = []string{}
972}
973
974func (m *MockFs) ViewId() (id string) {
975 return m.viewId
976}
977
978func (m *MockFs) SetViewId(id string) {
979 m.viewId = id
980}
981func (m *MockFs) SetDeviceNumber(deviceNumber uint64) {
982 m.deviceNumber = deviceNumber
983}