archivescanner

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: GPL-3.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DetectFormat

func DetectFormat(ctx context.Context, bsdtarPath, archivePath string) (format, compression string, err error)

DetectFormat runs bsdtar -tvvf on the given archive and returns the "Archive Format: ..., Compression: ..." trailer line. This can be used to determine the appropriate file extension for a blob that has no original filename recorded.

func RescanOne added in v0.8.0

func RescanOne(
	ctx context.Context,
	sqldb *sql.DB,
	store blobstore.Store,
	scanner Scanner,
	archiveSha256 string,
	logger *slog.Logger,
) error

RescanOne re-reads a single archive and replaces its recorded inventory, even if it was already inventoried. The archive is scanned first and only then is the old inventory swapped for the new one in a single short transaction, so a failed scan leaves the existing inventory untouched and the database write lock is never held while bsdtar runs. Content hashes cached on the old entries are carried over where they are still valid, and the scanned timestamp of every version of the archive is refreshed.

func ScanOne

func ScanOne(
	ctx context.Context,
	sqldb *sql.DB,
	queries *dbq.Queries,
	store blobstore.Store,
	scanner Scanner,
	archiveSha256 string,
	logger *slog.Logger,
) error

ScanOne scans a single archive by sha256 and commits its inventory in a single transaction. If the archive has already been inventoried it is a no-op. Returns an error only if the scan or commit fails - the caller is responsible for deciding how loudly to surface that.

Types

type Entry

type Entry struct {
	// Position is the zero-based index of this entry in the archive listing
	// Used as the canonical key since archives may contain duplicate paths
	Position int

	// RawPath is the path as it appears in the archive, unmodified
	RawPath string

	// Type is the entry type derived from the permission string
	Type EntryType

	// SizeBytes is the uncompressed size as reported by the archive header
	// Zero for directories; may be zero for symlinks
	SizeBytes int64

	// LinkTarget is the symlink destination, only set when Type == EntryTypeSymlink
	LinkTarget string

	// ParseError is non-empty if this line could not be fully parsed.
	// The entry is still recorded with whatever fields were extractable,
	// and Type will be EntryTypeOther
	ParseError string
}

Entry represents a single entry parsed from `bsdtar -tvvf` output. It reflects the raw archive contents before any remap rules are applied. Paths are not normalized or validated here - that is the planner's job.

func Parse

func Parse(r io.Reader) ([]Entry, error)

Parse reads the output of `bsdtar -tvvf <archive>` and returns one Entry per line. It never returns an error for individual bad lines - those are recorded in Entry.ParseError so the caller gets a complete picture of the archive. A non-nil error return means the reader itself failed.

bsdtar -tvvf produces lines like:

-rw-r--r--  0 0      0      123456 Jan  1 00:00 path/to/file
lrwxrwxrwx  0 0      0           0 Jan  1 00:00 link -> target
drwxr-xr-x  0 0      0           0 Jan  1 00:00 some/dir/

bsdtar prints a summary trailer as the final line, e.g.:

"Archive Format: POSIX ustar format,  Compression: gzip"
"Archive Format: RAR,  Compression: none"
"Archive Format: 7-Zip,  Compression: none"

This line is skipped and does not produce an Entry.

type EntryType

type EntryType string

EntryType represents the type of an entry within an archive, as reported by bsdtar.

const (
	EntryTypeFile    EntryType = "file"
	EntryTypeDir     EntryType = "dir"
	EntryTypeSymlink EntryType = "symlink"
	EntryTypeOther   EntryType = "other"
)

type ScanAllResult

type ScanAllResult struct {
	Scanned int
	Failed  int
}

ScanAllResult summarizes the outcome of a ScanAll run

func RescanAll added in v0.8.0

func RescanAll(
	ctx context.Context,
	sqldb *sql.DB,
	queries *dbq.Queries,
	store blobstore.Store,
	scanner Scanner,
	logger *slog.Logger,
) (ScanAllResult, error)

RescanAll re-reads every archive in the blob store and replaces its recorded inventory (see RescanOne). Each archive is handled in its own transaction, and individual failures are logged but do not abort the run; an archive that fails keeps its existing inventory.

func ScanAll

func ScanAll(
	ctx context.Context,
	sqldb *sql.DB,
	queries *dbq.Queries,
	store blobstore.Store,
	scanner Scanner,
	logger *slog.Logger,
) (ScanAllResult, error)

ScanAll scans all archives that have not yet had their inventory populated Each archive is scanned and committed in its own transaction so progress is saved as we go - a failure on one archive does not affect others Individual archive failures are logged but do not abort the run

type ScanResult

type ScanResult struct {
	Entries []Entry
	// Warnings contains any output bsdtar wrote to stderr. A non-empty
	// Warnings does not indicate failure - the scan succeeded but bsdtar
	// had something to say (e.g. unsupported extended attributes).
	Warnings string
}

ScanResult is the output of a successful scan

type Scanner

type Scanner struct {
	// BsdtarPath is the path to the bsdtar binary. If empty, "bsdtar" is
	// looked up via PATH
	BsdtarPath string
}

Scanner invokes bsdtar to list archive contents

func (Scanner) Scan

func (s Scanner) Scan(ctx context.Context, archivePath string) (ScanResult, error)

Scan runs `bsdtar -tvvf <archivePath>` and returns the parsed entries A non-zero exit from bsdtar is treated as a hard error; the stderr output is included in the returned error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL