2025-09-05 09:04:21 -04:00

121 lines
3.7 KiB
Ruby

# typed: strict
# frozen_string_literal: true
module OS
module Linux
# Helper functions for querying `ld` information.
module Ld
# This is a list of known paths to the host dynamic linker on Linux if
# the host glibc is new enough. Brew will fail to create a symlink for
# ld.so if the host linker cannot be found in this list.
DYNAMIC_LINKERS = %w[
/lib64/ld-linux-x86-64.so.2
/lib64/ld64.so.2
/lib/ld-linux.so.3
/lib/ld-linux.so.2
/lib/ld-linux-aarch64.so.1
/lib/ld-linux-armhf.so.3
/system/bin/linker64
/system/bin/linker
].freeze
# The path to the system's dynamic linker or `nil` if not found
sig { returns(T.nilable(Pathname)) }
def self.system_ld_so
@system_ld_so ||= T.let(nil, T.nilable(Pathname))
@system_ld_so ||= begin
linker = DYNAMIC_LINKERS.find { |s| File.executable? s }
Pathname(linker) if linker
end
end
sig { params(brewed: T::Boolean).returns(String) }
def self.ld_so_diagnostics(brewed: true)
@ld_so_diagnostics ||= T.let({}, T.nilable(T::Hash[Pathname, String]))
ld_so_target = if brewed
ld_so = HOMEBREW_PREFIX/"lib/ld.so"
return "" unless ld_so.exist?
ld_so.readlink
else
ld_so = system_ld_so
return "" unless ld_so&.exist?
ld_so
end
@ld_so_diagnostics[ld_so_target] ||= begin
ld_so_output = Utils.popen_read(ld_so, "--list-diagnostics")
ld_so_output if $CHILD_STATUS.success?
end
@ld_so_diagnostics[ld_so_target].to_s
end
sig { params(brewed: T::Boolean).returns(String) }
def self.sysconfdir(brewed: true)
fallback_sysconfdir = "/etc"
match = ld_so_diagnostics(brewed:).match(/path.sysconfdir="(.+)"/)
return fallback_sysconfdir unless match
match.captures.compact.first || fallback_sysconfdir
end
sig { params(brewed: T::Boolean).returns(T::Array[String]) }
def self.system_dirs(brewed: true)
dirs = []
ld_so_diagnostics(brewed:).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), brewed: T::Boolean).returns(T::Array[String]) }
def self.library_paths(conf_path = "ld.so.conf", brewed: true)
conf_file = Pathname(sysconfdir(brewed:))/conf_path
return [] unless conf_file.exist?
return [] unless conf_file.file?
return [] unless conf_file.readable?
@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+/)
wildcard = Pathname(line.sub(/^\s*include\s+/, "")).expand_path(directory)
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