mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
190 lines
5.3 KiB
Go
190 lines
5.3 KiB
Go
package fileresolver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/anchore/archiver/v3"
|
|
"github.com/anchore/stereoscope/pkg/filetree"
|
|
"github.com/anchore/syft/internal/log"
|
|
"github.com/anchore/syft/syft/file"
|
|
)
|
|
|
|
type archiveAccessPath struct {
|
|
realPath string
|
|
accessPath string
|
|
archiveRealPath string
|
|
archiveDepth int
|
|
}
|
|
|
|
const (
|
|
archiveTempPathPattern = "syft-archivePaths-"
|
|
archiveContentTempPathPattern = "syft-archive-contents-"
|
|
)
|
|
|
|
var ErrSkipPath = errors.New("skip path")
|
|
|
|
var _ file.Resolver = (*Directory)(nil)
|
|
|
|
// Directory implements path and content access for the directory data source.
|
|
type Directory struct {
|
|
FiletreeResolver
|
|
path string
|
|
indexer *directoryIndexer
|
|
}
|
|
|
|
func NewFromDirectory(root, base string, maxArchiveRecursiveIndexDepth int, pathFilters ...PathIndexVisitor) (*Directory, func() error, error) {
|
|
var cleanupFn = func() error { return nil }
|
|
directory, err := newFromDirectoryWithoutIndex(root, base, pathFilters...)
|
|
if err != nil {
|
|
return nil, cleanupFn, err
|
|
}
|
|
|
|
if err = directory.buildIndex(); err != nil {
|
|
return nil, cleanupFn, fmt.Errorf("unable to build index: %w", err)
|
|
}
|
|
|
|
if maxArchiveRecursiveIndexDepth != 0 && len(directory.indexer.archivePaths) > 0 {
|
|
archiveTempDir, err := os.MkdirTemp("", archiveTempPathPattern)
|
|
if err != nil {
|
|
return nil, cleanupFn, fmt.Errorf("unable to create tempdir for archive processing: %w", err)
|
|
}
|
|
|
|
return directory, func() error {
|
|
return os.RemoveAll(archiveTempDir)
|
|
}, directory.buildArchiveIndex(archiveTempDir, directory.indexer.archivePaths, maxArchiveRecursiveIndexDepth)
|
|
}
|
|
|
|
return directory, cleanupFn, nil
|
|
}
|
|
|
|
func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
|
|
chroot, err := NewChrootContextFromCWD(root, base)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to interpret chroot context: %w", err)
|
|
}
|
|
|
|
cleanRoot := chroot.Root()
|
|
cleanBase := chroot.Base()
|
|
|
|
return &Directory{
|
|
path: cleanRoot,
|
|
FiletreeResolver: FiletreeResolver{
|
|
Chroot: *chroot,
|
|
Tree: filetree.New(),
|
|
Index: filetree.NewIndex(),
|
|
Opener: nativeOSFileOpener,
|
|
},
|
|
indexer: newDirectoryIndexer(cleanRoot, cleanBase, pathFilters...),
|
|
}, nil
|
|
}
|
|
|
|
func (r *Directory) buildIndex() error {
|
|
if r.indexer == nil {
|
|
return fmt.Errorf("no directory indexer configured")
|
|
}
|
|
tree, index, err := r.indexer.build()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.Tree = tree
|
|
r.Index = index
|
|
r.SearchContext = filetree.NewSearchContext(tree, index)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Directory) buildArchiveIndex(archiveTempDir string, archives []string, maxArchiveIndexDepth int) error {
|
|
archivesToIndex := make([]archiveAccessPath, len(archives))
|
|
for i, archive := range archives {
|
|
archivesToIndex[i] = archiveAccessPath{
|
|
realPath: r.Chroot.ToChrootPath(archive),
|
|
accessPath: r.Chroot.ToChrootPath(archive),
|
|
archiveRealPath: archive,
|
|
}
|
|
}
|
|
|
|
loop:
|
|
for {
|
|
var currentArchivePath archiveAccessPath
|
|
switch len(archivesToIndex) {
|
|
case 0:
|
|
break loop
|
|
case 1:
|
|
currentArchivePath, archivesToIndex = archivesToIndex[0], nil
|
|
default:
|
|
currentArchivePath, archivesToIndex = archivesToIndex[0], archivesToIndex[1:]
|
|
}
|
|
|
|
if maxArchiveIndexDepth != -1 && currentArchivePath.archiveDepth >= maxArchiveIndexDepth {
|
|
continue
|
|
}
|
|
|
|
envelopedUnarchiver, err := archiver.ByExtension(currentArchivePath.archiveRealPath)
|
|
if err != nil {
|
|
return fmt.Errorf("could not determine archive envelope type: %w", err)
|
|
}
|
|
|
|
unarchiver, ok := envelopedUnarchiver.(archiver.Unarchiver)
|
|
if !ok {
|
|
return fmt.Errorf("unarchiver %T is not an archiver", envelopedUnarchiver)
|
|
}
|
|
|
|
archiveDestinationTempDir, err := os.MkdirTemp(archiveTempDir, archiveContentTempPathPattern)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create tempdir for archive processing: %w", err)
|
|
}
|
|
|
|
archiveDestinationPath, err := filepath.EvalSymlinks(archiveDestinationTempDir)
|
|
if err != nil {
|
|
var pathErr *os.PathError
|
|
if errors.As(err, &pathErr) {
|
|
// we can't index the path, but we shouldn't consider this to be fatal
|
|
// TODO: known-unknowns
|
|
log.WithFields("archivePath", archiveDestinationTempDir, "error", err).Trace("unable to evaluate symlink while indexing branch")
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err = unarchiver.Unarchive(currentArchivePath.archiveRealPath, archiveDestinationPath); err != nil {
|
|
return fmt.Errorf("unable to unarchive nested file: %w", err)
|
|
}
|
|
|
|
d, err := newFromDirectoryWithoutIndex(archiveDestinationPath, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = d.buildIndex(); err != nil {
|
|
return fmt.Errorf("unable to build index of archive: %w", err)
|
|
}
|
|
|
|
for _, archive := range d.indexer.archivePaths {
|
|
archivesToIndex = append(archivesToIndex, archiveAccessPath{
|
|
realPath: currentArchivePath.realPath,
|
|
accessPath: strings.Replace(archive, archiveDestinationPath, currentArchivePath.accessPath, 1),
|
|
archiveRealPath: archive,
|
|
archiveDepth: currentArchivePath.archiveDepth + 1,
|
|
})
|
|
}
|
|
|
|
d.realPath = currentArchivePath.realPath
|
|
d.accessPath = currentArchivePath.accessPath
|
|
d.archiveRealPath = archiveDestinationPath
|
|
|
|
r.archives = append(r.archives, &d.FiletreeResolver)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stringer to represent a directory path data source
|
|
func (r Directory) String() string {
|
|
return fmt.Sprintf("dir:%s", r.path)
|
|
}
|