blob: 640645be63d383efd95f1ccf84c93b4f030bed50 [file] [log] [blame]
#!/usr/bin/env bash
# Copyright 1999-2023 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 dedupdebug installsources nostrip splitdebug xattr
exp_tf PORTAGE_RESTRICT binchecks dedupdebug 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
prepstrip=false
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
# We can avoid scanelf calls for binaries we already
# checked in install_qa_check (where we generate
# NEEDED for everything installed).
#
# EAPI 7+ has controlled stripping (dostrip) though
# which is why estrip has the queue/dequeue logic,
# so we need to take the intersection of:
# 1. files scanned earlier (all ELF installed)
# (note that this should be a superset of 2., so we don't
# need to worry about unknown files appearing)
#
# 2. the files we're interested in right now
scanelf_results=()
if [[ -f "${PORTAGE_BUILDDIR}"/build-info/NEEDED ]] ; then
# The arguments may not be exact files (probably aren't), but search paths/directories
# which should then be searched recursively.
while IFS= read -r needed_entry ; do
for find_path in "${find_paths[@]}" ; do
# NEEDED has a bunch of entries like:
# /usr/lib64/libfoo.so libc.so
#
# find_path entries may be exact paths (like /usr/lib64/libfoo.so)
# or instead /usr/lib64, or ${ED}/usr, etc.
#
# We check if the beginning (i.e. first entry) of the NEEDED line
# matches the path given
# e.g. find_path="/usr/lib64" will match needed_entry="/usr/lib64/libfoo.so libc.so".
needed_entry_file="${needed_entry% *}"
if [[ "${needed_entry_file}" =~ ^${find_path##${D}} ]] ; then
scanelf_results+=( "${D}${needed_entry_file}" )
fi
done
done < "${PORTAGE_BUILDDIR}"/build-info/NEEDED
else
mapfile -t scanelf_results < <(scanelf -yqRBF '#k%F' -k '.symtab' "${find_paths[@]}")
fi
while IFS= read -r path; do
>> "${path}.estrip" || die
done < <(
(( ${#scanelf_results[@]} )) && printf "%s\n" "${scanelf_results[@]}"
find "${find_paths[@]}" -type f ! -type l -name '*.a'
)
unset scanelf_results needed_entry needed_entry_file find_path
fi
exit 0
;;
--dequeue)
[[ $# -eq 1 ]] || die "${0##*/}: $1 takes no additional arguments"
break
;;
--prepallstrip)
[[ $# -eq 1 ]] || die "${0##*/}: $1 takes no additional arguments"
prepstrip=true
break
;;
*)
die "${0##*/}: unknown arguments '$*'"
exit 1
;;
esac
shift
done
set -- "${ED}"
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
dwz=$(type -P dwz)
[[ ${dwz} ]] && dwz_found=true || dwz_found=false
dwz_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}")
}
# Try to create a symlink.
# Return success if it already exists.
__try_symlink() {
local target=$1
local name=$2
# Check for an existing link before and after in case we are racing against
# another process.
[[ -L ${name} ]] ||
ln -s "${target}" "${name}" ||
[[ -L ${name} ]] ||
die "failed to create symlink '${name}'"
}
# Usage: dedup_elf_debug <src> <inode_dedupdebug>
dedup_elf_debug() {
${FEATURES_dedupdebug} || return 0
${PORTAGE_RESTRICT_dedupdebug} && return 0
debug-print-function "${FUNCNAME}" "$@"
if ! ${dwz_found} ; then
if ! ${dwz_warned} ; then
dwz_warned=true
ewarn "FEATURES=dedupdebug is enabled but the dwz binary could not be"
ewarn "found. This feature will not work unless dwz is installed!"
fi
return 0
fi
local src=$1 # File to dedup debug symbols
local inode_dedupdebug=$2 # Temp path for hard link tracking
# We already dedupdebug-ed this inode.
[[ -L ${inode_dedupdebug} ]] && return 0
"${dwz}" -- "${src}"
touch "${inode_dedupdebug}"
}
# Usage: save_elf_debug <src> <inode_debug> [splitdebug]
save_elf_debug() {
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.
__try_symlink "${dst}" "${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
__try_symlink "${dst_buildid_rel}" "${buildid_file}.debug"
__try_symlink "${src_buildid_rel}" "${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}"
dedup_elf_debug "${x}" "${inode_link}_dedupdebug"
fi
if ${strip_this} ; then
# See if we can split & strip at the same time
if ${splitdebug} && [[ -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
if ${splitdebug} ; then
save_elf_debug "${x}" "${inode_link}_debug"
fi
${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 ! ${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 < <(
# NEEDED may not exist for some packages (bug #862606)
if [[ -f "${PORTAGE_BUILDDIR}"/build-info/NEEDED ]] ; then
while IFS= read -r needed_entry ; do
needed_entry="${needed_entry% *}"
needed_contents+=( "${D%/}${needed_entry}" )
done < "${PORTAGE_BUILDDIR}"/build-info/NEEDED
fi
# Use sort -u to eliminate duplicates (bug #445336).
(
[[ -n ${needed_contents[@]} ]] && printf "%s\n" "${needed_contents[@]}"
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 -S "${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}".
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
if ${FEATURES_splitdebug} && ! ${PORTAGE_RESTRICT_splitdebug} ; then
splitdebug=true
else
splitdebug=false
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 because 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
[[ ${x} == *.ko ]] || splitdebug=false
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}"