Skip to content

Commit

Permalink
fix link cycles with non-existent paths (#148)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>

Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman authored Nov 30, 2022
1 parent d24c9d6 commit e38c6f1
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 6 deletions.
23 changes: 17 additions & 6 deletions pkg/filetree/filetree.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*filenode
var currentNode *filenode.FileNode
var err error
if strategy.FollowAncestorLinks {
currentNode, err = t.resolveAncestorLinks(normalizedPath)
currentNode, err = t.resolveAncestorLinks(normalizedPath, nil)
if err != nil {
return currentNode, err
}
Expand All @@ -185,14 +185,14 @@ func (t *FileTree) node(p file.Path, strategy linkResolutionStrategy) (*filenode
}

if strategy.FollowBasenameLinks {
currentNode, err = t.resolveNodeLinks(currentNode, !strategy.DoNotFollowDeadBasenameLinks)
currentNode, err = t.resolveNodeLinks(currentNode, !strategy.DoNotFollowDeadBasenameLinks, nil)
}
return currentNode, err
}

// return FileNode of the basename in the given path (no resolution is done at or past the basename). Note: it is
// assumed that the given path has already been normalized.
func (t *FileTree) resolveAncestorLinks(path file.Path) (*filenode.FileNode, error) {
func (t *FileTree) resolveAncestorLinks(path file.Path, attemptedPaths internal.Set) (*filenode.FileNode, error) {
// performance optimization... see if there is a node at the path (as if it is a real path). If so,
// use it, otherwise, continue with ancestor resolution
currentNode, err := t.node(path, linkResolutionStrategy{})
Expand Down Expand Up @@ -248,7 +248,7 @@ func (t *FileTree) resolveAncestorLinks(path file.Path) (*filenode.FileNode, err
// links until the next Node is resolved (or not).
isLastPart := idx == len(pathParts)-1
if !isLastPart && currentNode.IsLink() {
currentNode, err = t.resolveNodeLinks(currentNode, true)
currentNode, err = t.resolveNodeLinks(currentNode, true, attemptedPaths)
if err != nil {
// only expected to happen on cycles
return currentNode, err
Expand All @@ -266,11 +266,16 @@ func (t *FileTree) resolveAncestorLinks(path file.Path) (*filenode.FileNode, err

// followNode takes the given FileNode and resolves all links at the base of the real path for the node (this implies
// that NO ancestors are considered).
func (t *FileTree) resolveNodeLinks(n *filenode.FileNode, followDeadBasenameLinks bool) (*filenode.FileNode, error) {
func (t *FileTree) resolveNodeLinks(n *filenode.FileNode, followDeadBasenameLinks bool, attemptedPaths internal.Set) (*filenode.FileNode, error) {
if n == nil {
return nil, fmt.Errorf("cannot resolve links with nil Node given")
}

// we need to short-circuit link resolution that never resolves (cycles) due to a cycle referencing nodes that do not exist
if attemptedPaths == nil {
attemptedPaths = internal.NewStringSet()
}

// note: this assumes that callers are passing paths in which the constituent parts are NOT symlinks
var lastNode *filenode.FileNode

Expand Down Expand Up @@ -318,8 +323,14 @@ func (t *FileTree) resolveNodeLinks(n *filenode.FileNode, followDeadBasenameLink
// preserve the current Node for the next loop (in case we shouldn't follow a potentially dead link)
lastNode = currentNode

// break any cycles with non-existent paths (before attempting to look the path up again)
if attemptedPaths.Contains(string(nextPath)) {
return nil, ErrLinkCycleDetected
}

// get the next Node (based on the next path)
currentNode, err = t.resolveAncestorLinks(nextPath)
attemptedPaths.Add(string(nextPath))
currentNode, err = t.resolveAncestorLinks(nextPath, attemptedPaths)
if err != nil {
// only expected to occur upon cycle detection
return currentNode, err
Expand Down
18 changes: 18 additions & 0 deletions pkg/filetree/filetree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package filetree
import (
"errors"
"fmt"
"github.com/stretchr/testify/require"
"testing"

"github.com/anchore/stereoscope/internal"
Expand Down Expand Up @@ -787,6 +788,23 @@ func TestFileTree_File_CycleDetection(t *testing.T) {

}

func TestFileTree_File_DeadCycleDetection(t *testing.T) {
tr := NewFileTree()
_, err := tr.AddSymLink("/somewhere/acorn", "noobaa-core/../acorn/bin/acorn")
require.NoError(t, err)

// the test.... do we stop when a cycle is detected?
exists, _, err := tr.File("/somewhere/acorn", FollowBasenameLinks)
if err != ErrLinkCycleDetected {
t.Fatalf("should have gotten an error on resolving a file")
}

if exists {
t.Errorf("resolution should not exist in cycle")
}

}

func TestFileTree_AllFiles(t *testing.T) {
tr := NewFileTree()

Expand Down

0 comments on commit e38c6f1

Please sign in to comment.