Files
syft/internal/archive/README.md
T
2026-09-22 00:23:36 -04:00

63 lines
3.2 KiB
Markdown

# internal/archive
Opens one archive as its own filesystem so the ordinary catalogers can run against its contents.
The archive cataloger task (`internal/task/archive_tasks.go`) drives the recursion; this package
does everything for a single archive.
## Flow
```
Extract(ctx, reader, fileSystemID, archivePath, limiter, exclusions)
acquireContent read in place if the reader is already seekable, else hold in memory / write to disk
identifyFormat mholt/archives sniffing first, end-of-central-directory fallback for stub-prefixed zips
extractInto every entry -> EntryStore (excluded entries are never stored)
NewIndex file.Resolver over the store's entries
digestsOf SHA-1 of the archive itself
```
`Extract` returns `nil` for content that is not an archive, `ErrDiskLimitReached` when the archive's
own bytes cannot be placed, and otherwise an `Extracted` whose `Cleanup` releases everything.
## Where bytes live
| what | in memory while... | otherwise |
|---|---|---|
| the archive's own bytes | the reader is not seekable and memory admits them | `<workdir>/archive` |
| its entries' content | memory admits them | `<workdir>/entries`, one file for all entries, located by offset |
Anything already random-access (an `*os.File`, or a nested archive read out of its parent's store)
is used in place and costs nothing. The work directory is created only when something is written,
so most archives never touch the filesystem. Held archive bytes are released as soon as extraction
and digesting finish.
## Limits
`Limits` bound what one scan holds *at once*; each falls as archives are released. For both limits,
positive is the bound, zero forbids the resource, negative is unbounded.
- `Limiter` is scan-wide; `Charge` is one archive's draw on it. Bytes are charged as they land,
never from declared sizes.
- Memory refused → content moves to disk. Disk refused → the archive's own bytes cannot be placed
(`ErrDiskLimitReached`, archive skipped) or an entry cannot be stored (`Extracted.Truncated`,
the entries stored so far are still cataloged).
- Index bookkeeping is approximated as `approxIndexBytesPerEntry` (2 KiB) plus name length per
entry, charged to memory and falling back to disk so a zero memory limit still indexes.
## Index (the resolver)
Paths are archive-relative with no leading slash. Directories implied by entry paths exist for
`HasPath`. Links resolve inside the archive only; a glob matching a link and its target returns one
location. `FilesByGlob` narrows by the pattern's last segment through a base-name index
(`internal/index`) when it is a literal, `*suffix` or `prefix*`, and otherwise matches every file
with doublestar.
## Other pieces
- `Exclusions`: the scan's `**/` exclusion patterns, matched against each entry path and its parent
directories at extraction time. Sourced from `source.PathExcluder`.
- `Traversal`: put on the context by the task so a cataloger (java) knows which archive it is inside.
`VirtualPath(location)` builds the `outer.war:WEB-INF/lib/inner.jar` style chain from the
`ArchivePath` the index stamps on every location.
- `entryHeader`: cleans entry names to archive-relative paths, drops device nodes and fifos, and
refuses names longer than PATH_MAX.