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	Stat(path string) (stats os.FileInfo, err error)
55	ReadDir(path string) (contents []DirEntryInfo, 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// 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
79type dirEntryInfo struct {
80	name       string
81	mode       os.FileMode
82	modeExists bool
83}
84
85var _ DirEntryInfo = os.FileInfo(nil)
86
87func (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
92// osFs implements FileSystem using the local disk.
93type osFs struct{}
94
95var _ FileSystem = (*osFs)(nil)
96
97func (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
103func (osFs) Stat(path string) (stats os.FileInfo, err error) {
104	return os.Stat(path)
105}
106
107func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
108	entries, err := readdir(path)
109	if err != nil {
110		return nil, err
111	}
112	for _, entry := range entries {
113		contents = append(contents, entry)
114	}
115
116	return contents, nil
117}
118
119func (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
194var _ FileSystem = (*MockFs)(nil)
195
196type mockInode struct {
197	modTime     time.Time
198	permTime    time.Time
199	sys         interface{}
200	inodeNumber uint64
201	readErr     error
202}
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())
251	}
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	}
262	if parentNode.readErr != nil {
263		return "", &os.PathError{
264			Op:   "read",
265			Path: path,
266			Err:  parentNode.readErr,
267		}
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
281		if link.readErr != nil {
282			return "", &os.PathError{
283				Op:   "read",
284				Path: path,
285				Err:  link.readErr,
286			}
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	}
318	if file.readErr != nil {
319		return nil, &os.PathError{
320			Op:   "open",
321			Path: fileName,
322			Err:  file.readErr,
323		}
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
384	mode         os.FileMode
385	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 {
398	return m.mode
399}
400
401func (m *mockFileInfo) ModTime() time.Time {
402	return m.modTime
403}
404
405func (m *mockFileInfo) IsDir() bool {
406	return m.mode&os.ModeDir != 0
407}
408
409func (m *mockFileInfo) Sys() interface{} {
410	return nil
411}
412
413func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
414	return &mockFileInfo{
415		path:         filepath.Base(path),
416		size:         1,
417		modTime:      d.modTime,
418		permTime:     d.permTime,
419		mode:         os.ModeDir,
420		inodeNumber:  d.inodeNumber,
421		deviceNumber: m.deviceNumber,
422	}
423
424}
425
426func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
427	return &mockFileInfo{
428		path:         filepath.Base(path),
429		size:         1,
430		modTime:      f.modTime,
431		permTime:     f.permTime,
432		mode:         0,
433		inodeNumber:  f.inodeNumber,
434		deviceNumber: m.deviceNumber,
435	}
436}
437
438func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
439	return &mockFileInfo{
440		path:         filepath.Base(path),
441		size:         1,
442		modTime:      l.modTime,
443		permTime:     l.permTime,
444		mode:         os.ModeSymlink,
445		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
493func (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
503func (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
526func (m *MockFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
527	// 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	}
537	results := []DirEntryInfo{}
538	dir, err := m.getDir(path, false)
539	if err != nil {
540		return nil, err
541	}
542	if dir.readErr != nil {
543		return nil, &os.PathError{
544			Op:   "read",
545			Path: path,
546			Err:  dir.readErr,
547		}
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	}
583	if sourceParentDir.readErr != nil {
584		return &os.PathError{
585			Op:   "move",
586			Path: sourcePath,
587			Err:  sourceParentDir.readErr,
588		}
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	}
605	if destParentDir.readErr != nil {
606		return &os.PathError{
607			Op:   "move",
608			Path: destParentPath,
609			Err:  destParentDir.readErr,
610		}
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	}
699	if parentDir.readErr != nil {
700		return &os.PathError{
701			Op:   "write",
702			Path: parentPath,
703			Err:  parentDir.readErr,
704		}
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 {
713		readErr := parentDir.files[baseName].readErr
714		if readErr != nil {
715			return &os.PathError{
716				Op:   "write",
717				Path: filePath,
718				Err:  readErr,
719			}
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
733	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
745	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
755
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	}
778	if parent.readErr != nil {
779		return nil, &os.PathError{
780			Op:   "stat",
781			Path: path,
782			Err:  parent.readErr,
783		}
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	}
830	if parentDir.readErr != nil {
831		return &os.PathError{
832			Op:   "remove",
833			Path: path,
834			Err:  parentDir.readErr,
835		}
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)
871	if newParentDir.readErr != nil {
872		return &os.PathError{
873			Op:   "link",
874			Path: newPath,
875			Err:  newParentDir.readErr,
876		}
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	}
905	if parentDir.readErr != nil {
906		return &os.PathError{
907			Op:   "removeAll",
908			Path: path,
909			Err:  parentDir.readErr,
910		}
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 {
935	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 {
943	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	}
952	if parentDir.readErr != nil {
953		return &os.PathError{
954			Op:   "chmod",
955			Path: parentPath,
956			Err:  parentDir.readErr,
957		}
958	}
959
960	inode, err := m.getInode(parentDir, leaf)
961	if err != nil {
962		return err
963	}
964	inode.readErr = readErr
965	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}
984