#!/bin/bash
###############################################################################
#                                                                             #
# Pakfire - The IPFire package management system                              #
# Copyright (C) 2021 Pakfire development team                                 #
#                                                                             #
# This program is free software: you can redistribute it and/or modify        #
# it under the terms of the GNU General Public License as published by        #
# the Free Software Foundation, either version 3 of the License, or           #
# (at your option) any later version.                                         #
#                                                                             #
# This program is distributed in the hope that it will be useful,             #
# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
# GNU General Public License for more details.                                #
#                                                                             #
# You should have received a copy of the GNU General Public License           #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
#                                                                             #
###############################################################################

# XXX should this be hardcoded?
SOURCE_DIR="/builddir/source"

DEBUG_DIR="/usr/lib/debug"
DEBUG_SOURCE_DIR="/usr/src/debug"

error() {
	echo "$@" >&2
}

build_id() {
	local file="${1}"

	LANG=C readelf -n "${file}" | sed -n "/Build ID/ { s/.*: //p; q; }"
}

list_sources() {
	local file="${1}"

	LANG=C readelf "${file}" --debug-dump 2>/dev/null | \
		awk '/DW_AT_name +:/{name=$NF}/DW_AT_comp_dir +:/{{if (name == "<artificial>") next}{if (name !~ /^[<\/]/) {printf "%s/", $NF}}{print name}}'
}

extract_sources() {
	local file="${1}"

	local source
	for source in $(list_sources "${file}"); do
		local destination="${source/${SOURCE_DIR}/${debug_source_dir}}"
		if [ -e "${source}" -a ! -e "${destination}" ]; then
			mkdir -p "${destination%/*}"
			if ! install -m 444 -o root -g root "${source}" "${destination}"; then
				return 1
			fi
		fi
	done

	return 0
}

process_file() {
	local file="${1}"

	# The path of the file in BUILDROOT
	local path="${file/${buildroot}/}"

	echo "  ${path}"

	local build_id="$(build_id "${file}")"

	# Check if this file has already been stripped
	if [ -n "${build_id}" ]; then
		if [ -f "${debug_dir}/.build-id/${build_id:0:2}/${build_id:2}.debug" ]; then
			return 0
		fi
	else
		if [ -f "${debug_dir}/${path}.debug" ]; then
			return 0
		fi
	fi

	# Fetch any capabilities
	local capabilities="$(getfattr --no-dereference --name="security.capability" \
		--absolute-names --dump "${file}" 2>/dev/null)"

	# Extract sources
	if ! extract_sources "${file}"; then
		error "Could not extract sources from ${file}"
		return 1
	fi

	mkdir -p "${debug_dir}${path%/*}"

	# Copy all debug sections to the debug directory
	if ! objcopy --only-keep-debug "${file}" "${debug_dir}/${path}.debug"; then
		error "Could not extract debug information of ${path}"
		return 1
	fi

	local tempfile="$(mktemp "${file}.XXXXXX")"

	# Add a debuglink section to the binary
	if ! objcopy --add-gnu-debuglink="${debug_dir}/${path}.debug" "${file}" "${tempfile}"; then
		error "Could not add debuglink section to ${path}"
		rm -f "${tempfile}"
		return 1
	fi

	# Create any hardlinks
	local link
	for link in $(find "${buildroot}" -not -path "${file}" -samefile "${file}"); do
		# Strip BUILDROOT
		link="${link#${buildroot}}"

		mkdir -p "${debug_dir}/${link%/*}"
		if ! ln "${debug_dir}/${path}.debug" "${debug_dir}/${link}.debug"; then
			return 1
		fi
	done

	# Create links using build_id
	if [ -n "${build_id}" ]; then
		mkdir -p "${debug_dir}/.build-id/${build_id:0:2}"

		# Link to the binary
		if ! ln -s --relative "${file}" \
				"${debug_dir}/.build-id/${build_id:0:2}/${build_id:2}"; then
			return 1
		fi

		# Link the debug files
		if ! ln -s --relative "${debug_dir}/${path}.debug" \
				"${debug_dir}/.build-id/${build_id:0:2}/${build_id:2}.debug"; then
			return 1
		fi
	fi

	# Strip all debug symbols
	if ! strip --strip-debug "${file}" -o "${tempfile}"; then
		error "Could not strip ${path}"
		rm -f "${tempfile}"
		return 1
	fi

	# Copy back the stripped file
	cat "${tempfile}" > "${file}"
	rm -f "${tempfile}"

	# Restore capabilities
	if [ -n "${capabilities}" ]; then
		setfattr --no-dereference --restore=<(echo "${capabilities}")
	fi

	return 0
}

main() {
	local buildroot="${1}"
	shift

	# Check if BUILDROOT exists
	if [ ! -d "${buildroot}" ]; then
		error "BUILDROOT does not exist"
		return 1
	fi

	echo "Stripping unneeded symbols from binaries and libraries..."

	local debug_dir="${buildroot}${DEBUG_DIR}"
	local debug_source_dir="${buildroot}${DEBUG_SOURCE_DIR}"

	local file
	for file in $(find "${buildroot}" -type f \
			\( -perm -0100 -or -perm -0010 -or -perm -0001 \) | sort); do
		# Determine the file type
		local type="$(readelf -h "${file}" 2>/dev/null)"

		case "${type}" in
			# Libraries and Relocatable binaries
			*Type:*"DYN (Position-Independent Executable file)"*)
				;;

			*Type:*"DYN (Shared object file)"*)
				;;

			# Binaries
			*Type:*"EXEC (Executable file)"*)
				;;

			# Static libraries
			*Type:*"REL (Relocatable file)"*)
				;;

			# Skip any unrecognised files
			*)
				continue
				;;
		esac

		if ! process_file "${file}"; then
			error "Could not strip ${file/${buildroot}/}"
			return 1
		fi
	done

	return 0
}

main "$@" || exit $?
