blob: c6060c0084d49406529482081261671bd11898e0 [file] [log] [blame]
Jeff Gastonf1fd45e2017-08-09 18:25:28 -07001// Copyright 2016 Google Inc. All rights reserved.
2//
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"
27 "syscall"
28 "time"
29)
30
31var OsFs FileSystem = osFs{}
32
33func NewMockFs(files map[string][]byte) *MockFs {
34 workDir := "/cwd"
35 fs := &MockFs{
36 Clock: NewClock(time.Unix(2, 2)),
37 workDir: workDir,
38 }
39 fs.root = *fs.newDir()
40 fs.MkDirs(workDir)
41
42 for path, bytes := range files {
43 dir := filepath.Dir(path)
44 fs.MkDirs(dir)
45 fs.WriteFile(path, bytes, 0777)
46 }
47
48 return fs
49}
50
51type FileSystem interface {
52 // getting information about files
53 Open(name string) (file io.ReadCloser, err error)
54 Lstat(path string) (stats os.FileInfo, err error)
55 ReadDir(path string) (contents []os.FileInfo, err error)
56
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
71// osFs implements FileSystem using the local disk.
72type osFs struct{}
73
74func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
75
76func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
77 return os.Lstat(path)
78}
79
80func (osFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
81 sys := info.Sys()
82 linuxStats, ok := sys.(*syscall.Stat_t)
83 if ok {
84 return linuxStats.Ino, nil
85 }
86 return 0, fmt.Errorf("%v is not a *syscall.Stat_t", sys)
87}
88
89func (osFs) DeviceNumber(info os.FileInfo) (number uint64, err error) {
90 sys := info.Sys()
91 linuxStats, ok := sys.(*syscall.Stat_t)
92 if ok {
93 return linuxStats.Dev, nil
94 }
95 return 0, fmt.Errorf("%v is not a *syscall.Stat_t", sys)
96}
97
98func (osFs) PermTime(info os.FileInfo) (when time.Time, err error) {
99 sys := info.Sys()
100 linuxStats, ok := sys.(*syscall.Stat_t)
101 if ok {
102 return time.Unix(linuxStats.Ctim.Sec, linuxStats.Ctim.Nsec), nil
103 }
104 return time.Date(0, 0, 0, 0, 0, 0, 0, nil), fmt.Errorf("%v is not a *syscall.Stat_t", sys)
105}
106
107func (osFs) ReadDir(path string) (contents []os.FileInfo, err error) {
108 return ioutil.ReadDir(path)
109}
110func (osFs) Rename(oldPath string, newPath string) error {
111 return os.Rename(oldPath, newPath)
112}
113
114func (osFs) WriteFile(path string, data []byte, perm os.FileMode) error {
115 return ioutil.WriteFile(path, data, perm)
116}
117
118func (osFs) Remove(path string) error {
119 return os.Remove(path)
120}
121
122func (osFs) RemoveAll(path string) error {
123 return os.RemoveAll(path)
124}
125
126func (osFs) ViewId() (id string) {
127 user, err := user.Current()
128 if err != nil {
129 return ""
130 }
131 username := user.Username
132
133 hostname, err := os.Hostname()
134 if err != nil {
135 return ""
136 }
137
138 return username + "@" + hostname
139}
140
141type Clock struct {
142 time time.Time
143}
144
145func NewClock(startTime time.Time) *Clock {
146 return &Clock{time: startTime}
147
148}
149
150func (c *Clock) Tick() {
151 c.time = c.time.Add(time.Microsecond)
152}
153
154func (c *Clock) Time() time.Time {
155 return c.time
156}
157
158// given "/a/b/c/d", pathSplit returns ("/a/b/c", "d")
159func pathSplit(path string) (dir string, leaf string) {
160 dir, leaf = filepath.Split(path)
161 if dir != "/" && len(dir) > 0 {
162 dir = dir[:len(dir)-1]
163 }
164 return dir, leaf
165}
166
167// MockFs supports singlethreaded writes and multithreaded reads
168type MockFs struct {
169 // configuration
170 viewId string //
171 deviceNumber uint64
172
173 // implementation
174 root mockDir
175 Clock *Clock
176 workDir string
177 nextInodeNumber uint64
178
179 // history of requests, for tests to check
180 StatCalls []string
181 ReadDirCalls []string
182 aggregatesLock sync.Mutex
183}
184
185type mockInode struct {
186 modTime time.Time
187 permTime time.Time
188 sys interface{}
189 inodeNumber uint64
190 readable bool
191}
192
193func (m mockInode) ModTime() time.Time {
194 return m.modTime
195}
196
197func (m mockInode) Sys() interface{} {
198 return m.sys
199}
200
201type mockFile struct {
202 bytes []byte
203
204 mockInode
205}
206
207type mockLink struct {
208 target string
209
210 mockInode
211}
212
213type mockDir struct {
214 mockInode
215
216 subdirs map[string]*mockDir
217 files map[string]*mockFile
218 symlinks map[string]*mockLink
219}
220
221func (m *MockFs) resolve(path string, followLastLink bool) (result string, err error) {
222 if !filepath.IsAbs(path) {
223 path = filepath.Join(m.workDir, path)
224 }
225 path = filepath.Clean(path)
226
227 return m.followLinks(path, followLastLink, 10)
228}
229
230// note that followLinks can return a file path that doesn't exist
231func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canonicalPath string, err error) {
232 if path == "/" {
233 return path, nil
234 }
235
236 parentPath, leaf := pathSplit(path)
237 if parentPath == path {
238 err = fmt.Errorf("Internal error: %v yields itself as a parent", path)
239 panic(err.Error())
240 return "", fmt.Errorf("Internal error: %v yields itself as a parent", path)
241 }
242
243 parentPath, err = m.followLinks(parentPath, true, count)
244 if err != nil {
245 return "", err
246 }
247
248 parentNode, err := m.getDir(parentPath, false)
249 if err != nil {
250 return "", err
251 }
252 if !parentNode.readable {
253 return "", &os.PathError{
254 Op: "read",
255 Path: path,
256 Err: os.ErrPermission,
257 }
258 }
259
260 link, isLink := parentNode.symlinks[leaf]
261 if isLink && followLastLink {
262 if count <= 0 {
263 // probably a loop
264 return "", &os.PathError{
265 Op: "read",
266 Path: path,
267 Err: fmt.Errorf("too many levels of symbolic links"),
268 }
269 }
270
271 if !link.readable {
272 return "", &os.PathError{
273 Op: "read",
274 Path: path,
275 Err: os.ErrPermission,
276 }
277 }
278
279 target := m.followLink(link, parentPath)
280 return m.followLinks(target, followLastLink, count-1)
281 }
282 return path, nil
283}
284
285func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) {
286 return filepath.Clean(filepath.Join(parentPath, link.target))
287}
288
289func (m *MockFs) getFile(parentDir *mockDir, fileName string) (file *mockFile, err error) {
290 file, isFile := parentDir.files[fileName]
291 if !isFile {
292 _, isDir := parentDir.subdirs[fileName]
293 _, isLink := parentDir.symlinks[fileName]
294 if isDir || isLink {
295 return nil, &os.PathError{
296 Op: "open",
297 Path: fileName,
298 Err: os.ErrInvalid,
299 }
300 }
301
302 return nil, &os.PathError{
303 Op: "open",
304 Path: fileName,
305 Err: os.ErrNotExist,
306 }
307 }
308 if !file.readable {
309 return nil, &os.PathError{
310 Op: "open",
311 Path: fileName,
312 Err: os.ErrPermission,
313 }
314 }
315 return file, nil
316}
317
318func (m *MockFs) getInode(parentDir *mockDir, name string) (inode *mockInode, err error) {
319 file, isFile := parentDir.files[name]
320 if isFile {
321 return &file.mockInode, nil
322 }
323 link, isLink := parentDir.symlinks[name]
324 if isLink {
325 return &link.mockInode, nil
326 }
327 dir, isDir := parentDir.subdirs[name]
328 if isDir {
329 return &dir.mockInode, nil
330 }
331 return nil, &os.PathError{
332 Op: "stat",
333 Path: name,
334 Err: os.ErrNotExist,
335 }
336
337}
338
339func (m *MockFs) Open(path string) (io.ReadCloser, error) {
340 path, err := m.resolve(path, true)
341 if err != nil {
342 return nil, err
343 }
344
345 if err != nil {
346 return nil, err
347 }
348
349 parentPath, base := pathSplit(path)
350 parentDir, err := m.getDir(parentPath, false)
351 if err != nil {
352 return nil, err
353 }
354 file, err := m.getFile(parentDir, base)
355 if err != nil {
356 return nil, err
357 }
358 return struct {
359 io.Closer
360 *bytes.Reader
361 }{
362 ioutil.NopCloser(nil),
363 bytes.NewReader(file.bytes),
364 }, nil
365
366}
367
368// a mockFileInfo is for exporting file stats in a way that satisfies the FileInfo interface
369type mockFileInfo struct {
370 path string
371 size int64
372 modTime time.Time // time at which the inode's contents were modified
373 permTime time.Time // time at which the inode's permissions were modified
374 isDir bool
375 inodeNumber uint64
376 deviceNumber uint64
377}
378
379func (m *mockFileInfo) Name() string {
380 return m.path
381}
382
383func (m *mockFileInfo) Size() int64 {
384 return m.size
385}
386
387func (m *mockFileInfo) Mode() os.FileMode {
388 return 0
389}
390
391func (m *mockFileInfo) ModTime() time.Time {
392 return m.modTime
393}
394
395func (m *mockFileInfo) IsDir() bool {
396 return m.isDir
397}
398
399func (m *mockFileInfo) Sys() interface{} {
400 return nil
401}
402
403func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
404 return &mockFileInfo{
405 path: path,
406 size: 1,
407 modTime: d.modTime,
408 permTime: d.permTime,
409 isDir: true,
410 inodeNumber: d.inodeNumber,
411 deviceNumber: m.deviceNumber,
412 }
413
414}
415
416func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
417 return &mockFileInfo{
418 path: path,
419 size: 1,
420 modTime: f.modTime,
421 permTime: f.permTime,
422 isDir: false,
423 inodeNumber: f.inodeNumber,
424 deviceNumber: m.deviceNumber,
425 }
426}
427
428func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
429 return &mockFileInfo{
430 path: path,
431 size: 1,
432 modTime: l.modTime,
433 permTime: l.permTime,
434 isDir: false,
435 inodeNumber: l.inodeNumber,
436 deviceNumber: m.deviceNumber,
437 }
438}
439
440func (m *MockFs) Lstat(path string) (stats os.FileInfo, err error) {
441 // update aggregates
442 m.aggregatesLock.Lock()
443 m.StatCalls = append(m.StatCalls, path)
444 m.aggregatesLock.Unlock()
445
446 // resolve symlinks
447 path, err = m.resolve(path, false)
448 if err != nil {
449 return nil, err
450 }
451
452 // special case for root dir
453 if path == "/" {
454 return m.dirToFileInfo(&m.root, "/"), nil
455 }
456
457 // determine type and handle appropriately
458 parentPath, baseName := pathSplit(path)
459 dir, err := m.getDir(parentPath, false)
460 if err != nil {
461 return nil, err
462 }
463 subdir, subdirExists := dir.subdirs[baseName]
464 if subdirExists {
465 return m.dirToFileInfo(subdir, path), nil
466 }
467 file, fileExists := dir.files[baseName]
468 if fileExists {
469 return m.fileToFileInfo(file, path), nil
470 }
471 link, linkExists := dir.symlinks[baseName]
472 if linkExists {
473 return m.linkToFileInfo(link, path), nil
474 }
475 // not found
476 return nil, &os.PathError{
477 Op: "stat",
478 Path: path,
479 Err: os.ErrNotExist,
480 }
481}
482
483func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
484 mockInfo, ok := info.(*mockFileInfo)
485 if ok {
486 return mockInfo.inodeNumber, nil
487 }
488 return 0, fmt.Errorf("%v is not a mockFileInfo", info)
489}
490func (m *MockFs) DeviceNumber(info os.FileInfo) (number uint64, err error) {
491 mockInfo, ok := info.(*mockFileInfo)
492 if ok {
493 return mockInfo.deviceNumber, nil
494 }
495 return 0, fmt.Errorf("%v is not a mockFileInfo", info)
496}
497func (m *MockFs) PermTime(info os.FileInfo) (when time.Time, err error) {
498 mockInfo, ok := info.(*mockFileInfo)
499 if ok {
500 return mockInfo.permTime, nil
501 }
502 return time.Date(0, 0, 0, 0, 0, 0, 0, nil),
503 fmt.Errorf("%v is not a mockFileInfo", info)
504}
505
506func (m *MockFs) ReadDir(path string) (contents []os.FileInfo, err error) {
507 // update aggregates
508 m.aggregatesLock.Lock()
509 m.ReadDirCalls = append(m.ReadDirCalls, path)
510 m.aggregatesLock.Unlock()
511
512 // locate directory
513 path, err = m.resolve(path, true)
514 if err != nil {
515 return nil, err
516 }
517 results := []os.FileInfo{}
518 dir, err := m.getDir(path, false)
519 if err != nil {
520 return nil, err
521 }
522 if !dir.readable {
523 return nil, &os.PathError{
524 Op: "read",
525 Path: path,
526 Err: os.ErrPermission,
527 }
528 }
529 // describe its contents
530 for name, subdir := range dir.subdirs {
531 dirInfo := m.dirToFileInfo(subdir, name)
532 results = append(results, dirInfo)
533 }
534 for name, file := range dir.files {
535 info := m.fileToFileInfo(file, name)
536 results = append(results, info)
537 }
538 for name, link := range dir.symlinks {
539 info := m.linkToFileInfo(link, name)
540 results = append(results, info)
541 }
542 return results, nil
543}
544
545func (m *MockFs) Rename(sourcePath string, destPath string) error {
546 // validate source parent exists
547 sourcePath, err := m.resolve(sourcePath, false)
548 if err != nil {
549 return err
550 }
551 sourceParentPath := filepath.Dir(sourcePath)
552 sourceParentDir, err := m.getDir(sourceParentPath, false)
553 if err != nil {
554 return err
555 }
556 if sourceParentDir == nil {
557 return &os.PathError{
558 Op: "move",
559 Path: sourcePath,
560 Err: os.ErrNotExist,
561 }
562 }
563 if !sourceParentDir.readable {
564 return &os.PathError{
565 Op: "move",
566 Path: sourcePath,
567 Err: os.ErrPermission,
568 }
569 }
570
571 // validate dest parent exists
572 destPath, err = m.resolve(destPath, false)
573 destParentPath := filepath.Dir(destPath)
574 destParentDir, err := m.getDir(destParentPath, false)
575 if err != nil {
576 return err
577 }
578 if destParentDir == nil {
579 return &os.PathError{
580 Op: "move",
581 Path: destParentPath,
582 Err: os.ErrNotExist,
583 }
584 }
585 if !destParentDir.readable {
586 return &os.PathError{
587 Op: "move",
588 Path: destParentPath,
589 Err: os.ErrPermission,
590 }
591 }
592 // check the source and dest themselves
593 sourceBase := filepath.Base(sourcePath)
594 destBase := filepath.Base(destPath)
595
596 file, sourceIsFile := sourceParentDir.files[sourceBase]
597 dir, sourceIsDir := sourceParentDir.subdirs[sourceBase]
598 link, sourceIsLink := sourceParentDir.symlinks[sourceBase]
599
600 // validate that the source exists
601 if !sourceIsFile && !sourceIsDir && !sourceIsLink {
602 return &os.PathError{
603 Op: "move",
604 Path: sourcePath,
605 Err: os.ErrNotExist,
606 }
607
608 }
609
610 // validate the destination doesn't already exist as an incompatible type
611 _, destWasFile := destParentDir.files[destBase]
612 _, destWasDir := destParentDir.subdirs[destBase]
613 _, destWasLink := destParentDir.symlinks[destBase]
614
615 if destWasDir {
616 return &os.PathError{
617 Op: "move",
618 Path: destPath,
619 Err: errors.New("destination exists as a directory"),
620 }
621 }
622
623 if sourceIsDir && (destWasFile || destWasLink) {
624 return &os.PathError{
625 Op: "move",
626 Path: destPath,
627 Err: errors.New("destination exists as a file"),
628 }
629 }
630
631 if destWasFile {
632 delete(destParentDir.files, destBase)
633 }
634 if destWasDir {
635 delete(destParentDir.subdirs, destBase)
636 }
637 if destWasLink {
638 delete(destParentDir.symlinks, destBase)
639 }
640
641 if sourceIsFile {
642 destParentDir.files[destBase] = file
643 delete(sourceParentDir.files, sourceBase)
644 }
645 if sourceIsDir {
646 destParentDir.subdirs[destBase] = dir
647 delete(sourceParentDir.subdirs, sourceBase)
648 }
649 if sourceIsLink {
650 destParentDir.symlinks[destBase] = link
651 delete(destParentDir.symlinks, sourceBase)
652 }
653
654 destParentDir.modTime = m.Clock.Time()
655 sourceParentDir.modTime = m.Clock.Time()
656 return nil
657}
658
659func (m *MockFs) newInodeNumber() uint64 {
660 result := m.nextInodeNumber
661 m.nextInodeNumber++
662 return result
663}
664
665func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error {
666 filePath, err := m.resolve(filePath, true)
667 if err != nil {
668 return err
669 }
670 parentPath := filepath.Dir(filePath)
671 parentDir, err := m.getDir(parentPath, false)
672 if err != nil || parentDir == nil {
673 return &os.PathError{
674 Op: "write",
675 Path: parentPath,
676 Err: os.ErrNotExist,
677 }
678 }
679 if !parentDir.readable {
680 return &os.PathError{
681 Op: "write",
682 Path: parentPath,
683 Err: os.ErrPermission,
684 }
685 }
686
687 baseName := filepath.Base(filePath)
688 _, exists := parentDir.files[baseName]
689 if !exists {
690 parentDir.modTime = m.Clock.Time()
691 parentDir.files[baseName] = m.newFile()
692 } else {
693 if !parentDir.files[baseName].readable {
694 return &os.PathError{
695 Op: "write",
696 Path: filePath,
697 Err: os.ErrPermission,
698 }
699 }
700 }
701 file := parentDir.files[baseName]
702 file.bytes = data
703 file.modTime = m.Clock.Time()
704 return nil
705}
706
707func (m *MockFs) newFile() *mockFile {
708 newFile := &mockFile{}
709 newFile.inodeNumber = m.newInodeNumber()
710 newFile.modTime = m.Clock.Time()
711 newFile.permTime = newFile.modTime
712 newFile.readable = true
713 return newFile
714}
715
716func (m *MockFs) newDir() *mockDir {
717 newDir := &mockDir{
718 subdirs: make(map[string]*mockDir, 0),
719 files: make(map[string]*mockFile, 0),
720 symlinks: make(map[string]*mockLink, 0),
721 }
722 newDir.inodeNumber = m.newInodeNumber()
723 newDir.modTime = m.Clock.Time()
724 newDir.permTime = newDir.modTime
725 newDir.readable = true
726 return newDir
727}
728
729func (m *MockFs) newLink(target string) *mockLink {
730 newLink := &mockLink{
731 target: target,
732 }
733 newLink.inodeNumber = m.newInodeNumber()
734 newLink.modTime = m.Clock.Time()
735 newLink.permTime = newLink.modTime
736 newLink.readable = true
737
738 return newLink
739}
740func (m *MockFs) MkDirs(path string) error {
741 _, err := m.getDir(path, true)
742 return err
743}
744
745// getDir doesn't support symlinks
746func (m *MockFs) getDir(path string, createIfMissing bool) (dir *mockDir, err error) {
747 cleanedPath := filepath.Clean(path)
748 if cleanedPath == "/" {
749 return &m.root, nil
750 }
751
752 parentPath, leaf := pathSplit(cleanedPath)
753 if len(parentPath) >= len(path) {
754 return &m.root, nil
755 }
756 parent, err := m.getDir(parentPath, createIfMissing)
757 if err != nil {
758 return nil, err
759 }
760 if !parent.readable {
761 return nil, &os.PathError{
762 Op: "stat",
763 Path: path,
764 Err: os.ErrPermission,
765 }
766 }
767 childDir, dirExists := parent.subdirs[leaf]
768 if !dirExists {
769 if createIfMissing {
770 // confirm that a file with the same name doesn't already exist
771 _, fileExists := parent.files[leaf]
772 if fileExists {
773 return nil, &os.PathError{
774 Op: "mkdir",
775 Path: path,
776 Err: os.ErrExist,
777 }
778 }
779 // create this directory
780 childDir = m.newDir()
781 parent.subdirs[leaf] = childDir
782 parent.modTime = m.Clock.Time()
783 } else {
784 return nil, &os.PathError{
785 Op: "stat",
786 Path: path,
787 Err: os.ErrNotExist,
788 }
789 }
790 }
791 return childDir, nil
792
793}
794
795func (m *MockFs) Remove(path string) (err error) {
796 path, err = m.resolve(path, false)
797 parentPath, leaf := pathSplit(path)
798 if len(leaf) == 0 {
799 return fmt.Errorf("Cannot remove %v\n", path)
800 }
801 parentDir, err := m.getDir(parentPath, false)
802 if err != nil {
803 return err
804 }
805 if parentDir == nil {
806 return &os.PathError{
807 Op: "remove",
808 Path: path,
809 Err: os.ErrNotExist,
810 }
811 }
812 if !parentDir.readable {
813 return &os.PathError{
814 Op: "remove",
815 Path: path,
816 Err: os.ErrPermission,
817 }
818 }
819 _, isDir := parentDir.subdirs[leaf]
820 if isDir {
821 return &os.PathError{
822 Op: "remove",
823 Path: path,
824 Err: os.ErrInvalid,
825 }
826 }
827 _, isLink := parentDir.symlinks[leaf]
828 if isLink {
829 delete(parentDir.symlinks, leaf)
830 } else {
831 _, isFile := parentDir.files[leaf]
832 if !isFile {
833 return &os.PathError{
834 Op: "remove",
835 Path: path,
836 Err: os.ErrNotExist,
837 }
838 }
839 delete(parentDir.files, leaf)
840 }
841 parentDir.modTime = m.Clock.Time()
842 return nil
843}
844
845func (m *MockFs) Symlink(oldPath string, newPath string) (err error) {
846 newPath, err = m.resolve(newPath, false)
847 if err != nil {
848 return err
849 }
850
851 newParentPath, leaf := pathSplit(newPath)
852 newParentDir, err := m.getDir(newParentPath, false)
853 if !newParentDir.readable {
854 return &os.PathError{
855 Op: "link",
856 Path: newPath,
857 Err: os.ErrPermission,
858 }
859 }
860 if err != nil {
861 return err
862 }
863 newParentDir.symlinks[leaf] = m.newLink(oldPath)
864 return nil
865}
866
867func (m *MockFs) RemoveAll(path string) (err error) {
868 path, err = m.resolve(path, false)
869 if err != nil {
870 return err
871 }
872 parentPath, leaf := pathSplit(path)
873 if len(leaf) == 0 {
874 return fmt.Errorf("Cannot remove %v\n", path)
875 }
876 parentDir, err := m.getDir(parentPath, false)
877 if err != nil {
878 return err
879 }
880 if parentDir == nil {
881 return &os.PathError{
882 Op: "removeAll",
883 Path: path,
884 Err: os.ErrNotExist,
885 }
886 }
887 if !parentDir.readable {
888 return &os.PathError{
889 Op: "removeAll",
890 Path: path,
891 Err: os.ErrPermission,
892 }
893
894 }
895 _, isFile := parentDir.files[leaf]
896 _, isLink := parentDir.symlinks[leaf]
897 if isFile || isLink {
898 return m.Remove(path)
899 }
900 _, isDir := parentDir.subdirs[leaf]
901 if !isDir {
902 if !isDir {
903 return &os.PathError{
904 Op: "removeAll",
905 Path: path,
906 Err: os.ErrNotExist,
907 }
908 }
909 }
910
911 delete(parentDir.subdirs, leaf)
912 parentDir.modTime = m.Clock.Time()
913 return nil
914}
915
916func (m *MockFs) SetReadable(path string, readable bool) error {
917 path, err := m.resolve(path, false)
918 if err != nil {
919 return err
920 }
921 parentPath, leaf := filepath.Split(path)
922 parentDir, err := m.getDir(parentPath, false)
923 if err != nil {
924 return err
925 }
926 if !parentDir.readable {
927 return &os.PathError{
928 Op: "chmod",
929 Path: parentPath,
930 Err: os.ErrPermission,
931 }
932 }
933
934 inode, err := m.getInode(parentDir, leaf)
935 if err != nil {
936 return err
937 }
938 inode.readable = readable
939 inode.permTime = m.Clock.Time()
940 return nil
941}
942
943func (m *MockFs) ClearMetrics() {
944 m.ReadDirCalls = []string{}
945 m.StatCalls = []string{}
946}
947
948func (m *MockFs) ViewId() (id string) {
949 return m.viewId
950}
951
952func (m *MockFs) SetViewId(id string) {
953 m.viewId = id
954}
955func (m *MockFs) SetDeviceNumber(deviceNumber uint64) {
956 m.deviceNumber = deviceNumber
957}