mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
downloadable: skip repeated checksum verification of the same file
Multiple download objects can reference the same cached file within one process (the concurrent prefetch queue, the installer's download and the internal-API synthetic cask download), so a cask upgrade SHA-256 hashes each download up to three times and prints duplicated "Verifying checksum" lines under --verbose. Remember successful verifications for the process, keyed on path, checksum, size and modification time so a re-downloaded file is verified afresh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
38acf7eeda
commit
7d546e7314
@@ -14,6 +14,48 @@ module Downloadable
|
||||
abstract!
|
||||
requires_ancestor { Kernel }
|
||||
|
||||
class << self
|
||||
sig { params(filename: Pathname, checksum: T.nilable(Checksum)).returns(T::Boolean) }
|
||||
def already_verified?(filename, checksum)
|
||||
key = verification_key(filename, checksum)
|
||||
return false if key.nil?
|
||||
|
||||
verification_lock.synchronize { verified_downloads.include?(key) }
|
||||
end
|
||||
|
||||
sig { params(filename: Pathname, checksum: T.nilable(Checksum)).void }
|
||||
def record_verification(filename, checksum)
|
||||
key = verification_key(filename, checksum)
|
||||
return if key.nil?
|
||||
|
||||
verification_lock.synchronize { verified_downloads.add(key) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The size and modification time ensure a file downloaded again to the
|
||||
# same path (e.g. after `--force` cleared the cache) is verified again.
|
||||
sig { params(filename: Pathname, checksum: T.nilable(Checksum)).returns(T.nilable(String)) }
|
||||
def verification_key(filename, checksum)
|
||||
return if checksum.nil?
|
||||
|
||||
stat = filename.stat
|
||||
"#{filename.expand_path}|#{checksum.hexdigest}|#{stat.size}|#{stat.mtime.to_f}"
|
||||
rescue SystemCallError
|
||||
nil
|
||||
end
|
||||
|
||||
sig { returns(T::Set[String]) }
|
||||
def verified_downloads
|
||||
@verified_downloads ||= T.let(Set.new, T.nilable(T::Set[String]))
|
||||
end
|
||||
|
||||
sig { returns(Mutex) }
|
||||
def verification_lock
|
||||
@verification_lock ||= T.let(Mutex.new, T.nilable(Mutex))
|
||||
end
|
||||
end
|
||||
|
||||
sig { overridable.returns(T.nilable(T.any(String, URL))) }
|
||||
attr_reader :url
|
||||
|
||||
@@ -181,8 +223,13 @@ module Downloadable
|
||||
verifying!
|
||||
|
||||
if filename.file?
|
||||
ohai "Verifying checksum for '#{filename.basename}'" if verbose?
|
||||
filename.verify_checksum(checksum)
|
||||
if Downloadable.already_verified?(filename, checksum)
|
||||
odebug "Skipping checksum verification for '#{filename.basename}' (already verified in this run)"
|
||||
else
|
||||
ohai "Verifying checksum for '#{filename.basename}'" if verbose?
|
||||
filename.verify_checksum(checksum)
|
||||
Downloadable.record_verification(filename, checksum)
|
||||
end
|
||||
verified!
|
||||
end
|
||||
rescue ChecksumMissingError
|
||||
|
||||
@@ -301,6 +301,21 @@ RSpec.describe Resource do
|
||||
end
|
||||
end
|
||||
|
||||
specify "#verify_download_integrity skips files already verified in this process" do
|
||||
fn = mktmpdir/"test.tar.gz"
|
||||
fn.write "content"
|
||||
digest = Digest::SHA256.hexdigest("content")
|
||||
resource.sha256(digest)
|
||||
|
||||
other_resource = described_class.new("other")
|
||||
other_resource.sha256(digest)
|
||||
|
||||
expect(fn).to receive(:verify_checksum).once.and_call_original
|
||||
|
||||
resource.verify_download_integrity(fn)
|
||||
other_resource.verify_download_integrity(fn)
|
||||
end
|
||||
|
||||
specify "#verify_download_integrity_missing" do
|
||||
fn = Pathname.new("test")
|
||||
|
||||
@@ -312,7 +327,8 @@ RSpec.describe Resource do
|
||||
end
|
||||
|
||||
specify "#verify_download_integrity_mismatch" do
|
||||
fn = instance_double(Pathname, file?: true, basename: "foo")
|
||||
fn = Pathname.new("foo")
|
||||
allow(fn).to receive(:file?).and_return(true)
|
||||
checksum = resource.sha256(TEST_SHA256)
|
||||
|
||||
expect(fn).to receive(:verify_checksum)
|
||||
|
||||
Reference in New Issue
Block a user