Skip to content

Commit b49819c

Browse files
committed
Added loop detection to ReadDirRecursive* methods
1 parent 5647e37 commit b49819c

File tree

14 files changed

+87
-0
lines changed

14 files changed

+87
-0
lines changed

Diff for: readdir.go

+9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
package paths
3131

3232
import (
33+
"errors"
3334
"os"
3435
"strings"
3536
)
@@ -83,7 +84,15 @@ func (p *Path) ReadDirRecursive() (PathList, error) {
8384
func (p *Path) ReadDirRecursiveFiltered(recursionFilter ReadDirFilter, filters ...ReadDirFilter) (PathList, error) {
8485
var search func(*Path) (PathList, error)
8586

87+
explored := map[string]bool{}
8688
search = func(currPath *Path) (PathList, error) {
89+
canonical := currPath.Canonical().path
90+
if explored[canonical] {
91+
return nil, errors.New("directories symlink loop detected")
92+
}
93+
explored[canonical] = true
94+
defer delete(explored, canonical)
95+
8796
infos, err := os.ReadDir(currPath.path)
8897
if err != nil {
8998
return nil, err

Diff for: readdir_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"fmt"
3434
"os"
3535
"testing"
36+
"time"
3637

3738
"github.com/stretchr/testify/require"
3839
)
@@ -245,3 +246,71 @@ func TestReadDirRecursiveFiltered(t *testing.T) {
245246
pathEqualsTo(t, "testdata/fileset/test.txt", l[7])
246247
pathEqualsTo(t, "testdata/fileset/test.txt.gz", l[8])
247248
}
249+
250+
func TestReadDirRecursiveLoopDetection(t *testing.T) {
251+
loopsPath := New("testdata", "loops")
252+
unbuondedReaddir := func(testdir string) (PathList, error) {
253+
// This is required to unbound the recursion, otherwise it will stop
254+
// when the paths becomes too long due to the symlink loop: this is not
255+
// what we want, we are looking for an early detection of the loop.
256+
skipBrokenLinks := func(p *Path) bool {
257+
_, err := p.Stat()
258+
return err == nil
259+
}
260+
261+
var files PathList
262+
var err error
263+
done := make(chan bool)
264+
go func() {
265+
files, err = loopsPath.Join(testdir).ReadDirRecursiveFiltered(
266+
skipBrokenLinks,
267+
)
268+
done <- true
269+
}()
270+
require.Eventually(
271+
t,
272+
func() bool {
273+
select {
274+
case <-done:
275+
return true
276+
default:
277+
return false
278+
}
279+
},
280+
5*time.Second,
281+
10*time.Millisecond,
282+
"Infinite symlink loop while loading sketch",
283+
)
284+
return files, err
285+
}
286+
287+
for _, dir := range []string{"loop_1", "loop_2", "loop_3", "loop_4"} {
288+
l, err := unbuondedReaddir(dir)
289+
require.EqualError(t, err, "directories symlink loop detected", "loop not detected in %s", dir)
290+
require.Nil(t, l)
291+
}
292+
293+
{
294+
l, err := unbuondedReaddir("regular_1")
295+
require.NoError(t, err)
296+
require.Len(t, l, 4)
297+
l.Sort()
298+
pathEqualsTo(t, "testdata/loops/regular_1/dir1", l[0])
299+
pathEqualsTo(t, "testdata/loops/regular_1/dir1/file1", l[1])
300+
pathEqualsTo(t, "testdata/loops/regular_1/dir2", l[2])
301+
pathEqualsTo(t, "testdata/loops/regular_1/dir2/file1", l[3])
302+
}
303+
304+
{
305+
l, err := unbuondedReaddir("regular_2")
306+
require.NoError(t, err)
307+
require.Len(t, l, 6)
308+
l.Sort()
309+
pathEqualsTo(t, "testdata/loops/regular_2/dir1", l[0])
310+
pathEqualsTo(t, "testdata/loops/regular_2/dir1/file1", l[1])
311+
pathEqualsTo(t, "testdata/loops/regular_2/dir2", l[2])
312+
pathEqualsTo(t, "testdata/loops/regular_2/dir2/dir1", l[3])
313+
pathEqualsTo(t, "testdata/loops/regular_2/dir2/dir1/file1", l[4])
314+
pathEqualsTo(t, "testdata/loops/regular_2/dir2/file2", l[5])
315+
}
316+
}

Diff for: testdata/loops/loop_1/dir1/loop

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../dir1

Diff for: testdata/loops/loop_2/dir1/loop2

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../dir2

Diff for: testdata/loops/loop_2/dir2/loop1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../dir1

Diff for: testdata/loops/loop_3/dir1/loop2

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../dir2

Diff for: testdata/loops/loop_3/dir2/dir3/loop2

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../dir1/

Diff for: testdata/loops/loop_4/dir1/dir2/loop2

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../dir3

Diff for: testdata/loops/loop_4/dir1/dir3/dir4/loop1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../dir1

Diff for: testdata/loops/regular_1/dir1/file1

Whitespace-only changes.

Diff for: testdata/loops/regular_1/dir2

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dir1

Diff for: testdata/loops/regular_2/dir1/file1

Whitespace-only changes.

Diff for: testdata/loops/regular_2/dir2/dir1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../dir1

Diff for: testdata/loops/regular_2/dir2/file2

Whitespace-only changes.

0 commit comments

Comments
 (0)