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