Skip to content

Commit 510edbc

Browse files
deivid-rodriguezmbclumartinemde
authored andcommitted
[rubygems/rubygems] Fix ENAMETOOLONG error when creating compact index cache
If a custom rubygems source URI is long enough, Bundler may end up raising an `ENAMETOOLONG` error and crash. This commit fixes the problem by trimming the cache slug size to fit usual OS requirements. ruby/rubygems@df40ff1e14 Co-authored-by: mbclu <[email protected]> Co-authored-by: martinemde <[email protected]>
1 parent 13020ac commit 510edbc

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

lib/bundler/source/rubygems/remote.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def initialize(uri)
1616
@anonymized_uri = remove_auth(@uri).freeze
1717
end
1818

19+
MAX_CACHE_SLUG_HOST_SIZE = 255 - 1 - 32 # 255 minus dot minus MD5 length
20+
private_constant :MAX_CACHE_SLUG_HOST_SIZE
21+
1922
# @return [String] A slug suitable for use as a cache key for this
2023
# remote.
2124
#
@@ -28,10 +31,15 @@ def cache_slug
2831
host = cache_uri.to_s.start_with?("file://") ? nil : cache_uri.host
2932

3033
uri_parts = [host, cache_uri.user, cache_uri.port, cache_uri.path]
31-
uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.compact.join("."))
34+
uri_parts.compact!
35+
uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.join("."))
36+
37+
uri_parts.pop
38+
host_parts = uri_parts.join(".")
39+
return uri_digest if host_parts.empty?
3240

33-
uri_parts[-1] = uri_digest
34-
uri_parts.compact.join(".")
41+
shortened_host_parts = host_parts[0...MAX_CACHE_SLUG_HOST_SIZE]
42+
[shortened_host_parts, uri_digest].join(".")
3543
end
3644
end
3745

spec/bundler/install/global_cache_spec.rb

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
let(:source) { "http://localgemserver.test" }
88
let(:source2) { "http://gemserver.example.org" }
99

10+
def cache_base
11+
home(".bundle", "cache", "gems")
12+
end
13+
1014
def source_global_cache(*segments)
11-
home(".bundle", "cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments)
15+
cache_base.join("localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments)
1216
end
1317

1418
def source2_global_cache(*segments)
15-
home(".bundle", "cache", "gems", "gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments)
19+
cache_base.join("gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments)
1620
end
1721

1822
it "caches gems into the global cache on download" do
@@ -49,6 +53,39 @@ def source2_global_cache(*segments)
4953
expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("myrack-1.0.0.gem")}")
5054
end
5155

56+
it "uses a shorter path for the cache to not hit filesystem limits" do
57+
install_gemfile <<-G, artifice: "compact_index", verbose: true
58+
source "http://#{"a" * 255}.test"
59+
gem "myrack"
60+
G
61+
62+
expect(the_bundle).to include_gems "myrack 1.0.0"
63+
source_segment = "a" * 222 + ".a3cb26de2edfce9f509a65c611d99c4b"
64+
source_cache = cache_base.join(source_segment)
65+
cached_gem = source_cache.join("myrack-1.0.0.gem")
66+
expect(cached_gem).to exist
67+
ensure
68+
# We cleanup dummy files created by this spec manually because due to a
69+
# Ruby on Windows bug, `FileUtils.rm_rf` (run in our global after hook)
70+
# cannot traverse directories with such long names. So we delete
71+
# everything explicitly to workaround the bug. An alternative workaround
72+
# would be to shell out to `rm -rf`. That also works fine, but I went with
73+
# the more verbose and explicit approach. This whole ensure block can be
74+
# removed once/if https://bugs.ruby-lang.org/issues/21177 is fixed, and
75+
# once the fix propagates to all supported rubies.
76+
File.delete cached_gem
77+
Dir.rmdir source_cache
78+
79+
File.delete compact_index_cache_path.join(source_segment, "info", "myrack")
80+
Dir.rmdir compact_index_cache_path.join(source_segment, "info")
81+
File.delete compact_index_cache_path.join(source_segment, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073")
82+
Dir.rmdir compact_index_cache_path.join(source_segment, "info-etags")
83+
Dir.rmdir compact_index_cache_path.join(source_segment, "info-special-characters")
84+
File.delete compact_index_cache_path.join(source_segment, "versions")
85+
File.delete compact_index_cache_path.join(source_segment, "versions.etag")
86+
Dir.rmdir compact_index_cache_path.join(source_segment)
87+
end
88+
5289
describe "when the same gem from different sources is installed" do
5390
it "should use the appropriate one from the global cache" do
5491
bundle "config path.system true"

0 commit comments

Comments
 (0)