diff -r 6d1b6a68f775 Lib/ctypes/__init__.py --- a/Lib/ctypes/__init__.py Sat Dec 05 11:45:17 2015 -0800 +++ b/Lib/ctypes/__init__.py Sun May 08 05:47:13 2016 +0000 @@ -3,7 +3,7 @@ ###################################################################### """create and manipulate C data types in Python""" -import os as _os, sys as _sys +import os as _os, sys as _sys, subprocess as _subprocess __version__ = "1.1.0" @@ -12,6 +12,11 @@ from _ctypes import _Pointer from _ctypes import CFuncPtr as _CFuncPtr from _ctypes import __version__ as _ctypes_version from _ctypes import RTLD_LOCAL, RTLD_GLOBAL +# from _ctypes import RTLD_LOCAL, RTLD_GLOBAL, RTLD_NOW, RTLD_MEMBER +# RTLD_NOW and RTLD_MEMBER are not in _ctypes (yet) +# RTLD_MEMBER is not in _ctypes (yet), or whereever it needs to be defined +RTLD_NOW = 0x00000002 +RTLD_MEMBER = 0x00040000 from _ctypes import ArgumentError from struct import calcsize as _calcsize @@ -355,6 +360,20 @@ class CDLL(object): flags |= _FUNCFLAG_USE_ERRNO if use_last_error: flags |= _FUNCFLAG_USE_LASTERROR + if _sys.platform.startswith("aix"): + if name is None: + return + _name = name + # mode |= RTLD_NOW - maybe it could have been added to RTLD_DEFAULT above + # make sure, as this is what AIX expects + mode |= RTLD_NOW + # If name already ends with the char ')' then it is already processed + if _name[-1] != ')': + import ctypes.aixutil as aix + _name = aix.find_library(_name) + self._name = _name + if _name and _name[-1] == ")": + mode |= RTLD_MEMBER class _FuncPtr(_CFuncPtr): _flags_ = flags diff -r 6d1b6a68f775 Lib/ctypes/aixutil.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/ctypes/aixutil.py Sun May 08 05:47:13 2016 +0000 @@ -0,0 +1,223 @@ +# Author: M Felt, aixtools.net, 2016 +# ctype support for cdll interface to dlopen() for AIX +# find .so aka shared library members in .a files (i.e., archives) +# +# by default, on AIX dlopen follows the "blibpath" that can be seen +# by the command "/usr/bin/dump -XNN -H exe_so_member" +# 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 +# NITE: RTLD_NOW and RTLD_MEMBER are defined and assigned (for now) in ctypes/__init__.py +# +# find_library() returns the archive(member) string needed by dlopen() to open a member +# as part of an archive +# when versioned .so files are requested, e.g. libintl.so.8, libc.so.6, +# but it is not available -> None is returned +# +# 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, subprocess as _subprocess, 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 + +# return a file (stdout) of the dump command of an object +# stderr still goes to 'tty', even with stderr=None specified +# stderr=_os.devnull, +def _get_dumpH(_exe_so_or_a, p=None): + p = _subprocess.Popen(["/usr/bin/dump", + "-X%s"%_aixABI(), "-H", _exe_so_or_a], + universal_newlines=False, + stdout=_subprocess.PIPE) + p.wait() + return p.stdout + +# 64-bit archive members have different legacy names than 32-bit ones +def _get_member64(stem, lines): + # Recall that member names are in square brackets [] + # an old convention - insert _64 between libFOO and .so + expr = r'\[lib%s_*64\.so\]' % stem + res = _re.findall(expr, lines) + # two AIX conventions for 64-bit archive members: shr4_64.o and shr_64.o + # From memory, shr4_64.o is the later convention + if not res: + expr = r'\[shr4_64.o\]' + res = _re.findall(expr, lines) + if not res: + expr = r'\[shr_64.o\]' + res = _re.findall(expr, lines) + if res: + return res[0][1:-1] + else: + return None + +# Looking for a versioned .so filename +# Get the most recent/highest numbered version +def _get_version(stem, lines): + # 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.maxint ] + + expr = r'\[lib%s\.so\.[0-9]+.*\]' % stem + result = _re.findall(expr, lines) + if result: + result.sort(key=_num_version) + return result[-1] + return None + +# return an archive member matching name +# if a (versioned) .so member exists, it is returned rather than a legacy AIX member name +# the assumption is that a .so mamber was added to the library - and the old members are +# there to support legacy applications +# +# member names in in the dumpH output are in square brackets +# These get stripped and regular parenthesis are added by the caller +def _get_member(stem, name, lines): + # look first for an exact match + expr = r'\[%s\]' % name + res = _re.findall(expr, lines) + if res: + return res[0][1:-1] + elif _re.findall("\d$", name): + # if name ends in a digit, a specific version is wanted, this or nothing + return None + # look a generic match + expr = r'\[lib%s\.so\]' % stem + res = _re.findall(expr, lines) + if res: + return res[0][1:-1] + else: + # _get_version returns a single entry, not an array like re.findall + res = _get_version(stem, lines) + if res: + return res[1:-1] + # to be here res(ult) is still False + # now look for legacy AIX member names + # 64-bit legacy names - shr4.o and shr.o + if _aixABI() == 64: + return(_get_member64(stem, lines)) + # 32-bit searches + # legacy names - shr4.o and shr.o + # Not using elif because first exact match counts + # must try again after each mismatch + expr = r'\[shr4.o\]' + res = _re.findall(expr, lines) + if res: + return res[0][1:-1] + else: + expr = r'\[shr.o\]' + res = _re.findall(expr, lines) + # No Match, return None; else return member name without square brackets + if res: + return res[0][1:-1] + else: + return None + +# make an LIBPATH like string that will be searched directory by directory +# where the first match is THE match +def _getLibPath_aix(libpaths=None, f=None, x=None): + libpaths = _os.environ.get('LIBPATH') + f = _get_dumpH(_sys.executable) + try: + skipping = True; + for line in f: + 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.startswith('INDEX PATH'): + skipping = False; + finally: + return libpaths + +# search all LIBPATH paths for an archive with member +# return a AIX rtl/dlopen(able) target +def find_library(name, _name=None, shlib=None,envpath=None,lines=None): + # 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 + # AIX examines .a files first. If a suitable member is not found + # in libFOO.a then look for libFFO.so + # if .so file is found - return the fullpathname + # so that it is (more) likely dlopen() will use the .so file + # and ignore a potential libFOO.a, but without a shared member + # add a . to be certain of a '.' in the 'name' + # so that we can extract that value + # so that it has the same value as when passing -lFOO to the linker + _stem = "%s." % name + if _stem.find("lib") < 0: + _stem = _stem[0:_stem.find(".")] + else: + _stem = _stem[3:_stem.find(".")] + # FIXME: make three calls - find_version, find_latest, find_file + # FIXME: The current 'find_library' finds the 'latest' + + envpath = _os.environ.get('LIBPATH') + if envpath is None: + _os.environ['LIBPATH'] = "%s/lib" % _sys.prefix + paths = _getLibPath_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): + lines = _get_dumpH(_arfile).read() + member = _get_member(_stem, name, lines) + if member != None: + shlib = "lib%s.a(%s)" % (_stem, member) + return shlib + # look for a .so FILE last, no version checking done yet + # FIXME: here we could scan all directories for a file with an exact match + # except, this is suppossed to be a member name. Perhaps, wrap, not wrapped + # could distinquish whether member or full path + # Try matching original argument as a file + if envpath is None: + if _aixABI() == 32: + _os.environ['LIBPATH'] = "%s/lib" % _sys.prefix + else: + _os.environ['LIBPATH'] = "%s/lib64" % _sys.prefix + paths = _getLibPath_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" % name) + if _os.path.exists(shlib): + return shlib + # This is for people who are trying to match a file as "./libFOO.so" + # Not good practice, IMHO, but I have seen many examples of 'failures' + # when trying to load a shared directory in ./ + # FYI: better would be to add LIBPATH="." and/or add $prefix/lib as well + # e.g., export LIBPATH=".:/opt/lib" - or it may not load anyway! + if _os.path.exists(name): + return name + # if we are here, we have not found anything plausible + return None diff -r 6d1b6a68f775 Lib/ctypes/util.py --- a/Lib/ctypes/util.py Sat Dec 05 11:45:17 2015 -0800 +++ b/Lib/ctypes/util.py Sun May 08 05:47:13 2016 +0000 @@ -84,6 +84,14 @@ if os.name == "posix" and sys.platform = continue return None +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): + return aix.find_library(name) + elif os.name == "posix": # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump import re, tempfile, errno @@ -271,6 +279,44 @@ def test(): print cdll.LoadLibrary("libm.so") print cdll.LoadLibrary("libcrypt.so") print find_library("crypt") + print find_library("crypto") + print find_library("crypto.so") + print find_library("iconv") + + if sys.platform.startswith("aix"): + print "\nAdditional Tests for AIX" + print "call find_library(\"foo\")" + print "libintl.so.1:", find_library("libintl.so.1") # from /opt/freeware + print "libintl.so.8:", find_library("libintl.so.8") # from GNU + print "intl:", find_library("intl") # either .1 or .8 - latest installed + print "libintl.so.1: ", find_library("libintl.so.1") + print "libintl.so.8: ", find_library("libintl.so.8") + print "libintl.so: ", find_library("libintl.so") + + print "libcrypto.so: ", find_library("libcrypto.so") + print "c: ", find_library("c") + print "c.a: ", find_library("c.a") + print "c.so: ", find_library("c.so") + print "libc: ", find_library("libc") + print "libc.a: ", find_library("libc.a") + print "libc.so: ", find_library("libc.so") + print "libc.so.6: ", find_library("libc.so.6") +### + print "\ncall cdll.LoadLibrary(\"foo\")" + print "m: ",cdll.LoadLibrary("m") + print "libm.so: ",cdll.LoadLibrary("libm.so") + print "c: ",cdll.LoadLibrary("c") + print "c.a: ",cdll.LoadLibrary("c.a") + print "libc.a: ",cdll.LoadLibrary("libc.a") + print "libc.so: ",cdll.LoadLibrary("libc.so") + print "libc.so.6: ",cdll.LoadLibrary("libc.so.6") + print "libintl.so.1: ",cdll.LoadLibrary("libintl.so.1") # from /opt/freeware + print "libintl.so.8: ",cdll.LoadLibrary("libintl.so.8") # from GNU + print "intl: ",cdll.LoadLibrary("intl") # either .1 or .8 - latest installed + print "crypt: ",cdll.LoadLibrary("crypt") + print "crypto: ",cdll.LoadLibrary("crypto") + print "crypto.so: ",cdll.LoadLibrary("crypto.so") + print "libcrypto.so: ",cdll.LoadLibrary("libcrypto.so") if __name__ == "__main__": test()