From 0e306e7a89eb332b97e800955dbc5aff46c1430d Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 8 Oct 2021 17:50:17 -0700 Subject: [PATCH] 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 --- pathtools/glob.go | 4 ++++ pathtools/glob_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) 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 @@ func startGlob(fs FileSystem, pattern string, excludes []string, 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 @@ func TestGlobDontFollowDanglingSymlinks(t *testing.T) { } } +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)