Revert "Revert "Cacheable, multithreaded finder.""

Bug: 62455338
Test: m -j

This reverts commit d1abeb9d982b11fdf4047176d213acc8197c375f.

Change-Id: I9f73031636157511b5f1c6ce8a205e9bc91669ff
diff --git a/fs/fs.go b/fs/fs.go
new file mode 100644
index 0000000..c6060c0
--- /dev/null
+++ b/fs/fs.go
@@ -0,0 +1,957 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/user"
+	"path/filepath"
+	"sync"
+	"syscall"
+	"time"
+)
+
+var OsFs FileSystem = osFs{}
+
+func NewMockFs(files map[string][]byte) *MockFs {
+	workDir := "/cwd"
+	fs := &MockFs{
+		Clock:   NewClock(time.Unix(2, 2)),
+		workDir: workDir,
+	}
+	fs.root = *fs.newDir()
+	fs.MkDirs(workDir)
+
+	for path, bytes := range files {
+		dir := filepath.Dir(path)
+		fs.MkDirs(dir)
+		fs.WriteFile(path, bytes, 0777)
+	}
+
+	return fs
+}
+
+type FileSystem interface {
+	// getting information about files
+	Open(name string) (file io.ReadCloser, err error)
+	Lstat(path string) (stats os.FileInfo, err error)
+	ReadDir(path string) (contents []os.FileInfo, err error)
+
+	InodeNumber(info os.FileInfo) (number uint64, err error)
+	DeviceNumber(info os.FileInfo) (number uint64, err error)
+	PermTime(info os.FileInfo) (time time.Time, err error)
+
+	// changing contents of the filesystem
+	Rename(oldPath string, newPath string) (err error)
+	WriteFile(path string, data []byte, perm os.FileMode) (err error)
+	Remove(path string) (err error)
+	RemoveAll(path string) (err error)
+
+	// metadata about the filesystem
+	ViewId() (id string) // Some unique id of the user accessing the filesystem
+}
+
+// osFs implements FileSystem using the local disk.
+type osFs struct{}
+
+func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
+
+func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
+	return os.Lstat(path)
+}
+
+func (osFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
+	sys := info.Sys()
+	linuxStats, ok := sys.(*syscall.Stat_t)
+	if ok {
+		return linuxStats.Ino, nil
+	}
+	return 0, fmt.Errorf("%v is not a *syscall.Stat_t", sys)
+}
+
+func (osFs) DeviceNumber(info os.FileInfo) (number uint64, err error) {
+	sys := info.Sys()
+	linuxStats, ok := sys.(*syscall.Stat_t)
+	if ok {
+		return linuxStats.Dev, nil
+	}
+	return 0, fmt.Errorf("%v is not a *syscall.Stat_t", sys)
+}
+
+func (osFs) PermTime(info os.FileInfo) (when time.Time, err error) {
+	sys := info.Sys()
+	linuxStats, ok := sys.(*syscall.Stat_t)
+	if ok {
+		return time.Unix(linuxStats.Ctim.Sec, linuxStats.Ctim.Nsec), nil
+	}
+	return time.Date(0, 0, 0, 0, 0, 0, 0, nil), fmt.Errorf("%v is not a *syscall.Stat_t", sys)
+}
+
+func (osFs) ReadDir(path string) (contents []os.FileInfo, err error) {
+	return ioutil.ReadDir(path)
+}
+func (osFs) Rename(oldPath string, newPath string) error {
+	return os.Rename(oldPath, newPath)
+}
+
+func (osFs) WriteFile(path string, data []byte, perm os.FileMode) error {
+	return ioutil.WriteFile(path, data, perm)
+}
+
+func (osFs) Remove(path string) error {
+	return os.Remove(path)
+}
+
+func (osFs) RemoveAll(path string) error {
+	return os.RemoveAll(path)
+}
+
+func (osFs) ViewId() (id string) {
+	user, err := user.Current()
+	if err != nil {
+		return ""
+	}
+	username := user.Username
+
+	hostname, err := os.Hostname()
+	if err != nil {
+		return ""
+	}
+
+	return username + "@" + hostname
+}
+
+type Clock struct {
+	time time.Time
+}
+
+func NewClock(startTime time.Time) *Clock {
+	return &Clock{time: startTime}
+
+}
+
+func (c *Clock) Tick() {
+	c.time = c.time.Add(time.Microsecond)
+}
+
+func (c *Clock) Time() time.Time {
+	return c.time
+}
+
+// given "/a/b/c/d", pathSplit returns ("/a/b/c", "d")
+func pathSplit(path string) (dir string, leaf string) {
+	dir, leaf = filepath.Split(path)
+	if dir != "/" && len(dir) > 0 {
+		dir = dir[:len(dir)-1]
+	}
+	return dir, leaf
+}
+
+// MockFs supports singlethreaded writes and multithreaded reads
+type MockFs struct {
+	// configuration
+	viewId       string //
+	deviceNumber uint64
+
+	// implementation
+	root            mockDir
+	Clock           *Clock
+	workDir         string
+	nextInodeNumber uint64
+
+	// history of requests, for tests to check
+	StatCalls      []string
+	ReadDirCalls   []string
+	aggregatesLock sync.Mutex
+}
+
+type mockInode struct {
+	modTime     time.Time
+	permTime    time.Time
+	sys         interface{}
+	inodeNumber uint64
+	readable    bool
+}
+
+func (m mockInode) ModTime() time.Time {
+	return m.modTime
+}
+
+func (m mockInode) Sys() interface{} {
+	return m.sys
+}
+
+type mockFile struct {
+	bytes []byte
+
+	mockInode
+}
+
+type mockLink struct {
+	target string
+
+	mockInode
+}
+
+type mockDir struct {
+	mockInode
+
+	subdirs  map[string]*mockDir
+	files    map[string]*mockFile
+	symlinks map[string]*mockLink
+}
+
+func (m *MockFs) resolve(path string, followLastLink bool) (result string, err error) {
+	if !filepath.IsAbs(path) {
+		path = filepath.Join(m.workDir, path)
+	}
+	path = filepath.Clean(path)
+
+	return m.followLinks(path, followLastLink, 10)
+}
+
+// note that followLinks can return a file path that doesn't exist
+func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canonicalPath string, err error) {
+	if path == "/" {
+		return path, nil
+	}
+
+	parentPath, leaf := pathSplit(path)
+	if parentPath == path {
+		err = fmt.Errorf("Internal error: %v yields itself as a parent", path)
+		panic(err.Error())
+		return "", fmt.Errorf("Internal error: %v yields itself as a parent", path)
+	}
+
+	parentPath, err = m.followLinks(parentPath, true, count)
+	if err != nil {
+		return "", err
+	}
+
+	parentNode, err := m.getDir(parentPath, false)
+	if err != nil {
+		return "", err
+	}
+	if !parentNode.readable {
+		return "", &os.PathError{
+			Op:   "read",
+			Path: path,
+			Err:  os.ErrPermission,
+		}
+	}
+
+	link, isLink := parentNode.symlinks[leaf]
+	if isLink && followLastLink {
+		if count <= 0 {
+			// probably a loop
+			return "", &os.PathError{
+				Op:   "read",
+				Path: path,
+				Err:  fmt.Errorf("too many levels of symbolic links"),
+			}
+		}
+
+		if !link.readable {
+			return "", &os.PathError{
+				Op:   "read",
+				Path: path,
+				Err:  os.ErrPermission,
+			}
+		}
+
+		target := m.followLink(link, parentPath)
+		return m.followLinks(target, followLastLink, count-1)
+	}
+	return path, nil
+}
+
+func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) {
+	return filepath.Clean(filepath.Join(parentPath, link.target))
+}
+
+func (m *MockFs) getFile(parentDir *mockDir, fileName string) (file *mockFile, err error) {
+	file, isFile := parentDir.files[fileName]
+	if !isFile {
+		_, isDir := parentDir.subdirs[fileName]
+		_, isLink := parentDir.symlinks[fileName]
+		if isDir || isLink {
+			return nil, &os.PathError{
+				Op:   "open",
+				Path: fileName,
+				Err:  os.ErrInvalid,
+			}
+		}
+
+		return nil, &os.PathError{
+			Op:   "open",
+			Path: fileName,
+			Err:  os.ErrNotExist,
+		}
+	}
+	if !file.readable {
+		return nil, &os.PathError{
+			Op:   "open",
+			Path: fileName,
+			Err:  os.ErrPermission,
+		}
+	}
+	return file, nil
+}
+
+func (m *MockFs) getInode(parentDir *mockDir, name string) (inode *mockInode, err error) {
+	file, isFile := parentDir.files[name]
+	if isFile {
+		return &file.mockInode, nil
+	}
+	link, isLink := parentDir.symlinks[name]
+	if isLink {
+		return &link.mockInode, nil
+	}
+	dir, isDir := parentDir.subdirs[name]
+	if isDir {
+		return &dir.mockInode, nil
+	}
+	return nil, &os.PathError{
+		Op:   "stat",
+		Path: name,
+		Err:  os.ErrNotExist,
+	}
+
+}
+
+func (m *MockFs) Open(path string) (io.ReadCloser, error) {
+	path, err := m.resolve(path, true)
+	if err != nil {
+		return nil, err
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	parentPath, base := pathSplit(path)
+	parentDir, err := m.getDir(parentPath, false)
+	if err != nil {
+		return nil, err
+	}
+	file, err := m.getFile(parentDir, base)
+	if err != nil {
+		return nil, err
+	}
+	return struct {
+		io.Closer
+		*bytes.Reader
+	}{
+		ioutil.NopCloser(nil),
+		bytes.NewReader(file.bytes),
+	}, nil
+
+}
+
+// a mockFileInfo is for exporting file stats in a way that satisfies the FileInfo interface
+type mockFileInfo struct {
+	path         string
+	size         int64
+	modTime      time.Time // time at which the inode's contents were modified
+	permTime     time.Time // time at which the inode's permissions were modified
+	isDir        bool
+	inodeNumber  uint64
+	deviceNumber uint64
+}
+
+func (m *mockFileInfo) Name() string {
+	return m.path
+}
+
+func (m *mockFileInfo) Size() int64 {
+	return m.size
+}
+
+func (m *mockFileInfo) Mode() os.FileMode {
+	return 0
+}
+
+func (m *mockFileInfo) ModTime() time.Time {
+	return m.modTime
+}
+
+func (m *mockFileInfo) IsDir() bool {
+	return m.isDir
+}
+
+func (m *mockFileInfo) Sys() interface{} {
+	return nil
+}
+
+func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
+	return &mockFileInfo{
+		path:         path,
+		size:         1,
+		modTime:      d.modTime,
+		permTime:     d.permTime,
+		isDir:        true,
+		inodeNumber:  d.inodeNumber,
+		deviceNumber: m.deviceNumber,
+	}
+
+}
+
+func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
+	return &mockFileInfo{
+		path:         path,
+		size:         1,
+		modTime:      f.modTime,
+		permTime:     f.permTime,
+		isDir:        false,
+		inodeNumber:  f.inodeNumber,
+		deviceNumber: m.deviceNumber,
+	}
+}
+
+func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
+	return &mockFileInfo{
+		path:         path,
+		size:         1,
+		modTime:      l.modTime,
+		permTime:     l.permTime,
+		isDir:        false,
+		inodeNumber:  l.inodeNumber,
+		deviceNumber: m.deviceNumber,
+	}
+}
+
+func (m *MockFs) Lstat(path string) (stats os.FileInfo, err error) {
+	// update aggregates
+	m.aggregatesLock.Lock()
+	m.StatCalls = append(m.StatCalls, path)
+	m.aggregatesLock.Unlock()
+
+	// resolve symlinks
+	path, err = m.resolve(path, false)
+	if err != nil {
+		return nil, err
+	}
+
+	// special case for root dir
+	if path == "/" {
+		return m.dirToFileInfo(&m.root, "/"), nil
+	}
+
+	// determine type and handle appropriately
+	parentPath, baseName := pathSplit(path)
+	dir, err := m.getDir(parentPath, false)
+	if err != nil {
+		return nil, err
+	}
+	subdir, subdirExists := dir.subdirs[baseName]
+	if subdirExists {
+		return m.dirToFileInfo(subdir, path), nil
+	}
+	file, fileExists := dir.files[baseName]
+	if fileExists {
+		return m.fileToFileInfo(file, path), nil
+	}
+	link, linkExists := dir.symlinks[baseName]
+	if linkExists {
+		return m.linkToFileInfo(link, path), nil
+	}
+	// not found
+	return nil, &os.PathError{
+		Op:   "stat",
+		Path: path,
+		Err:  os.ErrNotExist,
+	}
+}
+
+func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
+	mockInfo, ok := info.(*mockFileInfo)
+	if ok {
+		return mockInfo.inodeNumber, nil
+	}
+	return 0, fmt.Errorf("%v is not a mockFileInfo", info)
+}
+func (m *MockFs) DeviceNumber(info os.FileInfo) (number uint64, err error) {
+	mockInfo, ok := info.(*mockFileInfo)
+	if ok {
+		return mockInfo.deviceNumber, nil
+	}
+	return 0, fmt.Errorf("%v is not a mockFileInfo", info)
+}
+func (m *MockFs) PermTime(info os.FileInfo) (when time.Time, err error) {
+	mockInfo, ok := info.(*mockFileInfo)
+	if ok {
+		return mockInfo.permTime, nil
+	}
+	return time.Date(0, 0, 0, 0, 0, 0, 0, nil),
+		fmt.Errorf("%v is not a mockFileInfo", info)
+}
+
+func (m *MockFs) ReadDir(path string) (contents []os.FileInfo, err error) {
+	// update aggregates
+	m.aggregatesLock.Lock()
+	m.ReadDirCalls = append(m.ReadDirCalls, path)
+	m.aggregatesLock.Unlock()
+
+	// locate directory
+	path, err = m.resolve(path, true)
+	if err != nil {
+		return nil, err
+	}
+	results := []os.FileInfo{}
+	dir, err := m.getDir(path, false)
+	if err != nil {
+		return nil, err
+	}
+	if !dir.readable {
+		return nil, &os.PathError{
+			Op:   "read",
+			Path: path,
+			Err:  os.ErrPermission,
+		}
+	}
+	// describe its contents
+	for name, subdir := range dir.subdirs {
+		dirInfo := m.dirToFileInfo(subdir, name)
+		results = append(results, dirInfo)
+	}
+	for name, file := range dir.files {
+		info := m.fileToFileInfo(file, name)
+		results = append(results, info)
+	}
+	for name, link := range dir.symlinks {
+		info := m.linkToFileInfo(link, name)
+		results = append(results, info)
+	}
+	return results, nil
+}
+
+func (m *MockFs) Rename(sourcePath string, destPath string) error {
+	// validate source parent exists
+	sourcePath, err := m.resolve(sourcePath, false)
+	if err != nil {
+		return err
+	}
+	sourceParentPath := filepath.Dir(sourcePath)
+	sourceParentDir, err := m.getDir(sourceParentPath, false)
+	if err != nil {
+		return err
+	}
+	if sourceParentDir == nil {
+		return &os.PathError{
+			Op:   "move",
+			Path: sourcePath,
+			Err:  os.ErrNotExist,
+		}
+	}
+	if !sourceParentDir.readable {
+		return &os.PathError{
+			Op:   "move",
+			Path: sourcePath,
+			Err:  os.ErrPermission,
+		}
+	}
+
+	// validate dest parent exists
+	destPath, err = m.resolve(destPath, false)
+	destParentPath := filepath.Dir(destPath)
+	destParentDir, err := m.getDir(destParentPath, false)
+	if err != nil {
+		return err
+	}
+	if destParentDir == nil {
+		return &os.PathError{
+			Op:   "move",
+			Path: destParentPath,
+			Err:  os.ErrNotExist,
+		}
+	}
+	if !destParentDir.readable {
+		return &os.PathError{
+			Op:   "move",
+			Path: destParentPath,
+			Err:  os.ErrPermission,
+		}
+	}
+	// check the source and dest themselves
+	sourceBase := filepath.Base(sourcePath)
+	destBase := filepath.Base(destPath)
+
+	file, sourceIsFile := sourceParentDir.files[sourceBase]
+	dir, sourceIsDir := sourceParentDir.subdirs[sourceBase]
+	link, sourceIsLink := sourceParentDir.symlinks[sourceBase]
+
+	// validate that the source exists
+	if !sourceIsFile && !sourceIsDir && !sourceIsLink {
+		return &os.PathError{
+			Op:   "move",
+			Path: sourcePath,
+			Err:  os.ErrNotExist,
+		}
+
+	}
+
+	// validate the destination doesn't already exist as an incompatible type
+	_, destWasFile := destParentDir.files[destBase]
+	_, destWasDir := destParentDir.subdirs[destBase]
+	_, destWasLink := destParentDir.symlinks[destBase]
+
+	if destWasDir {
+		return &os.PathError{
+			Op:   "move",
+			Path: destPath,
+			Err:  errors.New("destination exists as a directory"),
+		}
+	}
+
+	if sourceIsDir && (destWasFile || destWasLink) {
+		return &os.PathError{
+			Op:   "move",
+			Path: destPath,
+			Err:  errors.New("destination exists as a file"),
+		}
+	}
+
+	if destWasFile {
+		delete(destParentDir.files, destBase)
+	}
+	if destWasDir {
+		delete(destParentDir.subdirs, destBase)
+	}
+	if destWasLink {
+		delete(destParentDir.symlinks, destBase)
+	}
+
+	if sourceIsFile {
+		destParentDir.files[destBase] = file
+		delete(sourceParentDir.files, sourceBase)
+	}
+	if sourceIsDir {
+		destParentDir.subdirs[destBase] = dir
+		delete(sourceParentDir.subdirs, sourceBase)
+	}
+	if sourceIsLink {
+		destParentDir.symlinks[destBase] = link
+		delete(destParentDir.symlinks, sourceBase)
+	}
+
+	destParentDir.modTime = m.Clock.Time()
+	sourceParentDir.modTime = m.Clock.Time()
+	return nil
+}
+
+func (m *MockFs) newInodeNumber() uint64 {
+	result := m.nextInodeNumber
+	m.nextInodeNumber++
+	return result
+}
+
+func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error {
+	filePath, err := m.resolve(filePath, true)
+	if err != nil {
+		return err
+	}
+	parentPath := filepath.Dir(filePath)
+	parentDir, err := m.getDir(parentPath, false)
+	if err != nil || parentDir == nil {
+		return &os.PathError{
+			Op:   "write",
+			Path: parentPath,
+			Err:  os.ErrNotExist,
+		}
+	}
+	if !parentDir.readable {
+		return &os.PathError{
+			Op:   "write",
+			Path: parentPath,
+			Err:  os.ErrPermission,
+		}
+	}
+
+	baseName := filepath.Base(filePath)
+	_, exists := parentDir.files[baseName]
+	if !exists {
+		parentDir.modTime = m.Clock.Time()
+		parentDir.files[baseName] = m.newFile()
+	} else {
+		if !parentDir.files[baseName].readable {
+			return &os.PathError{
+				Op:   "write",
+				Path: filePath,
+				Err:  os.ErrPermission,
+			}
+		}
+	}
+	file := parentDir.files[baseName]
+	file.bytes = data
+	file.modTime = m.Clock.Time()
+	return nil
+}
+
+func (m *MockFs) newFile() *mockFile {
+	newFile := &mockFile{}
+	newFile.inodeNumber = m.newInodeNumber()
+	newFile.modTime = m.Clock.Time()
+	newFile.permTime = newFile.modTime
+	newFile.readable = true
+	return newFile
+}
+
+func (m *MockFs) newDir() *mockDir {
+	newDir := &mockDir{
+		subdirs:  make(map[string]*mockDir, 0),
+		files:    make(map[string]*mockFile, 0),
+		symlinks: make(map[string]*mockLink, 0),
+	}
+	newDir.inodeNumber = m.newInodeNumber()
+	newDir.modTime = m.Clock.Time()
+	newDir.permTime = newDir.modTime
+	newDir.readable = true
+	return newDir
+}
+
+func (m *MockFs) newLink(target string) *mockLink {
+	newLink := &mockLink{
+		target: target,
+	}
+	newLink.inodeNumber = m.newInodeNumber()
+	newLink.modTime = m.Clock.Time()
+	newLink.permTime = newLink.modTime
+	newLink.readable = true
+
+	return newLink
+}
+func (m *MockFs) MkDirs(path string) error {
+	_, err := m.getDir(path, true)
+	return err
+}
+
+// getDir doesn't support symlinks
+func (m *MockFs) getDir(path string, createIfMissing bool) (dir *mockDir, err error) {
+	cleanedPath := filepath.Clean(path)
+	if cleanedPath == "/" {
+		return &m.root, nil
+	}
+
+	parentPath, leaf := pathSplit(cleanedPath)
+	if len(parentPath) >= len(path) {
+		return &m.root, nil
+	}
+	parent, err := m.getDir(parentPath, createIfMissing)
+	if err != nil {
+		return nil, err
+	}
+	if !parent.readable {
+		return nil, &os.PathError{
+			Op:   "stat",
+			Path: path,
+			Err:  os.ErrPermission,
+		}
+	}
+	childDir, dirExists := parent.subdirs[leaf]
+	if !dirExists {
+		if createIfMissing {
+			// confirm that a file with the same name doesn't already exist
+			_, fileExists := parent.files[leaf]
+			if fileExists {
+				return nil, &os.PathError{
+					Op:   "mkdir",
+					Path: path,
+					Err:  os.ErrExist,
+				}
+			}
+			// create this directory
+			childDir = m.newDir()
+			parent.subdirs[leaf] = childDir
+			parent.modTime = m.Clock.Time()
+		} else {
+			return nil, &os.PathError{
+				Op:   "stat",
+				Path: path,
+				Err:  os.ErrNotExist,
+			}
+		}
+	}
+	return childDir, nil
+
+}
+
+func (m *MockFs) Remove(path string) (err error) {
+	path, err = m.resolve(path, false)
+	parentPath, leaf := pathSplit(path)
+	if len(leaf) == 0 {
+		return fmt.Errorf("Cannot remove %v\n", path)
+	}
+	parentDir, err := m.getDir(parentPath, false)
+	if err != nil {
+		return err
+	}
+	if parentDir == nil {
+		return &os.PathError{
+			Op:   "remove",
+			Path: path,
+			Err:  os.ErrNotExist,
+		}
+	}
+	if !parentDir.readable {
+		return &os.PathError{
+			Op:   "remove",
+			Path: path,
+			Err:  os.ErrPermission,
+		}
+	}
+	_, isDir := parentDir.subdirs[leaf]
+	if isDir {
+		return &os.PathError{
+			Op:   "remove",
+			Path: path,
+			Err:  os.ErrInvalid,
+		}
+	}
+	_, isLink := parentDir.symlinks[leaf]
+	if isLink {
+		delete(parentDir.symlinks, leaf)
+	} else {
+		_, isFile := parentDir.files[leaf]
+		if !isFile {
+			return &os.PathError{
+				Op:   "remove",
+				Path: path,
+				Err:  os.ErrNotExist,
+			}
+		}
+		delete(parentDir.files, leaf)
+	}
+	parentDir.modTime = m.Clock.Time()
+	return nil
+}
+
+func (m *MockFs) Symlink(oldPath string, newPath string) (err error) {
+	newPath, err = m.resolve(newPath, false)
+	if err != nil {
+		return err
+	}
+
+	newParentPath, leaf := pathSplit(newPath)
+	newParentDir, err := m.getDir(newParentPath, false)
+	if !newParentDir.readable {
+		return &os.PathError{
+			Op:   "link",
+			Path: newPath,
+			Err:  os.ErrPermission,
+		}
+	}
+	if err != nil {
+		return err
+	}
+	newParentDir.symlinks[leaf] = m.newLink(oldPath)
+	return nil
+}
+
+func (m *MockFs) RemoveAll(path string) (err error) {
+	path, err = m.resolve(path, false)
+	if err != nil {
+		return err
+	}
+	parentPath, leaf := pathSplit(path)
+	if len(leaf) == 0 {
+		return fmt.Errorf("Cannot remove %v\n", path)
+	}
+	parentDir, err := m.getDir(parentPath, false)
+	if err != nil {
+		return err
+	}
+	if parentDir == nil {
+		return &os.PathError{
+			Op:   "removeAll",
+			Path: path,
+			Err:  os.ErrNotExist,
+		}
+	}
+	if !parentDir.readable {
+		return &os.PathError{
+			Op:   "removeAll",
+			Path: path,
+			Err:  os.ErrPermission,
+		}
+
+	}
+	_, isFile := parentDir.files[leaf]
+	_, isLink := parentDir.symlinks[leaf]
+	if isFile || isLink {
+		return m.Remove(path)
+	}
+	_, isDir := parentDir.subdirs[leaf]
+	if !isDir {
+		if !isDir {
+			return &os.PathError{
+				Op:   "removeAll",
+				Path: path,
+				Err:  os.ErrNotExist,
+			}
+		}
+	}
+
+	delete(parentDir.subdirs, leaf)
+	parentDir.modTime = m.Clock.Time()
+	return nil
+}
+
+func (m *MockFs) SetReadable(path string, readable bool) error {
+	path, err := m.resolve(path, false)
+	if err != nil {
+		return err
+	}
+	parentPath, leaf := filepath.Split(path)
+	parentDir, err := m.getDir(parentPath, false)
+	if err != nil {
+		return err
+	}
+	if !parentDir.readable {
+		return &os.PathError{
+			Op:   "chmod",
+			Path: parentPath,
+			Err:  os.ErrPermission,
+		}
+	}
+
+	inode, err := m.getInode(parentDir, leaf)
+	if err != nil {
+		return err
+	}
+	inode.readable = readable
+	inode.permTime = m.Clock.Time()
+	return nil
+}
+
+func (m *MockFs) ClearMetrics() {
+	m.ReadDirCalls = []string{}
+	m.StatCalls = []string{}
+}
+
+func (m *MockFs) ViewId() (id string) {
+	return m.viewId
+}
+
+func (m *MockFs) SetViewId(id string) {
+	m.viewId = id
+}
+func (m *MockFs) SetDeviceNumber(deviceNumber uint64) {
+	m.deviceNumber = deviceNumber
+}