diff -N -ru -x '*.pyc' 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-10-04 18:04:14 +0000 @@ -334,6 +334,15 @@ 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 ")", as in + "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 + mode |= RTLD_MEMBER class _FuncPtr(_CFuncPtr): _flags_ = flags diff -N -ru -x '*.pyc' 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-10-04 21:42:10 +0000 @@ -0,0 +1,266 @@ +"""Lib/ctypes 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 ctypes.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 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, Feb-Sept 2016 +# I thank Martin Panter for his patience and comments + +from re import search, match, escape +from os import environ, path +from sys import executable +from ctypes import c_void_p, sizeof +from ctypes._util import _last_version +from subprocess import Popen, PIPE + +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 + """ + return sizeof(c_void_p) * 8 + +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 returns a list of (object, objectinfo) tuples. + """ + #dumpH parsing: + # 1. Line starts with /, ./, or ../ - set object name + # 2. If "INDEX" return object + # 3. get extra info (lines starting with [0-9]) + + def get_object(p): + object = None + for x in p.stdout: + if x.startswith(('/', './', '../')): + object = x + elif "INDEX" in x: + return object.rstrip('\n') + return None + + def get_objectinfo(p): + # as an object was found, return known paths, archives and members + # these lines start with a digit + lines = [] + for line in p.stdout: + if match("[0-9]", line): + lines.append(line) + else: + # Should be a blank separator line, safe to consume + break + return lines + + p = Popen(["/usr/bin/dump", "-X%s" % aixABI(), "-H", file], + universal_newlines=True, stdout=PIPE) + + objects = [] + while True: + object = get_object(p) + if object is None: + break + objects.append((object, get_objectinfo(p))) + + p.stdout.close() + p.wait() + return objects + +def get_shared(input): + """Internal support function: examine the get_dumpH() output and + return a list of all shareable objects indicated in the output the + character "[" is used to strip off the path information. + Note: the "[" and "]" characters that are part of dump -H output + are not removed here. + """ + list = [] + for (line, _) in input: + # potential member lines contain "[" + # otherwise, no processing needed + if "[" in line: + # Strip off trailing colon (:) + list.append(line[line.index("["):-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 "]" + """ + # member names in the dumpH output are between square brackets + expr = r'\[(%s)\]' % expr + matches = list(filter(None, (search(expr, line) for line in lines))) + if len(matches) == 1: + return matches[0].group(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 shared library support for + library members names for, e.g., shr.o in /usr/lib/libc.a for + 32-bit binary and shr_64.o in /usr/lib/libc.a for 64-bit binary. + """ + if aixABI() == 64: + # AIX 64-bit member is one of shr64.o, shr_64.o, or shr4_64.o + expr = r'shr4?_?64\.o' + member = get_exactMatch(expr, members) + if member: + return member + # 32-bit legacy names - both shr.o and shr4.o exist. + # shr.o is the preffered name so we look for shr.o first + # i.e., shr4.o is returned only when shr.o does not exist + else: + for name in ['shr.o', 'shr4.o']: + member = get_exactMatch(escape(name), members) + if member: + return member + return None + +def get_version(name, members): + """Internal support function: examine member list and return highest + numbered version - if it exists. This function is called when an + unversioned libFOO.a(libFOO.so) has not been found. + + Versioning for the member name is expected to follow + GNU LIBTOOL conventions: the highest version (x, then X.y, then X.Y.z) + * find [libFoo.so.X] + * find [libFoo.so.X.Y] + * find [libFoo.so.X.Y.Z] + + Before the GNU convention became the standard scheme regardless of + binary size AIX packagers used GNU convention "as-is" for 32-bit + archive members but used an "distinguishing" name for 64-bit members. + This scheme inserted either 64 or _64 between libFOO and .so + - generally libFOO_64.so, but occasionally libFOO64.so + """ + # the expression ending for versions must start as + # '.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 + # anything other that .so.digits.digits.digits + # with only the first .digits required is someone purposely + # trying to break the parser + exprs = [r'lib%s\.so\.[0-9]+[0-9.]*' % name, + r'lib%s_?64\.so\.[0-9]+[0-9.]*' % name] + for expr in exprs: + versions = [] + for line in members: + m = search(expr, line) + if m: + versions.append(m.group(0)) + if versions: + return _last_version(versions, '.') + return None + +def get_member(name, members): + """Internal support function: Return an archive member matching name. + 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 finally, legacy AIX naming scheme. + """ + # look first for a generic match - prepend lib and append .so + expr = r'lib%s\.so' % name + member = get_exactMatch(expr, members) + if member: + return member + # since an exact match with .so as extension of member name + # was not found, look for a versioned name + # If a versioned name is 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 = environ.get("LIBPATH") + if libpaths is None: + libpaths = environ.get("LD_LIBRARY_PATH") + if libpaths is None: + libpaths = [] + else: + libpaths = libpaths.split(":") + objects = get_dumpH(executable) + for (_, lines) in objects: + for line in lines: + # the second (optional) argument is PATH if it includes a / + path = line.split()[1] + if "/" in path: + libpaths.extend(path.split(":")) + return libpaths + +def find_library(name): + """AIX specific routine - to find an archive member that will dlopen() + + find_library() looks first for an archive (.a) with a suitable member. + If no archive is found, look for a .so file. + """ + + def find_shared(paths, name): + """ + paths is a list of directories to search for an archive. + name is the abbreviated name given to find_library(). + Process: search "paths" for archive, and if an archive is found + return the result of get_member(). + If an archive is not found then return None + """ + for dir in paths: + # /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 = path.join(dir, base) + if path.exists(archive): + members = get_shared(get_dumpH(archive)) + member = get_member(escape(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) + + # 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: + # /lib is a symbolic link to /usr/lib, skip it + if dir == "/lib": + continue + shlib = path.join(dir, soname) + if path.exists(shlib): + return soname + # if we are here, we have not found anything plausible + return None diff -N -ru -x '*.pyc' 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 1970-01-01 00:00:00 +0000 +++ python-3.6.0.177/Lib/ctypes/_util.py 2016-10-04 21:51:08 +0000 @@ -0,0 +1,15 @@ +from sys import maxsize + +def _last_version(libnames, sep): + def _num_version(libname): + # "libxyz.so.MAJOR.MINOR" => [MAJOR, MINOR] + parts = libname.split(sep) + nums = [] + try: + while parts: + nums.insert(0, int(parts.pop())) + except ValueError: + pass + return nums or [maxsize] + + return max(reversed(libnames), key=_num_version) diff -N -ru -x '*.pyc' 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-10-04 18:02:58 +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") diff -N -ru -x '*.pyc' 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-10-04 20:22:10 +0000 @@ -2,6 +2,7 @@ import shutil import subprocess import sys +from ctypes._util import _last_version # find_library(name) returns the pathname of a library, or None. if os.name == "nt": @@ -80,6 +81,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 @@ -170,17 +180,6 @@ if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")): - def _num_version(libname): - # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ] - parts = libname.split(b".") - nums = [] - try: - while parts: - nums.insert(0, int(parts.pop())) - except ValueError: - pass - return nums or [sys.maxsize] - def find_library(name): ename = re.escape(name) expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename) @@ -199,8 +198,7 @@ res = re.findall(expr, data) if not res: return _get_soname(_findLib_gcc(name)) - res.sort(key=_num_version) - return os.fsdecode(res[-1]) + return os.fsdecode(_last_version(res, b".")) elif sys.platform == "sunos5": @@ -313,10 +311,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 +327,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"))