Class: Downloadable::VerificationCache Private
- Includes:
- Context, Utils::Output::Mixin
- Defined in:
- downloadable.rb
Overview
This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.
Remembers the SHA-256 digest of each file hashed in this process, keyed by its resolved path, size and modification time, so a file is hashed at most once per on-disk state no matter how many download objects or verifications reference it.
Defined Under Namespace
Classes: RepeatedHashingError
Constant Summary collapse
- CACHE_MEDIATED_HASHING_KEY =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
:homebrew_verification_cache_mediated_hashing
Class Method Summary collapse
-
.check_repeated_hashing(filename) ⇒ void
private
Guards against repeated verification creeping in without this cache: every
Pathname#sha256call reports here and, inbrewcommands run by integration tests only, rehashing an unchanged file outside the cache raises. -
.on_disk_state(filename) ⇒ String?
private
The identity and change metadata that determine whether a file can be considered unchanged on disk: the device, inode, size and nanosecond modification and change times ensure a replaced or rewritten file is treated as new, even when its size and modification time are restored, as the change time cannot be set from userland.
- .while_hashing_through_cache(&_block) ⇒ T.type_parameter(:U) private
Instance Method Summary collapse
- #initialize ⇒ void constructor private
-
#invalidate!(filename) ⇒ void
private
Forgets the remembered digest for the file's current on-disk state, for callers that suspect its contents changed without the metadata that keys this cache changing, e.g.
-
#sha256(filename) ⇒ String
private
The file's SHA-256 digest, hashing its contents at most once per on-disk state in this process.
-
#verify(filename, checksum) ⇒ void
private
Verifies the file against the checksum.
Methods included from Utils::Output::Mixin
#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_cannot_install, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning
Methods included from Context
current, current=, #debug?, #deferred_environment_expansion?, #quiet?, #verbose?, #with_context
Constructor Details
#initialize ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
82 83 84 85 86 |
# File 'downloadable.rb', line 82 def initialize require "concurrent/map" @digests = T.let(Concurrent::Map.new, Concurrent::Map) end |
Class Method Details
.check_repeated_hashing(filename) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
Guards against repeated verification creeping in without this cache:
every Pathname#sha256 call reports here and, in brew commands
run by integration tests only, rehashing an unchanged file outside
the cache raises. Hashing is cheap there while a repeat in real use
would rehash a download that can be hundreds of megabytes.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'downloadable.rb', line 53 def check_repeated_hashing(filename) return if ENV["HOMEBREW_CHECK_REPEATED_HASHING"].blank? state = on_disk_state(filename) return if state.nil? require "concurrent/set" @hashed_states ||= T.let(Concurrent::Set.new, T.nilable(Concurrent::Set)) # `add?` atomically records the state and reports whether it was new. return unless @hashed_states.add?(state).nil? return if Thread.current[CACHE_MEDIATED_HASHING_KEY] raise RepeatedHashingError, <<~ERROR Refusing to hash '#{filename}' again: its unchanged contents were already hashed in this process. Verify downloads through `Downloadable#verify_download_integrity` so its digest cache reuses the existing hash. This check only runs when `$HOMEBREW_CHECK_REPEATED_HASHING` is set, e.g. in Homebrew's integration tests. ERROR end |
.on_disk_state(filename) ⇒ String?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
The identity and change metadata that determine whether a file can be considered unchanged on disk: the device, inode, size and nanosecond modification and change times ensure a replaced or rewritten file is treated as new, even when its size and modification time are restored, as the change time cannot be set from userland.
39 40 41 42 43 44 45 |
# File 'downloadable.rb', line 39 def on_disk_state(filename) stat = filename.stat "#{stat.dev}|#{stat.ino}|#{stat.size}|" \ "#{stat.mtime.to_i}.#{stat.mtime.nsec}|#{stat.ctime.to_i}.#{stat.ctime.nsec}" rescue SystemCallError nil end |
.while_hashing_through_cache(&_block) ⇒ T.type_parameter(:U)
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
73 74 75 76 77 78 |
# File 'downloadable.rb', line 73 def while_hashing_through_cache(&_block) Thread.current[CACHE_MEDIATED_HASHING_KEY] = true yield ensure Thread.current[CACHE_MEDIATED_HASHING_KEY] = nil end |
Instance Method Details
#invalidate!(filename) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
Forgets the remembered digest for the file's current on-disk state, for callers that suspect its contents changed without the metadata that keys this cache changing, e.g. in-place corruption suggested by a failed extraction.
123 124 125 126 |
# File 'downloadable.rb', line 123 def invalidate!(filename) key = key_for(filename) @digests.delete(key) if key end |
#sha256(filename) ⇒ String
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
The file's SHA-256 digest, hashing its contents at most once per on-disk state in this process.
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'downloadable.rb', line 104 def sha256(filename) key = key_for(filename) if key && (digest = @digests[key]) odebug "Skipping SHA-256 hashing for '#{filename.basename}' (unchanged since last hashed in this run)" return digest end digest = self.class.while_hashing_through_cache { filename.sha256 } # Only remember the digest when the file did not change while its # contents were being hashed. @digests[key] = digest if key && key == key_for(filename) digest end |
#verify(filename, checksum) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
Verifies the file against the checksum. Repeated verifications of a file unchanged on disk only compare against its remembered digest.
91 92 93 94 95 96 97 98 99 |
# File 'downloadable.rb', line 91 def verify(filename, checksum) raise ChecksumMissingError if checksum.blank? ohai "Verifying checksum for '#{filename.basename}'" if verbose? actual = Checksum.new(sha256(filename)) return if checksum == actual raise ChecksumMismatchError.new(filename, checksum, actual) end |