# HG changeset patch # Parent 5a05c0eeefc3c9d05aa55b6e5fa2c272cd22d619 diff -r 5a05c0eeefc3 Lib/ctypes/__init__.py --- a/Lib/ctypes/__init__.py Sat Aug 27 04:07:54 2016 +0000 +++ b/Lib/ctypes/__init__.py Sat Aug 27 04:25:38 2016 +0000 @@ -337,6 +337,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 ")", 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 name.rfind(".a(") > 0: + from _ctypes import RTLD_MEMBER + mode |= RTLD_MEMBER class _FuncPtr(_CFuncPtr): _flags_ = flags diff -r 5a05c0eeefc3 Lib/ctypes/_aix.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/ctypes/_aix.py Sat Aug 27 04:25:38 2016 +0000 @@ -0,0 +1,306 @@ +""" +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 been done with for "ce", "nt", "solaris", and "bsd" +with separate, detailed if: sections in utils.py +Author: M Felt, aixtools.net, July 2016 +""" +# p.s. - this code is identical to a submission for Python2.7 +# 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 + +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(object, p=None, lines=None): + """ + 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" aka empty lines, leading/trailing spaces. + As there is concern (via feedback) of a lockup, p.wait() is not called, + only p.stdout.close() is called - that should ensure the subprocess exits + Note: shell=False, i.e., dump is called without shell interaction + """ + import subprocess + p = subprocess.Popen(["/usr/bin/dump", "-X%s"%aixABI(), "-H", object], + universal_newlines=True, stdout=subprocess.PIPE) + # not interested in blank lines, leading/trailing spaces + lines=[line.strip() for line in p.stdout.readlines() if line.strip()] + # close process and wait (verify) it has completed + p.stdout.close() + # p.wait() ## comment rather than risk blocking while waiting + return lines + +# get_shared() - prefix documentation +# get_shared() examines dumpH output and returns a list of any shareable object +# i.e., is striping information re: static archive members +# any archive members with loader sections, is considered shareable +# In other words, members followed by: "Loader section is not available" +# are static members, not shared + +### An excerpt of (stripped) dump -X64 -H output looks like: +### That is, the first two ## are my comments, the first line with three # +### characters is normal output beginning with a single # +### (i.e., the line starting as #IMPfilID) + +##/usr/lib/libc.a[vsaveres_64.o]: +##Loader section is not available +##/usr/lib/libc.a[ptrgl_64_64.o]: +##Loader section is not available +##/usr/lib/libc.a[shr_64.o]: +##***Loader Section*** +##Loader Header Information +##VERSION# #SYMtableENT #RELOCent LENidSTR +##0x00000001 0x00000ab4 0x00002c63 0x0000002d +###IMPfilID OFFidSTR LENstrTBL OFFstrTBL +##0x00000003 0x0003c748 0x00009121 0x0003c775 +##***Import File Strings*** +##INDEX PATH BASE MEMBER +##0 /usr/lib:/lib +##1 / unix +##2 libcrypt.a shr_64.o +##/usr/lib/libc.a[posix_aio_64.o]: +##***Loader Section*** +##Loader Header Information + +def get_shared(input, list=None, cpy=None, idx=0): + """ + 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 + """ + cpy=input + list=[] + for line in input: + idx = idx + 1 + # potential member lines start with a "/" + # then the next line informs whether there is a loader section, or not + # otherwise, no processing needed + if line.startswith("/"): + next = cpy.pop(idx) + # if next line does not contain "not available", + # i.e., is not "Loader section is not available" + # then it has loader information and the member including + # e.g., append [shr_64.o] from: "/usr/lib/libc.a[shr_64.o]:" + if not "not available" in next: + list.append(line[line.find("["):line.rfind(":")]) + return(list) + +def get_exactMatch(expr, lines, line=None, member=None): + """ + 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] + # return member name between "[" and "]" + return match[match.find("[")+1:match.find("]")] + else: + return None + +# additional processing to deal with AIX legacy names for 64-bit members +def get_member64(name, 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 leagacy issues + """ + # Recall that member names are still bracketed between [] + # an old convention - insert _64 between libFOO and .so + # '\[%s_*64\.so\]' % name, -> has either _64 or 64 added to name + # Note: "version" line succeeds here only when there is only one + # versioned member. + # '\[lib%s_*64\.o\]' % name, -> legacy AIX scheme for libFOO 64-bit as a .o + # '\[lib%s.so.[0-9]+.*\]' % name, -> versioning + # '\[shr4*_*64.o\]']: -> legacy AIX names: shr_64.o, shr_64.o, shr64.o + for expr in [ + '\[lib%s_*64\.so\]' % name, + '\[lib%s.so.[0-9]+.*\]' % name, + '\[lib%s_*64\.o\]' % name, + '\[shr4*_*64.o\]']: + member = get_exactMatch(expr, members) + if member: + return member + return None + +# Get the most recent/highest numbered version - if it exists +# Only called when versioned is requested +# CHECKME - superfluous due to find_library() convention? +def get_version(name, members): + """ + Internal support function: + examine member list and return highest version number + """ + # "libxyz.so.CURRENT.REVISION.AGE" => [ CURRENT, REVISION, AGE ] + 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 ] + + expr = '%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 in the dumpH output are between square brackets +def get_member(name, members, member=None): + """ + Internal support function: + Given an 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 = '\[lib%s\.so\]' % name + member = get_exactMatch(expr, members) + if member: + return member + member = get_version(name, members) + if member: + return member + elif aixABI() == 64: + return(get_member64(name, members)) + # 32-bit legacy names - shr4.o and shr.o + else: + for expr in [ + '\[shr4.o\]', + '\[shr.o\]']: + member = get_exactMatch(expr, members) + if member: + return member + return None + +def getExecLibPath_aix(libpaths=None, x=None, lines=None, + line=None, skipping=None): + """ + Internal support function: + On AIX, the buildtime searchpath is stored in the executable. the command dump -H can extract this info + Looking at LIBPATH is an enhancement, and as issue9998 is still undecided, it is commented out + """ + # if LIBPATH is defined, add it to the paths to search + # to mimic dlopen() search behavior + # 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 + +def find_library(name, _name=None, lines=None, + path=None, member=None, shlib=None): + """ + 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) + """ + def find_parts(name): + """ + If a name includes path information use that for the search + otherwise examine the executable for default directories + """ + pathe = name.rfind("/") + if pathe > 0: + parts = (name[0:pathe], name[pathe+1:]) + paths = parts[0] + name = parts[1] + else: + paths = getExecLibPath_aix() + return (paths, name) + def find_shared(paths,name, _name=None): + """ + Search "paths" for archive, and if an archive is found + return the result of get_member(_name, 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 + _name = 'lib%s' % name + archive = os.path.join(dir, "%s.a" % _name) + if os.path.exists(archive): + members = get_shared(get_dumpH(archive)) + member = get_member(_name, members) + if member != None: + return(_name, member) + else: + return None + return None + + parts = find_parts(name) + paths = parts[0] + _name = parts[1] + shared = find_shared(paths,_name) + if shared != None: + if name.rfind("/") > 0: + # PATH is significant + shlib = "%s/%s.a(%s)" % (parts[0], shared[0], shared[1]) + else: + # SEARCHing is significant + shlib = "%s.a(%s)" % (shared[0], shared[1]) + return shlib + + # To get here, a member in 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, at a last effort, look for a .so file + # Check searchpath for .so file + _name = "lib%s.so" % _name + for dir in paths.split(":"): + # /lib is a symbolic link to /usr/lib, skip it + if dir == "/lib": + continue + shlib = os.path.join(dir, "%s" % _name) + if os.path.exists(shlib): + if name.rfind("/") > 0: + # PATH is significant + return shlib + else: + # SEARCHing is significant - strip path + return shlib.rsplit("/")[-1] + # if we are here, we have not found anything plausible + return None diff -r 5a05c0eeefc3 Lib/ctypes/util.py --- a/Lib/ctypes/util.py Sat Aug 27 04:07:54 2016 +0000 +++ b/Lib/ctypes/util.py Sat Aug 27 04:25:38 2016 +0000 @@ -89,6 +89,13 @@ except ValueError: continue return None +elif sys.platform.startswith("aix"): + # find shared_library members in .a files + # otherwise try to find a .so file + # will use info from /usr/bin/dump -H + 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 @@ -327,6 +334,7 @@ print(find_library("m")) print(find_library("c")) print(find_library("bz2")) + print(find_library("crypt")) # getattr ## print cdll.m @@ -338,10 +346,14 @@ print(cdll.LoadLibrary("libcrypto.dylib")) print(cdll.LoadLibrary("libSystem.dylib")) print(cdll.LoadLibrary("System.framework/System")) + if sys.platform.startswith("aix"): + if (sys.maxsize < 2**32): + print(cdll.LoadLibrary("libc.a(shr.o)")) + else: + print(cdll.LoadLibrary("libc.a(shr_64.o)")) else: print(cdll.LoadLibrary("libm.so")) print(cdll.LoadLibrary("libcrypt.so")) - print(find_library("crypt")) if __name__ == "__main__": test() diff -r 5a05c0eeefc3 Modules/_ctypes/_ctypes.c --- a/Modules/_ctypes/_ctypes.c Sat Aug 27 04:07:54 2016 +0000 +++ b/Modules/_ctypes/_ctypes.c Sat Aug 27 04:25:38 2016 +0000 @@ -5501,6 +5501,20 @@ PyModule_AddObject(m, "RTLD_LOCAL", PyLong_FromLong(RTLD_LOCAL)); PyModule_AddObject(m, "RTLD_GLOBAL", PyLong_FromLong(RTLD_GLOBAL)); +#ifdef RTLD_NOW +/* + * this value is set silently in callproc.c + * maybe this should be added in the interface as well? + * otherwise it is an undocumented bit + */ + PyModule_AddObject(m, "RTLD_NOW", PyLong_FromLong(RTLD_NOW)); +#endif +#ifdef RTLD_MEMBER +/* + * to add support for AIX shared_members stored in .a archives + */ + PyModule_AddObject(m, "RTLD_MEMBER", PyLong_FromLong(RTLD_MEMBER)); +#endif PyExc_ArgError = PyErr_NewException("ctypes.ArgumentError", NULL, NULL); if (PyExc_ArgError) {