diff -N -ru '--exclude=*.pyc' src/Python-2.7.12/Lib/ctypes/__init__.py python-2.7.12.2/Lib/ctypes/__init__.py --- src/Python-2.7.12/Lib/ctypes/__init__.py 2016-06-25 21:49:30 +0000 +++ python-2.7.12.2/Lib/ctypes/__init__.py 2016-09-28 12:55:15 +0000 @@ -352,6 +352,16 @@ flags |= _FUNCFLAG_USE_ERRNO if use_last_error: flags |= _FUNCFLAG_USE_LASTERROR + if _sys.platform.startswith("aix"): + """When the name contains ".a(" and ends with ")", + asin "libFOO.a(libFOO.so)" this is taken to be an + archive(member) syntax for dlopen(), and the mode is adjusted. + Otherwise, name is presented to dlopen() as a file argument. + """ + # from _ctypes import RTLD_NOW - not until Python3.7 + if name and name.endswith(")") and ".a(" in name: + RTLD_MEMBER = 0x00040000 + mode |= RTLD_MEMBER class _FuncPtr(_CFuncPtr): _flags_ = flags diff -N -ru '--exclude=*.pyc' src/Python-2.7.12/Lib/ctypes/_aix.py python-2.7.12.2/Lib/ctypes/_aix.py --- src/Python-2.7.12/Lib/ctypes/_aix.py 1970-01-01 00:00:00 +0000 +++ python-2.7.12.2/Lib/ctypes/_aix.py 2016-09-28 14:33:52 +0000 @@ -0,0 +1,316 @@ +"""Internal platform implementation of find_library() for AIX. +Lib/ctype support for LoadLibrary interface to dlopen() for AIX +s implemented as a separate file, similiar to Darwin support ctype.macholib.* +rather than as separate sections in utils.py for improved +isolation and (I hope) readability + +dlopen() is implemented on AIX as initAndLoad() - official documentation at: +https://www.ibm.com/support/knowledgecenter/ssw_aix_53/\ +com.ibm.aix.basetechref/doc/basetrf1/dlopen.htm?lang=en and +https://www.ibm.com/support/knowledgecenter/ssw_aix_53/\ +com.ibm.aix.basetechref/doc/basetrf1/load.htm?lang=en +""" +# Author: M Felt, aixtools.net, 2016 +# I thank Martin Panter for his patience and comments + +import os +import re +import sys +import subprocess + +def aixABI(): + """ + Internal support function: + return executable size - 32-bit, or 64-bit + This is vital to the search for suitable member in an archive + """ + if (sys.maxsize < 2**32): + return 32 + else: + return 64 + +def get_dumpH(file): + """Internal support function: + dump -H output provides info on archive/executable contents + and related paths to shareable members. + """ + #dumpH parseing: + # 1. Line starts with /, ./, or ../ - set object name + # 2. When the string "INDEX" is located -> return object + # 3. get path/archive/member info (lines starting with digits [0-9]) + + def get_object(p, object=None): + for line in p.stdout: + if (line.startswith('/') + or line.startswith('./') + or line.startswith('../')): + object = line + elif "INDEX" in line: + return object + return None + + def get_objectinfo(p): + # as an object was found, return known paths, archives and members + # these lines start with a digit + # an empty line or end-of-file terminates the for loop + lines = None + for line in p.stdout: + if re.match("[0-9]", line, flags=0): + if lines is None: + lines = line + else: + lines += line + else: + break + # lines should never be blank, the if: block is only for safety + if lines: + return lines.strip() + else: + return "" + + # The subprocess calls dump -H without shell assistence. + # Unneeded lines are removed by get_object() and get_objectinfo() + p = subprocess.Popen(["/usr/bin/dump", "-X%s"%aixABI(), "-H", file], + universal_newlines=True, stdout=subprocess.PIPE, shell=False) + lines = None + while 1: + object = get_object(p) + # get_object() returns None when archive has + # no (more) shareable members or end-of-file was reached + if object is None: + break + if lines is None: + lines = object + else: + lines += "\n\n%s" % object + lines += get_objectinfo(p) + + p.stdout.close() + p.wait() + return lines + +def get_members(input): + """Internal support function: return a list of all shareable objects""" + # the character "[" is identifies object_lines + # so that "/usr/lib/libFOO.a[member.so]:" returns as "[member.so]" + list=[] + if input is None: + return(list) + lines = input.split("\n") + for line in lines: + # potential member lines contain "[" + # otherwise, no processing needed + if "[" in line: + list.append(line[line.find("["):-1]) + return(list) + +def get_exactMatch(expr, lines): + """Internal support function: + Must be only one match, otherwise result is None + When there is a match, strip the leading "[" and trailing "]" + """ + matches = [m.group(0) for line in lines for m in [ + re.search(expr, line)] if m] + if len(matches) == 1: + return matches[0][1:-1] + else: + return None + +# additional processing to deal with AIX legacy names for 64-bit members +def get_legacy(members): + """Internal support function: + This routine resolves various legacy naming schemes starting in AIX4 + This routine catches various schemes for 64-bit legacy names + """ + if aixABI() == 64: + # AIX 64-bit member is usually shr_64.o although + # shr64.o or shr4_64.o have been seen - in "ancient" archives + expr = '\[shr4?_?64.o\]' + member = get_exactMatch(expr, members) + if member: + return member + # 32-bit legacy names + # shr.o and shr4.o are both available - frequently + # shr.o is preferred and returned rather than shr4.o + # i.e., shr4.o is returned only when shr.o does not exist + else: + for expr in [ + r'\[shr.o\]', + r'\[shr4.o\]']: + member = get_exactMatch(expr, members) + if member: + return member + return None + +# Get the most recent/highest numbered version - if it exists +# In an new ABI (perhaps for Python3.7) could be called +# as a request for the latest version installed, or a specific version +# Currently, only called when when unversioned (libFOO.so) is not available +def get_version(name, members): + """Internal support function: + Examine member list and return highest version number + """ + # This is called when libFFO.a(libFFO.so) has not been found. + # Version numbering is expected to follow GNU LIBTOOL conventions: + # d.1 find [libFoo.so.XX] + # d.2 find [libFoo.so.XX.YY] + # d.3 find [libFoo.so.XX.Y.ZZZ] + + # Before the GNU convention took hold there was a convention in AIX + # to use GNU convention "asis" for 32-bit archive members but in + # 64-bit members add either 64 or _64 between libFOO and .so + # to generally libFOO_64.so, but occaisionally libFOO64.so + # Generally, the second expression that looks for "64" + # in the member name will not be needed. + + # "libxyz.so.CURRENT.REVISION.AGE" => [ CURRENT, REVISION, AGE ] + # the original idea for _num_version comes from + # an earlier ctype.util internal function + def _num_version(libname): + parts = libname.split(".") + nums = [] + try: + while parts: + nums.insert(0, int(parts.pop())) + except ValueError: + pass + return nums or [ sys.maxsize ] + + # A "versioned" member name ends with .so.X plus + # additional, but optional dot + digit pairs + # while multiple, more specific expressions could be specified + # to search for .so.X, .so.X.Y and .so.X.Y.Z + # we chose to use a single re that finds the required part + # and then accepts additional '.' and digits + # + # imho, anything other that .so.digit(s).digit(s).digit(s) + # with only the first .so.digit(s) required is someone purposely + # trying to break the parser + for expr in [ + r'lib%s\.so\.[0-9]+[0-9.]*' % name, + r'lib%s_?64\.so\.[0-9]+[0-9.]*' % name]: + + versions = [m.group(0) for line in members for m in [ + re.search(expr, line)] if m] + if versions: + versions.sort(key=_num_version) + return versions[-1] + return None + +def get_member(name, members): + """Internal support function: + Given a list of members find and return the most appropriate result + Priority is given to generic libXXX.so, then a versioned libXXX.so.a.b.c + and lastly, legacy AIX naming scheme + """ + # look first for a generic match - prepend lib and append .so + # '\[lib%s\.so\]' % name -> most common + expr = r'\[lib%s.so\]' % name + member = get_exactMatch(expr, members) + if member: + return member + # libFOO.so was not found ... + member = get_version(name, members) + if member: + return member + else: + return get_legacy(members) + +def getExecLibPath_aix(): + """Internal support function: + On AIX, the searchpath is stored in the executable. + The command dump -H can extract this info. + Prefix searched libraries with LIBPATH, or LD_LIBRARY_PATH if defined. + Paths are appended based rather than inserted. + This mimics AIX dlopen() behavior. + """ + + # os.environ.get commented out in Python2.7 - see issue#9998 + # libpaths = os.environ.get("LIBPATH") + # if libpaths is None: + # libpaths = os.environ.get("LD_LIBRARY_PATH") + # so, libpaths is manually set to None + libpaths = None + lines = get_dumpH(sys.executable).split("\n") + for line in lines: + # valid lines begin with a digit (INDEX) + # the second (optional) argument is PATH if it includes a / + if re.match("[0-9]", line, flags=0): + path = line.split()[1] + else: + continue + if "/" in path: + if libpaths is None: + libpaths = path + else: + libpaths = "%s:%s" % (libpaths,path) + return libpaths + +def find_library(name): + """AIX platform implementation of find_library() + This routine is called by ctype.util.find_library(). + This routine should not be called directly! + + AIX loader behavior: + Find a library looking first for an archive (.a) with a suitable member + For the loader the member name is not relevant. The first archive object + shared or static that resolves an unknown symbol is used by "ld" + + To mimic AIX rtld behavior this routines looks first for an archive + libFOO.a and then examines the contents of the archive for shareable + members that also match either libFOO.so, libFOO.so.X.Y.Z (so-called + versioned library names) and finally AIX legacy names + usually shr.o (32-bit members) and shr_64.o (64-bit members) + + When no archive(member) is found, look for a libFOO.so file + When versioning is required - it must be provided via hard + or soft-link from libFOO.so to the correct version. + RTLD aka dlopen() does not do versioning. + """ + + # paths is where to look for archives "basename" + # _member is the member name based on archive basename + def find_shared(paths, name): + """ + Search "paths" for archive, and if an archive is found + return the result of get_member(member, archive). + If no archives are found return None + """ + for dir in paths.split(":"): + # /lib is a symbolic link to /usr/lib, skip it + if dir == "/lib": + continue + # "lib" is prefixed to emulate compiler name resolution, + # e.g., -lFOO to libFOO + base = 'lib%s.a' % name + archive = os.path.join(dir, "%s" % base) + if os.path.exists(archive): + members = get_members(get_dumpH(archive)) + member = get_member(name, members) + if member != None: + return(base, member) + else: + return(None, None) + return(None, None) + + libpaths = getExecLibPath_aix() + (base, member) = find_shared(libpaths, name) + if base != None: + return "%s(%s)" % (base, member) + + # Being here means a member in an archive has not been found + # In other words, either: + # a) a .a file was not found + # b) a .a file did not have a suitable member + + # Check libpaths for .so file + soname = "lib%s.so" % name + for dir in libpaths.split(":"): + # /lib is a symbolic link to /usr/lib, skip it + if dir == "/lib": + continue + shlib = os.path.join(dir, "%s" % soname) + if os.path.exists(shlib): + return soname + # Being here means nothing suitable was found + return None diff -N -ru '--exclude=*.pyc' src/Python-2.7.12/Lib/ctypes/test/test_loading.py python-2.7.12.2/Lib/ctypes/test/test_loading.py --- src/Python-2.7.12/Lib/ctypes/test/test_loading.py 2016-06-25 21:49:30 +0000 +++ python-2.7.12.2/Lib/ctypes/test/test_loading.py 2016-09-28 12:43:46 +0000 @@ -11,6 +11,11 @@ libc_name = "coredll" elif sys.platform == "cygwin": libc_name = "cygwin1.dll" +elif sys.platform.startswith("aix"): + if (sys.maxsize < 2**32): + libc_name = "libc.a(shr.o)" + else: + libc_name = "libc.a(shr_64.o)" else: libc_name = find_library("c") @@ -38,11 +43,16 @@ self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) def test_find(self): + # to track that at least one call to find_library() found something + found = False for name in ("c", "m"): lib = find_library(name) if lib: + found = True cdll.LoadLibrary(lib) CDLL(lib) + # test is FAILED if nothing was found + self.assertTrue(found) @unittest.skipUnless(os.name in ("nt", "ce"), 'test specific to Windows (NT/CE)') diff -N -ru '--exclude=*.pyc' src/Python-2.7.12/Lib/ctypes/util.py python-2.7.12.2/Lib/ctypes/util.py --- src/Python-2.7.12/Lib/ctypes/util.py 2016-06-25 21:49:30 +0000 +++ python-2.7.12.2/Lib/ctypes/util.py 2016-09-28 08:41:57 +0000 @@ -81,6 +81,14 @@ continue return None +if sys.platform.startswith("aix"): + # find .so members in .a files + # using dump loader header information + sys. + import ctypes._aix as aix + + def find_library(name): + return aix.find_library(name) + elif os.name == "posix": # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump import re, tempfile, errno @@ -247,10 +255,11 @@ print find_library("msvcrt") if os.name == "posix": - # find and load_version - print find_library("m") - print find_library("c") - print find_library("bz2") + # find + print("m\t:: %s" % find_library("m")) + print("c\t:: %s" % find_library("c")) + print("bz2\t:: %s" % find_library("bz2")) + print("crypt\t:: %s" % find_library("crypt")) # getattr ## print cdll.m @@ -262,6 +271,12 @@ print cdll.LoadLibrary("libcrypto.dylib") print cdll.LoadLibrary("libSystem.dylib") print cdll.LoadLibrary("System.framework/System") + elif sys.platform.startswith("aix"): + print("crypto\t:: %s" % cdll.LoadLibrary(find_library("crypto"))) + if (sys.maxsize < 2**32): + print("c\t:: %s" % cdll.LoadLibrary("libc.a(shr.o)")) + else: + print("c\t:: %s" % cdll.LoadLibrary("libc.a(shr_64.o)")) else: print cdll.LoadLibrary("libm.so") print cdll.LoadLibrary("libcrypt.so")