From 23971854b02088bace7aadeeed4cff3ed8561e17 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Tue, 5 Aug 2025 15:19:07 +0100 Subject: [PATCH] Fix file descriptor leak in Linux LD library path parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Library/Homebrew/os/linux/ld.rb | 36 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Library/Homebrew/os/linux/ld.rb b/Library/Homebrew/os/linux/ld.rb index b4a033b962..ce0e21fcbf 100644 --- a/Library/Homebrew/os/linux/ld.rb +++ b/Library/Homebrew/os/linux/ld.rb @@ -50,29 +50,37 @@ module OS 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.readlines.each do |line| - # Remove comments and leading/trailing whitespace - line.strip! - line.sub!(/\s*#.*$/, "") + 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 + 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) + Dir.glob(wildcard.to_s).each do |include_file| + paths += library_paths(include_file) + end + elsif line.empty? + next + else + paths << line end - elsif line.empty? - next - else - paths << line end end - paths.to_a + @library_paths_cache[cache_key] = paths.to_a end end end