
The library_paths method was using readlines which could leave file descriptors open due to Ruby's garbage collection behavior. When processing many packages during 'brew upgrade' or 'brew linkage', this caused "Too many open files" errors on Linux systems. Changes: - Replace readlines with explicit file.open block to ensure proper closure - Add caching to avoid repeatedly reading /etc/ld.so.conf during a session - Cache included files as well to optimize recursive include processing Fixes: #19866, #20302, #19177, #20223 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
88 lines
2.6 KiB
Ruby
88 lines
2.6 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
module OS
|
|
module Linux
|
|
# Helper functions for querying `ld` information.
|
|
module Ld
|
|
sig { returns(String) }
|
|
def self.brewed_ld_so_diagnostics
|
|
@brewed_ld_so_diagnostics ||= T.let({}, T.nilable(T::Hash[Pathname, String]))
|
|
|
|
brewed_ld_so = HOMEBREW_PREFIX/"lib/ld.so"
|
|
return "" unless brewed_ld_so.exist?
|
|
|
|
brewed_ld_so_target = brewed_ld_so.readlink
|
|
@brewed_ld_so_diagnostics[brewed_ld_so_target] ||= begin
|
|
ld_so_output = Utils.popen_read(brewed_ld_so, "--list-diagnostics")
|
|
ld_so_output if $CHILD_STATUS.success?
|
|
end
|
|
|
|
@brewed_ld_so_diagnostics[brewed_ld_so_target].to_s
|
|
end
|
|
|
|
sig { returns(String) }
|
|
def self.sysconfdir
|
|
fallback_sysconfdir = "/etc"
|
|
|
|
match = brewed_ld_so_diagnostics.match(/path.sysconfdir="(.+)"/)
|
|
return fallback_sysconfdir unless match
|
|
|
|
match.captures.compact.first || fallback_sysconfdir
|
|
end
|
|
|
|
sig { returns(T::Array[String]) }
|
|
def self.system_dirs
|
|
dirs = []
|
|
|
|
brewed_ld_so_diagnostics.split("\n").each do |line|
|
|
match = line.match(/path.system_dirs\[0x.*\]="(.*)"/)
|
|
next unless match
|
|
|
|
dirs << match.captures.compact.first
|
|
end
|
|
|
|
dirs
|
|
end
|
|
|
|
sig { params(conf_path: T.any(Pathname, String)).returns(T::Array[String]) }
|
|
def self.library_paths(conf_path = Pathname(sysconfdir)/"ld.so.conf")
|
|
conf_file = Pathname(conf_path)
|
|
return [] unless conf_file.exist?
|
|
|
|
@library_paths_cache ||= T.let({}, T.nilable(T::Hash[String, T::Array[String]]))
|
|
cache_key = conf_file.to_s
|
|
if (cached_library_path_contents = @library_paths_cache[cache_key])
|
|
return cached_library_path_contents
|
|
end
|
|
|
|
paths = Set.new
|
|
directory = conf_file.realpath.dirname
|
|
|
|
conf_file.open("r") do |file|
|
|
file.each_line do |line|
|
|
# Remove comments and leading/trailing whitespace
|
|
line.strip!
|
|
line.sub!(/\s*#.*$/, "")
|
|
|
|
if line.start_with?(/\s*include\s+/)
|
|
include_path = Pathname(line.sub(/^\s*include\s+/, "")).expand_path
|
|
wildcard = include_path.absolute? ? include_path : directory/include_path
|
|
|
|
Dir.glob(wildcard.to_s).each do |include_file|
|
|
paths += library_paths(include_file)
|
|
end
|
|
elsif line.empty?
|
|
next
|
|
else
|
|
paths << line
|
|
end
|
|
end
|
|
end
|
|
|
|
@library_paths_cache[cache_key] = paths.to_a
|
|
end
|
|
end
|
|
end
|
|
end
|