#!/bin/bash

#---------------------------------------------------------------------
# Build upload manifest out of the files found on the host filesystem
# for following three scenarios:
# 1) Executable - path or name
# 2) Shared library - name or path
# 3) Directory path - take everything from the folder with option of resolving *.so files
#---------------------------------------------------------------------

NAME=$1

MACHINE=$(uname -m)
if [ "${MACHINE}" == "x86_64" ]; then
	MACHINE="x86-64"
fi

argv0=${0##*/}
usage() {
	cat <<-EOF
	Produce manifest referencing files on the host filesystem

	Usage: ${argv0} [options] <ELF file> | <directory path> [<subdirectory path>]

	Options:
	  -l              Look for a shared library
	  -r              Resolve all SO dependencies in directory
	  -R              Make guest root path match the host, otherwise assume '/'; applies with directory path input
	  -h              Show this help output
	  -w              Write output to ./build/last/append.manifest
	  -i              Ignore missing library

	Examples:
	  ./scripts/manifest_from_host.sh ls                 # Create manifest for 'ls' executable
	  ./scripts/manifest_from_host.sh -r /some/directory # Create manifest out of the files in the directory
	  ./scripts/manifest_from_host.sh -l libz.so.1       # Create manifest for libz.so.1 library
	  ./scripts/manifest_from_host.sh -w ls && \
          ./script/build --append-manifest                   # Create manifest for 'ls' executable
	EOF
	exit ${1:-0}
}

find_library()
{
	local pattern="$1"
	local count=$(ldconfig -p | grep -P "$pattern" | grep "${MACHINE}" | wc -l)

	if [[ $count == 0 && $IGNORE_MISSING == false ]]; then
		echo "Could not find any so file matching $pattern" >&2
		return -1
	elif [[ $count == 0 && $IGNORE_MISSING == true ]]; then
		return 0
	elif [[ $count > 1 ]]; then
		echo 'Found more than one alternative:' >&2
		ldconfig -p | grep -P "$pattern"
		return -1
	else
		local so_name_path=$(ldconfig -p | grep -P "$pattern" | grep "${MACHINE}")
		so_name=$(echo $so_name_path | grep -Po 'lib[^ ]+.+?(?= \()')
		so_path=$(echo $so_name_path | grep -Po '(?<=> )/[^ ]+')
		return 0
	fi
}

output_manifest()
{
	local so_files="$1"
	local so_filter="$2"
	echo "# --------------------" | tee -a $OUTPUT
	echo "# Dependencies        " | tee -a $OUTPUT
	echo "# --------------------" | tee -a $OUTPUT
	if [[ $dl == "linux" ]]; then
		lddtree $so_files | grep -v "not found" | grep -v "$so_filter" | \
			sed 's/.*=> //' | awk '// { printf("%s: %s\n", $0, $0); }' | sort | uniq | tee -a $OUTPUT
		echo "/etc/ld.so.cache: /etc/ld.so.cache" | tee -a $OUTPUT
	elif [[ $conf_hide_symbols == 1 ]]; then
		lddtree $so_files | grep -v "not found" | grep -v "$so_filter" | grep -v "ld-linux-${MACHINE}" | \
			grep -Pv 'lib(gcc_s|resolv|c|m|pthread|dl|rt|aio|xenstore|crypt|selinux)\.so([\d.]+)?' | \
			sed 's/ =>/:/' | sed 's/^\s*lib/\/usr\/lib\/lib/' | sort | uniq | tee -a $OUTPUT
	else
		lddtree $so_files | grep -v "not found" | grep -v "$so_filter" | grep -v "ld-linux-${MACHINE}" | \
			grep -Pv 'lib(gcc_s|resolv|c|m|pthread|dl|rt|stdc\+\+|aio|xenstore|crypt|selinux)\.so([\d.]+)?' | \
			sed 's/ =>/:/' | sed 's/^\s*lib/\/usr\/lib\/lib/' | sort | uniq | tee -a $OUTPUT
	fi
}

detect_elf()
{
	local file_path="$1"
	local file_desc=$(file -L $file_path)
	local elf_filter=$(echo $file_desc | grep -P 'LSB shared object|LSB.*executable' | grep "${MACHINE}" | wc -l)
	if [[ $elf_filter == 1 ]]; then
		local shared_object_filter=$(echo $file_desc | grep -P 'LSB shared object' | wc -l)
		if [[ $shared_object_filter == 1 ]]; then
			local pie_filter=$(echo $file_desc | grep 'interpreter' | wc -l)
			if [[ $pie_filter == 1 ]]; then
				FILE_TYPE="PIE"
				LONG_NAME="(PIE) Position Independent Executable"
			else
				FILE_TYPE="SL"
				LONG_NAME="Shared Library"
			fi
		else
			FILE_TYPE="PDE"
			LONG_NAME="Position Dependent Executable"
		fi
	else
		FILE_TYPE="NON_ELF"
		LONG_NAME="Non ELF"
	fi
}

MODE="EXEC"
RESOLVE=false
OUTPUT="/dev/null"
DEFAULT_OUTPUT_FILE="$(dirname $0)/../build/last/append.manifest"
GUEST_ROOT=true
WRITE_CMD=false
IGNORE_MISSING=false

while getopts ilrRwh: OPT ; do
	case ${OPT} in
	l) MODE="LIB";;
	r) RESOLVE=true;;
	R) GUEST_ROOT=false;;
	w) WRITE_CMD=true
           OUTPUT="$DEFAULT_OUTPUT_FILE";;
        i) IGNORE_MISSING=true;;
	h) usage;;
	?) usage 1;;
	esac
done

shift $((OPTIND - 1))
[[ -z $1 ]] && usage 1

LDDTREE_INSTALLED=$(command -v lddtree)
if [ -z "$LDDTREE_INSTALLED" ]; then
	echo "Please install lddtree which is part of pax-utils package" >&2
	exit 1
fi

NAME_OR_PATH="$1"
SUBDIRECTORY_PATH="$2"

# Check if directory and disregard LIB mode if requested
if [[ -d $NAME_OR_PATH ]]; then
	GUEST_PATH_ROOT=""
	if [[ $GUEST_ROOT == false ]]; then
		GUEST_PATH_ROOT="$(realpath $NAME_OR_PATH)"
	fi
	echo "$GUEST_PATH_ROOT/$SUBDIRECTORY_PATH**: $(realpath $NAME_OR_PATH)/$SUBDIRECTORY_PATH**" | tee $OUTPUT
	if [[ $RESOLVE == true ]]; then
		SO_FILES=$(find $NAME_OR_PATH/$SUBDIRECTORY_PATH -type f -name \*so)
		output_manifest "$SO_FILES" "$NAME_OR_PATH/$SUBDIRECTORY_PATH"
	fi
	exit 0
fi

# Check if file exists
if [[ -f $NAME_OR_PATH ]]; then
	# Detect if NAME_PATH point to an ELF executable or library
	detect_elf $NAME_OR_PATH
	if [[ $FILE_TYPE != "NON_ELF" ]]; then
		echo "# $LONG_NAME" | tee $OUTPUT
		NAME=$(basename $NAME_OR_PATH)
		# Detect if ELF is an executable
		if [[ $FILE_TYPE == "SL" ]]; then
			# Library
			echo "/usr/lib/$NAME: $(realpath $NAME_OR_PATH)" | tee -a $OUTPUT
		else
			echo "/$NAME: $(realpath $NAME_OR_PATH)" | tee -a $OUTPUT
			if [[ $WRITE_CMD == true ]]; then
				printf "/$NAME --help" | tee "$(dirname $0)/../build/last/append_cmdline"
			fi
		fi
		REAL_PATH=$(realpath $NAME_OR_PATH)
		output_manifest "$REAL_PATH" "$REAL_PATH"
	else
		echo "The $NAME_OR_PATH is not ELF" >&2
		exit 1
	fi
else
	# Do not assume ELF shared library unless mode specifies it
	if [[ $MODE == "LIB" ]]; then
		find_library "$NAME_OR_PATH"
		if [[ $? == 0 ]]; then
		        if [[ "$so_path" != "" ]]; then
			        echo "# Shared library" | tee $OUTPUT
			        echo "/usr/lib/$so_name: $so_path" | tee -a $OUTPUT
			        output_manifest $so_path $so_path
                        else
                                exit 0
                        fi
		else
			exit 1
		fi
	else
		APP_PATH=$(which $NAME_OR_PATH)
		if [[ $? == 0 ]]; then
			FULL_PATH=$(realpath $APP_PATH)
			detect_elf $FULL_PATH
			if [[ $FILE_TYPE == "NON_ELF" ]]; then
				echo "The file $FULL_PATH is not an ELF" >&2
				exit 1
			else
				echo "# $LONG_NAME" | tee $OUTPUT
				echo "/$NAME_OR_PATH: $FULL_PATH" | tee -a $OUTPUT
				output_manifest $FULL_PATH $FULL_PATH
				if [[ $WRITE_CMD == true ]]; then
					printf "/$NAME_OR_PATH --help" | tee "$(dirname $0)/../build/last/append_cmdline"
				fi
			fi
		else
			echo "Failed to find '$NAME_OR_PATH'!" >&2
			exit 1
		fi
	fi
fi

echo "# --------------------" | tee -a $OUTPUT
exit 0
