aboutsummaryrefslogtreecommitdiffstats
path: root/finder
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2017-12-21 15:46:01 -0800
committerColin Cross <ccross@android.com>2017-12-22 13:56:17 -0800
commit8d6395c09d868751c85082d71356ad790e924df9 (patch)
treec2a369e5f3d766c609ebd980668d181ddaaf5c39 /finder
parent7b60cdd6e5d81ae6e929d61898f4faefb1ddb8e2 (diff)
downloadbuild_soong-8d6395c09d868751c85082d71356ad790e924df9.tar.gz
build_soong-8d6395c09d868751c85082d71356ad790e924df9.tar.bz2
build_soong-8d6395c09d868751c85082d71356ad790e924df9.zip
Move android/soong/fs to android/soong/finder/fs
The fs package is specific to finder, move it inside finder. Bug: 70897635 Test: m checkbuild Change-Id: Ie705f064a832141702a8e87fd59ed75c01018504
Diffstat (limited to 'finder')
-rw-r--r--finder/Android.bp2
-rw-r--r--finder/cmd/finder.go2
-rw-r--r--finder/finder.go2
-rw-r--r--finder/finder_test.go4
-rw-r--r--finder/fs/Android.bp36
-rw-r--r--finder/fs/fs.go935
-rw-r--r--finder/fs/fs_darwin.go49
-rw-r--r--finder/fs/fs_linux.go49
8 files changed, 1074 insertions, 5 deletions
diff --git a/finder/Android.bp b/finder/Android.bp
index b5c0e130..47b47c9d 100644
--- a/finder/Android.bp
+++ b/finder/Android.bp
@@ -30,7 +30,7 @@ bootstrap_go_package {
"finder_test.go",
],
deps: [
- "soong-fs",
+ "soong-finder-fs",
],
}
diff --git a/finder/cmd/finder.go b/finder/cmd/finder.go
index 70c1dc4a..ab9108f6 100644
--- a/finder/cmd/finder.go
+++ b/finder/cmd/finder.go
@@ -28,7 +28,7 @@ import (
"time"
"android/soong/finder"
- "android/soong/fs"
+ "android/soong/finder/fs"
)
var (
diff --git a/finder/finder.go b/finder/finder.go
index 2dd8e0b0..ce174757 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -30,7 +30,7 @@ import (
"sync/atomic"
"time"
- "android/soong/fs"
+ "android/soong/finder/fs"
)
// This file provides a Finder struct that can quickly search for files satisfying
diff --git a/finder/finder_test.go b/finder/finder_test.go
index 1522c680..29711fc2 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -21,12 +21,12 @@ import (
"os"
"path/filepath"
"reflect"
+ "runtime/debug"
"sort"
"testing"
"time"
- "android/soong/fs"
- "runtime/debug"
+ "android/soong/finder/fs"
)
// some utils for tests to use
diff --git a/finder/fs/Android.bp b/finder/fs/Android.bp
new file mode 100644
index 00000000..fe0a0d36
--- /dev/null
+++ b/finder/fs/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 2017 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.
+
+//
+// mock filesystem
+//
+
+bootstrap_go_package {
+ name: "soong-finder-fs",
+ pkgPath: "android/soong/finder/fs",
+ srcs: [
+ "fs.go",
+ ],
+ darwin: {
+ srcs: [
+ "fs_darwin.go",
+ ],
+ },
+ linux: {
+ srcs: [
+ "fs_linux.go",
+ ],
+ },
+}
+
diff --git a/finder/fs/fs.go b/finder/fs/fs.go
new file mode 100644
index 00000000..eff8ad07
--- /dev/null
+++ b/finder/fs/fs.go
@@ -0,0 +1,935 @@
+// Copyright 2017 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"
+ "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) 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
+ readErr error
+}
+
+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.readErr != nil {
+ return "", &os.PathError{
+ Op: "read",
+ Path: path,
+ Err: parentNode.readErr,
+ }
+ }
+
+ 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.readErr != nil {
+ return "", &os.PathError{
+ Op: "read",
+ Path: path,
+ Err: link.readErr,
+ }
+ }
+
+ 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.readErr != nil {
+ return nil, &os.PathError{
+ Op: "open",
+ Path: fileName,
+ Err: file.readErr,
+ }
+ }
+ 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.readErr != nil {
+ return nil, &os.PathError{
+ Op: "read",
+ Path: path,
+ Err: dir.readErr,
+ }
+ }
+ // 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.readErr != nil {
+ return &os.PathError{
+ Op: "move",
+ Path: sourcePath,
+ Err: sourceParentDir.readErr,
+ }
+ }
+
+ // 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.readErr != nil {
+ return &os.PathError{
+ Op: "move",
+ Path: destParentPath,
+ Err: destParentDir.readErr,
+ }
+ }
+ // 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.readErr != nil {
+ return &os.PathError{
+ Op: "write",
+ Path: parentPath,
+ Err: parentDir.readErr,
+ }
+ }
+
+ baseName := filepath.Base(filePath)
+ _, exists := parentDir.files[baseName]
+ if !exists {
+ parentDir.modTime = m.Clock.Time()
+ parentDir.files[baseName] = m.newFile()
+ } else {
+ readErr := parentDir.files[baseName].readErr
+ if readErr != nil {
+ return &os.PathError{
+ Op: "write",
+ Path: filePath,
+ Err: readErr,
+ }
+ }
+ }
+ 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
+ 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
+ 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
+
+ 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.readErr != nil {
+ return nil, &os.PathError{
+ Op: "stat",
+ Path: path,
+ Err: parent.readErr,
+ }
+ }
+ 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.readErr != nil {
+ return &os.PathError{
+ Op: "remove",
+ Path: path,
+ Err: parentDir.readErr,
+ }
+ }
+ _, 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.readErr != nil {
+ return &os.PathError{
+ Op: "link",
+ Path: newPath,
+ Err: newParentDir.readErr,
+ }
+ }
+ 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.readErr != nil {
+ return &os.PathError{
+ Op: "removeAll",
+ Path: path,
+ Err: parentDir.readErr,
+ }
+
+ }
+ _, 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 {
+ var readErr error
+ if !readable {
+ readErr = os.ErrPermission
+ }
+ return m.SetReadErr(path, readErr)
+}
+
+func (m *MockFs) SetReadErr(path string, readErr error) 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.readErr != nil {
+ return &os.PathError{
+ Op: "chmod",
+ Path: parentPath,
+ Err: parentDir.readErr,
+ }
+ }
+
+ inode, err := m.getInode(parentDir, leaf)
+ if err != nil {
+ return err
+ }
+ inode.readErr = readErr
+ 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
+}
diff --git a/finder/fs/fs_darwin.go b/finder/fs/fs_darwin.go
new file mode 100644
index 00000000..c0488358
--- /dev/null
+++ b/finder/fs/fs_darwin.go
@@ -0,0 +1,49 @@
+// Copyright 2017 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 (
+ "fmt"
+ "os"
+ "syscall"
+ "time"
+)
+
+func (osFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
+ sys := info.Sys()
+ darwinStats, ok := sys.(*syscall.Stat_t)
+ if ok {
+ return darwinStats.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()
+ darwinStats, ok := sys.(*syscall.Stat_t)
+ if ok {
+ return uint64(darwinStats.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()
+ darwinStats, ok := sys.(*syscall.Stat_t)
+ if ok {
+ return time.Unix(darwinStats.Ctimespec.Sec, darwinStats.Ctimespec.Nsec), nil
+ }
+ return time.Time{}, fmt.Errorf("%v is not a *syscall.Stat_t", sys)
+}
diff --git a/finder/fs/fs_linux.go b/finder/fs/fs_linux.go
new file mode 100644
index 00000000..0a22a2c9
--- /dev/null
+++ b/finder/fs/fs_linux.go
@@ -0,0 +1,49 @@
+// Copyright 2017 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 (
+ "fmt"
+ "os"
+ "syscall"
+ "time"
+)
+
+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.Time{}, fmt.Errorf("%v is not a *syscall.Stat_t", sys)
+}