Reduce comparison bundle recognition reads

This commit is contained in:
2026-08-13 03:52:20 +00:00
parent 0516ee148d
commit fb891fad07
4 changed files with 210 additions and 31 deletions

View File

@@ -73,6 +73,18 @@ var ErrUnrecognizedBundle = errors.New("unrecognized comparison bundle")
// PlanDestination performs the read-only comparison destination preflight.
func PlanDestination(workingDirectory, target string, replace bool) (DestinationPlan, error) {
return planDestination(workingDirectory, target, replace, RecognizeBundle)
}
func planDestination(workingDirectory, target string, replace bool, recognize func(string) (Manifest, error)) (DestinationPlan, error) {
plan, err := newDestinationPlan(workingDirectory, target, replace)
if err != nil {
return DestinationPlan{}, err
}
return inspectDestination(plan, recognize)
}
func newDestinationPlan(workingDirectory, target string, replace bool) (DestinationPlan, error) {
workingDirectory, err := absoluteCleanPath(workingDirectory)
if err != nil {
return DestinationPlan{}, newDestinationError(DestinationInvalidPath, workingDirectory, err)
@@ -91,7 +103,19 @@ func PlanDestination(workingDirectory, target string, replace bool) (Destination
return DestinationPlan{}, newDestinationError(DestinationInvalidPath, target, err)
}
plan := DestinationPlan{WorkingDirectory: workingDirectory, Target: target, Replace: replace}
return DestinationPlan{WorkingDirectory: workingDirectory, Target: target, Replace: replace}, nil
}
func reauthorizeDestination(plan DestinationPlan) (DestinationPlan, error) {
plan, err := newDestinationPlan(plan.WorkingDirectory, plan.Target, plan.Replace)
if err != nil {
return DestinationPlan{}, err
}
return inspectDestination(plan, nil)
}
func inspectDestination(plan DestinationPlan, recognize func(string) (Manifest, error)) (DestinationPlan, error) {
target := plan.Target
info, err := os.Lstat(target)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
@@ -117,11 +141,13 @@ func PlanDestination(workingDirectory, target string, replace bool) (Destination
plan.state = destinationEmpty
return plan, nil
}
if !replace {
if !plan.Replace {
return DestinationPlan{}, newDestinationError(DestinationNotEmpty, target, nil)
}
if _, err := RecognizeBundle(target); err != nil {
return DestinationPlan{}, newDestinationError(DestinationUnrecognized, target, err)
if recognize != nil {
if _, err := recognize(target); err != nil {
return DestinationPlan{}, newDestinationError(DestinationUnrecognized, target, err)
}
}
plan.state = destinationBundle
return plan, nil
@@ -188,7 +214,25 @@ func newDestinationError(kind DestinationErrorKind, target string, err error) er
// RecognizeBundle verifies that directory contains exactly one valid current
// comparison bundle. It never follows bundle entries through symlinks.
func RecognizeBundle(directory string) (Manifest, error) {
info, err := os.Lstat(directory)
return recognizeBundle(directory, defaultRecognitionOperations)
}
type recognitionOperations struct {
lstat func(string) (os.FileInfo, error)
readDir func(string) ([]os.DirEntry, error)
open func(string) (io.ReadCloser, error)
}
var defaultRecognitionOperations = recognitionOperations{
lstat: os.Lstat,
readDir: os.ReadDir,
open: func(path string) (io.ReadCloser, error) {
return os.Open(path)
},
}
func recognizeBundle(directory string, operations recognitionOperations) (Manifest, error) {
info, err := operations.lstat(directory)
if err != nil {
return Manifest{}, unrecognizedBundleError("inspect directory", err)
}
@@ -196,11 +240,11 @@ func RecognizeBundle(directory string) (Manifest, error) {
return Manifest{}, unrecognizedBundleError("directory is not a real directory", nil)
}
entries, err := os.ReadDir(directory)
entries, err := operations.readDir(directory)
if err != nil {
return Manifest{}, unrecognizedBundleError("read directory", err)
}
manifestData, err := readBundleFile(directory, ManifestFilename)
manifestData, err := readBundleFile(directory, ManifestFilename, operations)
if err != nil {
return Manifest{}, err
}
@@ -228,12 +272,15 @@ func RecognizeBundle(directory string) (Manifest, error) {
if _, ok := expected[entry.Name()]; !ok {
return Manifest{}, unrecognizedBundleError("directory has an undeclared entry", nil)
}
if _, err := readBundleFile(directory, entry.Name()); err != nil {
if entry.Name() == ManifestFilename || entry.Name() == DataPackageFilename {
continue
}
if err := inspectBundleFile(directory, entry.Name(), operations); err != nil {
return Manifest{}, err
}
}
dataPackage, err := readBundleFile(directory, DataPackageFilename)
dataPackage, err := readBundleFile(directory, DataPackageFilename, operations)
if err != nil {
return Manifest{}, err
}
@@ -243,23 +290,47 @@ func RecognizeBundle(directory string) (Manifest, error) {
return manifest, nil
}
func readBundleFile(directory, name string) ([]byte, error) {
func readBundleFile(directory, name string, operations recognitionOperations) ([]byte, error) {
file, err := openBundleFile(directory, name, operations)
if err != nil {
return nil, err
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return nil, unrecognizedBundleError("read bundle entry", err)
}
return data, nil
}
func inspectBundleFile(directory, name string, operations recognitionOperations) error {
file, err := openBundleFile(directory, name, operations)
if err != nil {
return err
}
if err := file.Close(); err != nil {
return unrecognizedBundleError("close bundle entry", err)
}
return nil
}
func openBundleFile(directory, name string, operations recognitionOperations) (io.ReadCloser, error) {
if !isArtifactBasename(name) {
return nil, unrecognizedBundleError("bundle entry name is unsafe", nil)
}
filePath := filepath.Join(directory, name)
info, err := os.Lstat(filePath)
info, err := operations.lstat(filePath)
if err != nil {
return nil, unrecognizedBundleError("inspect bundle entry", err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return nil, unrecognizedBundleError("bundle entry is not a regular file", nil)
}
data, err := os.ReadFile(filePath)
file, err := operations.open(filePath)
if err != nil {
return nil, unrecognizedBundleError("read bundle entry", err)
}
return data, nil
return file, nil
}
func decodeManifest(data []byte) (Manifest, error) {
@@ -499,6 +570,7 @@ type publishOperations struct {
rename func(string, string) error
removeAll func(string) error
beforeCommit func()
recognize func(string) (Manifest, error)
}
func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, operations publishOperations) (PublicationResult, error) {
@@ -508,6 +580,9 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
if operations.removeAll == nil {
operations.removeAll = os.RemoveAll
}
if operations.recognize == nil {
operations.recognize = RecognizeBundle
}
if err := bundle.Validate(); err != nil {
return PublicationResult{}, fmt.Errorf("validate comparison bundle: %w", err)
}
@@ -518,7 +593,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
if err := ctx.Err(); err != nil {
return PublicationResult{}, err
}
plan, err = PlanDestination(plan.WorkingDirectory, plan.Target, plan.Replace)
plan, err = reauthorizeDestination(plan)
if err != nil {
return PublicationResult{}, err
}
@@ -544,7 +619,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
return PublicationResult{}, err
}
currentPlan, err := PlanDestination(plan.WorkingDirectory, plan.Target, plan.Replace)
currentPlan, err := reauthorizeDestination(plan)
if err != nil {
return PublicationResult{}, err
}
@@ -569,7 +644,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
if err := operations.rename(currentPlan.Target, backupDirectory); err != nil {
return PublicationResult{}, fmt.Errorf("back up comparison destination %q: %w", currentPlan.Target, err)
}
if err := authorizeMovedDestination(currentPlan, backupDirectory); err != nil {
if err := authorizeMovedDestination(currentPlan, backupDirectory, operations.recognize); err != nil {
return PublicationResult{}, restoreMovedDestination(operations, backupDirectory, currentPlan.Target, err)
}
if err := ctx.Err(); err != nil {
@@ -600,8 +675,8 @@ func inspectBackupRecovery(backupDirectory string) (BackupRecoveryState, string)
return BackupRecoveryPartial, backupDirectory
}
func authorizeMovedDestination(plan DestinationPlan, backupDirectory string) error {
backupPlan, err := PlanDestination(plan.WorkingDirectory, backupDirectory, plan.Replace)
func authorizeMovedDestination(plan DestinationPlan, backupDirectory string, recognize func(string) (Manifest, error)) error {
backupPlan, err := planDestination(plan.WorkingDirectory, backupDirectory, plan.Replace, recognize)
if err != nil {
return fmt.Errorf("authorize moved comparison destination %q: %w", backupDirectory, err)
}