fix(auth): close Task 15 review round two

This commit is contained in:
2026-08-18 07:21:24 +02:00
parent 225ffc8e20
commit fe190e7046
10 changed files with 305 additions and 64 deletions
+52 -13
View File
@@ -38,7 +38,7 @@ type restoreDependencies struct {
// public Create, which would re-acquire the non-reentrant lock and deadlock the transaction.
checkpoint func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error)
prepareRecovery func(context.Context, config.Installation, string) (PreflightResult, error)
recover func(context.Context, config.Installation, PreflightResult, bool) error
recover func(context.Context, config.Installation, PreflightResult, *stagedArchive, bool) error
cleanupCheckpoint func(string) error
acquireTransaction func(config.Installation) (*lifecycle.Transaction, error)
runner archiveRunner
@@ -119,11 +119,42 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
}
defer recovery.CloseArchive()
// Both immutable archives are staged while the lifecycle transaction is held and before
// maintenance, service stops, or destination writes. Keeping both files open reserves their
// combined staging capacity, so checkpoint recovery never needs a new allocation after a
// candidate mutation has begun.
candidateStage, err := preflight.StageArchive(ctx)
if err != nil {
cleanupErr := deps.cleanupCheckpoint(checkpoint.Path)
return result, errors.Join(err, cleanupErr)
}
defer func() {
if closeErr := candidateStage.Close(); closeErr != nil {
result = RestoreResult{}
resultErr = errors.Join(resultErr, closeErr)
}
}()
recoveryStage, err := recovery.StageArchive(ctx)
if err != nil {
cleanupErr := deps.cleanupCheckpoint(checkpoint.Path)
return result, errors.Join(err, cleanupErr)
}
defer func() {
if closeErr := recoveryStage.Close(); closeErr != nil {
result = RestoreResult{}
resultErr = errors.Join(resultErr, closeErr)
}
}()
if err := ensureCombinedRestoreCapacity(preflight, recovery); err != nil {
cleanupErr := deps.cleanupCheckpoint(checkpoint.Path)
return result, errors.Join(err, cleanupErr)
}
state := restoreTransactionState{}
defer func() {
if state.recoveryRequired(resultErr) {
recoveryContext, cancel := boundedCleanupContext()
recoveryErr := deps.recover(recoveryContext, installation, recovery, state.wasRunning)
recoveryErr := deps.recover(recoveryContext, installation, recovery, recoveryStage, state.wasRunning)
cancel()
if recoveryErr != nil {
resultErr = errors.Join(resultErr, fmt.Errorf("restore recovery checkpoint: %w", recoveryErr))
@@ -212,18 +243,8 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
return result, err
}
}
staged, err := preflight.StageArchive(ctx)
if err != nil {
return result, err
}
defer func() {
if closeErr := staged.Close(); closeErr != nil {
result = RestoreResult{}
resultErr = errors.Join(resultErr, closeErr)
}
}()
state.mutated = true
if err := restoreVerifiedEntries(ctx, installation, preflight, staged.file, deps.restoreFile, deps.restoreVolume); err != nil {
if err := restoreVerifiedEntries(ctx, installation, preflight, candidateStage.file, deps.restoreFile, deps.restoreVolume); err != nil {
return result, err
}
if err := deps.resetAuthenticationState(ctx, installation, deps.runner); err != nil {
@@ -244,6 +265,24 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
return result, nil
}
func ensureCombinedRestoreCapacity(candidate, recovery PreflightResult) error {
if candidate.freeBytes == nil || candidate.stagingRoot == "" || candidate.stagingRoot != recovery.stagingRoot {
return errors.New("candidate and recovery archives do not share controlled restore staging")
}
if recovery.RequiredBytes > ^uint64(0)-candidate.RequiredBytes {
return errors.New("combined candidate and recovery restore capacity exceeds supported size")
}
required := candidate.RequiredBytes + recovery.RequiredBytes
freeBytes, err := candidate.freeBytes(candidate.stagingRoot)
if err != nil {
return errors.New("check combined candidate and recovery staging capacity")
}
if freeBytes < required {
return fmt.Errorf("insufficient free disk space for combined candidate and recovery staging: need %d bytes, have %d", required, freeBytes)
}
return nil
}
func verifyRestoreTransaction(ctx context.Context, installation config.Installation, deps restoreDependencies) error {
for _, name := range []string{"health", "doctor", "pi", "workspace"} {
check := deps.verify[name]