Fail the Finder in case of unexpected fs error
Permissions errors are ignored Errors on pruned dirs are also ignored Bug: 62455338 Test: m -j blueprint_tools # which runs unit tests Change-Id: I8ba85fdd0295deb7dc374a851212e7c850e76b75
This commit is contained in:
parent
a622de8498
commit
b629e184dd
4 changed files with 238 additions and 67 deletions
|
@ -127,9 +127,13 @@ func run() error {
|
||||||
usage()
|
usage()
|
||||||
return errors.New("Param 'db' must be nonempty")
|
return errors.New("Param 'db' must be nonempty")
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := []string{}
|
matches := []string{}
|
||||||
for i := 0; i < numIterations; i++ {
|
for i := 0; i < numIterations; i++ {
|
||||||
matches = runFind(params, logger)
|
matches, err = runFind(params, logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
findDuration := time.Since(startTime)
|
findDuration := time.Since(startTime)
|
||||||
logger.Printf("Found these %v inodes in %v :\n", len(matches), findDuration)
|
logger.Printf("Found these %v inodes in %v :\n", len(matches), findDuration)
|
||||||
|
@ -142,8 +146,11 @@ func run() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFind(params finder.CacheParams, logger *log.Logger) (paths []string) {
|
func runFind(params finder.CacheParams, logger *log.Logger) (paths []string, err error) {
|
||||||
service := finder.New(params, fs.OsFs, logger, dbPath)
|
service, err := finder.New(params, fs.OsFs, logger, dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
defer service.Shutdown()
|
defer service.Shutdown()
|
||||||
return service.FindAll()
|
return service.FindAll(), nil
|
||||||
}
|
}
|
||||||
|
|
125
finder/finder.go
125
finder/finder.go
|
@ -150,6 +150,8 @@ type Finder struct {
|
||||||
// temporary state
|
// temporary state
|
||||||
threadPool *threadPool
|
threadPool *threadPool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
fsErrs []fsErr
|
||||||
|
errlock sync.Mutex
|
||||||
|
|
||||||
// non-temporary state
|
// non-temporary state
|
||||||
modifiedFlag int32
|
modifiedFlag int32
|
||||||
|
@ -158,7 +160,7 @@ type Finder struct {
|
||||||
|
|
||||||
// New creates a new Finder for use
|
// New creates a new Finder for use
|
||||||
func New(cacheParams CacheParams, filesystem fs.FileSystem,
|
func New(cacheParams CacheParams, filesystem fs.FileSystem,
|
||||||
logger Logger, dbPath string) *Finder {
|
logger Logger, dbPath string) (f *Finder, err error) {
|
||||||
|
|
||||||
numThreads := runtime.NumCPU() * 2
|
numThreads := runtime.NumCPU() * 2
|
||||||
numDbLoadingThreads := numThreads
|
numDbLoadingThreads := numThreads
|
||||||
|
@ -172,7 +174,7 @@ func New(cacheParams CacheParams, filesystem fs.FileSystem,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
finder := &Finder{
|
f = &Finder{
|
||||||
numDbLoadingThreads: numDbLoadingThreads,
|
numDbLoadingThreads: numDbLoadingThreads,
|
||||||
numSearchingThreads: numSearchingThreads,
|
numSearchingThreads: numSearchingThreads,
|
||||||
cacheMetadata: metadata,
|
cacheMetadata: metadata,
|
||||||
|
@ -183,10 +185,23 @@ func New(cacheParams CacheParams, filesystem fs.FileSystem,
|
||||||
DbPath: dbPath,
|
DbPath: dbPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
finder.loadFromFilesystem()
|
f.loadFromFilesystem()
|
||||||
|
|
||||||
finder.verbosef("Done parsing db\n")
|
// check for any filesystem errors
|
||||||
return finder
|
err = f.getErr()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm that every path mentioned in the CacheConfig exists
|
||||||
|
for _, path := range cacheParams.RootDirs {
|
||||||
|
node := f.nodes.GetNode(filepath.Clean(path), false)
|
||||||
|
if node == nil || node.ModTime == 0 {
|
||||||
|
return nil, fmt.Errorf("%v does not exist\n", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindNamed searches for every cached file
|
// FindNamed searches for every cached file
|
||||||
|
@ -338,10 +353,6 @@ func (f *Finder) loadFromFilesystem() {
|
||||||
f.startWithoutExternalCache()
|
f.startWithoutExternalCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
f.verbosef("Waiting for pending requests to complete\n")
|
|
||||||
f.threadPool.Wait()
|
|
||||||
f.verbosef("Is idle after %v\n", time.Now().Sub(startTime))
|
|
||||||
f.threadPool = nil
|
f.threadPool = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,6 +609,15 @@ func (p *threadPool) Wait() {
|
||||||
p.receivedRequests.Wait()
|
p.receivedRequests.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fsErr struct {
|
||||||
|
path string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e fsErr) String() string {
|
||||||
|
return e.path + ": " + e.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Finder) serializeCacheEntry(dirInfos []dirFullInfo) ([]byte, error) {
|
func (f *Finder) serializeCacheEntry(dirInfos []dirFullInfo) ([]byte, error) {
|
||||||
// group each dirFullInfo by its Device, to avoid having to repeat it in the output
|
// group each dirFullInfo by its Device, to avoid having to repeat it in the output
|
||||||
dirsByDevice := map[uint64][]PersistedDirInfo{}
|
dirsByDevice := map[uint64][]PersistedDirInfo{}
|
||||||
|
@ -943,13 +963,17 @@ func (f *Finder) startFromExternalCache() (err error) {
|
||||||
for i := range nodesToWalk {
|
for i := range nodesToWalk {
|
||||||
f.listDirsAsync(nodesToWalk[i])
|
f.listDirsAsync(nodesToWalk[i])
|
||||||
}
|
}
|
||||||
f.verbosef("Loaded db and statted its contents in %v\n", time.Since(startTime))
|
f.verbosef("Loaded db and statted known dirs in %v\n", time.Since(startTime))
|
||||||
|
f.threadPool.Wait()
|
||||||
|
f.verbosef("Loaded db and statted all dirs in %v\n", time.Now().Sub(startTime))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// startWithoutExternalCache starts scanning the filesystem according to the cache config
|
// startWithoutExternalCache starts scanning the filesystem according to the cache config
|
||||||
// startWithoutExternalCache should be called if startFromExternalCache is not applicable
|
// startWithoutExternalCache should be called if startFromExternalCache is not applicable
|
||||||
func (f *Finder) startWithoutExternalCache() {
|
func (f *Finder) startWithoutExternalCache() {
|
||||||
|
startTime := time.Now()
|
||||||
configDirs := f.cacheMetadata.Config.RootDirs
|
configDirs := f.cacheMetadata.Config.RootDirs
|
||||||
|
|
||||||
// clean paths
|
// clean paths
|
||||||
|
@ -977,6 +1001,10 @@ func (f *Finder) startWithoutExternalCache() {
|
||||||
f.verbosef("Starting find of %v\n", path)
|
f.verbosef("Starting find of %v\n", path)
|
||||||
f.startFind(path)
|
f.startFind(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.threadPool.Wait()
|
||||||
|
|
||||||
|
f.verbosef("Scanned filesystem (not using cache) in %v\n", time.Now().Sub(startTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInfoUpToDate tells whether <new> can confirm that results computed at <old> are still valid
|
// isInfoUpToDate tells whether <new> can confirm that results computed at <old> are still valid
|
||||||
|
@ -1114,6 +1142,79 @@ func (f *Finder) dumpDb() error {
|
||||||
|
|
||||||
f.verbosef("Wrote db in %v\n", time.Now().Sub(serializeDate))
|
f.verbosef("Wrote db in %v\n", time.Now().Sub(serializeDate))
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// canIgnoreFsErr checks for certain classes of filesystem errors that are safe to ignore
|
||||||
|
func (f *Finder) canIgnoreFsErr(err error) bool {
|
||||||
|
pathErr, isPathErr := err.(*os.PathError)
|
||||||
|
if !isPathErr {
|
||||||
|
// Don't recognize this error
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pathErr.Err == os.ErrPermission {
|
||||||
|
// Permission errors are ignored:
|
||||||
|
// https://issuetracker.google.com/37553659
|
||||||
|
// https://github.com/google/kati/pull/116
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if pathErr.Err == os.ErrNotExist {
|
||||||
|
// If a directory doesn't exist, that generally means the cache is out-of-date
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Don't recognize this error
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// onFsError should be called whenever a potentially fatal error is returned from a filesystem call
|
||||||
|
func (f *Finder) onFsError(path string, err error) {
|
||||||
|
if !f.canIgnoreFsErr(err) {
|
||||||
|
// We could send the errors through a channel instead, although that would cause this call
|
||||||
|
// to block unless we preallocated a sufficient buffer or spawned a reader thread.
|
||||||
|
// Although it wouldn't be too complicated to spawn a reader thread, it's still slightly
|
||||||
|
// more convenient to use a lock. Only in an unusual situation should this code be
|
||||||
|
// invoked anyway.
|
||||||
|
f.errlock.Lock()
|
||||||
|
f.fsErrs = append(f.fsErrs, fsErr{path: path, err: err})
|
||||||
|
f.errlock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardErrsForPrunedPaths removes any errors for paths that are no longer included in the cache
|
||||||
|
func (f *Finder) discardErrsForPrunedPaths() {
|
||||||
|
// This function could be somewhat inefficient due to being single-threaded,
|
||||||
|
// but the length of f.fsErrs should be approximately 0, so it shouldn't take long anyway.
|
||||||
|
relevantErrs := make([]fsErr, 0, len(f.fsErrs))
|
||||||
|
for _, fsErr := range f.fsErrs {
|
||||||
|
path := fsErr.path
|
||||||
|
node := f.nodes.GetNode(path, false)
|
||||||
|
if node != nil {
|
||||||
|
// The path in question wasn't pruned due to a failure to process a parent directory.
|
||||||
|
// So, the failure to process this path is important
|
||||||
|
relevantErrs = append(relevantErrs, fsErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.fsErrs = relevantErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// getErr returns an error based on previous calls to onFsErr, if any
|
||||||
|
func (f *Finder) getErr() error {
|
||||||
|
f.discardErrsForPrunedPaths()
|
||||||
|
|
||||||
|
numErrs := len(f.fsErrs)
|
||||||
|
if numErrs < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
maxNumErrsToInclude := 10
|
||||||
|
message := ""
|
||||||
|
if numErrs > maxNumErrsToInclude {
|
||||||
|
message = fmt.Sprintf("finder encountered %v errors: %v...", numErrs, f.fsErrs[:maxNumErrsToInclude])
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("finder encountered %v errors: %v", numErrs, f.fsErrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Finder) statDirAsync(dir *pathMap) {
|
func (f *Finder) statDirAsync(dir *pathMap) {
|
||||||
|
@ -1145,6 +1246,8 @@ func (f *Finder) statDirSync(path string) statResponse {
|
||||||
|
|
||||||
var stats statResponse
|
var stats statResponse
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// possibly record this error
|
||||||
|
f.onFsError(path, err)
|
||||||
// in case of a failure to stat the directory, treat the directory as missing (modTime = 0)
|
// in case of a failure to stat the directory, treat the directory as missing (modTime = 0)
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
@ -1248,6 +1351,8 @@ func (f *Finder) listDirSync(dir *pathMap) {
|
||||||
children, err := f.filesystem.ReadDir(path)
|
children, err := f.filesystem.ReadDir(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// possibly record this error
|
||||||
|
f.onFsError(path, err)
|
||||||
// if listing the contents of the directory fails (presumably due to
|
// if listing the contents of the directory fails (presumably due to
|
||||||
// permission denied), then treat the directory as empty
|
// permission denied), then treat the directory as empty
|
||||||
children = []os.FileInfo{}
|
children = []os.FileInfo{}
|
||||||
|
|
|
@ -16,18 +16,17 @@ package finder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sort"
|
"sort"
|
||||||
|
"testing"
|
||||||
"io/ioutil"
|
"time"
|
||||||
|
|
||||||
"android/soong/fs"
|
"android/soong/fs"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// some utils for tests to use
|
// some utils for tests to use
|
||||||
|
@ -36,6 +35,14 @@ func newFs() *fs.MockFs {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder {
|
func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder {
|
||||||
|
f, err := newFinderAndErr(t, filesystem, cacheParams)
|
||||||
|
if err != nil {
|
||||||
|
fatal(t, err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) (*Finder, error) {
|
||||||
cachePath := "/finder/finder-db"
|
cachePath := "/finder/finder-db"
|
||||||
cacheDir := filepath.Dir(cachePath)
|
cacheDir := filepath.Dir(cachePath)
|
||||||
filesystem.MkDirs(cacheDir)
|
filesystem.MkDirs(cacheDir)
|
||||||
|
@ -44,16 +51,25 @@ func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Fi
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.New(ioutil.Discard, "", 0)
|
logger := log.New(ioutil.Discard, "", 0)
|
||||||
finder := New(cacheParams, filesystem, logger, cachePath)
|
f, err := New(cacheParams, filesystem, logger, cachePath)
|
||||||
return finder
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func finderWithSameParams(t *testing.T, original *Finder) *Finder {
|
func finderWithSameParams(t *testing.T, original *Finder) *Finder {
|
||||||
return New(
|
f, err := finderAndErrorWithSameParams(t, original)
|
||||||
|
if err != nil {
|
||||||
|
fatal(t, err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func finderAndErrorWithSameParams(t *testing.T, original *Finder) (*Finder, error) {
|
||||||
|
f, err := New(
|
||||||
original.cacheMetadata.Config.CacheParams,
|
original.cacheMetadata.Config.CacheParams,
|
||||||
original.filesystem,
|
original.filesystem,
|
||||||
original.logger,
|
original.logger,
|
||||||
original.DbPath)
|
original.DbPath)
|
||||||
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
|
func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
|
||||||
|
@ -61,7 +77,7 @@ func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
|
||||||
filesystem.MkDirs(parent)
|
filesystem.MkDirs(parent)
|
||||||
err := filesystem.WriteFile(path, []byte(content), 0777)
|
err := filesystem.WriteFile(path, []byte(content), 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
fatal(t, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,21 +88,21 @@ func create(t *testing.T, path string, filesystem *fs.MockFs) {
|
||||||
func delete(t *testing.T, path string, filesystem *fs.MockFs) {
|
func delete(t *testing.T, path string, filesystem *fs.MockFs) {
|
||||||
err := filesystem.Remove(path)
|
err := filesystem.Remove(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
fatal(t, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
|
func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
|
||||||
err := filesystem.RemoveAll(path)
|
err := filesystem.RemoveAll(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
fatal(t, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
|
func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
|
||||||
err := filesystem.Rename(oldPath, newPath)
|
err := filesystem.Rename(oldPath, newPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
fatal(t, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +114,7 @@ func link(t *testing.T, newPath string, oldPath string, filesystem *fs.MockFs) {
|
||||||
}
|
}
|
||||||
err = filesystem.Symlink(oldPath, newPath)
|
err = filesystem.Symlink(oldPath, newPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
fatal(t, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func read(t *testing.T, path string, filesystem *fs.MockFs) string {
|
func read(t *testing.T, path string, filesystem *fs.MockFs) string {
|
||||||
|
@ -125,11 +141,20 @@ func setReadable(t *testing.T, path string, readable bool, filesystem *fs.MockFs
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setReadErr(t *testing.T, path string, readErr error, filesystem *fs.MockFs) {
|
||||||
|
err := filesystem.SetReadErr(path, readErr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fatal(t *testing.T, message string) {
|
func fatal(t *testing.T, message string) {
|
||||||
t.Error(message)
|
t.Error(message)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertSameResponse(t *testing.T, actual []string, expected []string) {
|
func assertSameResponse(t *testing.T, actual []string, expected []string) {
|
||||||
sort.Strings(actual)
|
sort.Strings(actual)
|
||||||
sort.Strings(expected)
|
sort.Strings(expected)
|
||||||
|
@ -280,11 +305,11 @@ func TestFilesystemRoot(t *testing.T) {
|
||||||
assertSameResponse(t, foundPaths, []string{createdPath})
|
assertSameResponse(t, foundPaths, []string{createdPath})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNonexistentPath(t *testing.T) {
|
func TestNonexistentDir(t *testing.T) {
|
||||||
filesystem := newFs()
|
filesystem := newFs()
|
||||||
create(t, "/tmp/findme.txt", filesystem)
|
create(t, "/tmp/findme.txt", filesystem)
|
||||||
|
|
||||||
finder := newFinder(
|
_, err := newFinderAndErr(
|
||||||
t,
|
t,
|
||||||
filesystem,
|
filesystem,
|
||||||
CacheParams{
|
CacheParams{
|
||||||
|
@ -292,11 +317,9 @@ func TestNonexistentPath(t *testing.T) {
|
||||||
IncludeFiles: []string{"findme.txt", "skipme.txt"},
|
IncludeFiles: []string{"findme.txt", "skipme.txt"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defer finder.Shutdown()
|
if err == nil {
|
||||||
|
fatal(t, "Did not fail when given a nonexistent root directory")
|
||||||
foundPaths := finder.FindNamedAt("/tmp/IAlsoDontExist", "findme.txt")
|
}
|
||||||
|
|
||||||
assertSameResponse(t, foundPaths, []string{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExcludeDirs(t *testing.T) {
|
func TestExcludeDirs(t *testing.T) {
|
||||||
|
@ -392,7 +415,7 @@ func TestUncachedDir(t *testing.T) {
|
||||||
t,
|
t,
|
||||||
filesystem,
|
filesystem,
|
||||||
CacheParams{
|
CacheParams{
|
||||||
RootDirs: []string{"/IDoNotExist"},
|
RootDirs: []string{"/tmp/b"},
|
||||||
IncludeFiles: []string{"findme.txt"},
|
IncludeFiles: []string{"findme.txt"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -483,7 +506,7 @@ func TestRootDirsContainedInOtherRootDirs(t *testing.T) {
|
||||||
t,
|
t,
|
||||||
filesystem,
|
filesystem,
|
||||||
CacheParams{
|
CacheParams{
|
||||||
RootDirs: []string{"/", "/a/b/c", "/a/b/c/d/e/f", "/a/b/c/d/e/f/g/h/i"},
|
RootDirs: []string{"/", "/tmp/a/b/c", "/tmp/a/b/c/d/e/f", "/tmp/a/b/c/d/e/f/g/h/i"},
|
||||||
IncludeFiles: []string{"findme.txt"},
|
IncludeFiles: []string{"findme.txt"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1571,3 +1594,33 @@ func TestFileNotPermitted(t *testing.T) {
|
||||||
// check results
|
// check results
|
||||||
assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
|
assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCacheEntryPathUnexpectedError(t *testing.T) {
|
||||||
|
// setup filesystem
|
||||||
|
filesystem := newFs()
|
||||||
|
create(t, "/tmp/a/hi.txt", filesystem)
|
||||||
|
|
||||||
|
// run the first finder
|
||||||
|
finder := newFinder(
|
||||||
|
t,
|
||||||
|
filesystem,
|
||||||
|
CacheParams{
|
||||||
|
RootDirs: []string{"/tmp"},
|
||||||
|
IncludeFiles: []string{"hi.txt"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
foundPaths := finder.FindAll()
|
||||||
|
filesystem.Clock.Tick()
|
||||||
|
finder.Shutdown()
|
||||||
|
// check results
|
||||||
|
assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
|
||||||
|
|
||||||
|
// make the directory not readable
|
||||||
|
setReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
|
||||||
|
|
||||||
|
// run the second finder
|
||||||
|
_, err := finderAndErrorWithSameParams(t, finder)
|
||||||
|
if err == nil {
|
||||||
|
fatal(t, "Failed to detect unexpected filesystem error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
68
fs/fs.go
68
fs/fs.go
|
@ -159,7 +159,7 @@ type mockInode struct {
|
||||||
permTime time.Time
|
permTime time.Time
|
||||||
sys interface{}
|
sys interface{}
|
||||||
inodeNumber uint64
|
inodeNumber uint64
|
||||||
readable bool
|
readErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mockInode) ModTime() time.Time {
|
func (m mockInode) ModTime() time.Time {
|
||||||
|
@ -221,11 +221,11 @@ func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canon
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if !parentNode.readable {
|
if parentNode.readErr != nil {
|
||||||
return "", &os.PathError{
|
return "", &os.PathError{
|
||||||
Op: "read",
|
Op: "read",
|
||||||
Path: path,
|
Path: path,
|
||||||
Err: os.ErrPermission,
|
Err: parentNode.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,11 +240,11 @@ func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !link.readable {
|
if link.readErr != nil {
|
||||||
return "", &os.PathError{
|
return "", &os.PathError{
|
||||||
Op: "read",
|
Op: "read",
|
||||||
Path: path,
|
Path: path,
|
||||||
Err: os.ErrPermission,
|
Err: link.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,11 +277,11 @@ func (m *MockFs) getFile(parentDir *mockDir, fileName string) (file *mockFile, e
|
||||||
Err: os.ErrNotExist,
|
Err: os.ErrNotExist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !file.readable {
|
if file.readErr != nil {
|
||||||
return nil, &os.PathError{
|
return nil, &os.PathError{
|
||||||
Op: "open",
|
Op: "open",
|
||||||
Path: fileName,
|
Path: fileName,
|
||||||
Err: os.ErrPermission,
|
Err: file.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return file, nil
|
return file, nil
|
||||||
|
@ -491,11 +491,11 @@ func (m *MockFs) ReadDir(path string) (contents []os.FileInfo, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !dir.readable {
|
if dir.readErr != nil {
|
||||||
return nil, &os.PathError{
|
return nil, &os.PathError{
|
||||||
Op: "read",
|
Op: "read",
|
||||||
Path: path,
|
Path: path,
|
||||||
Err: os.ErrPermission,
|
Err: dir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// describe its contents
|
// describe its contents
|
||||||
|
@ -532,11 +532,11 @@ func (m *MockFs) Rename(sourcePath string, destPath string) error {
|
||||||
Err: os.ErrNotExist,
|
Err: os.ErrNotExist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !sourceParentDir.readable {
|
if sourceParentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "move",
|
Op: "move",
|
||||||
Path: sourcePath,
|
Path: sourcePath,
|
||||||
Err: os.ErrPermission,
|
Err: sourceParentDir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,11 +554,11 @@ func (m *MockFs) Rename(sourcePath string, destPath string) error {
|
||||||
Err: os.ErrNotExist,
|
Err: os.ErrNotExist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !destParentDir.readable {
|
if destParentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "move",
|
Op: "move",
|
||||||
Path: destParentPath,
|
Path: destParentPath,
|
||||||
Err: os.ErrPermission,
|
Err: destParentDir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check the source and dest themselves
|
// check the source and dest themselves
|
||||||
|
@ -648,11 +648,11 @@ func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error
|
||||||
Err: os.ErrNotExist,
|
Err: os.ErrNotExist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !parentDir.readable {
|
if parentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "write",
|
Op: "write",
|
||||||
Path: parentPath,
|
Path: parentPath,
|
||||||
Err: os.ErrPermission,
|
Err: parentDir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,11 +662,12 @@ func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error
|
||||||
parentDir.modTime = m.Clock.Time()
|
parentDir.modTime = m.Clock.Time()
|
||||||
parentDir.files[baseName] = m.newFile()
|
parentDir.files[baseName] = m.newFile()
|
||||||
} else {
|
} else {
|
||||||
if !parentDir.files[baseName].readable {
|
readErr := parentDir.files[baseName].readErr
|
||||||
|
if readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "write",
|
Op: "write",
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
Err: os.ErrPermission,
|
Err: readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -681,7 +682,6 @@ func (m *MockFs) newFile() *mockFile {
|
||||||
newFile.inodeNumber = m.newInodeNumber()
|
newFile.inodeNumber = m.newInodeNumber()
|
||||||
newFile.modTime = m.Clock.Time()
|
newFile.modTime = m.Clock.Time()
|
||||||
newFile.permTime = newFile.modTime
|
newFile.permTime = newFile.modTime
|
||||||
newFile.readable = true
|
|
||||||
return newFile
|
return newFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,7 +694,6 @@ func (m *MockFs) newDir() *mockDir {
|
||||||
newDir.inodeNumber = m.newInodeNumber()
|
newDir.inodeNumber = m.newInodeNumber()
|
||||||
newDir.modTime = m.Clock.Time()
|
newDir.modTime = m.Clock.Time()
|
||||||
newDir.permTime = newDir.modTime
|
newDir.permTime = newDir.modTime
|
||||||
newDir.readable = true
|
|
||||||
return newDir
|
return newDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,7 +704,6 @@ func (m *MockFs) newLink(target string) *mockLink {
|
||||||
newLink.inodeNumber = m.newInodeNumber()
|
newLink.inodeNumber = m.newInodeNumber()
|
||||||
newLink.modTime = m.Clock.Time()
|
newLink.modTime = m.Clock.Time()
|
||||||
newLink.permTime = newLink.modTime
|
newLink.permTime = newLink.modTime
|
||||||
newLink.readable = true
|
|
||||||
|
|
||||||
return newLink
|
return newLink
|
||||||
}
|
}
|
||||||
|
@ -729,11 +727,11 @@ func (m *MockFs) getDir(path string, createIfMissing bool) (dir *mockDir, err er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !parent.readable {
|
if parent.readErr != nil {
|
||||||
return nil, &os.PathError{
|
return nil, &os.PathError{
|
||||||
Op: "stat",
|
Op: "stat",
|
||||||
Path: path,
|
Path: path,
|
||||||
Err: os.ErrPermission,
|
Err: parent.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
childDir, dirExists := parent.subdirs[leaf]
|
childDir, dirExists := parent.subdirs[leaf]
|
||||||
|
@ -781,11 +779,11 @@ func (m *MockFs) Remove(path string) (err error) {
|
||||||
Err: os.ErrNotExist,
|
Err: os.ErrNotExist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !parentDir.readable {
|
if parentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "remove",
|
Op: "remove",
|
||||||
Path: path,
|
Path: path,
|
||||||
Err: os.ErrPermission,
|
Err: parentDir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, isDir := parentDir.subdirs[leaf]
|
_, isDir := parentDir.subdirs[leaf]
|
||||||
|
@ -822,11 +820,11 @@ func (m *MockFs) Symlink(oldPath string, newPath string) (err error) {
|
||||||
|
|
||||||
newParentPath, leaf := pathSplit(newPath)
|
newParentPath, leaf := pathSplit(newPath)
|
||||||
newParentDir, err := m.getDir(newParentPath, false)
|
newParentDir, err := m.getDir(newParentPath, false)
|
||||||
if !newParentDir.readable {
|
if newParentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "link",
|
Op: "link",
|
||||||
Path: newPath,
|
Path: newPath,
|
||||||
Err: os.ErrPermission,
|
Err: newParentDir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -856,11 +854,11 @@ func (m *MockFs) RemoveAll(path string) (err error) {
|
||||||
Err: os.ErrNotExist,
|
Err: os.ErrNotExist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !parentDir.readable {
|
if parentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "removeAll",
|
Op: "removeAll",
|
||||||
Path: path,
|
Path: path,
|
||||||
Err: os.ErrPermission,
|
Err: parentDir.readErr,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -886,6 +884,14 @@ func (m *MockFs) RemoveAll(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockFs) SetReadable(path string, readable bool) error {
|
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)
|
path, err := m.resolve(path, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -895,11 +901,11 @@ func (m *MockFs) SetReadable(path string, readable bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !parentDir.readable {
|
if parentDir.readErr != nil {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "chmod",
|
Op: "chmod",
|
||||||
Path: parentPath,
|
Path: parentPath,
|
||||||
Err: os.ErrPermission,
|
Err: parentDir.readErr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -907,7 +913,7 @@ func (m *MockFs) SetReadable(path string, readable bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inode.readable = readable
|
inode.readErr = readErr
|
||||||
inode.permTime = m.Clock.Time()
|
inode.permTime = m.Clock.Time()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue