#!/bin/bash # Copyright 1999-2019 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 source "${PORTAGE_BIN_PATH}"/helper-functions.sh || exit 1 # avoid multiple calls to `has`. this creates things like: # FEATURES_foo=false # if "foo" is not in $FEATURES tf() { "$@" && echo true || echo false ; } exp_tf() { local flag var=$1 shift for flag in "$@" ; do eval ${var}_${flag}=$(tf has ${flag} ${!var}) done } exp_tf FEATURES compressdebug installsources nostrip splitdebug xattr exp_tf RESTRICT binchecks installsources splitdebug strip if ! ___eapi_has_prefix_variables; then EPREFIX= ED=${D} fi banner=false SKIP_STRIP=false if ${RESTRICT_strip} || ${FEATURES_nostrip} ; then SKIP_STRIP=true banner=true ${FEATURES_installsources} || exit 0 fi [[ ${__PORTAGE_HELPER} == prepstrip ]] && prepstrip=true || prepstrip=false if ! ${prepstrip}; then while [[ $# -gt 0 ]] ; do case $1 in --ignore) shift skip_dirs=() for skip; do if [[ -d ${ED%/}/${skip#/} ]]; then skip_dirs+=( "${ED%/}/${skip#/}" ) else rm -f "${ED%/}/${skip#/}.estrip" || die fi done if [[ ${skip_dirs[@]} ]]; then find "${skip_dirs[@]}" -name '*.estrip' -delete || die fi exit 0 ;; --queue) shift find_paths=() for path; do if [[ -e ${ED%/}/${path#/} ]]; then find_paths+=( "${ED%/}/${path#/}" ) fi done if [[ ${find_paths[@]} ]]; then while IFS= read -r path; do >> "${path}.estrip" || die done < <( scanelf -yqRBF '#k%F' -k '.symtab' "${find_paths[@]}" find "${find_paths[@]}" -type f ! -type l -name '*.a' ) fi exit 0 ;; --dequeue) [[ -n ${2} ]] && die "${0##*/}: --dequeue takes no additional arguments" break ;; *) die "${0##*/}: unknown arguments '$*'" exit 1 ;; esac shift done set -- "${ED}" fi PRESERVE_XATTR=false if [[ ${KERNEL} == linux ]] && ${FEATURES_xattr} ; then PRESERVE_XATTR=true if type -P getfattr >/dev/null && type -P setfattr >/dev/null ; then dump_xattrs() { getfattr -d -m - --absolute-names "$1" } restore_xattrs() { setfattr --restore=- } else dump_xattrs() { PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \ "${PORTAGE_PYTHON:-/usr/bin/python}" \ "${PORTAGE_BIN_PATH}/xattr-helper.py" --dump < <(echo -n "$1") } restore_xattrs() { PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \ "${PORTAGE_PYTHON:-/usr/bin/python}" \ "${PORTAGE_BIN_PATH}/xattr-helper.py" --restore } fi fi # look up the tools we might be using for t in STRIP:strip OBJCOPY:objcopy READELF:readelf RANLIB:ranlib ; do v=${t%:*} # STRIP t=${t#*:} # strip eval ${v}=\"${!v:-${CHOST}-${t}}\" type -P -- ${!v} >/dev/null || eval ${v}=${t} done # Figure out what tool set we're using to strip stuff unset SAFE_STRIP_FLAGS DEF_STRIP_FLAGS SPLIT_STRIP_FLAGS case $(${STRIP} --version 2>/dev/null) in *elfutils*) # dev-libs/elfutils # elfutils default behavior is always safe, so don't need to specify # any flags at all SAFE_STRIP_FLAGS="" DEF_STRIP_FLAGS="--remove-comment" SPLIT_STRIP_FLAGS="-f" ;; *GNU*) # sys-devel/binutils # We'll leave out -R .note for now until we can check out the relevance # of the section when it has the ALLOC flag set on it ... SAFE_STRIP_FLAGS="--strip-unneeded -N __gentoo_check_ldflags__" DEF_STRIP_FLAGS="-R .comment -R .GCC.command.line -R .note.gnu.gold-version" SPLIT_STRIP_FLAGS= ;; esac : ${PORTAGE_STRIP_FLAGS=${SAFE_STRIP_FLAGS} ${DEF_STRIP_FLAGS}} prepstrip_sources_dir=${EPREFIX}/usr/src/debug/${CATEGORY}/${PF} debugedit=$(type -P debugedit) if [[ -z ${debugedit} ]]; then debugedit_paths=( "${EPREFIX}/usr/libexec/rpm/debugedit" ) for x in "${debugedit_paths[@]}"; do if [[ -x ${x} ]]; then debugedit=${x} break fi done fi [[ ${debugedit} ]] && debugedit_found=true || debugedit_found=false debugedit_warned=false __multijob_init # Setup $T filesystem layout that we care about. tmpdir="${T}/prepstrip" rm -rf "${tmpdir}" mkdir -p "${tmpdir}"/{inodes,splitdebug,sources} # Usage: save_elf_sources save_elf_sources() { ${FEATURES_installsources} || return 0 ${RESTRICT_installsources} && return 0 if ! ${debugedit_found} ; then if ! ${debugedit_warned} ; then debugedit_warned=true ewarn "FEATURES=installsources is enabled but the debugedit binary could not be" ewarn "found. This feature will not work unless debugedit or rpm is installed!" fi return 0 fi local x=$1 # since we're editing the ELF here, we should recompute the build-id # (the -i flag below). save that output so we don't need to recompute # it later on in the save_elf_debug step. buildid=$("${debugedit}" -i \ -b "${WORKDIR}" \ -d "${prepstrip_sources_dir}" \ -l "${tmpdir}/sources/${x##*/}.${BASHPID:-$(__bashpid)}" \ "${x}") } # Usage: save_elf_debug [splitdebug file] save_elf_debug() { ${FEATURES_splitdebug} || return 0 ${RESTRICT_splitdebug} && return 0 # NOTE: Debug files must be installed in # ${EPREFIX}/usr/lib/debug/${EPREFIX} (note that ${EPREFIX} occurs # twice in this path) in order for gdb's debug-file-directory # lookup to work correctly. local x=$1 local inode_debug=$2 local splitdebug=$3 local d_noslash=${D%/} local y=${ED%/}/usr/lib/debug/${x:${#d_noslash}}.debug # dont save debug info twice [[ ${x} == *".debug" ]] && return 0 mkdir -p "${y%/*}" if [ -f "${inode_debug}" ] ; then ln "${inode_debug}" "${y}" || die "ln failed unexpectedly" else if [[ -n ${splitdebug} ]] ; then mv "${splitdebug}" "${y}" else local objcopy_flags="--only-keep-debug" ${FEATURES_compressdebug} && objcopy_flags+=" --compress-debug-sections" ${OBJCOPY} ${objcopy_flags} "${x}" "${y}" ${OBJCOPY} --add-gnu-debuglink="${y}" "${x}" fi # Only do the following if the debug file was # successfully created (see bug #446774). if [ $? -eq 0 ] ; then local args="a-x,o-w" [[ -g ${x} || -u ${x} ]] && args+=",go-r" chmod ${args} "${y}" ln "${y}" "${inode_debug}" || die "ln failed unexpectedly" fi fi # if we don't already have build-id from debugedit, look it up if [[ -z ${buildid} ]] ; then # convert the readelf output to something useful buildid=$(${READELF} -n "${x}" 2>/dev/null | awk '/Build ID:/{ print $NF; exit }') fi if [[ -n ${buildid} ]] ; then local buildid_dir="${ED%/}/usr/lib/debug/.build-id/${buildid:0:2}" local buildid_file="${buildid_dir}/${buildid:2}" mkdir -p "${buildid_dir}" [ -L "${buildid_file}".debug ] || ln -s "../../${x:$((${#d_noslash} + 1))}.debug" "${buildid_file}.debug" [ -L "${buildid_file}" ] || ln -s "/${x:$((${#d_noslash} + 1))}" "${buildid_file}" fi } # Usage: process_elf process_elf() { local x=$1 inode_link=$2 strip_flags=${*:3} local ed_noslash=${ED%/} local already_stripped lockfile xt_data __vecho " ${x:${#ed_noslash}}" # If two processes try to debugedit or strip the same hardlink at the # same time, it may corrupt files or cause loss of splitdebug info. # So, use a lockfile to prevent interference (easily observed with # dev-vcs/git which creates ~111 hardlinks to one file in # /usr/libexec/git-core). lockfile=${inode_link}_lockfile if ! ln "${inode_link}" "${lockfile}" 2>/dev/null ; then while [[ -f ${lockfile} ]] ; do sleep 1 done unset lockfile fi [ -f "${inode_link}_stripped" ] && already_stripped=true || already_stripped=false if ! ${already_stripped} ; then if ${PRESERVE_XATTR} ; then xt_data=$(dump_xattrs "${x}") fi save_elf_sources "${x}" fi if ${strip_this} ; then # see if we can split & strip at the same time if [[ -n ${SPLIT_STRIP_FLAGS} ]] ; then local shortname="${x##*/}.debug" local splitdebug="${tmpdir}/splitdebug/${shortname}.${BASHPID:-$(__bashpid)}" ${already_stripped} || \ ${STRIP} ${strip_flags} \ -f "${splitdebug}" \ -F "${shortname}" \ "${x}" save_elf_debug "${x}" "${inode_link}_debug" "${splitdebug}" else save_elf_debug "${x}" "${inode_link}_debug" ${already_stripped} || \ ${STRIP} ${strip_flags} "${x}" fi fi if ${already_stripped} ; then rm -f "${x}" || die "rm failed unexpectedly" ln "${inode_link}_stripped" "${x}" || die "ln failed unexpectedly" else ln "${x}" "${inode_link}_stripped" || die "ln failed unexpectedly" if [[ ${xt_data} ]] ; then restore_xattrs <<< "${xt_data}" fi fi [[ -n ${lockfile} ]] && rm -f "${lockfile}" } # Usage: process_ar process_ar() { local x=$1 local ed_noslash=${ED%/} __vecho " ${x:${#ed_noslash}}" if ${strip_this} ; then # If we have split debug enabled, then do not strip this. # There is no concept of splitdebug for objects not yet # linked in (only for finally linked ELFs), so we have to # retain the debug info in the archive itself. if ! ${FEATURES_splitdebug} || ${RESTRICT_splitdebug} ; then ${STRIP} -g "${x}" && ${RANLIB} "${x}" fi fi } # The existance of the section .symtab tells us that a binary is stripped. # We want to log already stripped binaries, as this may be a QA violation. # They prevent us from getting the splitdebug data. if ! ${RESTRICT_binchecks} ; then # We need to do the non-stripped scan serially first before we turn around # and start stripping the files ourselves. The log parsing can be done in # parallel though. log=${tmpdir}/scanelf-already-stripped.log scanelf -yqRBF '#k%F' -k '!.symtab' "$@" | sed -e "s#^${ED%/}/##" > "${log}" ( __multijob_child_init qa_var="QA_PRESTRIPPED_${ARCH/-/_}" [[ -n ${!qa_var} ]] && QA_PRESTRIPPED="${!qa_var}" if [[ -n ${QA_PRESTRIPPED} && -s ${log} && \ ${QA_STRICT_PRESTRIPPED-unset} = unset ]] ; then shopts=$- set -o noglob for x in ${QA_PRESTRIPPED} ; do sed -e "s#^${x#/}\$##" -i "${log}" done set +o noglob set -${shopts} fi sed -e "/^\$/d" -e "s#^#/#" -i "${log}" if [[ -s ${log} ]] ; then __vecho -e "\n" eqawarn "QA Notice: Pre-stripped files found:" eqawarn "$(<"${log}")" else rm -f "${log}" fi ) & __multijob_post_fork fi # Since strip creates a new inode, we need to know the initial set of # inodes in advance, so that we can avoid interference due to trying # to strip the same (hardlinked) file multiple times in parallel. # See bug #421099. if [[ ${USERLAND} == BSD ]] ; then get_inode_number() { stat -f '%i' "$1"; } else get_inode_number() { stat -c '%i' "$1"; } fi cd "${tmpdir}/inodes" || die "cd failed unexpectedly" if ${prepstrip}; then while read -r x ; do inode_link=$(get_inode_number "${x}") || die "stat failed unexpectedly" echo "${x}" >> "${inode_link}" || die "echo failed unexpectedly" done < <( # Use sort -u to eliminate duplicates for bug #445336. ( scanelf -yqRBF '#k%F' -k '.symtab' "$@" find "$@" -type f ! -type l -name '*.a' ) | LC_ALL=C sort -u ) else while IFS= read -d '' -r x ; do inode_link=$(get_inode_number "${x%.estrip}") || die "stat failed unexpectedly" echo "${x%.estrip}" >> "${inode_link}" || die "echo failed unexpectedly" done < <(find "${ED}" -name '*.estrip' -delete -print0) fi # Now we look for unstripped binaries. for inode_link in $(shopt -s nullglob; echo *) ; do while read -r x do if ! ${banner} ; then __vecho "strip: ${STRIP} ${PORTAGE_STRIP_FLAGS}" banner=true fi ( __multijob_child_init f=$(file "${x}") || exit 0 [[ -z ${f} ]] && exit 0 if ${SKIP_STRIP} ; then strip_this=false elif ! ${prepstrip}; then strip_this=true else # The noglob funk is to support STRIP_MASK="/*/booga" and to keep # the for loop from expanding the globs. # The eval echo is to support STRIP_MASK="/*/{booga,bar}" sex. set -o noglob strip_this=true for m in $(eval echo ${STRIP_MASK}) ; do [[ ${x#${ED%/}} == ${m} ]] && strip_this=false && break done set +o noglob fi # In Prefix we are usually an unprivileged user, so we can't strip # unwritable objects. Make them temporarily writable for the # stripping. was_not_writable=false if [[ ! -w ${x} ]] ; then was_not_writable=true chmod u+w "${x}" fi # only split debug info for final linked objects # or kernel modules as debuginfo for intermediatary # files (think crt*.o from gcc/glibc) is useless and # actually causes problems. install sources for all # elf types though cause that stuff is good. buildid= if [[ ${f} == *"current ar archive"* ]] ; then process_ar "${x}" elif [[ ${f} == *"SB executable"* || ${f} == *"SB pie executable"* || ${f} == *"SB shared object"* ]] ; then process_elf "${x}" "${inode_link}" ${PORTAGE_STRIP_FLAGS} elif [[ ${f} == *"SB relocatable"* ]] ; then process_elf "${x}" "${inode_link}" ${SAFE_STRIP_FLAGS} fi if ${was_not_writable} ; then chmod u-w "${x}" fi ) & __multijob_post_fork done < "${inode_link}" done # With a bit more work, we could run the rsync processes below in # parallel, but not sure that'd be an overall improvement. __multijob_finish cd "${tmpdir}"/sources/ && cat * > "${tmpdir}/debug.sources" 2>/dev/null if [[ -s ${tmpdir}/debug.sources ]] && \ ${FEATURES_installsources} && \ ! ${RESTRICT_installsources} && \ ${debugedit_found} then __vecho "installsources: rsyncing source files" [[ -d ${D%/}/${prepstrip_sources_dir#/} ]] || mkdir -p "${D%/}/${prepstrip_sources_dir#/}" grep -zv '/<[^/>]*>$' "${tmpdir}"/debug.sources | \ (cd "${WORKDIR}"; LANG=C sort -z -u | \ rsync -tL0 --chmod=ugo-st,a+r,go-w,Da+x,Fa-x --files-from=- "${WORKDIR}/" "${D%/}/${prepstrip_sources_dir#/}/" ) # Preserve directory structure. # Needed after running save_elf_sources. # https://bugzilla.redhat.com/show_bug.cgi?id=444310 while read -r -d $'\0' emptydir do >> "${emptydir}"/.keepdir done < <(find "${D%/}/${prepstrip_sources_dir#/}/" -type d -empty -print0) fi cd "${T}" rm -rf "${tmpdir}"