mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
fix: hash each file once in the file digest cataloger (#5491)
Symlinks resolve to their target, so the same coordinates could show up once per path that reaches them and get hashed again each time. The results map hid this but the work was repeated. Drop repeated coordinates before digesting. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -50,6 +50,18 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
}
|
||||
}
|
||||
|
||||
// symlinks resolve to the same file, so drop repeated coordinates to avoid hashing the same contents more than once
|
||||
seen := make(map[file.Coordinates]struct{}, len(locations))
|
||||
unique := locations[:0]
|
||||
for _, l := range locations {
|
||||
if _, ok := seen[l.Coordinates]; ok {
|
||||
continue
|
||||
}
|
||||
seen[l.Coordinates] = struct{}{}
|
||||
unique = append(unique, l)
|
||||
}
|
||||
locations = unique
|
||||
|
||||
prog := catalogingProgress(int64(len(locations)))
|
||||
|
||||
err := sync.Collect(&ctx, cataloging.ExecutorFile, sync.ToSeq(locations), func(location file.Location) ([]file.Digest, error) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -207,6 +208,45 @@ func TestFileDigestCataloger_GivenCoordinates(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
// countingResolver records how many times each coordinate's contents are opened
|
||||
type countingResolver struct {
|
||||
file.Resolver
|
||||
lock sync.Mutex
|
||||
counts map[file.Coordinates]int
|
||||
}
|
||||
|
||||
func (r *countingResolver) FileContentsByLocation(l file.Location) (io.ReadCloser, error) {
|
||||
r.lock.Lock()
|
||||
r.counts[l.Coordinates]++
|
||||
r.lock.Unlock()
|
||||
return r.Resolver.FileContentsByLocation(l)
|
||||
}
|
||||
|
||||
func TestDigestsCataloger_hashesEachCoordinateOnce(t *testing.T) {
|
||||
testImage := "image-file-type-mix"
|
||||
|
||||
testutils.SkipWithoutLinuxContainers(t)
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: testImage,
|
||||
})
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
// /symlink-1 and /file-1.txt both resolve to the same coordinates
|
||||
counter := &countingResolver{Resolver: resolver, counts: make(map[file.Coordinates]int)}
|
||||
|
||||
actual, err := NewCataloger([]crypto.Hash{crypto.MD5}).Catalog(context.Background(), counter)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, actual)
|
||||
|
||||
for c, n := range counter.counts {
|
||||
assert.Equal(t, 1, n, "contents of %+v opened %d times", c, n)
|
||||
}
|
||||
}
|
||||
|
||||
type panicReader struct{}
|
||||
|
||||
func (panicReader) Read([]byte) (int, error) { panic("boom") }
|
||||
|
||||
Reference in New Issue
Block a user