Handle dangling symlinks when following symlinks
When globbing with following symlinks enabled, treat dangling symlinks
as files instead of erroring.
Bug: 202547639
Test: TestGlobFollowDanglingSymlinks
Change-Id: Ic1b241d3fcf1bc6989cb724d00c2b97fefa8dcdb
diff --git a/pathtools/glob.go b/pathtools/glob.go
index 14cdacf..4da4496 100644
--- a/pathtools/glob.go
+++ b/pathtools/glob.go
@@ -130,6 +130,10 @@
info, err = fs.Lstat(match)
} else {
info, err = fs.Stat(match)
+ if err != nil && os.IsNotExist(err) {
+ // ErrNotExist from Stat may be due to a dangling symlink, retry with lstat.
+ info, err = fs.Lstat(match)
+ }
}
if err != nil {
return GlobResult{}, err
diff --git a/pathtools/glob_test.go b/pathtools/glob_test.go
index d847bad..37af483 100644
--- a/pathtools/glob_test.go
+++ b/pathtools/glob_test.go
@@ -721,6 +721,57 @@
}
}
+var globFollowDanglingSymlinkTestCases = []globTestCase{
+ {
+ pattern: `**/*`,
+ matches: []string{"a/", "b/", "c/", "d/", "dangling", "e", "f", "a/a/", "a/a/a", "a/a/f", "b/a/", "b/a/a", "b/a/f", "c/a", "c/f", "d/a", "d/f"},
+ deps: []string{".", "a", "a/a", "b", "b/a", "c", "d"},
+ },
+ {
+ pattern: `dangling`,
+ matches: []string{"dangling"},
+ deps: []string{"dangling"},
+ },
+}
+
+func TestMockGlobFollowDanglingSymlinks(t *testing.T) {
+ files := []string{
+ "a/a/a",
+ "a/a/f -> ../../f",
+ "b -> a",
+ "c -> a/a",
+ "d -> c",
+ "e -> a/a/a",
+ "f",
+ "dangling -> missing",
+ }
+
+ mockFiles := make(map[string][]byte)
+
+ for _, f := range files {
+ mockFiles[f] = nil
+ }
+
+ mock := MockFs(mockFiles)
+
+ for _, testCase := range globFollowDanglingSymlinkTestCases {
+ t.Run(testCase.pattern, func(t *testing.T) {
+ testGlob(t, mock, testCase, FollowSymlinks)
+ })
+ }
+}
+
+func TestGlobFollowDanglingSymlinks(t *testing.T) {
+ os.Chdir("testdata/dangling")
+ defer os.Chdir("../..")
+
+ for _, testCase := range globFollowDanglingSymlinkTestCases {
+ t.Run(testCase.pattern, func(t *testing.T) {
+ testGlob(t, OsFs, testCase, FollowSymlinks)
+ })
+ }
+}
+
func testGlob(t *testing.T, fs FileSystem, testCase globTestCase, follow ShouldFollowSymlinks) {
t.Helper()
result, err := fs.Glob(testCase.pattern, testCase.excludes, follow)