Skip to content

Commit 188c765

Browse files
hsbtclaude
andcommitted
Add a filesystem access hook to Gem::CompactIndexClient
Bundler's client wraps every cache read and write in SharedHelpers.filesystem_access so permission problems surface as friendly Bundler errors instead of bare Errno exceptions. Funnel the same operations through an injectable hook, defaulting to a plain yield, so Bundler can install its helper when it switches to this client. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6f12475 commit 188c765

4 files changed

Lines changed: 50 additions & 8 deletions

File tree

lib/rubygems/compact_index_client.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ def self.debug
3232

3333
class Error < StandardError; end
3434

35+
# Filesystem reads and writes funnel through this hook so that a host
36+
# (e.g. Bundler) can translate low-level Errno exceptions into its own
37+
# friendlier errors. The default just yields the path.
38+
def self.filesystem_access(path, action = :write, &block)
39+
if @filesystem_access
40+
@filesystem_access.call(path, action, &block)
41+
else
42+
yield path
43+
end
44+
end
45+
46+
def self.filesystem_access=(hook)
47+
@filesystem_access = hook
48+
end
49+
3550
require_relative "compact_index_client/cache"
3651
require_relative "compact_index_client/cache_file"
3752
require_relative "compact_index_client/http_fetcher"

lib/rubygems/compact_index_client/cache.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,16 @@ def validate_name!(name)
8686

8787
def checksum_for_file(path)
8888
return unless path.file?
89-
Digest::MD5.file(path).hexdigest
89+
Gem::CompactIndexClient.filesystem_access(path, :read) do
90+
Digest::MD5.file(path).hexdigest
91+
end
9092
end
9193

9294
def mkdir(name)
9395
directory.join(name).tap do |dir|
94-
FileUtils.mkdir_p(dir)
96+
Gem::CompactIndexClient.filesystem_access(dir) do
97+
FileUtils.mkdir_p(dir)
98+
end
9599
end
96100
end
97101

@@ -112,7 +116,7 @@ def already_fetched?(remote_path)
112116

113117
def read(path)
114118
return unless path.file?
115-
path.read
119+
Gem::CompactIndexClient.filesystem_access(path, :read, &:read)
116120
end
117121
end
118122
end

lib/rubygems/compact_index_client/cache_file.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ def self.copy(path, &block)
2525
new(path) do |file|
2626
file.initialize_digests
2727

28-
path.open("rb") do |s|
29-
file.open {|f| IO.copy_stream(s, f) }
28+
Gem::CompactIndexClient.filesystem_access(path, :read) do
29+
path.open("rb") do |s|
30+
file.open {|f| IO.copy_stream(s, f) }
31+
end
3032
end
3133

3234
yield file
@@ -91,8 +93,10 @@ def digests?
9193
# Open the temp file for writing, reusing original permissions, yielding the IO object.
9294
def open(write_mode = "wb", perm = @perm, &block)
9395
raise ClosedError, "Cannot reopen closed file" if @closed
94-
path.open(write_mode, perm) do |f|
95-
yield digests? ? Gem::Package::DigestIO.new(f, @digests) : f
96+
Gem::CompactIndexClient.filesystem_access(path, :write) do
97+
path.open(write_mode, perm) do |f|
98+
yield digests? ? Gem::Package::DigestIO.new(f, @digests) : f
99+
end
96100
end
97101
end
98102

@@ -126,7 +130,9 @@ def verify
126130
# The file is permanently closed.
127131
def commit
128132
raise ClosedError, "Cannot commit closed file" if @closed
129-
FileUtils.mv(path, original_path)
133+
Gem::CompactIndexClient.filesystem_access(original_path, :write) do
134+
FileUtils.mv(path, original_path)
135+
end
130136
@closed = true
131137
end
132138

test/rubygems/test_gem_compact_index_client.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ def test_reset_refetches_versions
109109
assert_equal %w[versions versions], @fetcher.requests
110110
end
111111

112+
def test_filesystem_access_hook_wraps_reads_and_writes
113+
accesses = []
114+
Gem::CompactIndexClient.filesystem_access = lambda do |path, action, &block|
115+
accesses << [path.basename.to_s, action]
116+
block.call(path)
117+
end
118+
119+
client = Gem::CompactIndexClient.new(File.join(@tempdir, "hooked_index"), @fetcher)
120+
client.versions
121+
122+
assert_includes accesses, ["info", :write]
123+
assert_includes accesses, ["versions", :write]
124+
assert_includes accesses, ["versions", :read]
125+
ensure
126+
Gem::CompactIndexClient.filesystem_access = nil
127+
end
128+
112129
def test_info_uses_local_cache_when_checksum_matches
113130
@client.versions # prime info checksums and write cache
114131
@client.info("a")

0 commit comments

Comments
 (0)