diff --git a/src/benchmark.py b/src/benchmark.py index c8fc3cdf634a386d2b45ec6f56cf2a31508e5e5c..50faf0055a41b67e7704c9561a81c83ae6dfe090 100644 --- a/src/benchmark.py +++ b/src/benchmark.py @@ -168,7 +168,7 @@ class Benchmark (object): for r in self.requirements: exe = find_cmd(r) if exe is not None: - self.results["facts"]["libcs"][r] = src.facter.get_libc_version(bin=exe) + self.results["facts"]["libcs"][r] = src.facter.libc_ver(bin=exe) else: raise Exception("Requirement: {} not found".format(r)) diff --git a/src/facter.py b/src/facter.py index 8786aa3bb3cd4b731e0f88607e22f14e55bf4978..c90306de441fe727c35c6a0f40bf4ab023bf9d4f 100644 --- a/src/facter.py +++ b/src/facter.py @@ -1,7 +1,7 @@ +import ctypes import multiprocessing import os import platform -import sys import src.globalvars as gv @@ -19,6 +19,56 @@ def collect_facts(): with open(os.path.join(gv.builddir, "ccinfo"), "r") as ccinfo: gv.facts["cc"] = ccinfo.readlines()[-1][:-1] -def get_libc_version(bin=None): - bin = bin or sys.executable - platform.libc_ver(executable=bin) + + +# Copied from pip. +# https://github.com/pypa/pip/blob/master/src/pip/_internal/utils/glibc.py +# Licensed under MIT. +def glibc_version_string(bin=None): + "Returns glibc version string, or None if not using glibc." + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + process_namespace = ctypes.CDLL(bin) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + +# platform.libc_ver regularly returns completely nonsensical glibc +# versions. E.g. on my computer, platform says: +# +# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.7') +# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.9') +# +# But the truth is: +# +# ~$ ldd --version +# ldd (Debian GLIBC 2.22-11) 2.22 +# +# This is unfortunate, because it means that the linehaul data on libc +# versions that was generated by pip 8.1.2 and earlier is useless and +# misleading. Solution: instead of using platform, use our code that actually +# works. +def libc_ver(bin=None): + glibc_version = glibc_version_string(bin) + if glibc_version is None: + # For non-glibc platforms, fall back on platform.libc_ver + return platform.libc_ver() + else: + return ("glibc", glibc_version)