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

source "${PORTAGE_BIN_PATH:-/usr/lib/portage/bin}"/helper-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 compressdebug installsources nostrip splitdebug
exp_tf RESTRICT binchecks installsources strip

[[ " ${FEATURES} " == *" force-prefix "* ]] || \
	case "${EAPI}" in 0|1|2) EPREFIX= 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 -R .GCC.command.line"
	SPLIT_STRIP_FLAGS=
	;;
esac
: ${PORTAGE_STRIP_FLAGS=${SAFE_STRIP_FLAGS} ${DEF_STRIP_FLAGS}}

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

type -P debugedit >/dev/null && 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: inode_var_name: <file>
inode_file_link() {
	echo -n "${tmpdir}/inodes/"
	if  [[ ${USERLAND} == "BSD" ]] ; then
		stat -f '%i' "$1"
	else
		stat -c '%i' "$1"
	fi
}

# Usage: save_elf_sources <elf>
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
	[[ -f $(inode_file_link "${x}") ]] && 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 "${tmpdir}/sources/${x##*/}.${BASHPID}" \
		"${x}")
}

# Usage: save_elf_debug <elf> [splitdebug file]
save_elf_debug() {
	${FEATURES_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 splitdebug=$2
	local y=${ED}usr/lib/debug/${x:${#D}}.debug

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

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

	local inode=$(inode_file_link "${x}")
	if [[ -f ${inode} ]] ; then
		ln "${inode}" "${y}"
	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
		local args="a-x,o-w"
		[[ -g ${x} || -u ${x} ]] && args+=",go-r"
		chmod ${args} "${y}"
		ln "${y}" "${inode}"
	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:${#D}}.debug" "${buildid_file}.debug"
		ln -s "/${x:${#D}}" "${buildid_file}"
	fi
}

# Usage: process_elf <elf>
process_elf() {
	local x=$1 strip_flags=${*:2}

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

	# 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).
	local lockfile=$(inode_file_link "${x}")_lockfile
	if ! ln "${x}" "${lockfile}" 2>/dev/null ; then
		while [[ -f ${lockfile} ]] ; do
			sleep 1
		done
		unset lockfile
	fi

	save_elf_sources "${x}"

	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}"
			${STRIP} ${strip_flags} \
				-f "${splitdebug}" \
				-F "${shortname}" \
				"${x}"
			save_elf_debug "${x}" "${splitdebug}"
		else
			save_elf_debug "${x}"
			${STRIP} ${strip_flags} "${x}"
		fi
	fi

	[[ -n ${lockfile} ]] && rm -f "${lockfile}"
}

# 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
	# 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

# 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

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

	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

	# 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
		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

	if ${was_not_writable} ; then
		chmod u-w "${x}"
	fi
	) &
	multijob_post_fork
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}"
