(root)/
Python-3.11.7/
Lib/
ctypes/
util.py
       1  import os
       2  import shutil
       3  import subprocess
       4  import sys
       5  
       6  # find_library(name) returns the pathname of a library, or None.
       7  if os.name == "nt":
       8  
       9      def _get_build_version():
      10          """Return the version of MSVC that was used to build Python.
      11  
      12          For Python 2.3 and up, the version number is included in
      13          sys.version.  For earlier versions, assume the compiler is MSVC 6.
      14          """
      15          # This function was copied from Lib/distutils/msvccompiler.py
      16          prefix = "MSC v."
      17          i = sys.version.find(prefix)
      18          if i == -1:
      19              return 6
      20          i = i + len(prefix)
      21          s, rest = sys.version[i:].split(" ", 1)
      22          majorVersion = int(s[:-2]) - 6
      23          if majorVersion >= 13:
      24              majorVersion += 1
      25          minorVersion = int(s[2:3]) / 10.0
      26          # I don't think paths are affected by minor version in version 6
      27          if majorVersion == 6:
      28              minorVersion = 0
      29          if majorVersion >= 6:
      30              return majorVersion + minorVersion
      31          # else we don't know what version of the compiler this is
      32          return None
      33  
      34      def find_msvcrt():
      35          """Return the name of the VC runtime dll"""
      36          version = _get_build_version()
      37          if version is None:
      38              # better be safe than sorry
      39              return None
      40          if version <= 6:
      41              clibname = 'msvcrt'
      42          elif version <= 13:
      43              clibname = 'msvcr%d' % (version * 10)
      44          else:
      45              # CRT is no longer directly loadable. See issue23606 for the
      46              # discussion about alternative approaches.
      47              return None
      48  
      49          # If python was built with in debug mode
      50          import importlib.machinery
      51          if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES:
      52              clibname += 'd'
      53          return clibname+'.dll'
      54  
      55      def find_library(name):
      56          if name in ('c', 'm'):
      57              return find_msvcrt()
      58          # See MSDN for the REAL search order.
      59          for directory in os.environ['PATH'].split(os.pathsep):
      60              fname = os.path.join(directory, name)
      61              if os.path.isfile(fname):
      62                  return fname
      63              if fname.lower().endswith(".dll"):
      64                  continue
      65              fname = fname + ".dll"
      66              if os.path.isfile(fname):
      67                  return fname
      68          return None
      69  
      70  elif os.name == "posix" and sys.platform == "darwin":
      71      from ctypes.macholib.dyld import dyld_find as _dyld_find
      72      def find_library(name):
      73          possible = ['lib%s.dylib' % name,
      74                      '%s.dylib' % name,
      75                      '%s.framework/%s' % (name, name)]
      76          for name in possible:
      77              try:
      78                  return _dyld_find(name)
      79              except ValueError:
      80                  continue
      81          return None
      82  
      83  elif sys.platform.startswith("aix"):
      84      # AIX has two styles of storing shared libraries
      85      # GNU auto_tools refer to these as svr4 and aix
      86      # svr4 (System V Release 4) is a regular file, often with .so as suffix
      87      # AIX style uses an archive (suffix .a) with members (e.g., shr.o, libssl.so)
      88      # see issue#26439 and _aix.py for more details
      89  
      90      from ctypes._aix import find_library
      91  
      92  elif os.name == "posix":
      93      # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
      94      import re, tempfile
      95  
      96      def _is_elf(filename):
      97          "Return True if the given file is an ELF file"
      98          elf_header = b'\x7fELF'
      99          with open(filename, 'br') as thefile:
     100              return thefile.read(4) == elf_header
     101  
     102      def _findLib_gcc(name):
     103          # Run GCC's linker with the -t (aka --trace) option and examine the
     104          # library name it prints out. The GCC command will fail because we
     105          # haven't supplied a proper program with main(), but that does not
     106          # matter.
     107          expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
     108  
     109          c_compiler = shutil.which('gcc')
     110          if not c_compiler:
     111              c_compiler = shutil.which('cc')
     112          if not c_compiler:
     113              # No C compiler available, give up
     114              return None
     115  
     116          temp = tempfile.NamedTemporaryFile()
     117          try:
     118              args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
     119  
     120              env = dict(os.environ)
     121              env['LC_ALL'] = 'C'
     122              env['LANG'] = 'C'
     123              try:
     124                  proc = subprocess.Popen(args,
     125                                          stdout=subprocess.PIPE,
     126                                          stderr=subprocess.STDOUT,
     127                                          env=env)
     128              except OSError:  # E.g. bad executable
     129                  return None
     130              with proc:
     131                  trace = proc.stdout.read()
     132          finally:
     133              try:
     134                  temp.close()
     135              except FileNotFoundError:
     136                  # Raised if the file was already removed, which is the normal
     137                  # behaviour of GCC if linking fails
     138                  pass
     139          res = re.findall(expr, trace)
     140          if not res:
     141              return None
     142  
     143          for file in res:
     144              # Check if the given file is an elf file: gcc can report
     145              # some files that are linker scripts and not actual
     146              # shared objects. See bpo-41976 for more details
     147              if not _is_elf(file):
     148                  continue
     149              return os.fsdecode(file)
     150  
     151  
     152      if sys.platform == "sunos5":
     153          # use /usr/ccs/bin/dump on solaris
     154          def _get_soname(f):
     155              if not f:
     156                  return None
     157  
     158              try:
     159                  proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
     160                                          stdout=subprocess.PIPE,
     161                                          stderr=subprocess.DEVNULL)
     162              except OSError:  # E.g. command not found
     163                  return None
     164              with proc:
     165                  data = proc.stdout.read()
     166              res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
     167              if not res:
     168                  return None
     169              return os.fsdecode(res.group(1))
     170      else:
     171          def _get_soname(f):
     172              # assuming GNU binutils / ELF
     173              if not f:
     174                  return None
     175              objdump = shutil.which('objdump')
     176              if not objdump:
     177                  # objdump is not available, give up
     178                  return None
     179  
     180              try:
     181                  proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
     182                                          stdout=subprocess.PIPE,
     183                                          stderr=subprocess.DEVNULL)
     184              except OSError:  # E.g. bad executable
     185                  return None
     186              with proc:
     187                  dump = proc.stdout.read()
     188              res = re.search(br'\sSONAME\s+([^\s]+)', dump)
     189              if not res:
     190                  return None
     191              return os.fsdecode(res.group(1))
     192  
     193      if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
     194  
     195          def _num_version(libname):
     196              # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
     197              parts = libname.split(b".")
     198              nums = []
     199              try:
     200                  while parts:
     201                      nums.insert(0, int(parts.pop()))
     202              except ValueError:
     203                  pass
     204              return nums or [sys.maxsize]
     205  
     206          def find_library(name):
     207              ename = re.escape(name)
     208              expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
     209              expr = os.fsencode(expr)
     210  
     211              try:
     212                  proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
     213                                          stdout=subprocess.PIPE,
     214                                          stderr=subprocess.DEVNULL)
     215              except OSError:  # E.g. command not found
     216                  data = b''
     217              else:
     218                  with proc:
     219                      data = proc.stdout.read()
     220  
     221              res = re.findall(expr, data)
     222              if not res:
     223                  return _get_soname(_findLib_gcc(name))
     224              res.sort(key=_num_version)
     225              return os.fsdecode(res[-1])
     226  
     227      elif sys.platform == "sunos5":
     228  
     229          def _findLib_crle(name, is64):
     230              if not os.path.exists('/usr/bin/crle'):
     231                  return None
     232  
     233              env = dict(os.environ)
     234              env['LC_ALL'] = 'C'
     235  
     236              if is64:
     237                  args = ('/usr/bin/crle', '-64')
     238              else:
     239                  args = ('/usr/bin/crle',)
     240  
     241              paths = None
     242              try:
     243                  proc = subprocess.Popen(args,
     244                                          stdout=subprocess.PIPE,
     245                                          stderr=subprocess.DEVNULL,
     246                                          env=env)
     247              except OSError:  # E.g. bad executable
     248                  return None
     249              with proc:
     250                  for line in proc.stdout:
     251                      line = line.strip()
     252                      if line.startswith(b'Default Library Path (ELF):'):
     253                          paths = os.fsdecode(line).split()[4]
     254  
     255              if not paths:
     256                  return None
     257  
     258              for dir in paths.split(":"):
     259                  libfile = os.path.join(dir, "lib%s.so" % name)
     260                  if os.path.exists(libfile):
     261                      return libfile
     262  
     263              return None
     264  
     265          def find_library(name, is64 = False):
     266              return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name))
     267  
     268      else:
     269  
     270          def _findSoname_ldconfig(name):
     271              import struct
     272              if struct.calcsize('l') == 4:
     273                  machine = os.uname().machine + '-32'
     274              else:
     275                  machine = os.uname().machine + '-64'
     276              mach_map = {
     277                  'x86_64-64': 'libc6,x86-64',
     278                  'ppc64-64': 'libc6,64bit',
     279                  'sparc64-64': 'libc6,64bit',
     280                  's390x-64': 'libc6,64bit',
     281                  'ia64-64': 'libc6,IA-64',
     282                  }
     283              abi_type = mach_map.get(machine, 'libc6')
     284  
     285              # XXX assuming GLIBC's ldconfig (with option -p)
     286              regex = r'\s+(lib%s\.[^\s]+)\s+\(%s'
     287              regex = os.fsencode(regex % (re.escape(name), abi_type))
     288              try:
     289                  with subprocess.Popen(['/sbin/ldconfig', '-p'],
     290                                        stdin=subprocess.DEVNULL,
     291                                        stderr=subprocess.DEVNULL,
     292                                        stdout=subprocess.PIPE,
     293                                        env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
     294                      res = re.search(regex, p.stdout.read())
     295                      if res:
     296                          return os.fsdecode(res.group(1))
     297              except OSError:
     298                  pass
     299  
     300          def _findLib_ld(name):
     301              # See issue #9998 for why this is needed
     302              expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
     303              cmd = ['ld', '-t']
     304              libpath = os.environ.get('LD_LIBRARY_PATH')
     305              if libpath:
     306                  for d in libpath.split(':'):
     307                      cmd.extend(['-L', d])
     308              cmd.extend(['-o', os.devnull, '-l%s' % name])
     309              result = None
     310              try:
     311                  p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
     312                                       stderr=subprocess.PIPE,
     313                                       universal_newlines=True)
     314                  out, _ = p.communicate()
     315                  res = re.findall(expr, os.fsdecode(out))
     316                  for file in res:
     317                      # Check if the given file is an elf file: gcc can report
     318                      # some files that are linker scripts and not actual
     319                      # shared objects. See bpo-41976 for more details
     320                      if not _is_elf(file):
     321                          continue
     322                      return os.fsdecode(file)
     323              except Exception:
     324                  pass  # result will be None
     325              return result
     326  
     327          def find_library(name):
     328              # See issue #9998
     329              return _findSoname_ldconfig(name) or \
     330                     _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
     331  
     332  ################################################################
     333  # test code
     334  
     335  def test():
     336      from ctypes import cdll
     337      if os.name == "nt":
     338          print(cdll.msvcrt)
     339          print(cdll.load("msvcrt"))
     340          print(find_library("msvcrt"))
     341  
     342      if os.name == "posix":
     343          # find and load_version
     344          print(find_library("m"))
     345          print(find_library("c"))
     346          print(find_library("bz2"))
     347  
     348          # load
     349          if sys.platform == "darwin":
     350              print(cdll.LoadLibrary("libm.dylib"))
     351              print(cdll.LoadLibrary("libcrypto.dylib"))
     352              print(cdll.LoadLibrary("libSystem.dylib"))
     353              print(cdll.LoadLibrary("System.framework/System"))
     354          # issue-26439 - fix broken test call for AIX
     355          elif sys.platform.startswith("aix"):
     356              from ctypes import CDLL
     357              if sys.maxsize < 2**32:
     358                  print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}")
     359                  print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}")
     360                  # librpm.so is only available as 32-bit shared library
     361                  print(find_library("rpm"))
     362                  print(cdll.LoadLibrary("librpm.so"))
     363              else:
     364                  print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}")
     365                  print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}")
     366              print(f"crypt\t:: {find_library('crypt')}")
     367              print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}")
     368              print(f"crypto\t:: {find_library('crypto')}")
     369              print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}")
     370          else:
     371              print(cdll.LoadLibrary("libm.so"))
     372              print(cdll.LoadLibrary("libcrypt.so"))
     373              print(find_library("crypt"))
     374  
     375  if __name__ == "__main__":
     376      test()