| #!/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 PORTAGE_RESTRICT binchecks installsources splitdebug strip |
| |
| if ! ___eapi_has_prefix_variables; then |
| EPREFIX= ED=${D} |
| fi |
| |
| banner=false |
| SKIP_STRIP=false |
| if ${PORTAGE_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 <elf> |
| save_elf_sources() { |
| ${FEATURES_installsources} || return 0 |
| ${PORTAGE_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 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 <src> <inode_debug> [splitdebug] |
| save_elf_debug() { |
| ${FEATURES_splitdebug} || return 0 |
| ${PORTAGE_RESTRICT_splitdebug} && return 0 |
| |
| debug-print-function "${FUNCNAME}" "$@" |
| |
| # 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 src=$1 # File from which we extract symbols. |
| local inode_debug=$2 # Temp path for hard link tracking |
| local splitdebug=$3 # Existing debug file optionally created by eu-strip in parent function |
| |
| # Source paths |
| local src_basename=${src##*/} |
| local src_dirname=${src%/*} |
| |
| # Destination paths |
| local dst_dirname=${ED%/}/usr/lib/debug/${src_dirname#${D%/}/} |
| local dst_basename dst |
| |
| # dont save debug info twice |
| [[ ${src} == *".debug" ]] && return 0 |
| |
| mkdir -p "${dst_dirname}" || die "failed to create directory '${dst_dirname}'" |
| |
| if [[ -L ${inode_debug} ]] ; then |
| # We already created a debug file for this inode. |
| # Read back the file name, and create another hard link if necessary. |
| dst_basename=$(readlink "${inode_debug}") || die "failed to read link '${inode_debug}'" |
| dst_basename=${dst_basename##*/} |
| dst=${dst_dirname}/${dst_basename} |
| if [[ ! -e ${dst} ]]; then |
| debug-print "creating hard link: target: '${inode_debug}' name: '${dst}'" |
| ln -L "${inode_debug}" "${dst}" || die "failed to create hard link '${dst}'" |
| fi |
| else |
| dst_basename=${src_basename}.debug |
| dst=${dst_dirname}/${dst_basename} |
| if [[ -n ${splitdebug} ]] ; then |
| mv "${splitdebug}" "${dst}" |
| else |
| local objcopy_flags="--only-keep-debug" |
| ${FEATURES_compressdebug} && objcopy_flags+=" --compress-debug-sections" |
| ${OBJCOPY} ${objcopy_flags} "${src}" "${dst}" && |
| ${OBJCOPY} --add-gnu-debuglink="${dst}" "${src}" |
| 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 ${src} || -u ${src} ]] && args+=",go-r" |
| chmod ${args} "${dst}" |
| # symlink so we can read the name back. |
| ln -s "${dst}" "${inode_debug}" || die "failed to create symlink '${inode_debug}'" |
| |
| # 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 "${src}" 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}" |
| local src_buildid_rel="../../../../../${src#${ED%/}/}" |
| local dst_buildid_rel="../../${dst#${ED%/}/usr/lib/debug/}" |
| mkdir -p "${buildid_dir}" || die |
| [[ -L "${buildid_file}".debug ]] || ln -s "${dst_buildid_rel}" "${buildid_file}.debug" || die "failed to create symlink '${buildid_file}.debug'" |
| [[ -L "${buildid_file}" ]] || ln -s "${src_buildid_rel}" "${buildid_file}" || die "failed to create symlink '${buildid_file}'" |
| fi |
| fi |
| fi |
| |
| } |
| |
| # Usage: process_elf <elf> |
| process_elf() { |
| local x=$1 inode_link=$2 strip_flags=${*:3} |
| local ed_noslash=${ED%/} |
| local already_stripped xt_data |
| local lockfile=${inode_link}_lockfile |
| local locktries=100 |
| |
| __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). |
| while ! ln "${inode_link}" "${lockfile}" 2>/dev/null; do |
| (( --locktries > 0 )) || die "failed to acquire lock '${lockfile}'" |
| sleep 1 |
| done |
| |
| [ -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 <ar archive> |
| 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} || ${PORTAGE_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 ! ${PORTAGE_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} && \ |
| ! ${PORTAGE_RESTRICT_installsources} && \ |
| ${debugedit_found} |
| then |
| __vecho "installsources: rsyncing source files" |
| [[ -d ${D%/}/${prepstrip_sources_dir#/} ]] || mkdir -p "${D%/}/${prepstrip_sources_dir#/}" |
| # skip installation of ".../<foo>" (system headers? why inner slashes are forbidden?) |
| # skip syncing of ".../foo/" (complete directories) |
| grep -zv -e '/<[^/>]*>$' -e '/$' "${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}" |