diff options
author | Colin Cross <ccross@android.com> | 2017-12-21 15:46:01 -0800 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2017-12-22 13:56:17 -0800 |
commit | 8d6395c09d868751c85082d71356ad790e924df9 (patch) | |
tree | c2a369e5f3d766c609ebd980668d181ddaaf5c39 /finder | |
parent | 7b60cdd6e5d81ae6e929d61898f4faefb1ddb8e2 (diff) | |
download | build_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.bp | 2 | ||||
-rw-r--r-- | finder/cmd/finder.go | 2 | ||||
-rw-r--r-- | finder/finder.go | 2 | ||||
-rw-r--r-- | finder/finder_test.go | 4 | ||||
-rw-r--r-- | finder/fs/Android.bp | 36 | ||||
-rw-r--r-- | finder/fs/fs.go | 935 | ||||
-rw-r--r-- | finder/fs/fs_darwin.go | 49 | ||||
-rw-r--r-- | finder/fs/fs_linux.go | 49 |
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) +} |