#!/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 <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 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 <elf> [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 <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 <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} || ${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}"
