libctypes util findlibrary support for aix similar approach as done for darwin support by using separate files but unlike darwin no extension such as ctypes macholib dlopen is an interface to aix initandload primary documentation at https www ibm comsupportknowledgecenterensswaix61com ibm aix basetrf1dlopen htm https www ibm comsupportknowledgecenterensswaix61com ibm aix basetrf1load htm aix supports two styles for dlopen svr4 system v release 4 which is common on posix platforms but also a bsd style aka svr3 from aix 5 3 difference addendum december 2004 2 9 svr4 linking affinity nowadays there are two major object file formats used by the operating systems xcoff the coff enhanced by ibm and others the original coff common object file format was the base of svr3 and bsd 4 2 systems elf executable and linking format that was developed by att and is a base for svr4 unix while the shared library content is identical on aix one is located as a filepath name svr4 style and the other is located as a member of an archive and the archive is located as a filepath name the key difference arises when supporting multiple abi formats i e 32 and 64 bit for svr4 either only one abi is supported or there are two directories or there are different file names the most common solution for multiple abi is multiple directories for the xcoff aka aix style one directory one archive file is sufficient as multiple shared libraries can be in the archive even sharing the same name in documentation the archive is also referred to as the base and the shared library object is referred to as the member for dlopen on aix read initandload the calls are similar default activity occurs when no path information is provided when path information is provided dlopen does not search any other directories for svr4 the shared library name is the name of the file expected libfoo so for aix the shared library is expressed as basemember the search is for the base e g libfoo a and once the base is found the shared library identified by member e g libfoo so or shr o is located and loaded the mode bit rtldmember tells initandload that it needs to use the aix svr3 naming style executable bit size 32 or 64 used to filter the search in an archive by size e g x64 libxyz so major minor major minor nestedfunction but placed at module level nestedfunction but placed at module level as an ldheader was found return known paths archives and members these lines start with a digit blank line separator consume line and end for loop parse the header of the loader section of executable and archives this function calls usrbindump h as a subprocess and returns a list of ldheader ldheaderinfo tuples getldheaders parsing 1 find a line that starts with or set as ldheader 2 if index in occurs in a following line return ldheader 3 get info lines starting with 09 be sure to read to the endoffile getting all entries extract the shareable objects from ldheaders character is used to strip off the path information note the and characters that are part of dump h output are not removed here potential member lines contain otherwise no processing needed strip off trailing colon must be only one match otherwise result is none when there is a match strip leading and trailing member names in the ldheaders output are between square brackets additional processing to deal with aix legacy names for 64bit members this routine provides historical aka legacy naming schemes started in aix4 shared library support for library members names e g in usrliblibc a the member name shr o for 32bit binary and shr64 o for 64bit binary aix 64bit member is one of shr64 o shr64 o or shr464 o 32bit legacy names both shr o and shr4 o exist shr o is the preferred name so we look for shr o first i e shr4 o is returned only when shr o does not exist sort list of members and return highest numbered version if it exists this function is called when an unversioned libfoo alibfoo 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 asis for 32bit archive members but used an distinguishing name for 64bit members this scheme inserted either 64 or 64 between libfoo and so generally libfoo64 so but occasionally libfoo64 so the expression ending for versions must start as so 09 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 after the first required dot digit any combination of additional dot digits pairs are accepted anything more than libfoo so digits digits digits should be seen as a member name outside normal expectations return an archive member matching the request in name name is the library name without any prefix like lib suffix like so or version number 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 since an exact match with so as suffix was not found look for a versioned name if a versioned name is not found look for aix legacy member name on aix the buildtime searchpath is stored in the executable as loader header information the command usrbindump h extracts this info prefix searched libraries with ldlibrarypath preferred or libpath if defined these paths are appended to the paths to libraries the python executable is linked with this mimics aix dlopen behavior the second optional argument is path if it includes a paths is a list of directories to search for an archive name is the abbreviated name given to findlibrary process search paths for archive and if an archive is found return the result of getmember if an archive is not found then return none lib is a symbolic link to usrlib skip it lib is prefixed to emulate compiler name resolution e g lc to libc aix implementation of ctypes util findlibrary find an archive member that will dlopen if not available also search for a file or link with a so suffix aix supports two types of schemes that can be used with dlopen the socalled systemv release4 svr4 format is commonly suffixed with so while the default aix scheme has the library archive ending with the suffix a as an archive has multiple members e g 32bit and 64bit in one file the argument passed to dlopen must include both the library and the member names in a single string findlibrary looks first for an archive a with a suitable member if no archivemember pair is found look for a so file 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 lib is a symbolic link to usrlib skip it if we are here we have not found anything plausible executable bit size 32 or 64 used to filter the search in an archive by size e g x64 libxyz so major minor major minor nested function but placed at module level nested function but placed at module level as an ld_header was found return known paths archives and members these lines start with a digit blank line separator consume line and end for loop parse the header of the loader section of executable and archives this function calls usr bin dump h as a subprocess and returns a list of ld_header ld_header_info tuples get_ld_headers parsing 1 find a line that starts with or set as ld_header 2 if index in occurs in a following line return ld_header 3 get info lines starting with 0 9 be sure to read to the end of file getting all entries extract the shareable objects from ld_headers character is used to strip off the path information note the and characters that are part of dump h output are not removed here potential member lines contain otherwise no processing needed strip off trailing colon must be only one match otherwise result is none when there is a match strip leading and trailing member names in the ld_headers output are between square brackets additional processing to deal with aix legacy names for 64 bit members this routine provides historical aka legacy naming schemes started in aix4 shared library support for library members names e g in usr lib libc a the member name shr o for 32 bit binary and shr_64 o for 64 bit binary aix 64 bit member is one of shr64 o shr_64 o or shr4_64 o 32 bit legacy names both shr o and shr4 o exist shr o is the preferred name so we look for shr o first i e shr4 o is returned only when shr o does not exist sort list of members 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 after the first required dot digit any combination of additional dot digits pairs are accepted anything more than libfoo so digits digits digits should be seen as a member name outside normal expectations return an archive member matching the request in name name is the library name without any prefix like lib suffix like so or version number 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 since an exact match with so as suffix was not found look for a versioned name if a versioned name is not found look for aix legacy member name on aix the buildtime searchpath is stored in the executable as loader header information the command usr bin dump h extracts this info prefix searched libraries with ld_library_path preferred or libpath if defined these paths are appended to the paths to libraries the python executable is linked with this mimics aix dlopen behavior the second optional argument is path if it includes a 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 lib is a symbolic link to usr lib skip it lib is prefixed to emulate compiler name resolution e g lc to libc aix implementation of ctypes util find_library find an archive member that will dlopen if not available also search for a file or link with a so suffix aix supports two types of schemes that can be used with dlopen the so called systemv release4 svr4 format is commonly suffixed with so while the default aix scheme has the library archive ending with the suffix a as an archive has multiple members e g 32 bit and 64 bit in one file the argument passed to dlopen must include both the library and the member names in a single string find_library looks first for an archive a with a suitable member if no archive member pair is found look for a so file 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 lib is a symbolic link to usr lib skip it if we are here we have not found anything plausible
__author__ = "Michael Felt <aixtools@felt.demon.nl>"

import re
from os import environ, path
from sys import executable
from ctypes import c_void_p, sizeof
from subprocess import Popen, PIPE, DEVNULL



AIX_ABI = sizeof(c_void_p) * 8


from sys import maxsize
def _last_version(libnames, sep):
    def _num_version(libname):
        
        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)

def get_ld_header(p):
    
    ld_header = None
    for line in p.stdout:
        if line.startswith(('/', './', '../')):
            ld_header = line
        elif "INDEX" in line:
            return ld_header.rstrip('\n')
    return None

def get_ld_header_info(p):
    
    
    
    info = []
    for line in p.stdout:
        if re.match("[0-9]", line):
            info.append(line)
        else:
            
            break
    return info

def get_ld_headers(file):
    
    
    
    
    
    ldr_headers = []
    p = Popen(["/usr/bin/dump", f"-X{AIX_ABI}", "-H", file],
        universal_newlines=True, stdout=PIPE, stderr=DEVNULL)
    
    while ld_header := get_ld_header(p):
        ldr_headers.append((ld_header, get_ld_header_info(p)))
    p.stdout.close()
    p.wait()
    return ldr_headers

def get_shared(ld_headers):
    
    shared = []
    for (line, _) in ld_headers:
        
        
        if "[" in line:
            
            shared.append(line[line.index("["):-1])
    return shared

def get_one_match(expr, lines):
    
    
    expr = rf'\[({expr})\]'
    matches = list(filter(None, (re.search(expr, line) for line in lines)))
    if len(matches) == 1:
        return matches[0].group(1)
    else:
        return None


def get_legacy(members):
    
    if AIX_ABI == 64:
        
        expr = r'shr4?_?64\.o'
        member = get_one_match(expr, members)
        if member:
            return member
    else:
        
        
        
        for name in ['shr.o', 'shr4.o']:
            member = get_one_match(re.escape(name), members)
            if member:
                return member
    return None

def get_version(name, members):
    
    
    
    
    
    
    
    
    
    exprs = [rf'lib{name}\.so\.[0-9]+[0-9.]*',
        rf'lib{name}_?64\.so\.[0-9]+[0-9.]*']
    for expr in exprs:
        versions = []
        for line in members:
            m = re.search(expr, line)
            if m:
                versions.append(m.group(0))
        if versions:
            return _last_version(versions, '.')
    return None

def get_member(name, members):
    
    
    expr = rf'lib{name}\.so'
    member = get_one_match(expr, members)
    if member:
        return member
    elif AIX_ABI == 64:
        expr = rf'lib{name}64\.so'
        member = get_one_match(expr, members)
    if member:
        return member
    
    
    
    member = get_version(name, members)
    if member:
        return member
    else:
        return get_legacy(members)

def get_libpaths():
    
    libpaths = environ.get("LD_LIBRARY_PATH")
    if libpaths is None:
        libpaths = environ.get("LIBPATH")
    if libpaths is None:
        libpaths = []
    else:
        libpaths = libpaths.split(":")
    objects = get_ld_headers(executable)
    for (_, lines) in objects:
        for line in lines:
            
            path = line.split()[1]
            if "/" in path:
                libpaths.extend(path.split(":"))
    return libpaths

def find_shared(paths, name):
    
    for dir in paths:
        
        if dir == "/lib":
            continue
        
        
        base = f'lib{name}.a'
        archive = path.join(dir, base)
        if path.exists(archive):
            members = get_shared(get_ld_headers(archive))
            member = get_member(re.escape(name), members)
            if member is not None:
                return (base, member)
            else:
                return (None, None)
    return (None, None)

def find_library(name):
    

    libpaths = get_libpaths()
    (base, member) = find_shared(libpaths, name)
    if base is not None:
        return f"{base}({member})"

    
    
    
    
    
    
    
    
    
    soname = f"lib{name}.so"
    for dir in libpaths:
        
        if dir == "/lib":
            continue
        shlib = path.join(dir, soname)
        if path.exists(shlib):
            return soname
    
    return None