Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/runtime_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,22 @@ func writeRuntimeReceipt(root, release, manifestSHA, archiveSHA string) error {
path := filepath.Join(root, runtimeReceiptFilename)
return os.WriteFile(path, data, 0o644)
}

// runtimeArchiveMatches reports whether the installed runtime came from the
// same authenticated archive, whatever release it was recorded under. A new
// release that ships the unchanged archive must not download it again.
func runtimeArchiveMatches(root, archiveSHA string) bool {
data, err := os.ReadFile(filepath.Join(root, runtimeReceiptFilename))
if err != nil || len(data) > maxInstallReceiptBytes {
return false
}
var receipt runtimeReceipt
if json.Unmarshal(data, &receipt) != nil || receipt.Schema != 1 ||
!validSHA256(archiveSHA) || receipt.ArchiveSHA256 != normalizedSHA256(archiveSHA) ||
!validSHA256(receipt.Executable.SHA256) {
return false
}
info, err := os.Lstat(filepath.Join(root, "bin", "qemu-system-x86_64w.exe"))
return err == nil && info.Mode().IsRegular() && info.Size() == receipt.Executable.Size &&
info.ModTime().UnixNano() == receipt.Executable.ModTimeUnixNano
}
29 changes: 29 additions & 0 deletions app/runtime_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,32 @@ func TestRuntimeReceiptSurvivesRepositoryTransfer(t *testing.T) {
t.Fatal("runtime receipt did not survive the repository transfer")
}
}

func TestRuntimeArchiveMatchesAcrossReleases(t *testing.T) {
root := t.TempDir()
if err := os.MkdirAll(filepath.Join(root, "bin"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "bin", "qemu-system-x86_64w.exe"), []byte("qemu"), 0o644); err != nil {
t.Fatal(err)
}
archiveSHA := strings.Repeat("ab", 32)
if err := writeRuntimeReceipt(root, "https://example.test/v0.0.13-preview", strings.Repeat("cd", 32), archiveSHA); err != nil {
t.Fatal(err)
}
if !runtimeArchiveMatches(root, archiveSHA) {
t.Fatal("same archive not recognized")
}
if !runtimeArchiveMatches(root, strings.ToUpper(archiveSHA)) {
t.Fatal("digest case should not matter")
}
if runtimeArchiveMatches(root, strings.Repeat("ef", 32)) {
t.Fatal("different archive accepted")
}
if err := os.WriteFile(filepath.Join(root, "bin", "qemu-system-x86_64w.exe"), []byte("changed"), 0o644); err != nil {
t.Fatal(err)
}
if runtimeArchiveMatches(root, archiveSHA) {
t.Fatal("modified executable accepted")
}
}
9 changes: 9 additions & 0 deletions app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,15 @@ func ensureRuntime(cfg *config, release, sumsSHA256 string) (string, error) {
if runtimeReceiptMatches(root, release, sumsSHA256, archiveSHA) {
return root, nil
}
if runtimeArchiveMatches(root, archiveSHA) {
// Same archive under a new release: adopt the new release identity
// instead of downloading and unpacking identical bytes.
if err := writeRuntimeReceipt(root, release, sumsSHA256, archiveSHA); err != nil {
return "", fmt.Errorf("recording runtime release: %w", err)
}
logf("runtime archive unchanged in %s; kept the installed runtime", releaseVersion(release))
return root, nil
}
executable := filepath.Join(root, "bin", "qemu-system-x86_64w.exe")
_, executableErr := os.Stat(executable)
_, receiptErr := os.Stat(filepath.Join(root, runtimeReceiptFilename))
Expand Down