#!/bin/bash
# Copyright 1999-2011 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

source "${PORTAGE_BIN_PATH:-/usr/lib/portage/bin}"/isolated-functions.sh

# 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 installsources nostrip splitdebug
exp_tf RESTRICT binchecks installsources strip

case "$EAPI" in 0|1|2) ED=${D} ;; esac

banner=false
SKIP_STRIP=false
if ${RESTRICT_strip} || ${FEATURES_nostrip} ; then
	SKIP_STRIP=true
	banner=true
	${FEATURES_installsources} || exit 0
fi

# look up the tools we might be using
for t in STRIP:strip OBJCOPY:objcopy READELF:readelf ; 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"
	DEF_STRIP_FLAGS="-R .comment"
	SPLIT_STRIP_FLAGS=
	;;
esac
: ${PORTAGE_STRIP_FLAGS=${SAFE_STRIP_FLAGS} ${DEF_STRIP_FLAGS}}

prepstrip_sources_dir=/usr/src/debug/${CATEGORY}/${PF}

type -P debugedit >/dev/null && debugedit_found=true || debugedit_found=false
debugedit_warned=false

unset ${!INODE_*}

inode_var_name() {
	if  [[ $USERLAND = BSD ]] ; then
		stat -f 'INODE_%d_%i' "$1"
	else
		stat -c 'INODE_%d_%i' "$1"
	fi
}

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"
			ewarn "be found. This feature will not work unless debugedit is installed!"
		fi
		return 0
	fi

	local x=$1
	local inode=$(inode_var_name "$x")
	[[ -n ${!inode} ]] && return 0

	# 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 "${T}"/debug.sources \
		"${x}")
}

save_elf_debug() {
	${FEATURES_splitdebug} || return 0

	local x=$1
	local y="${ED}usr/lib/debug/${x:${#ED}}.debug"

	# dont save debug info twice
	[[ ${x} == *".debug" ]] && return 0

	mkdir -p "${y%/*}"

	local inode=$(inode_var_name "$x")
	if [[ -n ${!inode} ]] ; then
		ln "${ED}usr/lib/debug/${!inode:${#ED}}.debug" "$y"
	else
		eval $inode=\$x
		if [[ -e ${T}/prepstrip.split.debug ]] ; then
			mv "${T}"/prepstrip.split.debug "${y}"
		else
			${OBJCOPY} --only-keep-debug "${x}" "${y}"
			${OBJCOPY} --add-gnu-debuglink="${y}" "${x}"
		fi
		local args="a-x,o-w"
		[[ -g ${x} || -u ${x} ]] && args+=",go-r"
		chmod ${args} "${y}"
	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} -x .note.gnu.build-id "${x}" 2>/dev/null \
			| awk '$NF ~ /GNU/ { getline; printf $2$3$4$5; getline; print $2 }')
	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}"
		ln -s "../../${x:${#ED}}.debug" "${buildid_file}.debug"
		ln -s "/${x:${#ED}}" "${buildid_file}"
	fi
}

process_elf() {
	local x=$1 strip_flags=${*:2}

	vecho "   ${x:${#ED}}"
	save_elf_sources "${x}"

	if ${strip_this} ; then
		# see if we can split & strip at the same time
		if [[ -n ${SPLIT_STRIP_FLAGS} ]] ; then
			${STRIP} ${strip_flags} \
				-f "${T}"/prepstrip.split.debug \
				-F "${x##*/}.debug" \
				"${x}"
			save_elf_debug "${x}"
		else
			save_elf_debug "${x}"
			${STRIP} ${strip_flags} "${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} && ! ${RESTRICT_strip} ; then
	log=$T/scanelf-already-stripped.log
	qa_var="QA_PRESTRIPPED_${ARCH/-/_}"
	[[ -n ${!qa_var} ]] && QA_PRESTRIPPED="${!qa_var}"
	scanelf -yqRBF '#k%F' -k '!.symtab' "$@" | sed -e "s#^${ED}##" > "$log"
	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
fi

# Now we look for unstripped binaries.
for x in \
	$(scanelf -yqRBF '#k%F' -k '.symtab' "$@") \
	$(find "$@" -type f -name '*.a')
do
	if ! ${banner} ; then
		vecho "strip: ${STRIP} ${PORTAGE_STRIP_FLAGS}"
		banner=true
	fi

	f=$(file "${x}") || continue
	[[ -z ${f} ]] && continue

	if ! ${SKIP_STRIP} ; then
		# 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
	else
		strip_this=false
	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
		vecho "   ${x:${#ED}}"
		if ${strip_this} ; then
			# hmm, can we split debug/sources for .a ?
			${STRIP} -g "${x}"
		fi
	elif [[ ${f} == *"SB executable"* || ${f} == *"SB shared object"* ]] ; then
		process_elf "${x}" ${PORTAGE_STRIP_FLAGS}
	elif [[ ${f} == *"SB relocatable"* ]] ; then
		process_elf "${x}" ${SAFE_STRIP_FLAGS}
	fi
done

if [[ -s ${T}/debug.sources ]] && \
   ${FEATURES_installsources} && \
   ! ${RESTRICT_installsources} && \
   ${debugedit_found}
then
	vecho "installsources: rsyncing source files"
	[[ -d ${ED}${prepstrip_sources_dir} ]] || mkdir -p "${ED}${prepstrip_sources_dir}"
	grep -zv '/<[^/>]*>$' "${T}"/debug.sources | \
		(cd "${WORKDIR}"; LANG=C sort -z -u | \
		rsync -tL0 --files-from=- "${WORKDIR}/" "${ED}${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 "${ED}${prepstrip_sources_dir}/" -type d -empty -print0)
fi
