From d2d37098bcddda166e7e7a23b434c16493b162a2 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Wed, 28 Oct 2009 19:38:53 +0000 Subject: [PATCH] Add preserve-libs support for AIX' XCOFF, patch by Michael Haubenwallner svn path=/main/branches/prefix/; revision=14745 --- bin/misc-functions.sh | 269 +++++++++++++++++++++++++++++++++++ pym/portage/__init__.py | 2 + pym/portage/dbapi/vartree.py | 254 ++++++++++++++++++++++++++++++++- 3 files changed, 523 insertions(+), 2 deletions(-) diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh index 8be1176dc..53808a6eb 100644 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -436,6 +436,114 @@ install_qa_check() { PORTAGE_QUIET=${tmp_quiet} fi + if [[ ${CHOST} == *-aix* ]] && ! hasq binchecks ${RESTRICT}; then + local tmp_quiet=${PORTAGE_QUIET} + local queryline deplib + local insecure_rpath_list= undefined_symbols_list= + + # display warnings when using stricter because we die afterwards + if has stricter ${FEATURES} ; then + unset PORTAGE_QUIET + fi + + rm -f "${PORTAGE_BUILDDIR}"/build-info/NEEDED.XCOFF.1 + find "${ED}" -not -type d -exec \ + "${EPREFIX}/usr/bin/aixdll-query" '{}' FILE MEMBER FLAGS FORMAT RUNPATH DEPLIBS ';' \ + > "${T}"/needed 2>/dev/null + + # Symlinking archive libraries is not a good idea on aix, + # as there is nothing like "soname" on pure filesystem level. + # So we create a copy instead of the symlink. + local prev_FILE= + while read queryline + do + local FILE= MEMBER= FLAGS= FORMAT= RUNPATH= DEPLIBS= + eval ${queryline} + + if [[ ${prev_FILE} != ${FILE} ]]; then + prev_FILE=${FILE} + if [[ -n ${MEMBER} || " ${FLAGS} " == *" SHROBJ "* ]] && [[ -h ${FILE} ]]; then + local target=$(readlink "${FILE}") + if [[ ${target} == /* ]]; then + target=${D}${target} + else + target=${FILE%/*}/${target} + fi + rm -f "${FILE}" || die "cannot prune ${FILE#${ED}}" + cp -f "${target}" "${FILE}" || die "cannot copy ${target#${ED}} to ${FILE#${ED}}" + fi + fi + done <"${T}"/needed + + prev_FILE= + while read queryline + do + local FILE= MEMBER= FLAGS= FORMAT= RUNPATH= DEPLIBS= + eval ${queryline} + + if [[ ${prev_FILE} != ${FILE} ]]; then + # Save NEEDED information for the archive library stub + echo "${FORMAT##* }${FORMAT%%-*};${FILE#${D%/}};${FILE##*/};;" >> "${PORTAGE_BUILDDIR}"/build-info/NEEDED.XCOFF.1 + fi + + # Make sure we disallow insecure RUNPATH's + # Don't want paths that point to the tree where the package was built + # (older, broken libtools would do this). Also check for null paths + # because the loader will search $PWD when it finds null paths. + # And we really want absolute paths only. + if [[ -n $(echo ":${RUNPATH}:" | grep -E "(${PORTAGE_BUILDDIR}|::|:[^/])") ]]; then + insecure_rpath_list="${insecure_rpath_list}\n${FILE}" + fi + + # Although we do have runtime linking, we don't want undefined symbols. + # AIX does indicate this by needing either '.' or '..' + local needed=${FILE##*/} + for deplib in ${DEPLIBS}; do + eval deplib=${deplib} + if [[ ${deplib} == '.' || ${deplib} == '..' ]]; then + undefined_symbols_list="${undefined_symbols_list}\n${FILE}" + else + needed="${needed},${deplib}" + fi + done + + FILE=${FILE#${D%/}} + + [[ -n ${MEMBER} ]] && MEMBER="[${MEMBER}]" + # Save NEEDED information + echo "${FORMAT##* }${FORMAT%%-*};${FILE}${MEMBER};${FILE##*/}${MEMBER};${RUNPATH};${needed}" >> "${PORTAGE_BUILDDIR}"/build-info/NEEDED.XCOFF.1 + done <"${T}"/needed + + if [[ -n ${undefined_symbols_list} ]]; then + vecho -ne '\a\n' + eqawarn "QA Notice: The following files contain undefined symbols." + eqawarn " Please file a bug about this at http://bugs.gentoo.org/" + eqawarn " with 'prefix' as the maintaining herd of the package." + eqawarn "${undefined_symbols_list}" + vecho -ne '\a\n' + fi + + if [[ -n ${insecure_rpath_list} ]] ; then + vecho -ne '\a\n' + eqawarn "QA Notice: The following files contain insecure RUNPATH's" + eqawarn " Please file a bug about this at http://bugs.gentoo.org/" + eqawarn " with 'prefix' as the maintaining herd of the package." + eqawarn "${insecure_rpath_list}" + vecho -ne '\a\n' + if [[ -n ${x} ]] || has stricter ${FEATURES} ; then + insecure_rpath=1 + fi + fi + + if [[ ${insecure_rpath} -eq 1 ]] ; then + die "Aborting due to serious QA concerns with RUNPATH/RPATH" + elif [[ -n ${die_msg} ]] && has stricter ${FEATURES} ; then + die "Aborting due to QA concerns: ${die_msg}" + fi + + PORTAGE_QUIET=${tmp_quiet} + fi + local unsafe_files=$(find "${ED}" -type f '(' -perm -2002 -o -perm -4002 ')') if [[ -n ${unsafe_files} ]] ; then eqawarn "QA Notice: Unsafe files detected (set*id and world writable)" @@ -862,6 +970,167 @@ postinst_bsdflags() { mtree -e -p "${EROOT}" -U -k flags < "${T}/bsdflags.mtree" &> /dev/null } +preinst_aix() { + if [[ ${CHOST} != *-aix* ]] || hasq binchecks ${RESTRICT}; then + return 0 + fi + local ar strip + if type ${CHOST}-ar >/dev/null 2>&1 && type ${CHOST}-strip >/dev/null 2>&1; then + ar=${CHOST}-ar + strip=${CHOST}-strip + elif [[ ${CBUILD} == "${CHOST}" ]] && type ar >/dev/null 2>&1 && type strip >/dev/null 2>&1; then + ar=ar + strip=strip + elif [[ -x /usr/ccs/bin/ar && -x /usr/ccs/bin/strip ]]; then + ar=/usr/ccs/bin/ar + strip=/usr/ccs/bin/strip + else + die "cannot find where to use 'ar' and 'strip' from" + fi + + local archive prev_archive= archives=() + while read archive; do + archive=${archive#*;} + archive=${archive%%;*} + if [[ ${archive} == *'['*']' ]]; then + archive=${archive%[*} + [[ ${prev_archive} == ${archive} ]] && continue + prev_archive=${archive} + archives[${#archives[@]}]=${archive#${EPREFIX}/} + fi + done < "${PORTAGE_BUILDDIR}"/build-info/NEEDED.XCOFF.1 + unset prev_archive + + local libmetadir members preservemembers member contentmember chmod400files=() prunedirs=() + for archive in "${archives[@]}"; do + libmetadir=${ED}${archive%/*}/.${archive##*/} + [[ ! -e ${libmetadir} ]] || rm -rf "${libmetadir}" || die "cannot prune ${libmetadir}" + mkdir "${libmetadir}" || die "cannot create ${libmetadir}" + pushd "${libmetadir}" >/dev/null || die "cannot cd to ${libmetadir}" + ${ar} -X32_64 -x "${ED}${archive}" || die "cannot unpack ${archive}" + members=" $(echo *) " + preservemembers= + if [[ -e ${EROOT}${archive} ]]; then + for member in $(${ar} -X32_64 -t "${EROOT}${archive}"); do + [[ ${members} == *" ${member} "* ]] && continue + preservemembers="${preservemembers} ${member}" + done + if [[ -n ${preservemembers} ]]; then + einfo "preserving (on spec)${preservemembers}" + ${ar} -X32_64 -x "${EROOT}${archive}" ${preservemembers} || die "cannot extract ${preservemembers} from ${EROOT}${archive}" + chmod u+w ${preservemembers} || die "cannot chmod${preservedmembers}" + ${strip} -X32_64 -e ${preservemembers} || die "cannot strip${preservemembers}" + ${ar} -X32_64 -q "${ED}${archive}" ${preservemembers} || die "cannot update ${archive}" + eend $? + fi + fi + for member in ${members}; do + contentmember="${archive%/*}/.${archive##*/}[${member}]" + # portage does os.lstat() on merged files every now + # and then, so keep stamp-files for archive members + # around to get the preserve-libs feature working. + { echo "Please leave this file alone, it is an important helper" + echo "for portage to implement the 'preserve-libs' feature on AIX." + } > "${ED}${contentmember}" || die "cannot create ${contentmember}" + chmod400files[${#chmod400files[@]}]=${ED}${contentmember} + done + popd >/dev/null || die "cannot leave ${libmetadir}" + prunedirs[${#prunedirs[@]}]=${libmetadir} + done + [[ ${#chmod400files[@]} == 0 ]] || + chmod 0400 "${chmod400files[@]}" || die "cannot chmod ${chmod400files[@]}" + [[ ${#prunedirs[@]} == 0 ]] || + rm -rf "${prunedirs[@]}" || die "cannot prune ${prunedirs[@]}" + return 0 +} + +postinst_aix() { + if [[ ${CHOST} != *-aix* ]] || hasq binchecks ${RESTRICT}; then + return 0 + fi + local MY_PR=${PR%r0} + local ar strip + if type ${CHOST}-ar >/dev/null 2>&1 && type ${CHOST}-strip >/dev/null 2>&1; then + ar=${CHOST}-ar + strip=${CHOST}-strip + elif [[ ${CBUILD} == "${CHOST}" ]] && type ar >/dev/null 2>&1 && type strip >/dev/null 2>&1; then + ar=ar + strip=strip + elif [[ -x /usr/ccs/bin/ar && -x /usr/ccs/bin/strip ]]; then + ar=/usr/ccs/bin/ar + strip=/usr/ccs/bin/strip + else + die "cannot find where to use 'ar' and 'strip' from" + fi + + local member contentmember activecontentmembers= prev_archive= archive activearchives= + while read member; do + member=${member#*;} # drop "^type;" + member=${member%%;*} # drop ";soname;runpath;needed$" + [[ ${member##*/} == *'['*']' ]] || continue + contentmember=${member%/*}/.${member##*/} + activecontentmembers="${activecontentmembers}:(${contentmember}):" + archive=${member%[*} + [[ ${prev_archive} != ${archive} ]] || continue + prev_archive=${archive} + activearchives="${activearchives}:(${archive}):" + done < "${PORTAGE_BUILDDIR}"/build-info/NEEDED.XCOFF.1 + + local type allcontentmembers= all_archives=() + prev_archive= + while read type contentmember; do + contentmember=${contentmember% *} # drop " timestamp$" + contentmember=${contentmember% *} # drop " hash$" + [[ ${contentmember##*/} == *'['*']' ]] || continue + allcontentmembers="${allcontentmembers}:(${contentmember}):" + member=${contentmember%/.*}/${contentmember##*/.} + archive=${member%[*} + [[ ${prev_archive} != ${archive} ]] || continue + prev_archive=${archive} + all_archives[${#all_archives[@]}]=${archive} + done < "${EPREFIX}/var/db/pkg/${CATEGORY}/${P}${MY_PR:+-}${MY_PR}/CONTENTS" + + local delmembers oldmembers libmetadir prunemembers=() prunedirs=() + for archive in "${all_archives[@]}"; do + [[ -r ${ROOT}${archive} && -w ${ROOT}${archive} ]] || + chmod a+r,u+w "${ROOT}${archive}" || die "cannot chmod ${archive}" + delmembers= oldmembers= + for member in $(${ar} -X32_64 -t "${ROOT}${archive}"); do + contentmember="${archive%/*}/.${archive##*/}[${member}]" + if [[ ${allcontentmembers} != *":(${contentmember}):"* ]]; then + # not existent any more, just drop it + delmembers="${delmembers} ${member}" + prunemembers[${#prunemembers[@]}]=${ROOT}${contentmember} + elif [[ ${activecontentmembers} != *":(${contentmember}):"* ]]; then + oldmembers="${oldmembers} ${member}" + fi + done + if [[ -n ${delmembers} ]]; then + einfo "dropping${delmembers}" + ${ar} -X32_64 -z -o -d "${ROOT}${archive}" ${delmembers} || die "cannot remove${delmembers} from ${archive}" + eend $? + fi + if [[ -n ${oldmembers} && ${activearchives} != *":(${archive}):"* ]]; then + einfo "preserving (extra)${oldmembers}" + libmetadir=${ROOT}${archive%/*}/.${archive##*/} + [[ ! -e ${libmetadir} ]] || rm -rf "${libmetadir}" || die "cannot prune ${libmetadir}" + mkdir "${libmetadir}" || die "cannot create ${libmetadir}" + pushd "${libmetadir}" >/dev/null || die "cannot cd to ${libmetadir}" + ${ar} -X32_64 -x "${ROOT}${archive}" ${oldmembers} || die "cannot unpack ${archive}" + ${strip} -e ${oldmembers} || die "cannot strip ${oldmembers}" + ${ar} -X32_64 -z -o -r "${ROOT}${archive}" ${oldmembers} || die "cannot update${oldmembers} in ${archive}" + popd > /dev/null || die "cannot leave ${libmetadir}" + prunedirs[${#prunedirs[@]}]=${libmetadir} + eend $? + fi + done + [[ ${#prunedirs[@]} == 0 ]] || + rm -rf "${prunedirs[@]}" || die "cannot prune ${prunedirs[@]}" + [[ ${#prunemembers[@]} == 0 ]] || + rm -f "${prunemembers[@]}" || die "cannot prune ${contentmenbers[@]}" + return 0 +} + preinst_mask() { if [ -z "${D}" ]; then eerror "${FUNCNAME}: D is unset" diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 165db71b4..6d24a8321 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -5655,6 +5655,7 @@ _post_phase_cmds = { "install_symlink_html_docs"], "preinst" : [ + "preinst_aix", "preinst_bsdflags", "preinst_sfperms", "preinst_selinux_labels", @@ -5662,6 +5663,7 @@ _post_phase_cmds = { "preinst_mask"], "postinst" : [ + "postinst_aix", "postinst_bsdflags"] } diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 210a1d553..c3da0fecb 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -5,7 +5,7 @@ from __future__ import print_function __all__ = ["PreservedLibsRegistry", "LinkageMap", - "LinkageMapMachO", "LinkageMapPeCoff", + "LinkageMapMachO", "LinkageMapPeCoff", "LinkageMapXCoff" "vardbapi", "vartree", "dblink"] + \ ["write_contents", "tar_contents"] @@ -26,7 +26,7 @@ portage.proxy.lazyimport.lazyimport(globals(), ) from portage.const import CACHE_PATH, CONFIG_MEMORY_FILE, \ - PORTAGE_PACKAGE_ATOM, PRIVATE_PATH, VDB_PATH, EPREFIX, EPREFIX_LSTRIP + PORTAGE_PACKAGE_ATOM, PRIVATE_PATH, VDB_PATH, EPREFIX, EPREFIX_LSTRIP, BASH_BINARY from portage.data import portage_gid, portage_uid, secpass from portage.dbapi import dbapi from portage.exception import CommandNotFound, \ @@ -1479,6 +1479,252 @@ class LinkageMapPeCoff(LinkageMap): arch_map[needed_soname] = soname_map soname_map.consumers.add(obj_key) +class LinkageMapXCoff(LinkageMap): + + """Models dynamic linker dependencies.""" + + _needed_aux_key = "NEEDED.XCOFF.1" + + class _ObjectKey(LinkageMap._ObjectKey): + + def __init__(self, obj, root): + LinkageMap._ObjectKey.__init__(self, obj, root) + + def _generate_object_key(self, obj, root): + """ + Generate object key for a given object. + + @param object: path to a file + @type object: string (example: '/usr/bin/bar') + @rtype: 2-tuple of types (long, int) if object exists. string if + object does not exist. + @return: + 1. 2-tuple of object's inode and device from a stat call, if object + exists. + 2. realpath of object if object does not exist. + + """ + + os = _os_merge + + try: + _unicode_encode(obj, + encoding=_encodings['merge'], errors='strict') + except UnicodeEncodeError: + # The package appears to have been merged with a + # different value of sys.getfilesystemencoding(), + # so fall back to utf_8 if appropriate. + try: + _unicode_encode(obj, + encoding=_encodings['fs'], errors='strict') + except UnicodeEncodeError: + pass + else: + os = portage.os + + abs_path = os.path.join(root, obj.lstrip(os.sep)) + try: + object_stat = os.stat(abs_path) + except OSError: + # Use the realpath as the key if the file does not exists on the + # filesystem. + return os.path.realpath(abs_path) + # Return a tuple of the device and inode, as well as the basename, + # because of hardlinks the device and inode might be identical. + return (object_stat.st_dev, object_stat.st_ino, os.path.basename(abs_path.rstrip(os.sep))) + + def file_exists(self): + """ + Determine if the file for this key exists on the filesystem. + + @rtype: Boolean + @return: + 1. True if the file exists. + 2. False if the file does not exist or is a broken symlink. + + """ + return isinstance(self._key, tuple) + + class _LibGraphNode(_ObjectKey): + __slots__ = ("alt_paths",) + + def __init__(self, obj, root): + LinkageMapXCoff._ObjectKey.__init__(self, obj, root) + self.alt_paths = set() + + def __str__(self): + return str(sorted(self.alt_paths)) + + def rebuild(self, exclude_pkgs=None, include_file=None): + """ + Raises CommandNotFound if there are preserved libs + and the scanelf binary is not available. + """ + + os = _os_merge + root = self._root + root_len = len(root) - 1 + self._clear_cache() + self._defpath.update(getlibpaths(self._root)) + libs = self._libs + obj_key_cache = self._obj_key_cache + obj_properties = self._obj_properties + + lines = [] + + # Data from include_file is processed first so that it + # overrides any data from previously installed files. + if include_file is not None: + lines += grabfile(include_file) + + aux_keys = [self._needed_aux_key] + for cpv in self._dbapi.cpv_all(): + if exclude_pkgs is not None and cpv in exclude_pkgs: + continue + lines += self._dbapi.aux_get(cpv, aux_keys)[0].split('\n') + # Cache NEEDED.* files avoid doing excessive IO for every rebuild. + self._dbapi.flush_cache() + + # have to call scanelf for preserved libs here as they aren't + # registered in NEEDED.XCOFF.1 files + if self._dbapi.plib_registry and self._dbapi.plib_registry.getPreservedLibs(): + for items in self._dbapi.plib_registry.getPreservedLibs().values(): + for x in items: + args = [BASH_BINARY, "-c", ':' + + '; member="' + x + '"' + + '; archive=${member}' + + '; if [[ ${member##*/} == .*"["*"]" ]]' + + '; then member=${member%/.*}/${member##*/.}' + + '; archive=${member%[*}' + + '; fi' + + '; member=${member#${archive}}' + + '; [[ -r ${archive} ]] || chmod a+r "${archive}"' + + '; eval $(aixdll-query "${archive}${member}" FILE MEMBER FLAGS FORMAT RUNPATH DEPLIBS)' + + '; [[ -n ${member} ]] && needed=${FILE##*/} || needed=' + + '; for deplib in ${DEPLIBS}' + + '; do eval deplib=${deplib}' + + '; if [[ ${deplib} != "." && ${deplib} != ".." ]]' + + '; then needed="${needed}${needed:+,}${deplib}"' + + '; fi' + + '; done' + + '; [[ -n ${MEMBER} ]] && MEMBER="[${MEMBER}]"' + + '; [[ " ${FLAGS} " == *" SHROBJ "* ]] && soname=${FILE##*/}${MEMBER} || soname=' + + '; echo "${FORMAT##* }${FORMAT%%-*};${FILE#${ROOT%/}}${MEMBER};${soname};${RUNPATH};${needed}"' + + '; [[ -n ${member} ]] || echo "${FORMAT##* }${FORMAT%%-*};${FILE#${ROOT%/}};${FILE##*/};;"' + ] + try: + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + raise CommandNotFound("aixdll-query via " + argv[0]) + else: + for l in proc.stdout: + try: + l = _unicode_decode(l, + encoding=_encodings['content'], errors='strict') + except UnicodeDecodeError: + l = _unicode_decode(l, + encoding=_encodings['content'], errors='replace') + writemsg_level(_("\nError decoding characters " \ + "returned from aixdll-query: %s\n\n") % (l,), + level=logging.ERROR, noiselevel=-1) + l = l.rstrip("\n") + if not l: + continue + fields = l.split(";") + if len(fields) < 5: + writemsg_level(_("\nWrong number of fields " \ + "returned from aixdll-query: %s\n\n") % (l,), + level=logging.ERROR, noiselevel=-1) + continue + fields[1] = fields[1][root_len:] + lines.append(";".join(fields)) + proc.wait() + + for l in lines: + l = l.rstrip("\n") + if not l: + continue + fields = l.split(";") + if len(fields) < 5: + writemsg_level(_("\nWrong number of fields " \ + "in %s: %s\n\n") % (self._needed_aux_key, l), + level=logging.ERROR, noiselevel=-1) + continue + arch = fields[0] + + def as_contentmember(obj): + if obj.endswith("]"): + if obj.find("/") >= 0: + return obj[:obj.rfind("/")] + "/." + obj[obj.rfind("/")+1:] + return "." + obj + return obj + + obj = as_contentmember(fields[1]) + soname = as_contentmember(fields[2]) + path = set([normalize_path(x) \ + for x in filter(None, fields[3].replace( + "${ORIGIN}", os.path.dirname(obj)).replace( + "$ORIGIN", os.path.dirname(obj)).split(":"))]) + needed = [as_contentmember(x) for x in fields[4].split(",") if x] + + obj_key = self._obj_key(obj) + indexed = True + myprops = obj_properties.get(obj_key) + if myprops is None: + indexed = False + myprops = (arch, needed, path, soname, set()) + obj_properties[obj_key] = myprops + # All object paths are added into the obj_properties tuple. + myprops[4].add(obj) + + # Don't index the same file more that once since only one + # set of data can be correct and therefore mixing data + # may corrupt the index (include_file overrides previously + # installed). + if indexed: + continue + + arch_map = libs.get(arch) + if arch_map is None: + arch_map = {} + libs[arch] = arch_map + if soname: + soname_map = arch_map.get(soname) + if soname_map is None: + soname_map = self._soname_map_class( + providers=set(), consumers=set()) + arch_map[soname] = soname_map + soname_map.providers.add(obj_key) + for needed_soname in needed: + soname_map = arch_map.get(needed_soname) + if soname_map is None: + soname_map = self._soname_map_class( + providers=set(), consumers=set()) + arch_map[needed_soname] = soname_map + soname_map.consumers.add(obj_key) + + def getSoname(self, obj): + """ + Return the soname associated with an object. + + @param obj: absolute path to an object + @type obj: string (example: '/usr/bin/bar') + @rtype: string + @return: soname as a string + + """ + if not self._libs: + self.rebuild() + if isinstance(obj, self._ObjectKey): + obj_key = obj + if obj_key not in self._obj_properties: + raise KeyError("%s not in object list" % obj_key) + return self._obj_properties[obj_key][3] + if obj not in self._obj_key_cache: + raise KeyError("%s not in object list" % obj) + return self._obj_properties[self._obj_key_cache[obj]][3] class vardbapi(dbapi): @@ -1552,6 +1798,8 @@ class vardbapi(dbapi): self.linkmap = LinkageMapMachO(self) elif chost.find('interix') >= 0 or chost.find('winnt') >= 0: self.linkmap = LinkageMapPeCoff(self) + elif chost.find('aix'): + self.linkmap = LinkageMapXCoff(self) else: self.linkmap = LinkageMap(self) self._owners = self._owners_db(self) @@ -3698,6 +3946,8 @@ class dblink(object): node = LinkageMapMachO._LibGraphNode(path, root) elif chost.find('interix') >= 0 or chost.find('winnt') >= 0: node = LinkageMapPeCoff._LibGraphNode(path, root) + elif chost.find('aix') >= 0: + node = LinkageMapXCoff._LibGraphNode(path, root) else: node = LinkageMap._LibGraphNode(path, root) alt_path_node = lib_graph.get(node) -- 2.26.2