diff '--exclude=*.pyc' -N -ru src/Python-3.6.0b1/Lib/ctypes/__init__.py python-3.6.0.177/Lib/ctypes/__init__.py --- src/Python-3.6.0b1/Lib/ctypes/__init__.py 2016-09-12 16:22:31 +0000 +++ python-3.6.0.177/Lib/ctypes/__init__.py 2016-09-26 12:36:14 +0000 @@ -334,6 +334,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 + """ + if name and name.endswith(")") and ".a(" in name: + # from _ctypes import RTLD_MEMBER + RTLD_MEMBER = 0x00040000 + mode |= RTLD_MEMBER class _FuncPtr(_CFuncPtr): _flags_ = flags diff '--exclude=*.pyc' -N -ru src/Python-3.6.0b1/Lib/ctypes/_aix.py python-3.6.0.177/Lib/ctypes/_aix.py --- src/Python-3.6.0b1/Lib/ctypes/_aix.py 1970-01-01 00:00:00 +0000 +++ python-3.6.0.177/Lib/ctypes/_aix.py 2016-09-26 14:43:20 +0000 @@ -0,0 +1,307 @@ +""" +Lib/ctype support for LoadLibrary interface to dlopen() for AIX +Similar kind of support (i.e., as a separate file) +as has been done for Darwin support ctype.macholib.* +rather than as separate, detailed if: sections in utils.py + +dlopen() is an interface to AIX initAndLoad() - primary documentation at: +https://www.ibm.com/support/knowledgecenter/ssw_aix_53/com.ibm.aix.basetechref/doc/basetrf1/dlopen.htm?lang=en +https://www.ibm.com/support/knowledgecenter/ssw_aix_53/com.ibm.aix.basetechref/doc/basetrf1/load.htm?lang=en +""" +# Author: M Felt, aixtools.net, Feb-Sept 2016 +# I thank Martin Panter for his patience and comments + +import re, os, sys + +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. This function call dump -H as a subprocess + and strips the output of "noise". + Note: shell=False, i.e., /usr/bin/dump is called without shell assistence + """ + #dumpH parseing: + # 1. Line starts with /, ./, or ../ - set object name + # 2. If "INDEX" return object + # 3. get extra info (lines starting with [0-9]) + + import subprocess + def get_object(p, object=None): + for x in p.stdout: + if (x.startswith('/') or x.startswith('./') or x.startswith('../')): + object = x + elif "INDEX" in x: + return object + return None + + def get_objectinfo(p, lines=None): + # as an object was found, return known paths, archives and members + # these lines start with a digit + for line in p.stdout: + if re.match("[0-9]", line, flags=0): + if lines is None: + lines = line + else: + lines += line + else: + break + if lines: + return lines.strip() + else: + return None + + 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) + 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_shared(input): + """ + Internal support function: + examine the stripped dump -H output and return a list of all shareable objects + indicated in the text + the character "[" is used to strip off the path information + Note: the "[" and "]" characters that are part of dump -H output are not processed here + """ + 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: + # match=matches[0] + # since the expr alwats starts/stops with the brackets + # return member name between "[" and "]" which are in expr! + 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 historical aka legacy naming schemes started in AIX4 + for shared library support for library members names for, e.g., libc.a + This routine addresses specific issues for 64-bit legacy issues + """ + if aixABI() == 64: + expr = '\[shr4?_?64.o\]' # AIX 64-bit member is one of, whereas 32-bit often has two names + member = get_exactMatch(expr, members) + if member: + return member + # 32-bit legacy names - shr.o and shr4.o -- shr.o is the default name since AIX5 + # so we look for shr.o first, 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 function is called when libFFO.a(libFFO.so) has not been found + + Versioning for the member name 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] + + Unfortunately, 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 - so generally libFOO_64.so, but occaisionally + (read noteably for libssl.a and libcrypto.a) the member names are 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 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 ] + + # the expression ending for versions must start as + # r'.so.\[0-9\] , i.e., *\.so\.[at least one digit] + # while multiple, more specific expressions could be specified + # to search for .so.X, .so.X.Y and .so.X.Y.Z + # we are accepting any combination of digits and '.' + # after the first required digit + # imho, anything other that .so.digits.digits.digits + # with only the first .digits required is someone purposelys + # 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 + +# return an archive member matching name +# (versioned) .so members have priority over legacy AIX member name +# member names in the dumpH output are between square brackets +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 + # because an exact match with .so as extension of member name + # was not found, look for a versioned name + # When a versioned name is also not found, look for AIX legacy member name + member = get_version(name, members) + if member: + return member + else: + return get_legacy(members) + +def getExecLibPath_aix(): + """ + Internal support function: + On AIX, the buildtime 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 + Additional paths are appended based on paths to libraries the python executable is linked with + This mimics AIX dlopen() behavior + """ + + libpaths = os.environ.get("LIBPATH") + if libpaths is None: + libpaths = os.environ.get("LD_LIBRARY_PATH") + 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 specific routine - that knows how to find members in archives + + Find a library looking first for an archive (.a) with a suitable member, + return .so if found, otherwise highest version number + If no archive is found, look for a .so file (no versioning, symbolic link + must point to correct version) + """ + + # 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., -lc to libc + base = 'lib%s.a' % name + archive = os.path.join(dir, "%s" % base) + if os.path.exists(archive): + members = get_shared(get_dumpH(archive)) + member = get_member(name, members) + if member != None: + return(base, member) + else: + return(None, None) + return(None, None) + + # , _name=None, lines=None, + # path=None + libpaths = getExecLibPath_aix() + (base, member) = find_shared(libpaths, name) + if base != None: + return "%s(%s)" % (base, member) + + # To get here, 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 + # So, look for a .so file + # Check libpaths for .so file + # Note, the installation must prepare a link from a .so + # to a versioned file + # This is common practice by GNU libtool on other platforms + 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 + # if we are here, we have not found anything plausible + return None diff '--exclude=*.pyc' -N -ru src/Python-3.6.0b1/Lib/ctypes/test/test_loading.py python-3.6.0.177/Lib/ctypes/test/test_loading.py --- src/Python-3.6.0b1/Lib/ctypes/test/test_loading.py 2016-09-12 16:22:31 +0000 +++ python-3.6.0.177/Lib/ctypes/test/test_loading.py 2016-09-26 12:56:16 +0000 @@ -13,6 +13,11 @@ libc_name = find_library("c") 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") @@ -40,9 +45,12 @@ self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) + # this should only have libraries that are known to exist on all platforms + # it is known than "m" aka libm does not have a shared library on AIX def test_find(self): - for name in ("c", "m"): + for name in ("c", "ssl"): lib = find_library(name) + self.assertTrue(lib) if lib: cdll.LoadLibrary(lib) CDLL(lib) diff '--exclude=*.pyc' -N -ru src/Python-3.6.0b1/Lib/ctypes/util.py python-3.6.0.177/Lib/ctypes/util.py --- src/Python-3.6.0b1/Lib/ctypes/util.py 2016-09-12 16:22:31 +0000 +++ python-3.6.0.177/Lib/ctypes/util.py 2016-09-26 13:07:39 +0000 @@ -80,6 +80,15 @@ continue return None +elif sys.platform.startswith("aix"): + # using a new file to not clutter up this one + # Behavior is to look for a member in an archive + # otherwise try to find a .so file + # uses info from /usr/bin/dump -H - rather than ldconfig -p + 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 @@ -313,10 +322,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 @@ -328,6 +338,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"))