diff -rNu Python-2.7.11/Lib/ctypes/__init__.py Python-2.7.11.4/Lib/ctypes/__init__.py --- Python-2.7.11/Lib/ctypes/__init__.py 2015-12-05 19:46:56 +0000 +++ Python-2.7.11.4/Lib/ctypes/__init__.py 2016-05-11 11:26:27 +0000 @@ -355,6 +355,25 @@ flags |= _FUNCFLAG_USE_ERRNO if use_last_error: flags |= _FUNCFLAG_USE_LASTERROR + if _sys.platform.startswith("aix"): + # adding RTLD_NOW is already 'forced' in Modules/_ctypes/callproc.c + # left here (as a comment) to remind it is required by AIX + from _ctypes import RTLD_NOW + mode |= RTLD_NOW + # If name already ends with the char ')' then find_library pre-processing is assummed + # while name may be NULL (to open PyDLL) need to verify it is non-null + # before doing any string operations + if name and not name.endswith(")"): + import ctypes.aixutil as aix + name = aix.find_library(name) + if name: + self._name = name + # If name ends with the char ')' then make sure it has RTLD_MEMBER in mode + # this is a seperate test because find_library may have been called before CDLL + if name and name.endswith(")"): + from _ctypes import RTLD_MEMBER + # RTLD_MEMBER = 0x00040000 + mode |= RTLD_MEMBER class _FuncPtr(_CFuncPtr): _flags_ = flags diff -rNu Python-2.7.11/Lib/ctypes/aixutil.py Python-2.7.11.4/Lib/ctypes/aixutil.py --- Python-2.7.11/Lib/ctypes/aixutil.py 1970-01-01 00:00:00 +0000 +++ Python-2.7.11.4/Lib/ctypes/aixutil.py 2016-05-10 17:08:40 +0000 @@ -0,0 +1,245 @@ +# Author: M Felt, aixtools.net, May 2016 +# ctype support for cdll interface to dlopen() for AIX +# find .so aka shared library members in .a files (i.e., archives) +# +# dlopen() is an interface to AIX initAndLoad() - these are documented 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 +# +# like GNU, when a full-path is given dlopen() does not search for the shared library +# but just opens the file. Until this addition, that was the only real hope AFTER the +# files were ALSO installed in places like /usr/lib and /usr/lib64 (cdll would find these) +# as well as a full or relative path name. +# Frequent issues with AIX and ctypes.util.find_library() is that the "posix" code, +# by default, relied on the existance of the output of "ldconfig -p" to locate shared library locations +# This caused delays, as it is unusual for ldonfig to be available, and unknown if +# if could report AIX legacy shared members that /bin/rtld would find in normal operations +# +# Think of ctypes.aix.find_library() as the utility that searches .a archives for the member you specify +# this can be as either a stem - as common with linking - "-lc" or find_library("c") +# also accepted is the stem prefixed by "lib", e.g., find_library("libintl") +# When prefixed by "lib" a suffix (is common practice) of ".so" is accepted, e.g. find_library("libc.so") +# Finally, also common in python code is a versioned specification, e.g. find_library("libintl.so.1") +# in these cases, a member is found only if the versions match exactly +# +# examples: +# aix.find_library("libiconv.so") libiconv.a(shr4_64.o) +# aix.find_library("libintl.so") libintl.a(libintl.so.1) +# aix.find_library("libintl.so.1") libintl.a(libintl.so.1) +# aix.find_library("libintl.so.2") None ## should be None! +# aix.find_library("libintl.so.8") None ## might be: libintl.a(libintl.so.8) e.g., if aixtools.gnu.gettext installed + +# Currently, my packaging of python does not insert ${prefix}/lib in the library search path +# instead, if LIBPATH is not already defined, it is defined in the environment so that +# dlopen() will look there as well. + +# As python executable probably does not include the $prefix/lib +# implied in the ./configure --prefix=/foo/dir +# then LIBPATH, when not already defined, is set to sys.prefix/lib +# +# With this 'assist' AIX dlopen() will also search ${prefix}/lib for libraries +# +# NOTE: RTLD_NOW is AIX default, regardless (as of March 2016) (set above!) +# NOTE: RTLD_MEMBER is needed by dlopen to open a shared object from within an archive +# +# find_library() returns the archive(member) string needed by dlopen() to open a member +# as part of an archive, rather than finding a directory path - more in line with default dlopen() processing +# +# when a 'generic' .so file is requested, e.g., libintl.so, of libc.so +# - if something is available, that is returned +# +# "stem" names, such as "c" or "intl" find the latest available version, +# or nothing, if a shared library does not exist (e.g., "m", and "libm" will probably fail) + +import re as _re, os as _os, sys as _sys + +# need machine size to find the correct member in an archive +def _aixABI(): + if (_sys.maxsize < 2**32): + return 32 + else: + return 64 + +# execute and pre-scan output of /usr/bin/dump ... -H ... +def _get_dumpH(_exe_so_or_a, p=None, lines=None): + import subprocess as _subprocess + p = _subprocess.Popen(["/usr/bin/dump", + "-X%s"%_aixABI(), "-H", _exe_so_or_a], + universal_newlines=True, + stdout=_subprocess.PIPE) + lines=[line.strip() for line in p.stdout.readlines() if line.strip()] + p.stdout.close() + p.wait() + return lines + +# process dumpH output and return only members with loader sections +def _get_shared(input, list=None, cpy=None, idx=0): + cpy=input + list=[] + for line in input: + idx = idx + 1 + if line.startswith("/"): + next = cpy.pop(idx) + if not "not" in next: + list.append(line[line.find("["):line.rfind(":")]) + return(list) + +# find the singular match, if more - undefined, and None is None +def _get_match(expr, lines, line=None, member=None): + matches = [m.group(0) for line in lines for m in [_re.search(expr, line)] if m] + if len(matches) == 1: + match=matches[0] + member = match[match.find("[")+1:match.find("]")] + return member + else: + return None + +# sometimes a generic name gets 64 appended to it, and AIX 64-bit +# archive members have different legacy names than 32-bit ones +def _get_member64(stem, members): + # Recall that member names are in square brackets [] + # an old convention - insert _64 between libFOO and .so + for expr in [ + '\[lib%s_*64\.so\]' % stem, + '\[lib%s.so.[0-9]+.*\]' % stem, + '\[lib%s_*64\.o\]' % stem, + '\[shr4_64.o\]', + '\[shr4*_*64.o\]']: + member = _get_match(expr, members) + if member: + return member + return None + +# Get the most recent/highest numbered version - if it exists +def _get_version(stem, members): + # sort function, borrowed from below + def _num_version(libname): + # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ] + parts = libname.split(".") + nums = [] + try: + while parts: + nums.insert(0, int(parts.pop())) + except ValueError: + pass + return nums or [ _sys.maxsize ] + + expr = r'\[lib%s.so.[0-9]+.*]' % stem + 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][1:-1] + return None + +# return an archive member matching name +# (versioned) .so members have priority over legacy AIX member name +# +# member names in in the dumpH output are in square brackets +# These get stripped by _get_match() and regular parenthesis are added by the caller +def _get_member(stem, name, members): + # look first for an exact match, then a generic match + for expr in [ + '\[%s\]' % name, + '\[lib%s\.so\]' % stem]: + member = _get_match(expr, members) + if member: + return member + # if name ends in a digit, a specific version is wanted, + # if not found above as an exact match do not look for + # a different version + if _re.findall("\d$", name): + return None + else: + member = _get_version(stem, members) + if member: + return member + elif _aixABI() == 64: + return(_get_member64(stem, members)) + # 32-bit legacy names - shr4.o and shr.o + else: + for expr in [ + '\[shr4.o\]', + '\[shr.o\]']: + member = _get_match(expr, members) + if member: + return member + return None + +# make an LIBPATH like string that will be searched directory by directory +# where the first match is THE match +def _getExecLibPath_aix(libpaths=None, x=None, lines=None, line=None, skipping=None): + libpaths = _os.environ.get('LIBPATH') + lines = _get_dumpH(_sys.executable) + skipping = True; + for line in lines: + if skipping == False: + x = line.split()[1] + if (x.startswith('/') or x.startswith('./') or x.startswith('../')): + if libpaths is not None: + libpaths = "%s:%s" % (libpaths, x) + else: + libpaths = x + elif line.find("INDEX PATH") == 0: + skipping = False; + return libpaths + +# A) exact match (relative/full-path name) +# B) normal dlopen() processing, member in a archive +# C) OLD behavior, "gnu"/"posix" like - find a file in a directory +def find_library(name, _name=None, _stem=None, envpath=None, + lines=None, path=None, member=None, shlib=None): + ##### A) if an argument resolves as a filename, return ASIS + # This is for people who are trying to match a file as "./libFOO.so" + # or using a fixed (fullpath) + if _os.path.exists(name): + return name + ##### B) the default AIX behavior is to find a member within a .a archive + # find the shared library for AIX + # name needs to in it's shortest form: libFOO.a, libFOO.so.1.0.1, etc. + # are all FOO, as in link arguments -lFOO + _stem = "%s." % name + if _stem.find("lib") < 0: + _stem = _stem[0:_stem.find(".")] + else: + _stem = _stem[3:_stem.find(".")] + # for how LIBPATH and/or LD_LIBRARY_LD are used by dlopen() (actually loadAndInit()) + # the expectation is that normal python packaging, on AIX, will not include ${prefix}/lib in + # the library search path - aka -W,-blibpath value - we add it here when LIBPATH is not defined already + # if the GNU autotools are configured to add ${prefix}/lib for all compilers, and also ${prefix)/lib64 + # when using gcc (that may expect seperate directories rather than .a archives) + envpath = _os.environ.get('LIBPATH') + if envpath is None: + _os.environ['LIBPATH'] = "%s/lib" % _sys.prefix + paths = _getExecLibPath_aix() + for dir in paths.split(":"): + if dir == "/lib": + continue + # AIX rtl searches .a, by default, if it exists + _arfile = _os.path.join(dir, "lib%s.a" % _stem) + if _os.path.exists(_arfile): + members = _get_shared(_get_dumpH(_arfile)) + member = _get_member(_stem, name, members) + if member != None: + shlib = "lib%s.a(%s)" % (_stem, member) + return shlib + ##### C) look for a .so FILE based on the request + if envpath is None: + if _aixABI() == 64: + _os.environ['LIBPATH'] = "%s/lib64" % _sys.prefix + paths = _getExecLibPath_aix() + # same as above! so comments only + # else: + # _os.environ['LIBPATH'] = "%s/lib" % _sys.prefix + # paths = _getExecLibPath_aix() + for dir in paths.split(":"): + if dir == "/lib": + continue + shlib = _os.path.join(dir, "%s" % name) + if _os.path.exists(shlib): + return shlib + # Try for a generic match + shlib = _os.path.join(dir, "lib%s.so" % _stem) + if _os.path.exists(shlib): + return shlib + # if we are here, we have not found anything plausible + return None diff -rNu Python-2.7.11/Lib/ctypes/util.py Python-2.7.11.4/Lib/ctypes/util.py --- Python-2.7.11/Lib/ctypes/util.py 2015-12-05 19:46:56 +0000 +++ Python-2.7.11.4/Lib/ctypes/util.py 2016-05-11 11:26:27 +0000 @@ -71,7 +71,16 @@ def find_library(name): return name -if os.name == "posix" and sys.platform == "darwin": +if sys.platform.startswith("aix"): + # find .so members in .a files + # using dump loader header information + sys. + import ctypes.aixutil as aix + + def find_library(name): + # print ("aix.find: ", name) + return aix.find_library(name) + +elif os.name == "posix" and sys.platform == "darwin": from ctypes.macholib.dyld import dyld_find as _dyld_find def find_library(name): possible = ['lib%s.dylib' % name, @@ -256,6 +265,13 @@ print find_library("m") print find_library("c") print find_library("bz2") + if sys.platform.startswith("aix"): + # examples of working with AIX and versions, when available + print("aix.find_library(\"libiconv.so\")", find_library("libiconv.so")) + print("aix.find_library(\"libintl.so\")", find_library("libintl.so")) + print("aix.find_library(\"libintl.so.1\")", find_library("libintl.so.1")) + print("aix.find_library(\"libintl.so.2\")", find_library("libintl.so.2"), ":: should be None!") + print("aix.find_library(\"libintl.so.8\")", find_library("libintl.so.8")) # getattr ## print cdll.m @@ -268,7 +284,17 @@ print cdll.LoadLibrary("libSystem.dylib") print cdll.LoadLibrary("System.framework/System") else: - print cdll.LoadLibrary("libm.so") + if sys.platform.startswith("aix"): + # libm does not exist as a shared library on AIX, so it fails here + # while test/test_loading.py captures the error correctly + # the test here did not, so calling for libc.so instead + print(cdll.LoadLibrary("libc.so")) + # This test is because libintl may still be only version .1 + # However, it may be version .8 - the latest of the two is returned + # See find_library() exambles above + print(cdll.LoadLibrary("libintl.so")) + else: + print(cdll.LoadLibrary("libm.so")) print cdll.LoadLibrary("libcrypt.so") print find_library("crypt") diff -rNu Python-2.7.11/Modules/_ctypes/_ctypes.c Python-2.7.11.4/Modules/_ctypes/_ctypes.c --- Python-2.7.11/Modules/_ctypes/_ctypes.c 2015-12-05 19:47:11 +0000 +++ Python-2.7.11.4/Modules/_ctypes/_ctypes.c 2016-05-11 11:26:27 +0000 @@ -5725,6 +5725,13 @@ PyModule_AddObject(m, "RTLD_LOCAL", PyInt_FromLong(RTLD_LOCAL)); PyModule_AddObject(m, "RTLD_GLOBAL", PyInt_FromLong(RTLD_GLOBAL)); +#ifdef RTLD_NOW + PyModule_AddObject(m, "RTLD_NOW", PyLong_FromLong(RTLD_NOW)); +#endif +#ifdef RTLD_MEMBER + PyModule_AddObject(m, "RTLD_MEMBER", PyLong_FromLong(RTLD_MEMBER)); +#endif + PyExc_ArgError = PyErr_NewException("ctypes.ArgumentError", NULL, NULL); if (PyExc_ArgError) {