From 5a072fbd100970e3f3a797b025113be1ad83e5f9 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 23 Apr 2020 20:12:32 -0400 Subject: [PATCH] Follow symlinks to directories when scanning --- scanner/change_detector.go | 27 ++++++++++++++++++++++++++- scanner/change_detector_test.go | 19 +++++++++++++++++++ tests/fixtures/symlink | 1 + tests/fixtures/symlink2dir | 1 + tests/fixtures/synlink_invalid | 1 + 5 files changed, 48 insertions(+), 1 deletion(-) create mode 120000 tests/fixtures/symlink create mode 120000 tests/fixtures/symlink2dir create mode 120000 tests/fixtures/synlink_invalid diff --git a/scanner/change_detector.go b/scanner/change_detector.go index 5b51d4b84..459975d0d 100644 --- a/scanner/change_detector.go +++ b/scanner/change_detector.go @@ -61,7 +61,12 @@ func (s *ChangeDetector) loadDir(dirPath string) (children []string, lastUpdated return } for _, f := range files { - if f.IsDir() { + isDir, err := IsDirOrSymlinkToDir(dirPath, f) + // Skip invalid symlinks + if err != nil { + continue + } + if isDir { children = append(children, filepath.Join(dirPath, f.Name())) } else { if f.ModTime().After(lastUpdated) { @@ -72,6 +77,26 @@ func (s *ChangeDetector) loadDir(dirPath string) (children []string, lastUpdated return } +// IsDirOrSymlinkToDir returns true if and only if the Dirent represents a file +// system directory, or a symbolic link to a directory. Note that if the Dirent +// is not a directory but is a symbolic link, this method will resolve by +// sending a request to the operating system to follow the symbolic link. +// Copied from github.com/karrick/godirwalk +func IsDirOrSymlinkToDir(baseDir string, info os.FileInfo) (bool, error) { + if info.IsDir() { + return true, nil + } + if info.Mode()&os.ModeSymlink == 0 { + return false, nil + } + // Does this symlink point to a directory? + info, err := os.Stat(filepath.Join(baseDir, info.Name())) + if err != nil { + return false, err + } + return info.IsDir(), nil +} + func (s *ChangeDetector) loadMap(dirMap dirInfoMap, path string, since time.Time, maybe bool) error { children, lastUpdated, err := s.loadDir(path) if err != nil { diff --git a/scanner/change_detector_test.go b/scanner/change_detector_test.go index 12b02a3c0..a3f3d9b2b 100644 --- a/scanner/change_detector_test.go +++ b/scanner/change_detector_test.go @@ -110,6 +110,25 @@ var _ = Describe("ChangeDetector", func() { Expect(deleted).To(BeEmpty()) Expect(changed).To(ConsistOf(P("a/b"))) }) + + Describe("IsDirOrSymlinkToDir", func() { + It("returns true for normal dirs", func() { + dir, _ := os.Stat("tests/fixtures") + Expect(IsDirOrSymlinkToDir("tests", dir)).To(BeTrue()) + }) + It("returns true for symlinks to dirs", func() { + dir, _ := os.Stat("tests/fixtures/symlink2dir") + Expect(IsDirOrSymlinkToDir("tests/fixtures", dir)).To(BeTrue()) + }) + It("returns false for files", func() { + dir, _ := os.Stat("tests/fixtures/test.mp3") + Expect(IsDirOrSymlinkToDir("tests/fixtures", dir)).To(BeFalse()) + }) + It("returns false for symlinks to files", func() { + dir, _ := os.Stat("tests/fixtures/symlink") + Expect(IsDirOrSymlinkToDir("tests/fixtures", dir)).To(BeFalse()) + }) + }) }) // I hate time-based tests.... diff --git a/tests/fixtures/symlink b/tests/fixtures/symlink new file mode 120000 index 000000000..64233a9e9 --- /dev/null +++ b/tests/fixtures/symlink @@ -0,0 +1 @@ +index.html \ No newline at end of file diff --git a/tests/fixtures/symlink2dir b/tests/fixtures/symlink2dir new file mode 120000 index 000000000..b870225aa --- /dev/null +++ b/tests/fixtures/symlink2dir @@ -0,0 +1 @@ +../ \ No newline at end of file diff --git a/tests/fixtures/synlink_invalid b/tests/fixtures/synlink_invalid new file mode 120000 index 000000000..d30fa5577 --- /dev/null +++ b/tests/fixtures/synlink_invalid @@ -0,0 +1 @@ +INVALID \ No newline at end of file