logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

_aix.py (12575B)


  1. """
  2. Lib/ctypes.util.find_library() support for AIX
  3. Similar approach as done for Darwin support by using separate files
  4. but unlike Darwin - no extension such as ctypes.macholib.*
  5. dlopen() is an interface to AIX initAndLoad() - primary documentation at:
  6. https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/dlopen.htm
  7. https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/load.htm
  8. AIX supports two styles for dlopen(): svr4 (System V Release 4) which is common on posix
  9. platforms, but also a BSD style - aka SVR3.
  10. From AIX 5.3 Difference Addendum (December 2004)
  11. 2.9 SVR4 linking affinity
  12. Nowadays, there are two major object file formats used by the operating systems:
  13. XCOFF: The COFF enhanced by IBM and others. The original COFF (Common
  14. Object File Format) was the base of SVR3 and BSD 4.2 systems.
  15. ELF: Executable and Linking Format that was developed by AT&T and is a
  16. base for SVR4 UNIX.
  17. While the shared library content is identical on AIX - one is located as a filepath name
  18. (svr4 style) and the other is located as a member of an archive (and the archive
  19. is located as a filepath name).
  20. The key difference arises when supporting multiple abi formats (i.e., 32 and 64 bit).
  21. For svr4 either only one ABI is supported, or there are two directories, or there
  22. are different file names. The most common solution for multiple ABI is multiple
  23. directories.
  24. For the XCOFF (aka AIX) style - one directory (one archive file) is sufficient
  25. as multiple shared libraries can be in the archive - even sharing the same name.
  26. In documentation the archive is also referred to as the "base" and the shared
  27. library object is referred to as the "member".
  28. For dlopen() on AIX (read initAndLoad()) the calls are similar.
  29. Default activity occurs when no path information is provided. When path
  30. information is provided dlopen() does not search any other directories.
  31. For SVR4 - the shared library name is the name of the file expected: libFOO.so
  32. For AIX - the shared library is expressed as base(member). The search is for the
  33. base (e.g., libFOO.a) and once the base is found the shared library - identified by
  34. member (e.g., libFOO.so, or shr.o) is located and loaded.
  35. The mode bit RTLD_MEMBER tells initAndLoad() that it needs to use the AIX (SVR3)
  36. naming style.
  37. """
  38. __author__ = "Michael Felt <aixtools@felt.demon.nl>"
  39. import re
  40. from os import environ, path
  41. from sys import executable
  42. from ctypes import c_void_p, sizeof
  43. from subprocess import Popen, PIPE, DEVNULL
  44. # Executable bit size - 32 or 64
  45. # Used to filter the search in an archive by size, e.g., -X64
  46. AIX_ABI = sizeof(c_void_p) * 8
  47. from sys import maxsize
  48. def _last_version(libnames, sep):
  49. def _num_version(libname):
  50. # "libxyz.so.MAJOR.MINOR" => [MAJOR, MINOR]
  51. parts = libname.split(sep)
  52. nums = []
  53. try:
  54. while parts:
  55. nums.insert(0, int(parts.pop()))
  56. except ValueError:
  57. pass
  58. return nums or [maxsize]
  59. return max(reversed(libnames), key=_num_version)
  60. def get_ld_header(p):
  61. # "nested-function, but placed at module level
  62. ld_header = None
  63. for line in p.stdout:
  64. if line.startswith(('/', './', '../')):
  65. ld_header = line
  66. elif "INDEX" in line:
  67. return ld_header.rstrip('\n')
  68. return None
  69. def get_ld_header_info(p):
  70. # "nested-function, but placed at module level
  71. # as an ld_header was found, return known paths, archives and members
  72. # these lines start with a digit
  73. info = []
  74. for line in p.stdout:
  75. if re.match("[0-9]", line):
  76. info.append(line)
  77. else:
  78. # blank line (separator), consume line and end for loop
  79. break
  80. return info
  81. def get_ld_headers(file):
  82. """
  83. Parse the header of the loader section of executable and archives
  84. This function calls /usr/bin/dump -H as a subprocess
  85. and returns a list of (ld_header, ld_header_info) tuples.
  86. """
  87. # get_ld_headers parsing:
  88. # 1. Find a line that starts with /, ./, or ../ - set as ld_header
  89. # 2. If "INDEX" in occurs in a following line - return ld_header
  90. # 3. get info (lines starting with [0-9])
  91. ldr_headers = []
  92. p = Popen(["/usr/bin/dump", f"-X{AIX_ABI}", "-H", file],
  93. universal_newlines=True, stdout=PIPE, stderr=DEVNULL)
  94. # be sure to read to the end-of-file - getting all entries
  95. while True:
  96. ld_header = get_ld_header(p)
  97. if ld_header:
  98. ldr_headers.append((ld_header, get_ld_header_info(p)))
  99. else:
  100. break
  101. p.stdout.close()
  102. p.wait()
  103. return ldr_headers
  104. def get_shared(ld_headers):
  105. """
  106. extract the shareable objects from ld_headers
  107. character "[" is used to strip off the path information.
  108. Note: the "[" and "]" characters that are part of dump -H output
  109. are not removed here.
  110. """
  111. shared = []
  112. for (line, _) in ld_headers:
  113. # potential member lines contain "["
  114. # otherwise, no processing needed
  115. if "[" in line:
  116. # Strip off trailing colon (:)
  117. shared.append(line[line.index("["):-1])
  118. return shared
  119. def get_one_match(expr, lines):
  120. """
  121. Must be only one match, otherwise result is None.
  122. When there is a match, strip leading "[" and trailing "]"
  123. """
  124. # member names in the ld_headers output are between square brackets
  125. expr = rf'\[({expr})\]'
  126. matches = list(filter(None, (re.search(expr, line) for line in lines)))
  127. if len(matches) == 1:
  128. return matches[0].group(1)
  129. else:
  130. return None
  131. # additional processing to deal with AIX legacy names for 64-bit members
  132. def get_legacy(members):
  133. """
  134. This routine provides historical aka legacy naming schemes started
  135. in AIX4 shared library support for library members names.
  136. e.g., in /usr/lib/libc.a the member name shr.o for 32-bit binary and
  137. shr_64.o for 64-bit binary.
  138. """
  139. if AIX_ABI == 64:
  140. # AIX 64-bit member is one of shr64.o, shr_64.o, or shr4_64.o
  141. expr = r'shr4?_?64\.o'
  142. member = get_one_match(expr, members)
  143. if member:
  144. return member
  145. else:
  146. # 32-bit legacy names - both shr.o and shr4.o exist.
  147. # shr.o is the preffered name so we look for shr.o first
  148. # i.e., shr4.o is returned only when shr.o does not exist
  149. for name in ['shr.o', 'shr4.o']:
  150. member = get_one_match(re.escape(name), members)
  151. if member:
  152. return member
  153. return None
  154. def get_version(name, members):
  155. """
  156. Sort list of members and return highest numbered version - if it exists.
  157. This function is called when an unversioned libFOO.a(libFOO.so) has
  158. not been found.
  159. Versioning for the member name is expected to follow
  160. GNU LIBTOOL conventions: the highest version (x, then X.y, then X.Y.z)
  161. * find [libFoo.so.X]
  162. * find [libFoo.so.X.Y]
  163. * find [libFoo.so.X.Y.Z]
  164. Before the GNU convention became the standard scheme regardless of
  165. binary size AIX packagers used GNU convention "as-is" for 32-bit
  166. archive members but used an "distinguishing" name for 64-bit members.
  167. This scheme inserted either 64 or _64 between libFOO and .so
  168. - generally libFOO_64.so, but occasionally libFOO64.so
  169. """
  170. # the expression ending for versions must start as
  171. # '.so.[0-9]', i.e., *.so.[at least one digit]
  172. # while multiple, more specific expressions could be specified
  173. # to search for .so.X, .so.X.Y and .so.X.Y.Z
  174. # after the first required 'dot' digit
  175. # any combination of additional 'dot' digits pairs are accepted
  176. # anything more than libFOO.so.digits.digits.digits
  177. # should be seen as a member name outside normal expectations
  178. exprs = [rf'lib{name}\.so\.[0-9]+[0-9.]*',
  179. rf'lib{name}_?64\.so\.[0-9]+[0-9.]*']
  180. for expr in exprs:
  181. versions = []
  182. for line in members:
  183. m = re.search(expr, line)
  184. if m:
  185. versions.append(m.group(0))
  186. if versions:
  187. return _last_version(versions, '.')
  188. return None
  189. def get_member(name, members):
  190. """
  191. Return an archive member matching the request in name.
  192. Name is the library name without any prefix like lib, suffix like .so,
  193. or version number.
  194. Given a list of members find and return the most appropriate result
  195. Priority is given to generic libXXX.so, then a versioned libXXX.so.a.b.c
  196. and finally, legacy AIX naming scheme.
  197. """
  198. # look first for a generic match - prepend lib and append .so
  199. expr = rf'lib{name}\.so'
  200. member = get_one_match(expr, members)
  201. if member:
  202. return member
  203. elif AIX_ABI == 64:
  204. expr = rf'lib{name}64\.so'
  205. member = get_one_match(expr, members)
  206. if member:
  207. return member
  208. # since an exact match with .so as suffix was not found
  209. # look for a versioned name
  210. # If a versioned name is not found, look for AIX legacy member name
  211. member = get_version(name, members)
  212. if member:
  213. return member
  214. else:
  215. return get_legacy(members)
  216. def get_libpaths():
  217. """
  218. On AIX, the buildtime searchpath is stored in the executable.
  219. as "loader header information".
  220. The command /usr/bin/dump -H extracts this info.
  221. Prefix searched libraries with LD_LIBRARY_PATH (preferred),
  222. or LIBPATH if defined. These paths are appended to the paths
  223. to libraries the python executable is linked with.
  224. This mimics AIX dlopen() behavior.
  225. """
  226. libpaths = environ.get("LD_LIBRARY_PATH")
  227. if libpaths is None:
  228. libpaths = environ.get("LIBPATH")
  229. if libpaths is None:
  230. libpaths = []
  231. else:
  232. libpaths = libpaths.split(":")
  233. objects = get_ld_headers(executable)
  234. for (_, lines) in objects:
  235. for line in lines:
  236. # the second (optional) argument is PATH if it includes a /
  237. path = line.split()[1]
  238. if "/" in path:
  239. libpaths.extend(path.split(":"))
  240. return libpaths
  241. def find_shared(paths, name):
  242. """
  243. paths is a list of directories to search for an archive.
  244. name is the abbreviated name given to find_library().
  245. Process: search "paths" for archive, and if an archive is found
  246. return the result of get_member().
  247. If an archive is not found then return None
  248. """
  249. for dir in paths:
  250. # /lib is a symbolic link to /usr/lib, skip it
  251. if dir == "/lib":
  252. continue
  253. # "lib" is prefixed to emulate compiler name resolution,
  254. # e.g., -lc to libc
  255. base = f'lib{name}.a'
  256. archive = path.join(dir, base)
  257. if path.exists(archive):
  258. members = get_shared(get_ld_headers(archive))
  259. member = get_member(re.escape(name), members)
  260. if member is not None:
  261. return (base, member)
  262. else:
  263. return (None, None)
  264. return (None, None)
  265. def find_library(name):
  266. """AIX implementation of ctypes.util.find_library()
  267. Find an archive member that will dlopen(). If not available,
  268. also search for a file (or link) with a .so suffix.
  269. AIX supports two types of schemes that can be used with dlopen().
  270. The so-called SystemV Release4 (svr4) format is commonly suffixed
  271. with .so while the (default) AIX scheme has the library (archive)
  272. ending with the suffix .a
  273. As an archive has multiple members (e.g., 32-bit and 64-bit) in one file
  274. the argument passed to dlopen must include both the library and
  275. the member names in a single string.
  276. find_library() looks first for an archive (.a) with a suitable member.
  277. If no archive+member pair is found, look for a .so file.
  278. """
  279. libpaths = get_libpaths()
  280. (base, member) = find_shared(libpaths, name)
  281. if base is not None:
  282. return f"{base}({member})"
  283. # To get here, a member in an archive has not been found
  284. # In other words, either:
  285. # a) a .a file was not found
  286. # b) a .a file did not have a suitable member
  287. # So, look for a .so file
  288. # Check libpaths for .so file
  289. # Note, the installation must prepare a link from a .so
  290. # to a versioned file
  291. # This is common practice by GNU libtool on other platforms
  292. soname = f"lib{name}.so"
  293. for dir in libpaths:
  294. # /lib is a symbolic link to /usr/lib, skip it
  295. if dir == "/lib":
  296. continue
  297. shlib = path.join(dir, soname)
  298. if path.exists(shlib):
  299. return soname
  300. # if we are here, we have not found anything plausible
  301. return None