Ejemplo n.º 1
0
    def detect_linuxbrew(self):
        if not is_linux():
            return

        self.linuxbrew_dir = get_linuxbrew_dir()

        if self.linuxbrew_dir:
            os.environ['PATH'] = os.path.join(self.linuxbrew_dir, 'bin') + ':' + os.environ['PATH']
Ejemplo n.º 2
0

# A resolved shared library dependency shown by ldd.
# Example (split across two lines):
#   libmaster.so => /home/mbautin/code/yugabyte/build/debug-gcc-dynamic/lib/libmaster.so
#   (0x00007f941fa5f000)
RESOLVED_DEP_RE = re.compile(r'^\s*(\S+)\s+=>\s+(\S.*\S)\s+[(]')

SYSTEM_LIBRARY_PATH_RE = re.compile(r'^/(usr|lib|lib64)/.*')
SYSTEM_LIBRARY_PATHS = ['/usr/lib', '/usr/lib64', '/lib', '/lib64',
                        # This is used on Ubuntu
                        '/usr/lib/x86_64-linux-gnu',
                        '/lib/x86_64-linux-gnu']

HOME_DIR = os.path.expanduser('~')
LINUXBREW_HOME = get_linuxbrew_dir()
LINUXBREW_CELLAR_GLIBC_DIR = safe_path_join(LINUXBREW_HOME, 'Cellar', 'glibc')
ADDITIONAL_LIB_NAME_GLOBS = ['libnss_*', 'libresolv*']
LINUXBREW_LDD_PATH = safe_path_join(LINUXBREW_HOME, 'bin', 'ldd')

YB_SCRIPT_BIN_DIR = os.path.join(YB_SRC_ROOT, 'bin')
YB_BUILD_SUPPORT_DIR = os.path.join(YB_SRC_ROOT, 'build-support')

PATCHELF_NOT_AN_ELF_EXECUTABLE = 'not an ELF executable'
PATCHELF_PATH = safe_path_join(LINUXBREW_HOME, 'bin', 'patchelf')

LIBRARY_PATH_RE = re.compile('^(.*[.]so)(?:$|[.].*$)')
LIBRARY_CATEGORIES = ['system', 'yb', 'yb-thirdparty', 'linuxbrew']


# This is an alternative to global variables, bundling a few commonly used things.
Ejemplo n.º 3
0
#   (0x00007f941fa5f000)
RESOLVED_DEP_RE = re.compile(r'^\s*(\S+)\s+=>\s+(\S.*\S)\s+[(]')

SYSTEM_LIBRARY_PATH_RE = re.compile(r'^/(usr|lib|lib64)/.*')
SYSTEM_LIBRARY_PATHS = [
    '/usr/lib',
    '/usr/lib64',
    '/lib',
    '/lib64',
    # This is used on Ubuntu
    '/usr/lib/x86_64-linux-gnu',
    '/lib/x86_64-linux-gnu'
]

HOME_DIR = os.path.expanduser('~')
LINUXBREW_HOME = get_linuxbrew_dir()
LINUXBREW_CELLAR_GLIBC_DIR = safe_path_join(LINUXBREW_HOME, 'Cellar', 'glibc')
ADDITIONAL_LIB_NAME_GLOBS = ['libnss_*', 'libresolv*']
LINUXBREW_LDD_PATH = safe_path_join(LINUXBREW_HOME, 'bin', 'ldd')

YB_SCRIPT_BIN_DIR = os.path.join(YB_SRC_ROOT, 'bin')
YB_BUILD_SUPPORT_DIR = os.path.join(YB_SRC_ROOT, 'build-support')

PATCHELF_NOT_AN_ELF_EXECUTABLE = 'not an ELF executable'
PATCHELF_PATH = safe_path_join(LINUXBREW_HOME, 'bin', 'patchelf')

LIBRARY_PATH_RE = re.compile('^(.*[.]so)(?:$|[.].*$)')
LIBRARY_CATEGORIES = ['system', 'yb', 'yb-thirdparty', 'linuxbrew']

# This is an alternative to global variables, bundling a few commonly used things.
DistributionContext = collections.namedtuple(
Ejemplo n.º 4
0
def main():
    """
    Main entry point. Returns True on success.
    """

    parser = argparse.ArgumentParser(
        description='Fix rpath for YugaByte and third-party binaries.')
    parser.add_argument('--verbose', dest='verbose', action='store_true',
                        help='Verbose output')
    args = parser.parse_args()

    log_level = logging.INFO
    if args.verbose:
        log_level = logging.DEBUG
    logging.basicConfig(
        level=log_level,
        format="[" + os.path.basename(__file__) + "] %(asctime)s %(levelname)s: %(message)s")

    is_success = True
    thirdparty_dir = os.path.realpath(
        os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'thirdparty'))

    elf_files = []

    if sys.platform == 'darwin':
        logging.info("fix_rpath.py does not do anything on Mac OS X")
        return True

    logging.info("Fixing rpath/runpath for the third-party binaries")

    if os.path.isdir(os.path.join(thirdparty_dir, 'installed', 'common')):
        logging.info("Using new directory hierarchy layout under thirdparty/installed")
        # This is an indication that we are using a new-style directory layout in
        # thirdparty/installed.
        prefix_dirs = [
                os.path.join(thirdparty_dir, 'installed', prefix_rel_dir)
                for prefix_rel_dir in ['common', 'tsan', 'uninstrumented']
            ] + [os.path.join(thirdparty_dir, 'clang-toolchain')] + [
                os.path.join(thirdparty_dir, 'build', instrumentation_type, glob_pattern)
                for instrumentation_type in ['uninstrumented', 'tsan']
                for glob_pattern in ['gflags-*', 'snappy-*']
            ]
    else:
        # Old-style directory structure, to be removed once migration is complete.
        logging.info("Using old directory hierarchy layout under thirdparty/installed*")
        prefix_dirs = [
            os.path.join(thirdparty_dir, prefix_rel_dir)
            for prefix_rel_dir in ['installed', 'installed-deps', 'installed-deps-tsan']
            ]

    global linuxbrew_dir
    linuxbrew_dir = get_linuxbrew_dir()
    assert linuxbrew_dir

    # We need patchelf as it is more flexible than chrpath and can in fact increase the length of
    # rpath if there is enough space. This could happen if rpath used to be longer but was reduced
    # by a previous patchelf / chrpath command. Another reason we need patchelf is to set the
    # interpreter (dynamic linker) path on binaries that used to point to Linuxbrew's dynamic linker
    # installed in a different location.
    global patchelf_path

    patchelf_possible_paths = ['/usr/bin/patchelf', os.path.join(linuxbrew_dir, 'bin', 'patchelf')]
    for patchelf_path_candidate in patchelf_possible_paths:
        if os.path.isfile(patchelf_path_candidate):
            patchelf_path = patchelf_path_candidate
            break
    if not patchelf_path:
        logging.error("Could not find patchelf in any of the paths: {}".format(
            patchelf_possible_paths))
        return False

    num_binaries_no_rpath_change = 0
    num_binaries_updated_rpath = 0

    dirs_to_search = []
    for pattern in prefix_dirs:
        dirs_to_search += glob.glob(pattern)
    logging.info(
            "Fixing rpath/interpreter path for ELF files in the following directories:{}".format(
                ("\n" + " " * 8).join([""] + dirs_to_search)))

    for prefix_dir in dirs_to_search:
        for file_dir, dirs, file_names in os.walk(prefix_dir):
            for file_name in file_names:
                if file_name.endswith('_patchelf_tmp'):
                    continue
                binary_path = os.path.join(file_dir, file_name)
                if ((os.access(binary_path, os.X_OK) or
                     file_name.endswith('.so') or
                     '.so.' in file_name)
                        and not os.path.islink(binary_path)
                        and not file_name.endswith('.sh')
                        and not file_name.endswith('.py')
                        and not file_name.endswith('.la')):

                    # Invoke readelf to read the current rpath.
                    readelf_subprocess = subprocess.Popen(
                        ['/usr/bin/readelf', '-d', binary_path],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)
                    readelf_stdout, readelf_stderr = readelf_subprocess.communicate()

                    if 'Not an ELF file' in readelf_stderr:
                        logging.debug("Not an ELF file: '%s', skipping" % binary_path)
                        continue

                    if not update_interpreter(binary_path):
                        is_success = False

                    if readelf_subprocess.returncode != 0:
                        logging.warn("readelf returned exit code %d for '%s', stderr:\n%s" % (
                            readelf_subprocess.returncode, binary_path, readelf_stderr))
                        return False

                    elf_files.append(binary_path)

                    # Collect and process all rpath entries and resolve $ORIGIN (the directory of
                    # the executable or binary itself). We will put $ORIGIN back later.

                    original_rpath_entries = []  # Entries with $ORIGIN left as is
                    original_real_rpath_entries = []  # Entries with $ORIGIN substituted

                    for readelf_out_line in readelf_stdout.split('\n'):
                        matcher = READELF_RPATH_RE.search(readelf_out_line)
                        if matcher:
                            for rpath_entry in matcher.groups()[0].split(':'):
                                # Remove duplicate entries but preserve order.
                                add_if_absent(original_rpath_entries, rpath_entry)
                                rpath_entry = rpath_entry.replace('$ORIGIN', file_dir)
                                rpath_entry = rpath_entry.replace('${ORIGIN}', file_dir)
                                # Ignore a special kind of rpath entry that we add just to increase
                                # the number of bytes reserved for rpath.
                                if not rpath_entry.startswith(
                                        '/tmp/making_sure_we_have_enough_room_to_set_rpath_later'):
                                    add_if_absent(original_real_rpath_entries,
                                                  os.path.realpath(rpath_entry))

                    new_relative_rpath_entries = []
                    logging.debug("Original rpath from '%s': %s" % (
                        binary_path, ':'.join(original_rpath_entries)))

                    # A special case: the glog build ends up missing the rpath entry for gflags.
                    if file_name.startswith('libglog.'):
                        add_if_absent(original_real_rpath_entries,
                                      os.path.realpath(os.path.dirname(binary_path)))

                    if not original_real_rpath_entries:
                        logging.debug("No rpath entries in '%s', skipping", binary_path)
                        continue

                    new_real_rpath_entries = []
                    for rpath_entry in original_real_rpath_entries:
                        real_rpath_entry = os.path.realpath(rpath_entry)
                        linuxbrew_rpath_match = LINUXBREW_PATH_RE.match(real_rpath_entry)
                        if linuxbrew_rpath_match:
                            # This is a Linuxbrew directory relative to the home directory of the
                            # user that built the third-party package. We need to substitute the
                            # current user's home directory, or the directory containing the
                            # .linuxbrew-yb-build directory, into that instead.
                            new_rpath_entry = os.path.join(linuxbrew_dir,
                                                           linuxbrew_rpath_match.group(1))
                        else:
                            rel_path = os.path.relpath(real_rpath_entry, os.path.realpath(file_dir))

                            # Normalize the new rpath entry by making it relative to $ORIGIN. This
                            # is only necessary for third-party libraries that may need to be moved
                            # from place to place.
                            if rel_path == '.':
                                new_rpath_entry = '$ORIGIN'
                            else:
                                new_rpath_entry = '$ORIGIN/' + rel_path
                            if len(new_rpath_entry) > 2 and new_rpath_entry.endswith('/.'):
                                new_rpath_entry = new_rpath_entry[:-2]

                        # Remove duplicate entries but preserve order. There may be further
                        # deduplication at this point, because we may have entries that only differ
                        # in presence/absence of a trailing slash, which only get normalized here.
                        add_if_absent(new_relative_rpath_entries, new_rpath_entry)
                        add_if_absent(new_real_rpath_entries, real_rpath_entry)

                    # We have to make rpath entries relative for third-party dependencies.
                    if original_rpath_entries == new_relative_rpath_entries:
                        logging.debug("No change in rpath entries for '%s' (already relative)",
                                      binary_path)
                        num_binaries_no_rpath_change += 1
                        continue

                    add_if_absent(new_relative_rpath_entries, os.path.join(linuxbrew_dir, 'lib'))
                    new_rpath_str = ':'.join(new_relative_rpath_entries)

                    # When using patchelf, this will actually set RUNPATH as a newer replacement
                    # for RPATH, with the difference being that RUNPATH can be overwritten by
                    # LD_LIBRARY_PATH, but we're OK with it.
                    # Note: pipes.quote is a way to escape strings for inclusion in shell
                    # commands.
                    set_rpath_cmd = [patchelf_path, '--set-rpath',
                                     new_rpath_str,
                                     binary_path]
                    logging.debug("Setting rpath on '%s' to '%s'" % (binary_path, new_rpath_str))
                    for i in range(10):
                        set_rpath_result = run_program(set_rpath_cmd, error_ok=True)
                        if set_rpath_result.returncode == 0 or \
                           'open: Resource temporarily unavailable' not in set_rpath_result.stderr:
                            break
                        logging.info(
                                "Re-trying to set rpath on '{}' (resource temporarily unavailable)",
                                binary_path)
                    if set_rpath_result.returncode != 0:
                        logging.warn(
                                "Could not set rpath on '{}': exit code {}, command: {}",
                                binary_path, set_rpath_result.returncode, set_rpath_cmd)
                        logging.warn("patchelf stderr: " + set_rpath_result.stderr)
                        is_success = False
                    num_binaries_updated_rpath += 1

    logging.info("Number of binaries with no rpath change: {}, updated rpath: {}".format(
        num_binaries_no_rpath_change, num_binaries_updated_rpath))

    could_resolve_all = True
    logging.info("Checking if all libraries can be resolved")
    for binary_path in elf_files:
        all_libs_found, missing_libs = run_ldd(binary_path, report_missing_libs=True)
        if not all_libs_found:
            could_resolve_all = False

    if could_resolve_all:
        logging.info("All libraries resolved successfully!")
    else:
        logging.error("Some libraries could not be resolved.")

    return is_success and could_resolve_all